Merge "Optimize framework's PNG resources"
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/TestPackageInstaller.java b/apct-tests/perftests/utils/src/android/perftests/utils/TestPackageInstaller.java
index 15a65ce..1397706 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/TestPackageInstaller.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/TestPackageInstaller.java
@@ -133,9 +133,9 @@
             mContext.registerReceiver(this, filter,
                     Context.RECEIVER_EXPORTED_UNAUDITED);
 
-            Intent intent = new Intent(action);
+            Intent intent = new Intent(action).setPackage(mContext.getPackageName());
             PendingIntent pending = PendingIntent.getBroadcast(mContext, sessionId, intent,
-                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
             return pending.getIntentSender();
         }
 
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
index 2bb82bd..f8dc3b0 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
@@ -32,6 +32,8 @@
     /** Stop execution of application's job. */
     @UnsupportedAppUsage
     void stopJob(in JobParameters jobParams);
+    /** Inform the job of a change in the network it should use. */
+    void onNetworkChanged(in JobParameters jobParams);
     /** Update JS of how much data has been downloaded. */
     void getTransferredDownloadBytes(in JobParameters jobParams, in JobWorkItem jobWorkItem);
     /** Update JS of how much data has been uploaded. */
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 18ddffb..4be8766 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -104,6 +104,12 @@
      */
     public static final int INTERNAL_STOP_REASON_USER_UI_STOP =
             JobProtoEnums.INTERNAL_STOP_REASON_USER_UI_STOP; // 11.
+    /**
+     * The app didn't respond quickly enough from JobScheduler's perspective.
+     * @hide
+     */
+    public static final int INTERNAL_STOP_REASON_ANR =
+            JobProtoEnums.INTERNAL_STOP_REASON_ANR; // 12.
 
     /**
      * All the stop reason codes. This should be regarded as an immutable array at runtime.
@@ -128,6 +134,7 @@
             INTERNAL_STOP_REASON_RTC_UPDATED,
             INTERNAL_STOP_REASON_SUCCESSFUL_FINISH,
             INTERNAL_STOP_REASON_USER_UI_STOP,
+            INTERNAL_STOP_REASON_ANR,
     };
 
     /**
@@ -149,6 +156,7 @@
             case INTERNAL_STOP_REASON_RTC_UPDATED: return "rtc_updated";
             case INTERNAL_STOP_REASON_SUCCESSFUL_FINISH: return "successful_finish";
             case INTERNAL_STOP_REASON_USER_UI_STOP: return "user_ui_stop";
+            case INTERNAL_STOP_REASON_ANR: return "anr";
             default: return "unknown:" + reasonCode;
         }
     }
@@ -290,7 +298,8 @@
     private final boolean mIsUserInitiated;
     private final Uri[] mTriggeredContentUris;
     private final String[] mTriggeredContentAuthorities;
-    private final Network network;
+    @Nullable
+    private Network mNetwork;
 
     private int mStopReason = STOP_REASON_UNDEFINED;
     private int mInternalStopReason = INTERNAL_STOP_REASON_UNKNOWN;
@@ -313,7 +322,7 @@
         this.mIsUserInitiated = isUserInitiated;
         this.mTriggeredContentUris = triggeredContentUris;
         this.mTriggeredContentAuthorities = triggeredContentAuthorities;
-        this.network = network;
+        this.mNetwork = network;
         this.mJobNamespace = namespace;
     }
 
@@ -330,7 +339,6 @@
      * @see JobScheduler#forNamespace(String)
      * @return The namespace this job was scheduled in. Will be {@code null} if there was no
      * explicit namespace set and this job is therefore in the default namespace.
-     * @hide
      */
     @Nullable
     public String getJobNamespace() {
@@ -478,7 +486,7 @@
      * @see JobInfo.Builder#setRequiredNetwork(NetworkRequest)
      */
     public @Nullable Network getNetwork() {
-        return network;
+        return mNetwork;
     }
 
     /**
@@ -573,9 +581,9 @@
         mTriggeredContentUris = in.createTypedArray(Uri.CREATOR);
         mTriggeredContentAuthorities = in.createStringArray();
         if (in.readInt() != 0) {
-            network = Network.CREATOR.createFromParcel(in);
+            mNetwork = Network.CREATOR.createFromParcel(in);
         } else {
-            network = null;
+            mNetwork = null;
         }
         mStopReason = in.readInt();
         mInternalStopReason = in.readInt();
@@ -583,6 +591,11 @@
     }
 
     /** @hide */
+    public void setNetwork(@Nullable Network network) {
+        mNetwork = network;
+    }
+
+    /** @hide */
     public void setStopReason(@StopReason int reason, int internalStopReason,
             String debugStopReason) {
         mStopReason = reason;
@@ -614,9 +627,9 @@
         dest.writeBoolean(mIsUserInitiated);
         dest.writeTypedArray(mTriggeredContentUris, flags);
         dest.writeStringArray(mTriggeredContentAuthorities);
-        if (network != null) {
+        if (mNetwork != null) {
             dest.writeInt(1);
-            network.writeToParcel(dest, flags);
+            mNetwork.writeToParcel(dest, flags);
         } else {
             dest.writeInt(0);
         }
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 33668c7..4aec484 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -271,7 +271,6 @@
      * but will instead create or update the job inside the current namespace. A JobScheduler
      * instance dedicated to a namespace must be used to schedule or update jobs in that namespace.
      * @see #getNamespace()
-     * @hide
      */
     @NonNull
     public JobScheduler forNamespace(@NonNull String namespace) {
@@ -282,7 +281,6 @@
      * Get the namespace this JobScheduler instance is operating in. A {@code null} value means
      * that the app has not specified a namespace for this instance, and it is therefore using the
      * default namespace.
-     * @hide
      */
     @Nullable
     public String getNamespace() {
@@ -395,14 +393,21 @@
     public abstract void cancel(int jobId);
 
     /**
-     * Cancel <em>all</em> jobs that have been scheduled by the calling application.
+     * Cancel all jobs that have been scheduled in the current namespace by the
+     * calling application.
+     *
+     * <p>
+     * Starting with Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, this
+     * will only cancel within the current namespace. If a namespace hasn't been explicitly set
+     * with {@link #forNamespace(String)}, then this will cancel jobs in the default namespace.
+     * To cancel all jobs scheduled by the application,
+     * use {@link #cancelInAllNamespaces()} instead.
      */
     public abstract void cancelAll();
 
     /**
      * Cancel <em>all</em> jobs that have been scheduled by the calling application, regardless of
      * namespace.
-     * @hide
      */
     public void cancelInAllNamespaces() {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
@@ -424,7 +429,6 @@
      * If a namespace hasn't been explicitly set with {@link #forNamespace(String)},
      * then this will return jobs in the default namespace.
      * This includes jobs that are currently started as well as those that are still waiting to run.
-     * @hide
      */
     @NonNull
     public Map<String, List<JobInfo>> getPendingJobsInAllNamespaces() {
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index 449c316..31d2266 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -27,6 +27,7 @@
 import android.compat.Compatibility;
 import android.content.Intent;
 import android.os.IBinder;
+import android.util.Log;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -131,6 +132,11 @@
                         return JobService.this.getTransferredUploadBytes(params, item);
                     }
                 }
+
+                @Override
+                public void onNetworkChanged(@NonNull JobParameters params) {
+                    JobService.this.onNetworkChanged(params);
+                }
             };
         }
         return mEngine.getBinder();
@@ -232,6 +238,27 @@
     public abstract boolean onStopJob(JobParameters params);
 
     /**
+     * This method is called that for a job that has a network constraint when the network
+     * to be used by the job changes. The new network object will be available via
+     * {@link JobParameters#getNetwork()}. Any network that results in this method call will
+     * match the job's requested network constraints.
+     *
+     * <p>
+     * For example, if a device is on a metered mobile network and then connects to an
+     * unmetered WiFi network, and the job has indicated that both networks satisfy its
+     * network constraint, then this method will be called to notify the job of the new
+     * unmetered WiFi network.
+     *
+     * @param params The parameters identifying this job, similar to what was supplied to the job in
+     *               the {@link #onStartJob(JobParameters)} callback, but with an updated network.
+     * @see JobInfo.Builder#setRequiredNetwork(android.net.NetworkRequest)
+     * @see JobInfo.Builder#setRequiredNetworkType(int)
+     */
+    public void onNetworkChanged(@NonNull JobParameters params) {
+        Log.w(TAG, "onNetworkChanged() not implemented. Must override in a subclass.");
+    }
+
+    /**
      * Update the amount of data this job is estimated to transfer after the job has started.
      *
      * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
index 697641e..79d87ed 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
@@ -76,6 +76,8 @@
     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;
+    /** Message that the network to use has changed. */
+    private static final int MSG_INFORM_OF_NETWORK_CHANGE = 8;
 
     private final IJobService mBinder;
 
@@ -128,6 +130,16 @@
         }
 
         @Override
+        public void onNetworkChanged(JobParameters jobParams) throws RemoteException {
+            JobServiceEngine service = mService.get();
+            if (service != null) {
+                service.mHandler.removeMessages(MSG_INFORM_OF_NETWORK_CHANGE);
+                service.mHandler.obtainMessage(MSG_INFORM_OF_NETWORK_CHANGE, jobParams)
+                        .sendToTarget();
+            }
+        }
+
+        @Override
         public void stopJob(JobParameters jobParams) throws RemoteException {
             JobServiceEngine service = mService.get();
             if (service != null) {
@@ -271,6 +283,16 @@
                     args.recycle();
                     break;
                 }
+                case MSG_INFORM_OF_NETWORK_CHANGE: {
+                    final JobParameters params = (JobParameters) msg.obj;
+                    try {
+                        JobServiceEngine.this.onNetworkChanged(params);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Error while executing job: " + params.getJobId());
+                        throw new RuntimeException(e);
+                    }
+                    break;
+                }
                 default:
                     Log.e(TAG, "Unrecognised message received.");
                     break;
@@ -386,6 +408,15 @@
     }
 
     /**
+     * Engine's report that the network for the job has changed.
+     *
+     * @see JobService#onNetworkChanged(JobParameters)
+     */
+    public void onNetworkChanged(@NonNull JobParameters params) {
+        Log.w(TAG, "onNetworkChanged() not implemented. Must override in a subclass.");
+    }
+
+    /**
      * Engine's request to get how much data has been downloaded.
      *
      * @hide
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 e0e0b4b..bf8984f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1179,6 +1179,25 @@
         assignJobsToContextsLocked();
     }
 
+    @Nullable
+    @GuardedBy("mLock")
+    JobServiceContext getRunningJobServiceContextLocked(JobStatus job) {
+        if (!mRunningJobs.contains(job)) {
+            return null;
+        }
+
+        for (int i = 0; i < mActiveServices.size(); i++) {
+            JobServiceContext jsc = mActiveServices.get(i);
+            final JobStatus executing = jsc.getRunningJobLocked();
+            if (executing == job) {
+                return jsc;
+            }
+        }
+        Slog.wtf(TAG, "Couldn't find running job on a context");
+        mRunningJobs.remove(job);
+        return null;
+    }
+
     @GuardedBy("mLock")
     boolean stopJobOnServiceContextLocked(JobStatus job,
             @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
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 4e52ed3..43f7279 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -61,6 +61,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ServiceInfo;
+import android.net.Network;
 import android.net.Uri;
 import android.os.BatteryManager;
 import android.os.BatteryManagerInternal;
@@ -152,6 +153,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -241,6 +243,7 @@
     final Object mLock = new Object();
     /** Master list of jobs. */
     final JobStore mJobs;
+    private final CountDownLatch mJobStoreLoadedLatch;
     /** Tracking the standby bucket state of each app */
     final StandbyTracker mStandbyTracker;
     /** Tracking amount of time each package runs for. */
@@ -1940,6 +1943,17 @@
     }
 
     @Override
+    public void onNetworkChanged(JobStatus jobStatus, Network newNetwork) {
+        synchronized (mLock) {
+            final JobServiceContext jsc =
+                    mConcurrencyManager.getRunningJobServiceContextLocked(jobStatus);
+            if (jsc != null) {
+                jsc.informOfNetworkChangeLocked(newNetwork);
+            }
+        }
+    }
+
+    @Override
     public void onRestrictedBucketChanged(List<JobStatus> jobs) {
         final int len = jobs.size();
         if (len == 0) {
@@ -2032,7 +2046,9 @@
         publishLocalService(JobSchedulerInternal.class, new LocalService());
 
         // Initialize the job store and set up any persisted jobs
-        mJobs = JobStore.initAndGet(this);
+        mJobStoreLoadedLatch = new CountDownLatch(1);
+        mJobs = JobStore.get(this);
+        mJobs.initAsync(mJobStoreLoadedLatch);
 
         mBatteryStateTracker = new BatteryStateTracker();
         mBatteryStateTracker.startTracking();
@@ -2100,7 +2116,7 @@
 
                     // And kick off the work to update the affected jobs, using a secondary
                     // thread instead of chugging away here on the main looper thread.
-                    new Thread(mJobTimeUpdater, "JobSchedulerTimeSetReceiver").start();
+                    mJobs.runWorkAsync(mJobTimeUpdater);
                 }
             }
         }
@@ -2138,7 +2154,15 @@
 
     @Override
     public void onBootPhase(int phase) {
-        if (PHASE_SYSTEM_SERVICES_READY == phase) {
+        if (PHASE_LOCK_SETTINGS_READY == phase) {
+            // This is the last phase before PHASE_SYSTEM_SERVICES_READY. We need to ensure that
+            // persisted jobs are loaded before we can proceed to PHASE_SYSTEM_SERVICES_READY.
+            try {
+                mJobStoreLoadedLatch.await();
+            } catch (InterruptedException e) {
+                Slog.e(TAG, "Couldn't wait on job store loading latch");
+            }
+        } else if (PHASE_SYSTEM_SERVICES_READY == phase) {
             mConstantsObserver.start();
             for (StateController controller : mControllers) {
                 controller.onSystemServicesReady();
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 b89337f..fb5d63e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -36,6 +36,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.net.Network;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -187,6 +188,8 @@
     private int mPendingInternalStopReason;
     private String mPendingDebugStopReason;
 
+    private Network mPendingNetworkChange;
+
     // Debugging: reason this job was last stopped.
     public String mStoppedReason;
 
@@ -292,6 +295,7 @@
             mRunningJob = job;
             mRunningJobWorkType = workType;
             mRunningCallback = new JobCallback();
+            mPendingNetworkChange = null;
             final boolean isDeadlineExpired =
                     job.hasDeadlineConstraint() &&
                             (job.getLatestRunTimeElapsed() < sElapsedRealtimeClock.millis());
@@ -515,6 +519,28 @@
         return Math.max(0, mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis - nowElapsed);
     }
 
+    void informOfNetworkChangeLocked(Network newNetwork) {
+        if (mVerb != VERB_EXECUTING) {
+            Slog.w(TAG, "Sending onNetworkChanged for a job that isn't started. " + mRunningJob);
+            if (mVerb == VERB_BINDING || mVerb == VERB_STARTING) {
+                // The network changed before the job has fully started. Hold the change push
+                // until the job has started executing.
+                mPendingNetworkChange = newNetwork;
+            }
+            return;
+        }
+        try {
+            mParams.setNetwork(newNetwork);
+            mPendingNetworkChange = null;
+            service.onNetworkChanged(mParams);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error sending onNetworkChanged to client.", e);
+            // The job's host app apparently crashed during the job, so we should reschedule.
+            closeAndCleanupJobLocked(/* reschedule */ true,
+                    "host crashed when trying to inform of network change");
+        }
+    }
+
     boolean isWithinExecutionGuaranteeTime() {
         return sElapsedRealtimeClock.millis()
                 < mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis;
@@ -972,6 +998,10 @@
                     return;
                 }
                 scheduleOpTimeOutLocked();
+                if (mPendingNetworkChange != null
+                        && !Objects.equals(mParams.getNetwork(), mPendingNetworkChange)) {
+                    informOfNetworkChangeLocked(mPendingNetworkChange);
+                }
                 if (mRunningJob.isUserVisibleJob()) {
                     mService.informObserversOfUserVisibleJobChange(this, mRunningJob, true);
                 }
@@ -1225,6 +1255,7 @@
         mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED;
         mPendingInternalStopReason = 0;
         mPendingDebugStopReason = null;
+        mPendingNetworkChange = null;
         removeOpTimeOutLocked();
         if (completedJob.isUserVisibleJob()) {
             mService.informObserversOfUserVisibleJobChange(this, completedJob, false);
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 9ec74e5..0dcb0b245 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -70,6 +70,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.StringJoiner;
+import java.util.concurrent.CountDownLatch;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -131,7 +132,7 @@
     private JobStorePersistStats mPersistInfo = new JobStorePersistStats();
 
     /** Used by the {@link JobSchedulerService} to instantiate the JobStore. */
-    static JobStore initAndGet(JobSchedulerService jobManagerService) {
+    static JobStore get(JobSchedulerService jobManagerService) {
         synchronized (sSingletonLock) {
             if (sSingleton == null) {
                 sSingleton = new JobStore(jobManagerService.getContext(),
@@ -147,6 +148,7 @@
     @VisibleForTesting
     public static JobStore initAndGetForTesting(Context context, File dataDir) {
         JobStore jobStoreUnderTest = new JobStore(context, new Object(), dataDir);
+        jobStoreUnderTest.init();
         jobStoreUnderTest.clearForTesting();
         return jobStoreUnderTest;
     }
@@ -181,10 +183,16 @@
         mXmlTimestamp = mJobsFile.exists()
                 ? mJobsFile.getLastModifiedTime() : mJobFileDirectory.lastModified();
         mRtcGood = (sSystemClock.millis() > mXmlTimestamp);
+    }
 
+    private void init() {
         readJobMapFromDisk(mJobSet, mRtcGood);
     }
 
+    void initAsync(CountDownLatch completionLatch) {
+        mIoHandler.post(new ReadJobMapFromDiskRunnable(mJobSet, mRtcGood, completionLatch));
+    }
+
     private AtomicFile createJobFile(String baseName) {
         return createJobFile(new File(mJobFileDirectory, baseName + ".xml"));
     }
@@ -202,6 +210,15 @@
     }
 
     /**
+     * Runs any necessary work asynchronously. If this is called after
+     * {@link #initAsync(CountDownLatch)}, this ensures the given work runs after
+     * the JobStore is initialized.
+     */
+    void runWorkAsync(@NonNull Runnable r) {
+        mIoHandler.post(r);
+    }
+
+    /**
      * Find all the jobs that were affected by RTC clock uncertainty at boot time.  Returns
      * parallel lists of the existing JobStatus objects and of new, equivalent JobStatus instances
      * with now-corrected time bounds.
@@ -998,14 +1015,21 @@
     private final class ReadJobMapFromDiskRunnable implements Runnable {
         private final JobSet jobSet;
         private final boolean rtcGood;
+        private final CountDownLatch mCompletionLatch;
 
         /**
          * @param jobSet Reference to the (empty) set of JobStatus objects that back the JobStore,
          *               so that after disk read we can populate it directly.
          */
         ReadJobMapFromDiskRunnable(JobSet jobSet, boolean rtcIsGood) {
+            this(jobSet, rtcIsGood, null);
+        }
+
+        ReadJobMapFromDiskRunnable(JobSet jobSet, boolean rtcIsGood,
+                @Nullable CountDownLatch completionLatch) {
             this.jobSet = jobSet;
             this.rtcGood = rtcIsGood;
+            this.mCompletionLatch = completionLatch;
         }
 
         @Override
@@ -1088,6 +1112,9 @@
             if (needFileMigration) {
                 migrateJobFilesAsync();
             }
+            if (mCompletionLatch != null) {
+                mCompletionLatch.countDown();
+            }
         }
 
         private List<JobStatus> readJobMapImpl(InputStream fis, boolean rtcIsGood, long nowElapsed)
diff --git a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
index 554f152..50064bd 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.Network;
 import android.util.ArraySet;
 
 import com.android.server.job.controllers.JobStatus;
@@ -57,6 +58,8 @@
 
     public void onDeviceIdleStateChanged(boolean deviceIdle);
 
+    void onNetworkChanged(JobStatus jobStatus, Network newNetwork);
+
     /**
      * Called when these jobs are added or removed from the
      * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index b491291..16f5c7f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -1125,6 +1125,17 @@
                     mFlexibilityController.isFlexibilitySatisfiedLocked(jobStatus));
         }
 
+        // Try to handle network transitions in a reasonable manner. See the lengthy note inside
+        // UidDefaultNetworkCallback for more details.
+        if (!changed && satisfied && jobStatus.network != null
+                && mService.isCurrentlyRunningLocked(jobStatus)) {
+            // The job's connectivity constraint continues to be satisfied even though the network
+            // has changed.
+            // Inform the job of the new network so that it can attempt to switch over. This is the
+            // ideal behavior for certain transitions such as going from a metered network to an
+            // unmetered network.
+            mStateChangedListener.onNetworkChanged(jobStatus, network);
+        }
 
         // Pass along the evaluated network for job to use; prevents race
         // conditions as default routes change over time, and opens the door to
@@ -1419,8 +1430,8 @@
         // the onBlockedStatusChanged() call, we re-evaluate the job, but keep it running
         // (assuming the new network satisfies constraints). The app continues to use the old
         // network (if they use the network object provided through JobParameters.getNetwork())
-        // because we don't notify them of the default network change. If the old network no
-        // longer satisfies requested constraints, then we have a problem. Depending on the order
+        // because we don't notify them of the default network change. If the old network later
+        // stops satisfying requested constraints, then we have a problem. Depending on the order
         // of calls, if the per-UID callback gets notified of the network change before the
         // general callback gets notified of the capabilities change, then the job's network
         // object will point to the new network and we won't stop the job, even though we told it
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 0b875cc..a2e8eb4 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
@@ -2098,7 +2098,7 @@
         } else {
             sb.append(" satisfied:0x").append(Integer.toHexString(satisfiedConstraints));
             sb.append(" unsatisfied:0x").append(Integer.toHexString(
-                    (satisfiedConstraints & mRequiredConstraintsOfInterest)
+                    (satisfiedConstraints & (mRequiredConstraintsOfInterest | IMPLICIT_CONSTRAINTS))
                             ^ mRequiredConstraintsOfInterest));
         }
         sb.append("}");
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index ed717c4..6998081 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -195,10 +195,35 @@
             return;
         }
 
+        if ("scheduling".equals(op)) {
+            setSchedulingEnabled(userId);
+            return;
+        }
+
         System.err.println("Unknown command");
         showUsage();
     }
 
+    private void setSchedulingEnabled(int userId) {
+        String arg = nextArg();
+        if (arg == null) {
+            showUsage();
+            return;
+        }
+
+        try {
+            boolean enable = Boolean.parseBoolean(arg);
+            mBmgr.setFrameworkSchedulingEnabledForUser(userId, enable);
+            System.out.println(
+                    "Backup scheduling is now "
+                            + (enable ? "enabled" : "disabled")
+                            + " for user "
+                            + userId);
+        } catch (RemoteException e) {
+            handleRemoteException(e);
+        }
+    }
+
     private void handleRemoteException(RemoteException e) {
         System.err.println(e.toString());
         System.err.println(BMGR_NOT_RUNNING_ERR);
@@ -944,6 +969,7 @@
         System.err.println("       bmgr activate BOOL");
         System.err.println("       bmgr activated");
         System.err.println("       bmgr autorestore BOOL");
+        System.err.println("       bmgr scheduling BOOL");
         System.err.println("");
         System.err.println("The '--user' option specifies the user on which the operation is run.");
         System.err.println("It must be the first argument before the operation.");
@@ -1021,6 +1047,9 @@
         System.err.println("");
         System.err.println("The 'autorestore' command enables or disables automatic restore when");
         System.err.println("a new package is installed.");
+        System.err.println("");
+        System.err.println("The 'scheduling' command enables or disables backup scheduling in the");
+        System.err.println("framework.");
     }
 
     private static class BackupMonitor extends IBackupManagerMonitor.Stub {
diff --git a/core/api/current.txt b/core/api/current.txt
index 5878fc5..4f1d7a7 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -89,6 +89,7 @@
     field public static final String DISABLE_KEYGUARD = "android.permission.DISABLE_KEYGUARD";
     field public static final String DUMP = "android.permission.DUMP";
     field public static final String ENFORCE_UPDATE_OWNERSHIP = "android.permission.ENFORCE_UPDATE_OWNERSHIP";
+    field public static final String EXECUTE_APP_ACTION = "android.permission.EXECUTE_APP_ACTION";
     field public static final String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR";
     field public static final String FACTORY_TEST = "android.permission.FACTORY_TEST";
     field public static final String FOREGROUND_SERVICE = "android.permission.FOREGROUND_SERVICE";
@@ -744,6 +745,7 @@
     field public static final int focusableInTouchMode = 16842971; // 0x10100db
     field public static final int focusedByDefault = 16844100; // 0x1010544
     field @Deprecated public static final int focusedMonthDateColor = 16843587; // 0x1010343
+    field public static final int focusedSearchResultHighlightColor;
     field public static final int font = 16844082; // 0x1010532
     field public static final int fontFamily = 16843692; // 0x10103ac
     field public static final int fontFeatureSettings = 16843959; // 0x10104b7
@@ -1355,6 +1357,7 @@
     field public static final int searchHintIcon = 16843988; // 0x10104d4
     field public static final int searchIcon = 16843907; // 0x1010483
     field public static final int searchMode = 16843221; // 0x10101d5
+    field public static final int searchResultHighlightColor;
     field public static final int searchSettingsDescription = 16843402; // 0x101028a
     field public static final int searchSuggestAuthority = 16843222; // 0x10101d6
     field public static final int searchSuggestIntentAction = 16843225; // 0x10101d9
@@ -8563,6 +8566,7 @@
     method public int getClipGrantFlags();
     method @NonNull public android.os.PersistableBundle getExtras();
     method public int getJobId();
+    method @Nullable public String getJobNamespace();
     method @Nullable public android.net.Network getNetwork();
     method public int getStopReason();
     method @NonNull public android.os.Bundle getTransientExtras();
@@ -8596,10 +8600,14 @@
     method public boolean canRunLongJobs();
     method public abstract void cancel(int);
     method public abstract void cancelAll();
+    method public void cancelInAllNamespaces();
     method public abstract int enqueue(@NonNull android.app.job.JobInfo, @NonNull android.app.job.JobWorkItem);
+    method @NonNull public android.app.job.JobScheduler forNamespace(@NonNull String);
     method @NonNull public abstract java.util.List<android.app.job.JobInfo> getAllPendingJobs();
+    method @Nullable public String getNamespace();
     method @Nullable public abstract android.app.job.JobInfo getPendingJob(int);
     method public int getPendingJobReason(int);
+    method @NonNull public java.util.Map<java.lang.String,java.util.List<android.app.job.JobInfo>> getPendingJobsInAllNamespaces();
     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
@@ -8627,6 +8635,7 @@
     ctor public JobService();
     method public final void jobFinished(android.app.job.JobParameters, boolean);
     method public final android.os.IBinder onBind(android.content.Intent);
+    method public void onNetworkChanged(@NonNull android.app.job.JobParameters);
     method public abstract boolean onStartJob(android.app.job.JobParameters);
     method public abstract boolean onStopJob(android.app.job.JobParameters);
     method public final void setNotification(@NonNull android.app.job.JobParameters, int, @NonNull android.app.Notification, int);
@@ -8641,6 +8650,7 @@
     ctor public JobServiceEngine(android.app.Service);
     method public final android.os.IBinder getBinder();
     method public void jobFinished(android.app.job.JobParameters, boolean);
+    method public void onNetworkChanged(@NonNull android.app.job.JobParameters);
     method public abstract boolean onStartJob(android.app.job.JobParameters);
     method public abstract boolean onStopJob(android.app.job.JobParameters);
     method public void setNotification(@NonNull android.app.job.JobParameters, int, @NonNull android.app.Notification, int);
@@ -10609,7 +10619,6 @@
     field public static final String ACTION_SEND_MULTIPLE = "android.intent.action.SEND_MULTIPLE";
     field public static final String ACTION_SET_WALLPAPER = "android.intent.action.SET_WALLPAPER";
     field public static final String ACTION_SHOW_APP_INFO = "android.intent.action.SHOW_APP_INFO";
-    field public static final String ACTION_SHOW_OUTPUT_SWITCHER = "android.intent.action.SHOW_OUTPUT_SWITCHER";
     field public static final String ACTION_SHOW_WORK_APPS = "android.intent.action.SHOW_WORK_APPS";
     field public static final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN";
     field public static final String ACTION_SYNC = "android.intent.action.SYNC";
@@ -11992,7 +12001,7 @@
     method @NonNull public int[] getChildSessionIds();
     method @NonNull public String[] getNames() throws java.io.IOException;
     method public int getParentSessionId();
-    method public boolean isKeepApplicationEnabledSetting();
+    method public boolean isApplicationEnabledSettingPersistent();
     method public boolean isMultiPackage();
     method public boolean isRequestUpdateOwnership();
     method public boolean isStaged();
@@ -12047,8 +12056,8 @@
     method @NonNull public android.os.UserHandle getUser();
     method public boolean hasParentSessionId();
     method public boolean isActive();
+    method public boolean isApplicationEnabledSettingPersistent();
     method public boolean isCommitted();
-    method public boolean isKeepApplicationEnabledSetting();
     method public boolean isMultiPackage();
     method public boolean isRequestUpdateOwnership();
     method public boolean isSealed();
@@ -12078,12 +12087,12 @@
     method public void setAppIcon(@Nullable android.graphics.Bitmap);
     method public void setAppLabel(@Nullable CharSequence);
     method public void setAppPackageName(@Nullable String);
+    method public void setApplicationEnabledSettingPersistent();
     method @Deprecated public void setAutoRevokePermissionsMode(boolean);
     method public void setInstallLocation(int);
     method public void setInstallReason(int);
     method public void setInstallScenario(int);
     method public void setInstallerPackageName(@Nullable String);
-    method public void setKeepApplicationEnabledSetting();
     method public void setMultiPackage();
     method public void setOriginatingUid(int);
     method public void setOriginatingUri(@Nullable android.net.Uri);
@@ -12702,7 +12711,7 @@
     field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1
     field public static final int FLAG_USE_APP_ZYGOTE = 8; // 0x8
     field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CAMERA}, anyOf={android.Manifest.permission.CAMERA}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40
-    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
+    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR, android.Manifest.permission.UWB_RANGING}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
     field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
     field @RequiresPermission(android.Manifest.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT) public static final int FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT = 4096; // 0x1000
     field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.BODY_SENSORS, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS}) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100
@@ -13366,9 +13375,8 @@
 
   public final class GetCredentialResponse implements android.os.Parcelable {
     ctor public GetCredentialResponse(@NonNull android.credentials.Credential);
-    ctor public GetCredentialResponse();
     method public int describeContents();
-    method @Nullable public android.credentials.Credential getCredential();
+    method @NonNull public android.credentials.Credential getCredential();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialResponse> CREATOR;
   }
@@ -25810,7 +25818,7 @@
   }
 
   public final class MediaProjectionConfig implements android.os.Parcelable {
-    method @NonNull public static android.media.projection.MediaProjectionConfig createConfigForDisplay(@IntRange(from=android.view.Display.DEFAULT_DISPLAY, to=android.view.Display.DEFAULT_DISPLAY) int);
+    method @NonNull public static android.media.projection.MediaProjectionConfig createConfigForDefaultDisplay();
     method @NonNull public static android.media.projection.MediaProjectionConfig createConfigForUserChoice();
     method public int describeContents();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -28377,24 +28385,15 @@
 
   public final class NfcAdapter {
     method public void disableForegroundDispatch(android.app.Activity);
-    method @Deprecated public void disableForegroundNdefPush(android.app.Activity);
     method public void disableReaderMode(android.app.Activity);
     method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
-    method @Deprecated public void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
     method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
     method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
     method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
     method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
-    method @Deprecated public boolean invokeBeam(android.app.Activity);
     method public boolean isEnabled();
-    method @Deprecated public boolean isNdefPushEnabled();
     method public boolean isSecureNfcEnabled();
     method public boolean isSecureNfcSupported();
-    method @Deprecated public void setBeamPushUris(android.net.Uri[], android.app.Activity);
-    method @Deprecated public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
-    method @Deprecated public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
-    method @Deprecated public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...);
-    method @Deprecated public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
     field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
     field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
     field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
@@ -28426,18 +28425,6 @@
     field public static final int STATE_TURNING_ON = 2; // 0x2
   }
 
-  @Deprecated public static interface NfcAdapter.CreateBeamUrisCallback {
-    method @Deprecated public android.net.Uri[] createBeamUris(android.nfc.NfcEvent);
-  }
-
-  @Deprecated public static interface NfcAdapter.CreateNdefMessageCallback {
-    method @Deprecated public android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent);
-  }
-
-  @Deprecated public static interface NfcAdapter.OnNdefPushCompleteCallback {
-    method @Deprecated public void onNdefPushComplete(android.nfc.NfcEvent);
-  }
-
   public static interface NfcAdapter.OnTagRemovedListener {
     method public void onTagRemoved();
   }
@@ -36411,7 +36398,6 @@
     field public static final String ACTION_MANAGE_ALL_SIM_PROFILES_SETTINGS = "android.settings.MANAGE_ALL_SIM_PROFILES_SETTINGS";
     field public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS";
     field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
-    field public static final String ACTION_MANAGE_APP_LONG_RUNNING_JOBS = "android.settings.MANAGE_APP_LONG_RUNNING_JOBS";
     field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS";
     field public static final String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION";
     field public static final String ACTION_MANAGE_SUPERVISOR_RESTRICTED_SETTING = "android.settings.MANAGE_SUPERVISOR_RESTRICTED_SETTING";
@@ -40535,7 +40521,9 @@
     method public android.os.IBinder onBind(android.content.Intent);
     method @NonNull public java.util.Set<java.lang.String> onGetSupportedVoiceActions(@NonNull java.util.Set<java.lang.String>);
     method public void onLaunchVoiceAssistFromKeyguard();
+    method public void onPrepareToShowSession(@NonNull android.os.Bundle, int);
     method public void onReady();
+    method public void onShowSessionFailed();
     method public void onShutdown();
     method public void setDisabledShowContext(int);
     method public final void setUiHints(@NonNull android.os.Bundle);
@@ -42774,6 +42762,7 @@
     field public static final String KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL = "show_video_call_charges_alert_dialog_bool";
     field public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL = "show_wfc_location_privacy_policy_bool";
     field @Deprecated public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
+    field public static final String KEY_SIM_COUNTRY_ISO_OVERRIDE_STRING = "sim_country_iso_override_string";
     field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
     field public static final String KEY_SMDP_SERVER_ADDRESS_STRING = "smdp_server_address_string";
     field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
@@ -44916,7 +44905,7 @@
     method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);
     method public boolean isWorldPhone();
     method @Deprecated public void listen(android.telephony.PhoneStateListener, int);
-    method @RequiresPermission(android.Manifest.permission.READ_BASIC_PHONE_STATE) public void purchasePremiumCapability(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @RequiresPermission(allOf={android.Manifest.permission.READ_BASIC_PHONE_STATE, android.Manifest.permission.INTERNET}) public void purchasePremiumCapability(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void rebootModem();
     method public void registerTelephonyCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback);
     method public void registerTelephonyCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback);
@@ -45097,7 +45086,7 @@
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED = 10; // 0xa
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12; // 0xc
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION = 14; // 0xe
-    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN = 5; // 0x5
+    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_FOREGROUND = 5; // 0x5
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15; // 0xf
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED = 11; // 0xb
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS = 1; // 0x1
@@ -50576,6 +50565,7 @@
     field public static final int AXIS_GENERIC_7 = 38; // 0x26
     field public static final int AXIS_GENERIC_8 = 39; // 0x27
     field public static final int AXIS_GENERIC_9 = 40; // 0x28
+    field public static final int AXIS_GESTURE_PINCH_SCALE_FACTOR = 52; // 0x34
     field public static final int AXIS_GESTURE_SCROLL_X_DISTANCE = 50; // 0x32
     field public static final int AXIS_GESTURE_SCROLL_Y_DISTANCE = 51; // 0x33
     field public static final int AXIS_GESTURE_X_OFFSET = 48; // 0x30
@@ -50616,6 +50606,7 @@
     field public static final int CLASSIFICATION_AMBIGUOUS_GESTURE = 1; // 0x1
     field public static final int CLASSIFICATION_DEEP_PRESS = 2; // 0x2
     field public static final int CLASSIFICATION_NONE = 0; // 0x0
+    field public static final int CLASSIFICATION_PINCH = 5; // 0x5
     field public static final int CLASSIFICATION_TWO_FINGER_SWIPE = 3; // 0x3
     field @NonNull public static final android.os.Parcelable.Creator<android.view.MotionEvent> CREATOR;
     field public static final int EDGE_BOTTOM = 2; // 0x2
@@ -59275,6 +59266,7 @@
     method public void append(CharSequence, int, int);
     method public void beginBatchEdit();
     method public boolean bringPointIntoView(int);
+    method public boolean bringPointIntoView(@IntRange(from=0) int, boolean);
     method public void clearComposingText();
     method public void debug(int);
     method public boolean didTouchFocusSelect();
@@ -59312,6 +59304,8 @@
     method public int getExtendedPaddingTop();
     method public android.text.InputFilter[] getFilters();
     method public int getFirstBaselineToTopHeight();
+    method @ColorInt public int getFocusedSearchResultHighlightColor();
+    method public int getFocusedSearchResultIndex();
     method @Nullable public String getFontFeatureSettings();
     method @Nullable public String getFontVariationSettings();
     method public boolean getFreezesText();
@@ -59356,6 +59350,8 @@
     method public android.text.TextPaint getPaint();
     method public int getPaintFlags();
     method public String getPrivateImeOptions();
+    method @ColorInt public int getSearchResultHighlightColor();
+    method @Nullable public int[] getSearchResultHighlights();
     method public int getSelectionEnd();
     method public int getSelectionStart();
     method @ColorInt public int getShadowColor();
@@ -59440,6 +59436,8 @@
     method public void setFallbackLineSpacing(boolean);
     method public void setFilters(android.text.InputFilter[]);
     method public void setFirstBaselineToTopHeight(@IntRange(from=0) @Px int);
+    method public void setFocusedSearchResultHighlightColor(@ColorInt int);
+    method public void setFocusedSearchResultIndex(int);
     method public void setFontFeatureSettings(@Nullable String);
     method public boolean setFontVariationSettings(@Nullable String);
     method protected boolean setFrame(int, int, int, int);
@@ -59487,6 +59485,8 @@
     method public void setPrivateImeOptions(String);
     method public void setRawInputType(int);
     method public void setScroller(android.widget.Scroller);
+    method public void setSearchResultHighlightColor(@ColorInt int);
+    method public void setSearchResultHighlights(@Nullable int...);
     method public void setSelectAllOnFocus(boolean);
     method public void setShadowLayer(float, float, float, int);
     method public final void setShowSoftInputOnFocus(boolean);
@@ -59526,6 +59526,7 @@
     method public void setWidth(int);
     field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
     field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1
+    field public static final int FOCUSED_SEARCH_RESULT_INDEX_NONE = -1; // 0xffffffff
   }
 
   public enum TextView.BufferType {
diff --git a/core/api/removed.txt b/core/api/removed.txt
index 1fa1e89..5c4fd10 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -252,6 +252,34 @@
 
 }
 
+package android.nfc {
+
+  public final class NfcAdapter {
+    method @Deprecated public void disableForegroundNdefPush(android.app.Activity);
+    method @Deprecated public void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
+    method @Deprecated public boolean invokeBeam(android.app.Activity);
+    method @Deprecated public boolean isNdefPushEnabled();
+    method @Deprecated public void setBeamPushUris(android.net.Uri[], android.app.Activity);
+    method @Deprecated public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
+    method @Deprecated public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
+    method @Deprecated public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...);
+    method @Deprecated public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
+  }
+
+  @Deprecated public static interface NfcAdapter.CreateBeamUrisCallback {
+    method public android.net.Uri[] createBeamUris(android.nfc.NfcEvent);
+  }
+
+  @Deprecated public static interface NfcAdapter.CreateNdefMessageCallback {
+    method public android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent);
+  }
+
+  @Deprecated public static interface NfcAdapter.OnNdefPushCompleteCallback {
+    method public void onNdefPushComplete(android.nfc.NfcEvent);
+  }
+
+}
+
 package android.os {
 
   public class BatteryManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 85f8813..aa7087e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -9668,18 +9668,14 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean addNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler, String[]);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disableNdefPush();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
-    method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
-    field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
   }
 
   public static interface NfcAdapter.ControllerAlwaysOnListener {
@@ -12588,6 +12584,11 @@
 
   public static final class VisualQueryDetectionService.Callback {
     ctor public VisualQueryDetectionService.Callback();
+    method public void onAttentionGained();
+    method public void onAttentionLost();
+    method public void onQueryDetected(@NonNull String) throws java.lang.IllegalStateException;
+    method public void onQueryFinished() throws java.lang.IllegalStateException;
+    method public void onQueryRejected() throws java.lang.IllegalStateException;
   }
 
   public class VoiceInteractionService extends android.app.Service {
@@ -13845,6 +13846,7 @@
     method @Deprecated public boolean disableCellBroadcastRange(int, int, int);
     method @Deprecated public boolean enableCellBroadcastRange(int, int, int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getPremiumSmsConsent(@NonNull String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.net.Uri getSmscIdentity();
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS) public void resetAllCellBroadcastRanges();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPremiumSmsConsent(@NonNull String, int);
@@ -15757,9 +15759,9 @@
 
   public static interface RcsUceAdapter.CapabilitiesCallback {
     method public void onCapabilitiesReceived(@NonNull java.util.List<android.telephony.ims.RcsContactUceCapability>);
-    method @Deprecated public void onComplete();
+    method public default void onComplete();
     method public default void onComplete(@Nullable android.telephony.ims.SipDetails);
-    method @Deprecated public void onError(int, long);
+    method public default void onError(int, long);
     method public default void onError(int, long, @Nullable android.telephony.ims.SipDetails);
   }
 
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index 2c5acf1..1c10356 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -140,6 +140,17 @@
 
 }
 
+package android.nfc {
+
+  public final class NfcAdapter {
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disableNdefPush();
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush();
+    method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
+    field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
+  }
+
+}
+
 package android.os {
 
   public class Build {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 84ac868..0b4a708 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3326,7 +3326,9 @@
 package android.view.inputmethod {
 
   public abstract class HandwritingGesture {
+    method @NonNull public static android.view.inputmethod.HandwritingGesture fromByteArray(@NonNull byte[]);
     method public final int getGestureType();
+    method @NonNull public final byte[] toByteArray();
     field public static final int GESTURE_TYPE_DELETE = 4; // 0x4
     field public static final int GESTURE_TYPE_DELETE_RANGE = 64; // 0x40
     field public static final int GESTURE_TYPE_INSERT = 2; // 0x2
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 3e21124..a381fea 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -30,6 +30,7 @@
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
 import android.content.Intent;
+import android.content.ServiceConnection;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityPresentationInfo;
 import android.content.pm.ApplicationInfo;
@@ -922,4 +923,30 @@
      * @param callingPid The PID mapped with the callback.
      */
     public abstract void unregisterStrictModeCallback(int callingPid);
+
+    /**
+     * Start a foreground service delegate.
+     * @param options foreground service delegate options.
+     * @param connection a service connection served as callback to caller.
+     * @return true if delegate is started successfully, false otherwise.
+     * @hide
+     */
+    public abstract boolean startForegroundServiceDelegate(
+            @NonNull ForegroundServiceDelegationOptions options,
+            @Nullable ServiceConnection connection);
+
+    /**
+     * Stop a foreground service delegate.
+     * @param options the foreground service delegate options.
+     * @hide
+     */
+    public abstract void stopForegroundServiceDelegate(
+            @NonNull ForegroundServiceDelegationOptions options);
+
+    /**
+     * Stop a foreground service delegate by service connection.
+     * @param connection service connection used to start delegate previously.
+     * @hide
+     */
+    public abstract void stopForegroundServiceDelegate(@NonNull ServiceConnection connection);
 }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 563f6d4..a14f3d3 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2489,7 +2489,7 @@
                 "RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO").setDefaultMode(
                 AppOpsManager.MODE_ALLOWED).build(),
         new AppOpInfo.Builder(OP_RUN_LONG_JOBS, OPSTR_RUN_LONG_JOBS, "RUN_LONG_JOBS")
-                .setPermission(Manifest.permission.RUN_LONG_JOBS).build(),
+                .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
             new AppOpInfo.Builder(OP_READ_MEDIA_VISUAL_USER_SELECTED,
                     OPSTR_READ_MEDIA_VISUAL_USER_SELECTED, "READ_MEDIA_VISUAL_USER_SELECTED")
                     .setPermission(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index e654b56..b91fa35 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3048,8 +3048,11 @@
             throw new UnsupportedOperationException(
                     "Cannot update device ID on a Context created with createDeviceContext()");
         }
-        mDeviceId = updatedDeviceId;
-        notifyOnDeviceChangedListeners(updatedDeviceId);
+
+        if (mDeviceId != updatedDeviceId) {
+            mDeviceId = updatedDeviceId;
+            notifyOnDeviceChangedListeners(updatedDeviceId);
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java b/core/java/android/app/ForegroundServiceDelegationOptions.java
similarity index 91%
rename from services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java
rename to core/java/android/app/ForegroundServiceDelegationOptions.java
index 5eb5a55..875e01f 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java
+++ b/core/java/android/app/ForegroundServiceDelegationOptions.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.server.am;
+package android.app;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.IApplicationThread;
 import android.content.ComponentName;
 
 import java.lang.annotation.Retention;
@@ -32,6 +31,8 @@
  * is higher than the app's actual process state if the app is in the background. This can help to
  * keep the app in the memory and extra run-time.
  * The app does not need to define an actual service component nor add it into manifest file.
+ *
+ * @hide
  */
 public class ForegroundServiceDelegationOptions {
 
@@ -191,6 +192,11 @@
         }
     }
 
+    /**
+     * The helper class to build the instance of {@link ForegroundServiceDelegate}.
+     *
+     * @hide
+     */
     public static class Builder {
         int mClientPid; // The actual app PID
         int mClientUid; // The actual app UID
@@ -202,51 +208,81 @@
         int mForegroundServiceTypes; // The foreground service types it consists of
         @DelegationService int mDelegationService; // The internal service's name, i.e. VOIP
 
+        /**
+         * Set the client app's PID.
+         */
         public Builder setClientPid(int clientPid) {
             mClientPid = clientPid;
             return this;
         }
 
+        /**
+         * Set the client app's UID.
+         */
         public Builder setClientUid(int clientUid) {
             mClientUid = clientUid;
             return this;
         }
 
+        /**
+         * Set the client app's package name.
+         */
         public Builder setClientPackageName(@NonNull String clientPackageName) {
             mClientPackageName = clientPackageName;
             return this;
         }
 
+        /**
+         * Set the notification ID from the client app.
+         */
         public Builder setClientNotificationId(int clientNotificationId) {
             mClientNotificationId = clientNotificationId;
             return this;
         }
 
+        /**
+         * Set the client app's application thread.
+         */
         public Builder setClientAppThread(@NonNull IApplicationThread clientAppThread) {
             mClientAppThread = clientAppThread;
             return this;
         }
 
+        /**
+         * Set the client instance of this service.
+         */
         public Builder setClientInstanceName(@NonNull String clientInstanceName) {
             mClientInstanceName = clientInstanceName;
             return this;
         }
 
+        /**
+         * Set stickiness of this service.
+         */
         public Builder setSticky(boolean isSticky) {
             mSticky = isSticky;
             return this;
         }
 
+        /**
+         * Set the foreground service type.
+         */
         public Builder setForegroundServiceTypes(int foregroundServiceTypes) {
             mForegroundServiceTypes = foregroundServiceTypes;
             return this;
         }
 
+        /**
+         * Set the delegation service type.
+         */
         public Builder setDelegationService(@DelegationService int delegationService) {
             mDelegationService = delegationService;
             return this;
         }
 
+        /**
+         * @return An instance of {@link ForegroundServiceDelegationOptions}.
+         */
         public ForegroundServiceDelegationOptions build() {
             return new ForegroundServiceDelegationOptions(mClientPid,
                 mClientUid,
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index f0c39ab..c19a865 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -259,12 +259,15 @@
                 new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE)
             }, true),
             new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                new RegularPermission(Manifest.permission.BLUETOOTH_ADVERTISE),
                 new RegularPermission(Manifest.permission.BLUETOOTH_CONNECT),
+                new RegularPermission(Manifest.permission.BLUETOOTH_SCAN),
                 new RegularPermission(Manifest.permission.CHANGE_NETWORK_STATE),
                 new RegularPermission(Manifest.permission.CHANGE_WIFI_STATE),
                 new RegularPermission(Manifest.permission.CHANGE_WIFI_MULTICAST_STATE),
                 new RegularPermission(Manifest.permission.NFC),
                 new RegularPermission(Manifest.permission.TRANSMIT_IR),
+                new RegularPermission(Manifest.permission.UWB_RANGING),
                 new UsbDevicePermission(),
                 new UsbAccessoryPermission(),
             }, false)
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index f2eced3..20869e0 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -47,6 +47,9 @@
 # AppOps
 per-file *AppOp* = file:/core/java/android/permission/OWNERS
 
+# Backup and Restore
+per-file IBackupAgent.aidl = file:/services/backup/OWNERS
+
 # LocaleManager
 per-file *Locale* = file:/services/core/java/com/android/server/locales/OWNERS
 
diff --git a/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS b/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
index 0ec8253..270cb18 100644
--- a/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
+++ b/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
@@ -1,5 +1,4 @@
 rubinxu@google.com
-acjohnston@google.com
 pgrafov@google.com
 ayushsha@google.com
 alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/EnterprisePlatform_OWNERS b/core/java/android/app/admin/EnterprisePlatform_OWNERS
index fb00fe5..6ce25cc 100644
--- a/core/java/android/app/admin/EnterprisePlatform_OWNERS
+++ b/core/java/android/app/admin/EnterprisePlatform_OWNERS
@@ -1,2 +1,5 @@
+# Assign bugs to android-enterprise-triage@google.com
 file:WorkDeviceExperience_OWNERS
+file:Provisioning_OWNERS
+file:WorkProfile_OWNERS
 file:EnterprisePlatformSecurity_OWNERS
\ No newline at end of file
diff --git a/core/java/android/app/admin/Provisioning_OWNERS b/core/java/android/app/admin/Provisioning_OWNERS
new file mode 100644
index 0000000..c59a9dc
--- /dev/null
+++ b/core/java/android/app/admin/Provisioning_OWNERS
@@ -0,0 +1,5 @@
+# Assign bugs to android-enterprise-triage@google.com
+petuska@google.com
+nupursn@google.com
+shreyacsingh@google.com
+alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/WorkDeviceExperience_OWNERS b/core/java/android/app/admin/WorkDeviceExperience_OWNERS
index 82afddd..2033343 100644
--- a/core/java/android/app/admin/WorkDeviceExperience_OWNERS
+++ b/core/java/android/app/admin/WorkDeviceExperience_OWNERS
@@ -1,5 +1,7 @@
+# Assign bugs to android-enterprise-triage@google.com
 work-device-experience+reviews@google.com
-scottjonathan@google.com
-kholoudm@google.com
-eliselliott@google.com
+scottjonathan@google.com #{LAST_RESORT_SUGGESTION}
+eliselliott@google.com #{LAST_RESORT_SUGGESTION}
+kholoudm@google.com #{LAST_RESORT_SUGGESTION}
+acjohnston@google.com #{LAST_RESORT_SUGGESTION}
 alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/core/java/android/app/admin/WorkProfile_OWNERS b/core/java/android/app/admin/WorkProfile_OWNERS
new file mode 100644
index 0000000..260b672
--- /dev/null
+++ b/core/java/android/app/admin/WorkProfile_OWNERS
@@ -0,0 +1,5 @@
+# Assign bugs to android-enterprise-triage@google.com
+liahav@google.com
+olit@google.com
+scottjonathan@google.com #{LAST_RESORT_SUGGESTION}
+alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index bad282e..ddeb2f9 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -16,7 +16,6 @@
 
 package android.app.backup;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -38,8 +37,6 @@
 import android.util.Log;
 import android.util.Pair;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
@@ -410,6 +407,33 @@
     }
 
     /**
+     * Enable/disable the framework backup scheduling entirely for the current user. When disabled,
+     * no Key/Value or Full backup jobs will be scheduled by the Android framework.
+     *
+     * <p>Note: This does not disable backups: only their scheduling is affected and backups can
+     * still be triggered manually.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
+     * @hide
+     */
+    @RequiresPermission(allOf = {android.Manifest.permission.BACKUP,
+            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)
+    public void setFrameworkSchedulingEnabled(boolean isEnabled) {
+        checkServiceBinder();
+        if (sService == null) {
+            Log.e(TAG, "setFrameworkSchedulingEnabled() couldn't connect");
+            return;
+        }
+
+        try {
+            sService.setFrameworkSchedulingEnabledForUser(mContext.getUserId(), isEnabled);
+        } catch (RemoteException e) {
+            Log.e(TAG, "setFrameworkSchedulingEnabled() couldn't connect");
+        }
+    }
+
+    /**
      * Report whether the backup mechanism is currently enabled.
      *
      * @hide
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index aeb4987..041c2a7 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -155,6 +155,22 @@
      */
     void setBackupEnabledForUser(int userId, boolean isEnabled);
 
+
+    /**
+     * Enable/disable the framework backup scheduling entirely. When disabled, no Key/Value or Full
+     * backup jobs will be scheduled by the Android framework.
+     *
+     * <p>Note: This does not disable backups: only their scheduling is affected and backups can
+     * still be triggered manually.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method. If
+     * {@code userId} is different from the calling user id, then the caller must additionally hold
+     * the android.permission.INTERACT_ACROSS_USERS_FULL permission.
+     *
+     * @param userId The user for which backup scheduling should be enabled/disabled.
+     */
+    void setFrameworkSchedulingEnabledForUser(int userId, boolean isEnabled);
+
     /**
      * {@link android.app.backup.IBackupManager.setBackupEnabledForUser} for the calling user id.
      */
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 4318c39..77cf49d 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3587,17 +3587,6 @@
     public static final String ACTION_MEDIA_BUTTON = "android.intent.action.MEDIA_BUTTON";
 
     /**
-     * Broadcast action: Launch System output switcher. Includes a single extra field,
-     * {@link #EXTRA_PACKAGE_NAME}, which specifies the package name of the calling app
-     * so that the system can get the corresponding MediaSession for the output switcher.
-     *
-     * @see #EXTRA_PACKAGE_NAME
-     */
-    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_SHOW_OUTPUT_SWITCHER =
-            "android.intent.action.SHOW_OUTPUT_SWITCHER";
-
-    /**
      * Broadcast Action:  The "Camera Button" was pressed.  Includes a single
      * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
      * caused the broadcast.
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index 9dd9c0f..9c1318e 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -64,7 +64,7 @@
 
     void requestUserPreapproval(in PackageInstaller.PreapprovalDetails details, in IntentSender statusReceiver);
 
-    boolean isKeepApplicationEnabledSetting();
+    boolean isApplicationEnabledSettingPersistent();
     boolean isRequestUpdateOwnership();
 
     ParcelFileDescriptor getAppMetadataFd();
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index df1340d..0b74dd1 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2010,9 +2010,9 @@
          * @return {@code true} if this session will keep the existing application enabled setting
          * after installation.
          */
-        public boolean isKeepApplicationEnabledSetting() {
+        public boolean isApplicationEnabledSettingPersistent() {
             try {
-                return mSession.isKeepApplicationEnabledSetting();
+                return mSession.isApplicationEnabledSettingPersistent();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -2265,7 +2265,7 @@
         /** {@hide} */
         public int requireUserAction = USER_ACTION_UNSPECIFIED;
         /** {@hide} */
-        public boolean keepApplicationEnabledSetting = false;
+        public boolean applicationEnabledSettingPersistent = false;
 
         /**
          * Construct parameters for a new package install session.
@@ -2310,7 +2310,7 @@
             rollbackDataPolicy = source.readInt();
             requireUserAction = source.readInt();
             packageSource = source.readInt();
-            keepApplicationEnabledSetting = source.readBoolean();
+            applicationEnabledSettingPersistent = source.readBoolean();
         }
 
         /** {@hide} */
@@ -2341,7 +2341,7 @@
             ret.rollbackDataPolicy = rollbackDataPolicy;
             ret.requireUserAction = requireUserAction;
             ret.packageSource = packageSource;
-            ret.keepApplicationEnabledSetting = keepApplicationEnabledSetting;
+            ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
             return ret;
         }
 
@@ -2839,8 +2839,8 @@
          * Request to keep the original application enabled setting. This will prevent the
          * application from being enabled if it was previously in a disabled state.
          */
-        public void setKeepApplicationEnabledSetting() {
-            this.keepApplicationEnabledSetting = true;
+        public void setApplicationEnabledSettingPersistent() {
+            this.applicationEnabledSettingPersistent = true;
         }
 
         /**
@@ -2895,7 +2895,8 @@
             pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode);
             pw.printPair("dataLoaderParams", dataLoaderParams);
             pw.printPair("rollbackDataPolicy", rollbackDataPolicy);
-            pw.printPair("keepApplicationEnabledSetting", keepApplicationEnabledSetting);
+            pw.printPair("applicationEnabledSettingPersistent",
+                    applicationEnabledSettingPersistent);
             pw.println();
         }
 
@@ -2936,7 +2937,7 @@
             dest.writeInt(rollbackDataPolicy);
             dest.writeInt(requireUserAction);
             dest.writeInt(packageSource);
-            dest.writeBoolean(keepApplicationEnabledSetting);
+            dest.writeBoolean(applicationEnabledSettingPersistent);
         }
 
         public static final Parcelable.Creator<SessionParams>
@@ -3139,7 +3140,7 @@
         public boolean isPreapprovalRequested;
 
         /** @hide */
-        public boolean keepApplicationEnabledSetting;
+        public boolean applicationEnabledSettingPersistent;
 
         /** @hide */
         public int pendingUserActionReason;
@@ -3197,7 +3198,7 @@
             requireUserAction = source.readInt();
             installerUid = source.readInt();
             packageSource = source.readInt();
-            keepApplicationEnabledSetting = source.readBoolean();
+            applicationEnabledSettingPersistent = source.readBoolean();
             pendingUserActionReason = source.readInt();
         }
 
@@ -3732,8 +3733,8 @@
          * Returns {@code true} if this session will keep the existing application enabled setting
          * after installation.
          */
-        public boolean isKeepApplicationEnabledSetting() {
-            return keepApplicationEnabledSetting;
+        public boolean isApplicationEnabledSettingPersistent() {
+            return applicationEnabledSettingPersistent;
         }
 
         /**
@@ -3811,7 +3812,7 @@
             dest.writeInt(requireUserAction);
             dest.writeInt(installerUid);
             dest.writeInt(packageSource);
-            dest.writeBoolean(keepApplicationEnabledSetting);
+            dest.writeBoolean(applicationEnabledSettingPersistent);
             dest.writeInt(pendingUserActionReason);
         }
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index fe06366..900454d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1552,7 +1552,7 @@
      *
      * @hide
      */
-    public static final int INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK = 0x00800000;
+    public static final int INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK = 0x01000000;
 
     /**
      * Flag parameter for {@link PackageInstaller.SessionParams} to indicate that the
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
index 6f07dd7..f900440 100644
--- a/core/java/android/content/pm/ResolveInfo.java
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
@@ -107,6 +108,14 @@
     public int match;
 
     /**
+     * UserHandle of originating user for ResolveInfo. This will help caller distinguish cross
+     * profile results from intent resolution.
+     * @hide
+     */
+    @Nullable
+    public UserHandle userHandle;
+
+    /**
      * Only set when returned by
      * {@link PackageManager#queryIntentActivityOptions}, this tells you
      * which of the given specific intents this result came from.  0 is the
@@ -418,6 +427,7 @@
         handleAllWebDataURI = orig.handleAllWebDataURI;
         mAutoResolutionAllowed = orig.mAutoResolutionAllowed;
         isInstantAppAvailable = orig.isInstantAppAvailable;
+        userHandle = orig.userHandle;
     }
 
     public String toString() {
@@ -441,6 +451,10 @@
             sb.append(" targetUserId=");
             sb.append(targetUserId);
         }
+
+        sb.append(" userHandle=");
+        sb.append(userHandle);
+
         sb.append('}');
         return sb.toString();
     }
@@ -483,6 +497,7 @@
         dest.writeInt(handleAllWebDataURI ? 1 : 0);
         dest.writeInt(mAutoResolutionAllowed ? 1 : 0);
         dest.writeInt(isInstantAppAvailable ? 1 : 0);
+        dest.writeInt(userHandle != null ? userHandle.getIdentifier() : UserHandle.USER_CURRENT);
     }
 
     public static final @android.annotation.NonNull Creator<ResolveInfo> CREATOR
@@ -532,6 +547,10 @@
         handleAllWebDataURI = source.readInt() != 0;
         mAutoResolutionAllowed = source.readInt() != 0;
         isInstantAppAvailable = source.readInt() != 0;
+        int userHandleId = source.readInt();
+        if (userHandleId != UserHandle.USER_CURRENT) {
+            userHandle = UserHandle.of(userHandleId);
+        }
     }
 
     public static class DisplayNameComparator
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 4e2acc0..0b503eb 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -214,12 +214,15 @@
      * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
      * {@link android.Manifest.permission#FOREGROUND_SERVICE_CONNECTED_DEVICE} and one of the
      * following permissions:
+     * {@link android.Manifest.permission#BLUETOOTH_ADVERTISE},
      * {@link android.Manifest.permission#BLUETOOTH_CONNECT},
+     * {@link android.Manifest.permission#BLUETOOTH_SCAN},
      * {@link android.Manifest.permission#CHANGE_NETWORK_STATE},
      * {@link android.Manifest.permission#CHANGE_WIFI_STATE},
      * {@link android.Manifest.permission#CHANGE_WIFI_MULTICAST_STATE},
      * {@link android.Manifest.permission#NFC},
      * {@link android.Manifest.permission#TRANSMIT_IR},
+     * {@link android.Manifest.permission#UWB_RANGING},
      * or has been granted the access to one of the attached USB devices/accessories.
      */
     @RequiresPermission(
@@ -227,12 +230,15 @@
                 Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE,
             },
             anyOf = {
+                Manifest.permission.BLUETOOTH_ADVERTISE,
                 Manifest.permission.BLUETOOTH_CONNECT,
+                Manifest.permission.BLUETOOTH_SCAN,
                 Manifest.permission.CHANGE_NETWORK_STATE,
                 Manifest.permission.CHANGE_WIFI_STATE,
                 Manifest.permission.CHANGE_WIFI_MULTICAST_STATE,
                 Manifest.permission.NFC,
                 Manifest.permission.TRANSMIT_IR,
+                Manifest.permission.UWB_RANGING,
             },
             conditional = true
     )
diff --git a/core/java/android/credentials/GetCredentialResponse.java b/core/java/android/credentials/GetCredentialResponse.java
index 576da8b..4f8b026 100644
--- a/core/java/android/credentials/GetCredentialResponse.java
+++ b/core/java/android/credentials/GetCredentialResponse.java
@@ -19,7 +19,6 @@
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -33,14 +32,14 @@
     /**
      * The credential that can be used to authenticate the user.
      */
-    @Nullable
+    @NonNull
     private final Credential mCredential;
 
     /**
      * Returns the credential that can be used to authenticate the user, or {@code null} if no
      * credential is available.
      */
-    @Nullable
+    @NonNull
     public Credential getCredential() {
         return mCredential;
     }
@@ -69,13 +68,6 @@
         mCredential = requireNonNull(credential, "credential must not be null");
     }
 
-    /**
-     * Constructs a {@link GetCredentialResponse}.
-     */
-    public GetCredentialResponse() {
-        mCredential = null;
-    }
-
     private GetCredentialResponse(@NonNull Parcel in) {
         Credential credential = in.readTypedObject(Credential.CREATOR);
         mCredential = credential;
diff --git a/core/java/android/hardware/input/KeyboardLayout.java b/core/java/android/hardware/input/KeyboardLayout.java
index 58f7759..0311da4 100644
--- a/core/java/android/hardware/input/KeyboardLayout.java
+++ b/core/java/android/hardware/input/KeyboardLayout.java
@@ -23,6 +23,7 @@
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Describes a keyboard layout.
@@ -30,6 +31,37 @@
  * @hide
  */
 public final class KeyboardLayout implements Parcelable, Comparable<KeyboardLayout> {
+
+    /** Undefined keyboard layout */
+    public static final String LAYOUT_TYPE_UNDEFINED = "undefined";
+
+    /** Qwerty-based keyboard layout */
+    public static final String LAYOUT_TYPE_QWERTY = "qwerty";
+
+    /** Qwertz-based keyboard layout */
+    public static final String LAYOUT_TYPE_QWERTZ = "qwertz";
+
+    /** Azerty-based keyboard layout */
+    public static final String LAYOUT_TYPE_AZERTY = "azerty";
+
+    /** Dvorak keyboard layout */
+    public static final String LAYOUT_TYPE_DVORAK = "dvorak";
+
+    /** Colemak keyboard layout */
+    public static final String LAYOUT_TYPE_COLEMAK = "colemak";
+
+    /** Workman keyboard layout */
+    public static final String LAYOUT_TYPE_WORKMAN = "workman";
+
+    /** Turkish-F keyboard layout */
+    public static final String LAYOUT_TYPE_TURKISH_F = "turkish_f";
+
+    /** Turkish-Q keyboard layout */
+    public static final String LAYOUT_TYPE_TURKISH_Q = "turkish_q";
+
+    /** Keyboard layout that has been enhanced with a large number of extra characters */
+    public static final String LAYOUT_TYPE_EXTENDED = "extended";
+
     private final String mDescriptor;
     private final String mLabel;
     private final String mCollection;
@@ -42,31 +74,24 @@
 
     /** Currently supported Layout types in the KCM files */
     private enum LayoutType {
-        UNDEFINED(0, "undefined"),
-        QWERTY(1, "qwerty"),
-        QWERTZ(2, "qwertz"),
-        AZERTY(3, "azerty"),
-        DVORAK(4, "dvorak"),
-        COLEMAK(5, "colemak"),
-        WORKMAN(6, "workman"),
-        TURKISH_F(7, "turkish_f"),
-        TURKISH_Q(8, "turkish_q"),
-        EXTENDED(9, "extended");
+        UNDEFINED(0, LAYOUT_TYPE_UNDEFINED),
+        QWERTY(1, LAYOUT_TYPE_QWERTY),
+        QWERTZ(2, LAYOUT_TYPE_QWERTZ),
+        AZERTY(3, LAYOUT_TYPE_AZERTY),
+        DVORAK(4, LAYOUT_TYPE_DVORAK),
+        COLEMAK(5, LAYOUT_TYPE_COLEMAK),
+        WORKMAN(6, LAYOUT_TYPE_WORKMAN),
+        TURKISH_F(7, LAYOUT_TYPE_TURKISH_F),
+        TURKISH_Q(8, LAYOUT_TYPE_TURKISH_Q),
+        EXTENDED(9, LAYOUT_TYPE_EXTENDED);
 
         private final int mValue;
         private final String mName;
         private static final Map<Integer, LayoutType> VALUE_TO_ENUM_MAP = new HashMap<>();
         static {
-            VALUE_TO_ENUM_MAP.put(UNDEFINED.mValue, UNDEFINED);
-            VALUE_TO_ENUM_MAP.put(QWERTY.mValue, QWERTY);
-            VALUE_TO_ENUM_MAP.put(QWERTZ.mValue, QWERTZ);
-            VALUE_TO_ENUM_MAP.put(AZERTY.mValue, AZERTY);
-            VALUE_TO_ENUM_MAP.put(DVORAK.mValue, DVORAK);
-            VALUE_TO_ENUM_MAP.put(COLEMAK.mValue, COLEMAK);
-            VALUE_TO_ENUM_MAP.put(WORKMAN.mValue, WORKMAN);
-            VALUE_TO_ENUM_MAP.put(TURKISH_F.mValue, TURKISH_F);
-            VALUE_TO_ENUM_MAP.put(TURKISH_Q.mValue, TURKISH_Q);
-            VALUE_TO_ENUM_MAP.put(EXTENDED.mValue, EXTENDED);
+            for (LayoutType type : LayoutType.values()) {
+                VALUE_TO_ENUM_MAP.put(type.mValue, type);
+            }
         }
 
         private static LayoutType of(int value) {
@@ -207,6 +232,9 @@
         // keyboards to be listed before lower priority keyboards.
         int result = Integer.compare(another.mPriority, mPriority);
         if (result == 0) {
+            result = Integer.compare(mLayoutType.mValue, another.mLayoutType.mValue);
+        }
+        if (result == 0) {
             result = mLabel.compareToIgnoreCase(another.mLabel);
         }
         if (result == 0) {
@@ -226,4 +254,21 @@
                 + ", vendorId: " + mVendorId
                 + ", productId: " + mProductId;
     }
+
+    /**
+     * Check if the provided layout type is supported/valid.
+     *
+     * @param layoutName name of layout type
+     * @return {@code true} if the provided layout type is supported/valid.
+     */
+    public static boolean isLayoutTypeValid(@NonNull String layoutName) {
+        Objects.requireNonNull(layoutName, "Provided layout name should not be null");
+        for (LayoutType layoutType : LayoutType.values()) {
+            if (layoutName.equals(layoutType.getName())) {
+                return true;
+            }
+        }
+        // Layout doesn't match any supported layout types
+        return false;
+    }
 }
diff --git a/core/java/android/nfc/BeamShareData.aidl b/core/java/android/nfc/BeamShareData.aidl
deleted file mode 100644
index a47e240..0000000
--- a/core/java/android/nfc/BeamShareData.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2013 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.nfc;
-
-parcelable BeamShareData;
diff --git a/core/java/android/nfc/BeamShareData.java b/core/java/android/nfc/BeamShareData.java
deleted file mode 100644
index 6a40f98..0000000
--- a/core/java/android/nfc/BeamShareData.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package android.nfc;
-
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.UserHandle;
-
-/**
- * Class to IPC data to be shared over Android Beam.
- * Allows bundling NdefMessage, Uris and flags in a single
- * IPC call. This is important as we want to reduce the
- * amount of IPC calls at "touch time".
- * @hide
- */
-public final class BeamShareData implements Parcelable {
-    public final NdefMessage ndefMessage;
-    public final Uri[] uris;
-    public final UserHandle userHandle;
-    public final int flags;
-
-    public BeamShareData(NdefMessage msg, Uri[] uris, UserHandle userHandle, int flags) {
-        this.ndefMessage = msg;
-        this.uris = uris;
-        this.userHandle = userHandle;
-        this.flags = flags;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        int urisLength = (uris != null) ? uris.length : 0;
-        dest.writeParcelable(ndefMessage, 0);
-        dest.writeInt(urisLength);
-        if (urisLength > 0) {
-            dest.writeTypedArray(uris, 0);
-        }
-        dest.writeParcelable(userHandle, 0);
-        dest.writeInt(this.flags);
-    }
-
-    public static final @android.annotation.NonNull Parcelable.Creator<BeamShareData> CREATOR =
-            new Parcelable.Creator<BeamShareData>() {
-        @Override
-        public BeamShareData createFromParcel(Parcel source) {
-            Uri[] uris = null;
-            NdefMessage msg = source.readParcelable(NdefMessage.class.getClassLoader(), android.nfc.NdefMessage.class);
-            int numUris = source.readInt();
-            if (numUris > 0) {
-                uris = new Uri[numUris];
-                source.readTypedArray(uris, Uri.CREATOR);
-            }
-            UserHandle userHandle = source.readParcelable(UserHandle.class.getClassLoader(), android.os.UserHandle.class);
-            int flags = source.readInt();
-
-            return new BeamShareData(msg, uris, userHandle, flags);
-        }
-
-        @Override
-        public BeamShareData[] newArray(int size) {
-            return new BeamShareData[size];
-        }
-    };
-}
diff --git a/core/java/android/nfc/IAppCallback.aidl b/core/java/android/nfc/IAppCallback.aidl
index 133146d..b06bf06 100644
--- a/core/java/android/nfc/IAppCallback.aidl
+++ b/core/java/android/nfc/IAppCallback.aidl
@@ -16,7 +16,6 @@
 
 package android.nfc;
 
-import android.nfc.BeamShareData;
 import android.nfc.Tag;
 
 /**
@@ -24,7 +23,5 @@
  */
 interface IAppCallback
 {
-    BeamShareData createBeamShareData(byte peerLlcpVersion);
-    oneway void onNdefPushComplete(byte peerLlcpVersion);
     oneway void onTagDiscovered(in Tag tag);
 }
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index de107a2..f6aa4b4 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -18,7 +18,6 @@
 
 import android.app.PendingIntent;
 import android.content.IntentFilter;
-import android.nfc.BeamShareData;
 import android.nfc.NdefMessage;
 import android.nfc.Tag;
 import android.nfc.TechListParcel;
@@ -47,24 +46,18 @@
     int getState();
     boolean disable(boolean saveState);
     boolean enable();
-    boolean enableNdefPush();
-    boolean disableNdefPush();
-    boolean isNdefPushEnabled();
     void pausePolling(int timeoutInMs);
     void resumePolling();
 
     void setForegroundDispatch(in PendingIntent intent,
             in IntentFilter[] filters, in TechListParcel techLists);
     void setAppCallback(in IAppCallback callback);
-    oneway void invokeBeam();
-    oneway void invokeBeamInternal(in BeamShareData shareData);
 
     boolean ignore(int nativeHandle, int debounceMs, ITagRemovedCallback callback);
 
     void dispatch(in Tag tag);
 
     void setReaderMode (IBinder b, IAppCallback callback, int flags, in Bundle extras);
-    void setP2pModes(int initatorModes, int targetModes);
 
     void addNfcUnlockHandler(INfcUnlockHandler unlockHandler, in int[] techList);
     void removeNfcUnlockHandler(INfcUnlockHandler unlockHandler);
diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java
index 911aaf3..8d75cac 100644
--- a/core/java/android/nfc/NfcActivityManager.java
+++ b/core/java/android/nfc/NfcActivityManager.java
@@ -19,9 +19,6 @@
 import android.app.Activity;
 import android.app.Application;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.ContentProvider;
-import android.content.Intent;
-import android.net.Uri;
 import android.nfc.NfcAdapter.ReaderCallback;
 import android.os.Binder;
 import android.os.Bundle;
@@ -110,14 +107,8 @@
     class NfcActivityState {
         boolean resumed = false;
         Activity activity;
-        NdefMessage ndefMessage = null;  // static NDEF message
-        NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null;
-        NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null;
-        NfcAdapter.CreateBeamUrisCallback uriCallback = null;
-        Uri[] uris = null;
-        int flags = 0;
-        int readerModeFlags = 0;
         NfcAdapter.ReaderCallback readerCallback = null;
+        int readerModeFlags = 0;
         Bundle readerModeExtras = null;
         Binder token;
 
@@ -137,24 +128,16 @@
             unregisterApplication(activity.getApplication());
             resumed = false;
             activity = null;
-            ndefMessage = null;
-            ndefMessageCallback = null;
-            onNdefPushCompleteCallback = null;
-            uriCallback = null;
-            uris = null;
+            readerCallback = null;
             readerModeFlags = 0;
+            readerModeExtras = null;
             token = null;
         }
         @Override
         public String toString() {
-            StringBuilder s = new StringBuilder("[").append(" ");
-            s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" ");
-            s.append(uriCallback).append(" ");
-            if (uris != null) {
-                for (Uri uri : uris) {
-                    s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]");
-                }
-            }
+            StringBuilder s = new StringBuilder("[");
+            s.append(readerCallback);
+            s.append("]");
             return s.toString();
         }
     }
@@ -245,92 +228,6 @@
         }
     }
 
-    public void setNdefPushContentUri(Activity activity, Uri[] uris) {
-        boolean isResumed;
-        synchronized (NfcActivityManager.this) {
-            NfcActivityState state = getActivityState(activity);
-            state.uris = uris;
-            isResumed = state.resumed;
-        }
-        if (isResumed) {
-            // requestNfcServiceCallback() verifies permission also
-            requestNfcServiceCallback();
-        } else {
-            // Crash API calls early in case NFC permission is missing
-            verifyNfcPermission();
-        }
-    }
-
-
-    public void setNdefPushContentUriCallback(Activity activity,
-            NfcAdapter.CreateBeamUrisCallback callback) {
-        boolean isResumed;
-        synchronized (NfcActivityManager.this) {
-            NfcActivityState state = getActivityState(activity);
-            state.uriCallback = callback;
-            isResumed = state.resumed;
-        }
-        if (isResumed) {
-            // requestNfcServiceCallback() verifies permission also
-            requestNfcServiceCallback();
-        } else {
-            // Crash API calls early in case NFC permission is missing
-            verifyNfcPermission();
-        }
-    }
-
-    public void setNdefPushMessage(Activity activity, NdefMessage message, int flags) {
-        boolean isResumed;
-        synchronized (NfcActivityManager.this) {
-            NfcActivityState state = getActivityState(activity);
-            state.ndefMessage = message;
-            state.flags = flags;
-            isResumed = state.resumed;
-        }
-        if (isResumed) {
-            // requestNfcServiceCallback() verifies permission also
-            requestNfcServiceCallback();
-        } else {
-            // Crash API calls early in case NFC permission is missing
-            verifyNfcPermission();
-        }
-    }
-
-    public void setNdefPushMessageCallback(Activity activity,
-            NfcAdapter.CreateNdefMessageCallback callback, int flags) {
-        boolean isResumed;
-        synchronized (NfcActivityManager.this) {
-            NfcActivityState state = getActivityState(activity);
-            state.ndefMessageCallback = callback;
-            state.flags = flags;
-            isResumed = state.resumed;
-        }
-        if (isResumed) {
-            // requestNfcServiceCallback() verifies permission also
-            requestNfcServiceCallback();
-        } else {
-            // Crash API calls early in case NFC permission is missing
-            verifyNfcPermission();
-        }
-    }
-
-    public void setOnNdefPushCompleteCallback(Activity activity,
-            NfcAdapter.OnNdefPushCompleteCallback callback) {
-        boolean isResumed;
-        synchronized (NfcActivityManager.this) {
-            NfcActivityState state = getActivityState(activity);
-            state.onNdefPushCompleteCallback = callback;
-            isResumed = state.resumed;
-        }
-        if (isResumed) {
-            // requestNfcServiceCallback() verifies permission also
-            requestNfcServiceCallback();
-        } else {
-            // Crash API calls early in case NFC permission is missing
-            verifyNfcPermission();
-        }
-    }
-
     /**
      * Request or unrequest NFC service callbacks.
      * Makes IPC call - do not hold lock.
@@ -351,86 +248,6 @@
         }
     }
 
-    /** Callback from NFC service, usually on binder thread */
-    @Override
-    public BeamShareData createBeamShareData(byte peerLlcpVersion) {
-        NfcAdapter.CreateNdefMessageCallback ndefCallback;
-        NfcAdapter.CreateBeamUrisCallback urisCallback;
-        NdefMessage message;
-        Activity activity;
-        Uri[] uris;
-        int flags;
-        NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion);
-        synchronized (NfcActivityManager.this) {
-            NfcActivityState state = findResumedActivityState();
-            if (state == null) return null;
-
-            ndefCallback = state.ndefMessageCallback;
-            urisCallback = state.uriCallback;
-            message = state.ndefMessage;
-            uris = state.uris;
-            flags = state.flags;
-            activity = state.activity;
-        }
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            // Make callbacks without lock
-            if (ndefCallback != null) {
-                message = ndefCallback.createNdefMessage(event);
-            }
-            if (urisCallback != null) {
-                uris = urisCallback.createBeamUris(event);
-                if (uris != null) {
-                    ArrayList<Uri> validUris = new ArrayList<Uri>();
-                    for (Uri uri : uris) {
-                        if (uri == null) {
-                            Log.e(TAG, "Uri not allowed to be null.");
-                            continue;
-                        }
-                        String scheme = uri.getScheme();
-                        if (scheme == null || (!scheme.equalsIgnoreCase("file") &&
-                                !scheme.equalsIgnoreCase("content"))) {
-                            Log.e(TAG, "Uri needs to have " +
-                                    "either scheme file or scheme content");
-                            continue;
-                        }
-                        uri = ContentProvider.maybeAddUserId(uri, activity.getUserId());
-                        validUris.add(uri);
-                    }
-
-                    uris = validUris.toArray(new Uri[validUris.size()]);
-                }
-            }
-            if (uris != null && uris.length > 0) {
-                for (Uri uri : uris) {
-                    // Grant the NFC process permission to read these URIs
-                    activity.grantUriPermission("com.android.nfc", uri,
-                            Intent.FLAG_GRANT_READ_URI_PERMISSION);
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-        return new BeamShareData(message, uris, activity.getUser(), flags);
-    }
-
-    /** Callback from NFC service, usually on binder thread */
-    @Override
-    public void onNdefPushComplete(byte peerLlcpVersion) {
-        NfcAdapter.OnNdefPushCompleteCallback callback;
-        synchronized (NfcActivityManager.this) {
-            NfcActivityState state = findResumedActivityState();
-            if (state == null) return;
-
-            callback = state.onNdefPushCompleteCallback;
-        }
-        NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion);
-        // Make callback without lock
-        if (callback != null) {
-            callback.onNdefPushComplete(event);
-        }
-    }
-
     @Override
     public void onTagDiscovered(Tag tag) throws RemoteException {
         NfcAdapter.ReaderCallback callback;
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 0c7f529..c4b3c22 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -338,7 +338,10 @@
      */
     public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
 
-    /** @hide */
+    /**
+     * @hide
+     * @removed
+     */
     @SystemApi
     public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 0x1;
 
@@ -374,7 +377,6 @@
     // Guarded by NfcAdapter.class
     static boolean sIsInitialized = false;
     static boolean sHasNfcFeature;
-    static boolean sHasBeamFeature;
 
     // Final after first constructor, except for
     // attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
@@ -438,7 +440,7 @@
      * A callback to be invoked when the system successfully delivers your {@link NdefMessage}
      * to another device.
      * @see #setOnNdefPushCompleteCallback
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @removed this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
@@ -464,7 +466,7 @@
      * content currently visible to the user. Alternatively, you can call {@link
      * #setNdefPushMessage setNdefPushMessage()} if the {@link NdefMessage} always contains the
      * same data.
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @removed this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
@@ -494,7 +496,7 @@
 
 
      /**
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @removed this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
@@ -526,26 +528,6 @@
     }
 
     /**
-     * Helper to check if this device has FEATURE_NFC_BEAM, but without using
-     * a context.
-     * Equivalent to
-     * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC_BEAM)
-     */
-    private static boolean hasBeamFeature() {
-        IPackageManager pm = ActivityThread.getPackageManager();
-        if (pm == null) {
-            Log.e(TAG, "Cannot get package manager, assuming no Android Beam feature");
-            return false;
-        }
-        try {
-            return pm.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM, 0);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Package manager query failed, assuming no Android Beam feature", e);
-            return false;
-        }
-    }
-
-    /**
      * Helper to check if this device has FEATURE_NFC, but without using
      * a context.
      * Equivalent to
@@ -624,7 +606,6 @@
     public static synchronized NfcAdapter getNfcAdapter(Context context) {
         if (!sIsInitialized) {
             sHasNfcFeature = hasNfcFeature();
-            sHasBeamFeature = hasBeamFeature();
             boolean hasHceFeature = hasNfcHceFeature();
             /* is this device meant to have NFC */
             if (!sHasNfcFeature && !hasHceFeature) {
@@ -1117,7 +1098,7 @@
      * @param uris an array of Uri(s) to push over Android Beam
      * @param activity activity for which the Uri(s) will be pushed
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @removed this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
@@ -1126,26 +1107,7 @@
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return;
-            }
         }
-        if (activity == null) {
-            throw new NullPointerException("activity cannot be null");
-        }
-        if (uris != null) {
-            for (Uri uri : uris) {
-                if (uri == null) throw new NullPointerException("Uri not " +
-                        "allowed to be null");
-                String scheme = uri.getScheme();
-                if (scheme == null || (!scheme.equalsIgnoreCase("file") &&
-                        !scheme.equalsIgnoreCase("content"))) {
-                    throw new IllegalArgumentException("URI needs to have " +
-                            "either scheme file or scheme content");
-                }
-            }
-        }
-        mNfcActivityManager.setNdefPushContentUri(activity, uris);
     }
 
     /**
@@ -1205,7 +1167,7 @@
      * @param callback callback, or null to disable
      * @param activity activity for which the Uri(s) will be pushed
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @removed this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
@@ -1214,14 +1176,7 @@
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return;
-            }
         }
-        if (activity == null) {
-            throw new NullPointerException("activity cannot be null");
-        }
-        mNfcActivityManager.setNdefPushContentUriCallback(activity, callback);
     }
 
     /**
@@ -1295,7 +1250,7 @@
      *        to only register one at a time, and to do so in that activity's
      *        {@link Activity#onCreate}
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @removed this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
@@ -1305,36 +1260,12 @@
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return;
-            }
-        }
-        int targetSdkVersion = getSdkVersion();
-        try {
-            if (activity == null) {
-                throw new NullPointerException("activity cannot be null");
-            }
-            mNfcActivityManager.setNdefPushMessage(activity, message, 0);
-            for (Activity a : activities) {
-                if (a == null) {
-                    throw new NullPointerException("activities cannot contain null");
-                }
-                mNfcActivityManager.setNdefPushMessage(a, message, 0);
-            }
-        } catch (IllegalStateException e) {
-            if (targetSdkVersion < android.os.Build.VERSION_CODES.JELLY_BEAN) {
-                // Less strict on old applications - just log the error
-                Log.e(TAG, "Cannot call API with Activity that has already " +
-                        "been destroyed", e);
-            } else {
-                // Prevent new applications from making this mistake, re-throw
-                throw(e);
-            }
         }
     }
 
     /**
      * @hide
+     * @removed
      */
     @SystemApi
     public void setNdefPushMessage(NdefMessage message, Activity activity, int flags) {
@@ -1343,10 +1274,6 @@
                 throw new UnsupportedOperationException();
             }
         }
-        if (activity == null) {
-            throw new NullPointerException("activity cannot be null");
-        }
-        mNfcActivityManager.setNdefPushMessage(activity, message, flags);
     }
 
     /**
@@ -1414,7 +1341,7 @@
      *        to only register one at a time, and to do so in that activity's
      *        {@link Activity#onCreate}
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @removed this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
@@ -1424,44 +1351,7 @@
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return;
-            }
         }
-        int targetSdkVersion = getSdkVersion();
-        try {
-            if (activity == null) {
-                throw new NullPointerException("activity cannot be null");
-            }
-            mNfcActivityManager.setNdefPushMessageCallback(activity, callback, 0);
-            for (Activity a : activities) {
-                if (a == null) {
-                    throw new NullPointerException("activities cannot contain null");
-                }
-                mNfcActivityManager.setNdefPushMessageCallback(a, callback, 0);
-            }
-        } catch (IllegalStateException e) {
-            if (targetSdkVersion < android.os.Build.VERSION_CODES.JELLY_BEAN) {
-                // Less strict on old applications - just log the error
-                Log.e(TAG, "Cannot call API with Activity that has already " +
-                        "been destroyed", e);
-            } else {
-                // Prevent new applications from making this mistake, re-throw
-                throw(e);
-            }
-        }
-    }
-
-    /**
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
-            int flags) {
-        if (activity == null) {
-            throw new NullPointerException("activity cannot be null");
-        }
-        mNfcActivityManager.setNdefPushMessageCallback(activity, callback, flags);
     }
 
     /**
@@ -1501,7 +1391,7 @@
      *        to only register one at a time, and to do so in that activity's
      *        {@link Activity#onCreate}
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @removed this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
@@ -1511,31 +1401,6 @@
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return;
-            }
-        }
-        int targetSdkVersion = getSdkVersion();
-        try {
-            if (activity == null) {
-                throw new NullPointerException("activity cannot be null");
-            }
-            mNfcActivityManager.setOnNdefPushCompleteCallback(activity, callback);
-            for (Activity a : activities) {
-                if (a == null) {
-                    throw new NullPointerException("activities cannot contain null");
-                }
-                mNfcActivityManager.setOnNdefPushCompleteCallback(a, callback);
-            }
-        } catch (IllegalStateException e) {
-            if (targetSdkVersion < android.os.Build.VERSION_CODES.JELLY_BEAN) {
-                // Less strict on old applications - just log the error
-                Log.e(TAG, "Cannot call API with Activity that has already " +
-                        "been destroyed", e);
-            } else {
-                // Prevent new applications from making this mistake, re-throw
-                throw(e);
-            }
         }
     }
 
@@ -1718,7 +1583,7 @@
      * @param activity the current foreground Activity that has registered data to share
      * @return whether the Beam animation was successfully invoked
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @removed this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
@@ -1727,37 +1592,8 @@
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return false;
-            }
         }
-        if (activity == null) {
-            throw new NullPointerException("activity may not be null.");
-        }
-        enforceResumed(activity);
-        try {
-            sService.invokeBeam();
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "invokeBeam: NFC process has died.");
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public boolean invokeBeam(BeamShareData shareData) {
-        try {
-            Log.e(TAG, "invokeBeamInternal()");
-            sService.invokeBeamInternal(shareData);
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "invokeBeam: NFC process has died.");
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
+        return false;
     }
 
     /**
@@ -1783,9 +1619,9 @@
      *
      * @param activity foreground activity
      * @param message a NDEF Message to push over NFC
-     * @throws IllegalStateException if the activity is not currently in the foreground
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated use {@link #setNdefPushMessage} instead
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable
+     * @removed this feature is removed. File sharing can work using other technology like
+     * Bluetooth.
      */
     @Deprecated
     public void enableForegroundNdefPush(Activity activity, NdefMessage message) {
@@ -1793,15 +1629,7 @@
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return;
-            }
         }
-        if (activity == null || message == null) {
-            throw new NullPointerException();
-        }
-        enforceResumed(activity);
-        mNfcActivityManager.setNdefPushMessage(activity, message, 0);
     }
 
     /**
@@ -1820,9 +1648,9 @@
      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
      *
      * @param activity the Foreground activity
-     * @throws IllegalStateException if the Activity has already been paused
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated use {@link #setNdefPushMessage} instead
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable
+     * @removed this feature is removed. File sharing can work using other technology like
+     * Bluetooth.
      */
     @Deprecated
     public void disableForegroundNdefPush(Activity activity) {
@@ -1830,17 +1658,7 @@
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return;
-            }
         }
-        if (activity == null) {
-            throw new NullPointerException();
-        }
-        enforceResumed(activity);
-        mNfcActivityManager.setNdefPushMessage(activity, null, 0);
-        mNfcActivityManager.setNdefPushMessageCallback(activity, null, 0);
-        mNfcActivityManager.setOnNdefPushCompleteCallback(activity, null);
     }
 
     /**
@@ -1965,40 +1783,24 @@
      * Enable NDEF Push feature.
      * <p>This API is for the Settings application.
      * @hide
+     * @removed
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public boolean enableNdefPush() {
-        if (!sHasNfcFeature) {
-            throw new UnsupportedOperationException();
-        }
-        try {
-            return sService.enableNdefPush();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
+        return false;
     }
 
     /**
      * Disable NDEF Push feature.
      * <p>This API is for the Settings application.
      * @hide
+     * @removed
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public boolean disableNdefPush() {
-        synchronized (NfcAdapter.class) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-        try {
-            return sService.disableNdefPush();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
+        return false;
     }
 
     /**
@@ -2024,26 +1826,17 @@
      * @see android.provider.Settings#ACTION_NFCSHARING_SETTINGS
      * @return true if NDEF Push feature is enabled
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @deprecated this feature is deprecated. File sharing can work using other technology like
+     * @removed this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
     @java.lang.Deprecated
-
     public boolean isNdefPushEnabled() {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
-            if (!sHasBeamFeature) {
-                return false;
-            }
         }
-        try {
-            return sService.isNdefPushEnabled();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
+        return false;
     }
 
     /**
@@ -2134,17 +1927,6 @@
     }
 
     /**
-     * @hide
-     */
-    public void setP2pModes(int initiatorModes, int targetModes) {
-        try {
-            sService.setP2pModes(initiatorModes, targetModes);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-        }
-    }
-
-    /**
      * Registers a new NFC unlock handler with the NFC service.
      *
      * <p />NFC unlock handlers are intended to unlock the keyguard in the presence of a trusted
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 34aa7ef..547d406 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1142,6 +1142,16 @@
                 @BatteryConsumer.ProcessState int processState);
 
 
+
+        /**
+         * Returns the battery consumption (in microcoulombs) of UID's camera usage, derived from
+         * on-device power measurement data.
+         * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+         *
+         * {@hide}
+         */
+        public abstract long getCameraEnergyConsumptionUC();
+
         /**
          * Returns the battery consumption (in microcoulombs) used by this uid for each
          * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
@@ -2921,6 +2931,15 @@
     public abstract long getWifiEnergyConsumptionUC();
 
     /**
+     * Returns the battery consumption (in microcoulombs) of camera, derived from on
+     * device power measurement data.
+     * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+     *
+     * {@hide}
+     */
+    public abstract long getCameraEnergyConsumptionUC();
+
+    /**
      * Returns the battery consumption (in microcoulombs) that each
      * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
      * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}) consumed.
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 249f486..32773a0 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -754,12 +754,9 @@
          * PackageManager.setComponentEnabledSetting} will now throw an
          * IllegalArgumentException if the given component class name does not
          * exist in the application's manifest.
-         * <li> {@link android.nfc.NfcAdapter#setNdefPushMessage
-         * NfcAdapter.setNdefPushMessage},
-         * {@link android.nfc.NfcAdapter#setNdefPushMessageCallback
-         * NfcAdapter.setNdefPushMessageCallback} and
-         * {@link android.nfc.NfcAdapter#setOnNdefPushCompleteCallback
-         * NfcAdapter.setOnNdefPushCompleteCallback} will throw
+         * <li> {@code NfcAdapter.setNdefPushMessage},
+         * {@code NfcAdapter.setNdefPushMessageCallback} and
+         * {@code NfcAdapter.setOnNdefPushCompleteCallback} will throw
          * IllegalStateException if called after the Activity has been destroyed.
          * <li> Accessibility services must require the new
          * {@link android.Manifest.permission#BIND_ACCESSIBILITY_SERVICE} permission or
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ec3ef9d..3886708 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -603,6 +603,8 @@
      * Output: When a package data uri is passed as input, the activity result is set to
      * {@link android.app.Activity#RESULT_OK} if the permission was granted to the app. Otherwise,
      * the result is set to {@link android.app.Activity#RESULT_CANCELED}.
+     *
+     * @hide
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_MANAGE_APP_LONG_RUNNING_JOBS =
@@ -1692,7 +1694,6 @@
      * Input: Nothing.
      * <p>
      * Output: Nothing
-     * @see android.nfc.NfcAdapter#isNdefPushEnabled()
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_NFCSHARING_SETTINGS =
@@ -8659,6 +8660,12 @@
         public static final String BACKUP_AUTO_RESTORE = "backup_auto_restore";
 
         /**
+         * Controls whether framework backup scheduling is enabled.
+         * @hide
+         */
+        public static final String BACKUP_SCHEDULING_ENABLED = "backup_scheduling_enabled";
+
+        /**
          * Indicates whether settings backup has been fully provisioned.
          * Type: int ( 0 = unprovisioned, 1 = fully provisioned )
          * @hide
diff --git a/core/java/android/service/voice/IVoiceInteractionService.aidl b/core/java/android/service/voice/IVoiceInteractionService.aidl
index 24819a6..efae5c1 100644
--- a/core/java/android/service/voice/IVoiceInteractionService.aidl
+++ b/core/java/android/service/voice/IVoiceInteractionService.aidl
@@ -16,6 +16,8 @@
 
 package android.service.voice;
 
+import android.os.Bundle;
+
 import com.android.internal.app.IVoiceActionCheckCallback;
 
 /**
@@ -28,4 +30,6 @@
     void launchVoiceAssistFromKeyguard();
     void getActiveServiceSupportedActions(in List<String> voiceActions,
      in IVoiceActionCheckCallback callback);
+    void prepareToShowSession(in Bundle args, int flags);
+    void showSessionFailed();
 }
diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java
index d8266f3..7926901 100644
--- a/core/java/android/service/voice/VisualQueryDetectionService.java
+++ b/core/java/android/service/voice/VisualQueryDetectionService.java
@@ -190,9 +190,86 @@
 
     /**
      * Callback for sending out signals and returning query results.
+     *
+     * On successful user attention, developers should call {@link Callback#onAttentionGained()}
+     * to enable the streaming of the query.
+     * <p>
+     * On user attention is lost, developers should call {@link Callback#onAttentionLost()} to
+     * disable the streaming of the query.
+     * <p>
+     * On query is detected and ready to stream, developers should call
+     * {@link Callback#onQueryDetected(String)} to return detected query to the
+     * {@link VisualQueryDetector}.
+     * <p>
+     * On streamed query should be rejected, clients should call {@link Callback#onQueryRejected()}
+     * to abandon query streamed to the {@link VisualQueryDetector}.
+     * <p>
+     * On streamed query is finished, clients should call {@link Callback#onQueryFinished()} to
+     * complete query streamed to {@link VisualQueryDetector}.
+     * <p>
+     * Before a call for {@link Callback#onQueryDetected(String)} is triggered,
+     * {@link Callback#onAttentionGained()} MUST be called to enable the streaming of query. A query
+     * streaming is also expected to be finished by calling either
+     * {@link Callback#onQueryFinished()} or {@link Callback#onQueryRejected()} before a new query
+     * should start streaming. When the service enters the state where query streaming should be
+     * disabled, {@link Callback#onAttentionLost()} MUST be called to block unnecessary streaming.
      */
     public static final class Callback {
-        //TODO: Add callback to send signals to VIS and SysUI.
+
+        /**
+         * Informs attention listener that the user attention is gained.
+         */
+        public void onAttentionGained() {
+            //TODO(b/265345361): call internal callbacks to send signal to the interactor
+        }
+
+        /**
+         * Informs attention listener that the user attention is lost.
+         */
+        public void onAttentionLost() {
+            //TODO(b/265345361): call internal callbacks to send signal to the interactor
+        }
+
+        /**
+         * Informs the {@link VisualQueryDetector} with the text content being captured about the
+         * query from the audio source. {@code partialQuery} is provided to the
+         * {@link VisualQueryDetector}. This method is expected to be only triggered if
+         * {@link Callback#onAttentionGained()} is called to put the service into the attention
+         * gained state.
+         *
+         * @param partialQuery Partially detected query in string.
+         * @throws IllegalStateException if method called without attention gained.
+         */
+        public void onQueryDetected(@NonNull String partialQuery) throws IllegalStateException {
+            //TODO(b/265345361): call internal callbacks to send signal to the interactor
+            //TODO(b/265381651): convert callback exceptions and throw IllegalStateException.
+        }
+
+        /**
+         * Informs the {@link VisualQueryDetector} to abandon the streamed partial query that has
+         * been sent to {@link VisualQueryDetector}.This method is expected to be only triggered if
+         * {@link Callback#onQueryDetected()} is called to put the service into the query streaming
+         * state.
+         *
+         * @throws IllegalStateException if method called without query streamed.
+         */
+        public void onQueryRejected() throws IllegalStateException {
+            //TODO(b/265345361): call internal callbacks to send signal to the interactor
+            //TODO(b/265381651): convert callback exceptions and throw IllegalStateException.
+        }
+
+        /**
+         * Informs {@link VisualQueryDetector} with the metadata to complete the streamed partial
+         * query that has been sent to {@link VisualQueryDetector}. This method is expected to be
+         * only triggered if {@link Callback#onQueryDetected()} is called to put the service into
+         * the query streaming state.
+         *
+         * @throws IllegalStateException if method called without query streamed.
+         */
+        public void onQueryFinished() throws IllegalStateException {
+            //TODO(b/265345361): call internal callbacks to send signal to the interactor
+            //TODO(b/265381651): convert callback exceptions and throw IllegalStateException.
+        }
     }
 
 }
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 9e1518d..d49b5a5 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -160,6 +160,20 @@
                             voiceActions,
                             callback));
         }
+
+        @Override
+        public void prepareToShowSession(Bundle args, int flags) {
+            Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
+                    VoiceInteractionService::onPrepareToShowSession,
+                    VoiceInteractionService.this, args, flags));
+        }
+
+        @Override
+        public void showSessionFailed() {
+            Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
+                    VoiceInteractionService::onShowSessionFailed,
+                    VoiceInteractionService.this));
+        }
     };
 
     IVoiceInteractionManagerService mSystemService;
@@ -184,6 +198,31 @@
     }
 
     /**
+     * Notify the interactor when the system prepares to show session. The system is going to
+     * bind the session service.
+     *
+     * @param args  The arguments that were supplied to {@link #showSession(Bundle, int)}.
+     * @param flags The show flags originally provided to {@link #showSession(Bundle, int)}.
+     * @see #showSession(Bundle, int)
+     * @see #onShowSessionFailed()
+     * @see VoiceInteractionSession#onShow(Bundle, int)
+     * @see VoiceInteractionSession#show(Bundle, int)
+     */
+    public void onPrepareToShowSession(@NonNull Bundle args, int flags) {
+    }
+
+    /**
+     * Called when the show session failed. E.g. When the system bound the session service failed.
+     *
+     * @see #showSession(Bundle, int)
+     * @see #onPrepareToShowSession(Bundle, int)
+     * @see VoiceInteractionSession#onShow(Bundle, int)
+     * @see VoiceInteractionSession#show(Bundle, int)
+     */
+    public void onShowSessionFailed() {
+    }
+
+    /**
      * Check whether the given service component is the currently active
      * VoiceInteractionService.
      */
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index b91019c..84bbdd1 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1283,6 +1283,8 @@
      * swipe gesture starts at X = 500 then moves to X = 400, this axis would have a value of
      * -0.1.
      * </ul>
+     * These values are relative to the state from the last event, not accumulated, so developers
+     * should make sure to process this axis value for all batched historical events.
      */
     public static final int AXIS_GESTURE_X_OFFSET = 48;
 
@@ -1300,6 +1302,8 @@
      * <li>For a touch pad, reports the distance that should be scrolled in the X axis as a result
      * of the user's two-finger scroll gesture, in display pixels.
      * </ul>
+     * These values are relative to the state from the last event, not accumulated, so developers
+     * should make sure to process this axis value for all batched historical events.
      */
     public static final int AXIS_GESTURE_SCROLL_X_DISTANCE = 50;
 
@@ -1310,6 +1314,19 @@
      */
     public static final int AXIS_GESTURE_SCROLL_Y_DISTANCE = 51;
 
+    /**
+     * Axis constant: pinch scale factor of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a touch pad, reports the change in distance between the fingers when the user is
+     * making a pinch gesture, as a proportion of the previous distance. For example, if the fingers
+     * were 50 units apart and are now 52 units apart, the scale factor would be 1.04.
+     * </ul>
+     * These values are relative to the state from the last event, not accumulated, so developers
+     * should make sure to process this axis value for all batched historical events.
+     */
+    public static final int AXIS_GESTURE_PINCH_SCALE_FACTOR = 52;
+
     // NOTE: If you add a new axis here you must also add it to:
     //  frameworks/native/include/android/input.h
     //  frameworks/native/libs/input/InputEventLabels.cpp
@@ -1369,6 +1386,7 @@
         names.append(AXIS_GESTURE_Y_OFFSET, "AXIS_GESTURE_Y_OFFSET");
         names.append(AXIS_GESTURE_SCROLL_X_DISTANCE, "AXIS_GESTURE_SCROLL_X_DISTANCE");
         names.append(AXIS_GESTURE_SCROLL_Y_DISTANCE, "AXIS_GESTURE_SCROLL_Y_DISTANCE");
+        names.append(AXIS_GESTURE_PINCH_SCALE_FACTOR, "AXIS_GESTURE_PINCH_SCALE_FACTOR");
     }
 
     /**
@@ -1522,11 +1540,22 @@
      */
     public static final int CLASSIFICATION_MULTI_FINGER_SWIPE = 4;
 
+    /**
+     * Classification constant: touchpad pinch.
+     *
+     * The current event stream represents the user pinching with two fingers on a touchpad. The
+     * gesture is centered around the current cursor position.
+     *
+     * @see #getClassification
+     */
+    public static final int CLASSIFICATION_PINCH = 5;
+
     /** @hide */
     @Retention(SOURCE)
     @IntDef(prefix = { "CLASSIFICATION" }, value = {
             CLASSIFICATION_NONE, CLASSIFICATION_AMBIGUOUS_GESTURE, CLASSIFICATION_DEEP_PRESS,
-            CLASSIFICATION_TWO_FINGER_SWIPE, CLASSIFICATION_MULTI_FINGER_SWIPE})
+            CLASSIFICATION_TWO_FINGER_SWIPE, CLASSIFICATION_MULTI_FINGER_SWIPE,
+            CLASSIFICATION_PINCH})
     public @interface Classification {};
 
     /**
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
index 6d64022..9e17620 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -40,6 +40,9 @@
 per-file ScaleGestureDetector.java = file:/services/core/java/com/android/server/input/OWNERS
 per-file KeyboardShortcut*.java = file:/services/core/java/com/android/server/input/OWNERS
 per-file KeyCharacterMap.java = file:/services/core/java/com/android/server/input/OWNERS
+per-file VelocityTracker.java = file:/services/core/java/com/android/server/input/OWNERS
+per-file VerifiedInputEvent.java = file:/services/core/java/com/android/server/input/OWNERS
+per-file VerifiedInputEvent.aidl = file:/services/core/java/com/android/server/input/OWNERS
 
 # InputWindowHandle
 per-file InputWindowHandle.java  = file:/services/core/java/com/android/server/input/OWNERS
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2114ce7..74e521a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -8823,6 +8823,10 @@
             mAdded = false;
             AnimationHandler.removeRequestor(this);
         }
+        if (mActiveSurfaceSyncGroup != null) {
+            mActiveSurfaceSyncGroup.markSyncReady();
+            mActiveSurfaceSyncGroup = null;
+        }
         WindowManagerGlobal.getInstance().doRemoveView(this);
     }
 
diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java
index e0fc426..1f4a7af 100644
--- a/core/java/android/view/inputmethod/HandwritingGesture.java
+++ b/core/java/android/view/inputmethod/HandwritingGesture.java
@@ -17,10 +17,13 @@
 package android.view.inputmethod;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.graphics.RectF;
 import android.inputmethodservice.InputMethodService;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.view.MotionEvent;
 
 import java.lang.annotation.Retention;
@@ -198,4 +201,56 @@
     public final String getFallbackText() {
         return mFallbackText;
     }
+
+    /**
+     * Dump data into a byte array so that you can pass the data across process boundary.
+     *
+     * @return byte array data.
+     * @see #fromByteArray(byte[])
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public final byte[] toByteArray() {
+        if (!(this instanceof Parcelable)) {
+            throw new UnsupportedOperationException(getClass() + " is not Parcelable");
+        }
+        final Parcelable self = (Parcelable) this;
+        if ((self.describeContents() & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
+            throw new UnsupportedOperationException("Gesture that contains FD is not supported");
+        }
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            ParcelableHandwritingGesture.of(this).writeToParcel(parcel, 0);
+            return parcel.marshall();
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+    }
+
+    /**
+     * Create a new instance from byte array obtained from {@link #toByteArray()}.
+     *
+     * @param buffer byte array obtained from {@link #toByteArray()}
+     * @return A new instance of {@link HandwritingGesture} subclass.
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public static HandwritingGesture fromByteArray(@NonNull byte[] buffer) {
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            parcel.unmarshall(buffer, 0, buffer.length);
+            parcel.setDataPosition(0);
+            return ParcelableHandwritingGesture.CREATOR.createFromParcel(parcel).get();
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+    }
 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 4005bc8..d6c2d30 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -937,6 +937,13 @@
     private List<Path> mHighlightPaths;
     private List<Paint> mHighlightPaints;
     private Highlights mHighlights;
+    private int[] mSearchResultHighlights = null;
+    private Paint mSearchResultHighlightPaint = null;
+    private Paint mFocusedSearchResultHighlightPaint = null;
+    private int mFocusedSearchResultHighlightColor = 0xFFFF9632;
+    private int mSearchResultHighlightColor = 0xFFFFFF00;
+
+    private int mFocusedSearchResultIndex = -1;
     private int mGesturePreviewHighlightStart = -1;
     private int mGesturePreviewHighlightEnd = -1;
     private Paint mGesturePreviewHighlightPaint;
@@ -4030,6 +4037,8 @@
      */
     private static class TextAppearanceAttributes {
         int mTextColorHighlight = 0;
+        int mSearchResultHighlightColor = 0;
+        int mFocusedSearchResultHighlightColor = 0;
         ColorStateList mTextColor = null;
         ColorStateList mTextColorHint = null;
         ColorStateList mTextColorLink = null;
@@ -4062,6 +4071,9 @@
         public String toString() {
             return "TextAppearanceAttributes {\n"
                     + "    mTextColorHighlight:" + mTextColorHighlight + "\n"
+                    + "    mSearchResultHighlightColor: " + mSearchResultHighlightColor + "\n"
+                    + "    mFocusedSearchResultHighlightColor: "
+                    + mFocusedSearchResultHighlightColor + "\n"
                     + "    mTextColor:" + mTextColor + "\n"
                     + "    mTextColorHint:" + mTextColorHint + "\n"
                     + "    mTextColorLink:" + mTextColorLink + "\n"
@@ -4100,6 +4112,11 @@
     static {
         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight,
                 com.android.internal.R.styleable.TextAppearance_textColorHighlight);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_searchResultHighlightColor,
+                com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor);
+        sAppearanceValues.put(
+                com.android.internal.R.styleable.TextView_focusedSearchResultHighlightColor,
+                com.android.internal.R.styleable.TextAppearance_focusedSearchResultHighlightColor);
         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor,
                 com.android.internal.R.styleable.TextAppearance_textColor);
         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint,
@@ -4174,6 +4191,16 @@
                     attributes.mTextColorHighlight =
                             appearance.getColor(attr, attributes.mTextColorHighlight);
                     break;
+                case com.android.internal.R.styleable.TextAppearance_searchResultHighlightColor:
+                    attributes.mSearchResultHighlightColor =
+                            appearance.getColor(attr, attributes.mSearchResultHighlightColor);
+                    break;
+                case com.android.internal.R.styleable
+                        .TextAppearance_focusedSearchResultHighlightColor:
+                    attributes.mFocusedSearchResultHighlightColor =
+                            appearance.getColor(attr,
+                                    attributes.mFocusedSearchResultHighlightColor);
+                    break;
                 case com.android.internal.R.styleable.TextAppearance_textColor:
                     attributes.mTextColor = appearance.getColorStateList(attr);
                     break;
@@ -4290,6 +4317,14 @@
             setHighlightColor(attributes.mTextColorHighlight);
         }
 
+        if (attributes.mSearchResultHighlightColor != 0) {
+            setSearchResultHighlightColor(attributes.mSearchResultHighlightColor);
+        }
+
+        if (attributes.mFocusedSearchResultHighlightColor != 0) {
+            setFocusedSearchResultHighlightColor(attributes.mFocusedSearchResultHighlightColor);
+        }
+
         if (attributes.mTextSize != -1) {
             mTextSizeUnit = attributes.mTextSizeUnit;
             setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */);
@@ -6176,6 +6211,238 @@
     }
 
     /**
+     * Sets the search result ranges with flatten range representation.
+     *
+     * Ranges are represented of flattened inclusive start and exclusive end integers array. The
+     * inclusive start offset of the {@code i}-th range is stored in {@code 2 * i}-th of the array.
+     * The exclusive end offset of the {@code i}-th range is stored in {@code 2* i + 1}-th of the
+     * array. For example, the two ranges: (1, 2) and (3, 4) are flattened into single int array
+     * [1, 2, 3, 4].
+     *
+     * TextView will render the search result with the highlights with specified color in the theme.
+     * If there is a focused search result, it is rendered with focused color. By calling this
+     * method, the focused search index will be cleared.
+     *
+     * @attr ref android.R.styleable#TextView_searchResultHighlightColor
+     * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
+     * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
+     * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
+     *
+     * @see #getSearchResultHighlights()
+     * @see #setFocusedSearchResultIndex(int)
+     * @see #getFocusedSearchResultIndex()
+     * @see #setSearchResultHighlightColor(int)
+     * @see #getSearchResultHighlightColor()
+     * @see #setFocusedSearchResultHighlightColor(int)
+     * @see #getFocusedSearchResultHighlightColor()
+     *
+     * @param ranges the flatten ranges of the search result. null for clear.
+     */
+    public void setSearchResultHighlights(@Nullable int... ranges) {
+        if (ranges == null) {
+            mSearchResultHighlights = null;
+            mHighlightPathsBogus = true;
+            return;
+        }
+        if (ranges.length % 2 == 1) {
+            throw new IllegalArgumentException(
+                    "Flatten ranges must have even numbered elements");
+        }
+        for (int j = 0; j < ranges.length / 2; ++j) {
+            int start = ranges[j * 2];
+            int end = ranges[j * 2 + 1];
+            if (start > end) {
+                throw new IllegalArgumentException(
+                        "Reverse range found in the flatten range: " + start + ", " + end + ""
+                                + " at " + j + "-th range");
+            }
+        }
+        mHighlightPathsBogus = true;
+        mSearchResultHighlights = ranges;
+        mFocusedSearchResultIndex = FOCUSED_SEARCH_RESULT_INDEX_NONE;
+        invalidate();
+    }
+
+    /**
+     * Gets the current search result ranges.
+     *
+     * @see #setSearchResultHighlights(int[])
+     * @see #setFocusedSearchResultIndex(int)
+     * @see #getFocusedSearchResultIndex()
+     * @see #setSearchResultHighlightColor(int)
+     * @see #getSearchResultHighlightColor()
+     * @see #setFocusedSearchResultHighlightColor(int)
+     * @see #getFocusedSearchResultHighlightColor()
+     *
+     * @return a flatten search result ranges. null if not available.
+     */
+    @Nullable
+    public int[] getSearchResultHighlights() {
+        return mSearchResultHighlights;
+    }
+
+    /**
+     * A special index used for {@link #setFocusedSearchResultIndex(int)} and
+     * {@link #getFocusedSearchResultIndex()} inidicating there is no focused search result.
+     */
+    public static final int FOCUSED_SEARCH_RESULT_INDEX_NONE = -1;
+
+    /**
+     * Sets the focused search result index.
+     *
+     * The focused search result is drawn in a focused color.
+     * Calling {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE} for clearing focused search result.
+     *
+     * This method must be called after setting search result ranges by
+     * {@link #setSearchResultHighlights(int[])}.
+     *
+     * @attr ref android.R.styleable#TextView_searchResultHighlightColor
+     * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
+     * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
+     * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
+     *
+     * @see #setSearchResultHighlights(int[])
+     * @see #getSearchResultHighlights()
+     * @see #setFocusedSearchResultIndex(int)
+     * @see #getFocusedSearchResultIndex()
+     * @see #setSearchResultHighlightColor(int)
+     * @see #getSearchResultHighlightColor()
+     * @see #setFocusedSearchResultHighlightColor(int)
+     * @see #getFocusedSearchResultHighlightColor()
+     *
+     * @param index a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE}
+     */
+    public void setFocusedSearchResultIndex(int index) {
+        if (mSearchResultHighlights == null) {
+            throw new IllegalArgumentException("Search result range must be set beforehand.");
+        }
+        if (index < -1 || index >= mSearchResultHighlights.length / 2) {
+            throw new IllegalArgumentException("Focused index(" + index + ") must be larger than "
+                    + "-1 and less than range count(" + (mSearchResultHighlights.length / 2) + ")");
+        }
+        mFocusedSearchResultIndex = index;
+        mHighlightPathsBogus = true;
+        invalidate();
+    }
+
+    /**
+     * Gets the focused search result index.
+     *
+     * @attr ref android.R.styleable#TextView_searchResultHighlightColor
+     * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
+     * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
+     * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
+     *
+     * @see #setSearchResultHighlights(int[])
+     * @see #getSearchResultHighlights()
+     * @see #setFocusedSearchResultIndex(int)
+     * @see #getFocusedSearchResultIndex()
+     * @see #setSearchResultHighlightColor(int)
+     * @see #getSearchResultHighlightColor()
+     * @see #setFocusedSearchResultHighlightColor(int)
+     * @see #getFocusedSearchResultHighlightColor()
+
+     * @return a focused search index or {@link #FOCUSED_SEARCH_RESULT_INDEX_NONE}
+     */
+    public int getFocusedSearchResultIndex() {
+        return mFocusedSearchResultIndex;
+    }
+
+    /**
+     * Sets the search result highlight color.
+     *
+     * @attr ref android.R.styleable#TextView_searchResultHighlightColor
+     * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
+     * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
+     * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
+     *
+     * @see #setSearchResultHighlights(int[])
+     * @see #getSearchResultHighlights()
+     * @see #setFocusedSearchResultIndex(int)
+     * @see #getFocusedSearchResultIndex()
+     * @see #setSearchResultHighlightColor(int)
+     * @see #getSearchResultHighlightColor()
+     * @see #setFocusedSearchResultHighlightColor(int)
+     * @see #getFocusedSearchResultHighlightColor()
+
+     * @param color a search result highlight color.
+     */
+    public void setSearchResultHighlightColor(@ColorInt int color) {
+        mSearchResultHighlightColor = color;
+    }
+
+    /**
+     * Gets the search result highlight color.
+     *
+     * @attr ref android.R.styleable#TextView_searchResultHighlightColor
+     * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
+     * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
+     * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
+     *
+     * @see #setSearchResultHighlights(int[])
+     * @see #getSearchResultHighlights()
+     * @see #setFocusedSearchResultIndex(int)
+     * @see #getFocusedSearchResultIndex()
+     * @see #setSearchResultHighlightColor(int)
+     * @see #getSearchResultHighlightColor()
+     * @see #setFocusedSearchResultHighlightColor(int)
+     * @see #getFocusedSearchResultHighlightColor()
+
+     * @return a search result highlight color.
+     */
+    @ColorInt
+    public int getSearchResultHighlightColor() {
+        return mSearchResultHighlightColor;
+    }
+
+    /**
+     * Sets focused search result highlight color.
+     *
+     * @attr ref android.R.styleable#TextView_searchResultHighlightColor
+     * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
+     * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
+     * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
+     *
+     * @see #setSearchResultHighlights(int[])
+     * @see #getSearchResultHighlights()
+     * @see #setFocusedSearchResultIndex(int)
+     * @see #getFocusedSearchResultIndex()
+     * @see #setSearchResultHighlightColor(int)
+     * @see #getSearchResultHighlightColor()
+     * @see #setFocusedSearchResultHighlightColor(int)
+     * @see #getFocusedSearchResultHighlightColor()
+
+     * @param color a focused search result highlight color.
+     */
+    public void setFocusedSearchResultHighlightColor(@ColorInt int color) {
+        mFocusedSearchResultHighlightColor = color;
+    }
+
+    /**
+     * Gets focused search result highlight color.
+     *
+     * @attr ref android.R.styleable#TextView_searchResultHighlightColor
+     * @attr ref android.R.styleable#TextAppearance_searchResultHighlightColor
+     * @attr ref android.R.styleable#TextView_focusedSearchResultHighlightColor
+     * @attr ref android.R.styleable#TextAppearance_focusedSearchResultHighlightColor
+     *
+     * @see #setSearchResultHighlights(int[])
+     * @see #getSearchResultHighlights()
+     * @see #setFocusedSearchResultIndex(int)
+     * @see #getFocusedSearchResultIndex()
+     * @see #setSearchResultHighlightColor(int)
+     * @see #getSearchResultHighlightColor()
+     * @see #setFocusedSearchResultHighlightColor(int)
+     * @see #getFocusedSearchResultHighlightColor()
+
+     * @return a focused search result highlight color.
+     */
+    @ColorInt
+    public int getFocusedSearchResultHighlightColor() {
+        return mFocusedSearchResultHighlightColor;
+    }
+
+    /**
      * Highlights the text range (from inclusive start offset to exclusive end offset) to show what
      * will be selected by the ongoing select handwriting gesture. While the gesture preview
      * highlight is shown, the selection or cursor is hidden. If the text or selection is changed,
@@ -8335,7 +8602,6 @@
             for (int i = 0; i < mHighlights.getSize(); ++i) {
                 final int[] ranges = mHighlights.getRanges(i);
                 final Paint paint = mHighlights.getPaint(i);
-
                 final Path path;
                 if (mPathRecyclePool.isEmpty()) {
                     path = new Path();
@@ -8363,6 +8629,8 @@
             }
         }
 
+        addSearchHighlightPaths();
+
         if (hasGesturePreviewHighlight()) {
             final Path path;
             if (mPathRecyclePool.isEmpty()) {
@@ -8381,6 +8649,67 @@
         mHighlightPathsBogus = false;
     }
 
+    private void addSearchHighlightPaths() {
+        if (mSearchResultHighlights != null) {
+            final Path searchResultPath;
+            if (mPathRecyclePool.isEmpty()) {
+                searchResultPath = new Path();
+            } else {
+                searchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
+                mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
+                searchResultPath.reset();
+            }
+            final Path focusedSearchResultPath;
+            if (mFocusedSearchResultIndex == FOCUSED_SEARCH_RESULT_INDEX_NONE) {
+                focusedSearchResultPath = null;
+            } else if (mPathRecyclePool.isEmpty()) {
+                focusedSearchResultPath = new Path();
+            } else {
+                focusedSearchResultPath = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
+                mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
+                focusedSearchResultPath.reset();
+            }
+
+            boolean atLeastOnePathAdded = false;
+            for (int j = 0; j < mSearchResultHighlights.length / 2; ++j) {
+                final int start = mSearchResultHighlights[2 * j];
+                final int end = mSearchResultHighlights[2 * j + 1];
+                if (start < end) {
+                    if (j == mFocusedSearchResultIndex) {
+                        mLayout.getSelection(start, end, (left, top, right, bottom, layout) ->
+                                focusedSearchResultPath.addRect(left, top, right, bottom,
+                                        Path.Direction.CW)
+                        );
+                    } else {
+                        mLayout.getSelection(start, end, (left, top, right, bottom, layout) ->
+                                searchResultPath.addRect(left, top, right, bottom,
+                                        Path.Direction.CW)
+                        );
+                        atLeastOnePathAdded = true;
+                    }
+                }
+            }
+            if (atLeastOnePathAdded) {
+                if (mSearchResultHighlightPaint == null) {
+                    mSearchResultHighlightPaint = new Paint();
+                }
+                mSearchResultHighlightPaint.setColor(mSearchResultHighlightColor);
+                mSearchResultHighlightPaint.setStyle(Paint.Style.FILL);
+                mHighlightPaths.add(searchResultPath);
+                mHighlightPaints.add(mSearchResultHighlightPaint);
+            }
+            if (focusedSearchResultPath != null) {
+                if (mFocusedSearchResultHighlightPaint == null) {
+                    mFocusedSearchResultHighlightPaint = new Paint();
+                }
+                mFocusedSearchResultHighlightPaint.setColor(mFocusedSearchResultHighlightColor);
+                mFocusedSearchResultHighlightPaint.setStyle(Paint.Style.FILL);
+                mHighlightPaths.add(focusedSearchResultPath);
+                mHighlightPaints.add(mFocusedSearchResultHighlightPaint);
+            }
+        }
+    }
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private Path getUpdatedHighlightPath() {
         Path highlight = null;
@@ -10826,6 +11155,26 @@
      * This has to be called after layout. Returns true if anything changed.
      */
     public boolean bringPointIntoView(int offset) {
+        return bringPointIntoView(offset, false);
+    }
+
+    /**
+     * Move the insertion position of the given offset into visible area of the View.
+     *
+     * If the View is focused or {@code requestRectWithoutFocus} is set to true, this API may call
+     * {@link View#requestRectangleOnScreen(Rect)} to bring the point to the visible area if
+     * necessary.
+     *
+     * @param offset an offset of the character.
+     * @param requestRectWithoutFocus True for calling {@link View#requestRectangleOnScreen(Rect)}
+     *                                in the unfocused state. False for calling it only the View has
+     *                                the focus.
+     * @return true if anything changed, otherwise false.
+     *
+     * @see #bringPointIntoView(int)
+     */
+    public boolean bringPointIntoView(@IntRange(from = 0) int offset,
+            boolean requestRectWithoutFocus) {
         if (isLayoutRequested()) {
             mDeferScroll = offset;
             return false;
@@ -11002,7 +11351,7 @@
             changed = true;
         }
 
-        if (isFocused()) {
+        if (requestRectWithoutFocus && isFocused()) {
             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
             // requestRectangleOnScreen() is in terms of content coordinates.
 
diff --git a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
index 2748ded..6a61656 100644
--- a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
+++ b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
@@ -79,11 +79,11 @@
     private int mAnrType;
     private int mDumpedProcessesCount = 0;
 
-    private long mFirstPidsDumpingStart;
+    private long mFirstPidsDumpingStartUptime;
     private long mFirstPidsDumpingDuration = 0;
-    private long mNativePidsDumpingStart;
+    private long mNativePidsDumpingStartUptime;
     private long mNativePidsDumpingDuration = 0;
-    private long mExtraPidsDumpingStart;
+    private long mExtraPidsDumpingStartUptime;
     private long mExtraPidsDumpingDuration = 0;
 
     private boolean mIsPushed = false;
@@ -223,37 +223,37 @@
 
     /** Records the start of pid dumping to file (subject and criticalEventSection). */
     public void dumpingFirstPidsStarted() {
-        mFirstPidsDumpingStart = getUptimeMillis();
+        mFirstPidsDumpingStartUptime = getUptimeMillis();
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "dumpingFirstPids");
     }
 
     /** Records the end of pid dumping to file (subject and criticalEventSection). */
     public void dumpingFirstPidsEnded() {
-        mFirstPidsDumpingDuration = getUptimeMillis() - mFirstPidsDumpingStart;
+        mFirstPidsDumpingDuration = getUptimeMillis() - mFirstPidsDumpingStartUptime;
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
     /** Records the start of pid dumping to file (subject and criticalEventSection). */
     public void dumpingNativePidsStarted() {
-        mNativePidsDumpingStart = getUptimeMillis();
+        mNativePidsDumpingStartUptime = getUptimeMillis();
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "dumpingNativePids");
     }
 
     /** Records the end of pid dumping to file (subject and criticalEventSection). */
     public void dumpingNativePidsEnded() {
-        mNativePidsDumpingDuration =  getUptimeMillis() - mNativePidsDumpingStart;
+        mNativePidsDumpingDuration =  getUptimeMillis() - mNativePidsDumpingStartUptime;
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
     /** Records the start of pid dumping to file (subject and criticalEventSection). */
     public void dumpingExtraPidsStarted() {
-        mExtraPidsDumpingStart = getUptimeMillis();
+        mExtraPidsDumpingStartUptime = getUptimeMillis();
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "dumpingExtraPids");
     }
 
     /** Records the end of pid dumping to file (subject and criticalEventSection). */
     public void dumpingExtraPidsEnded() {
-        mExtraPidsDumpingDuration =  getUptimeMillis() - mExtraPidsDumpingStart;
+        mExtraPidsDumpingDuration =  getUptimeMillis() - mExtraPidsDumpingStartUptime;
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -337,7 +337,7 @@
      * Returns latency data as a comma separated value string for inclusion in ANR report.
      */
     public String dumpAsCommaSeparatedArrayWithHeader() {
-        return "DurationsV1: " + mAnrTriggerUptime
+        return "DurationsV2: " + mAnrTriggerUptime
                 /* triggering_to_app_not_responding_duration = */
                 + "," + (mAppNotRespondingStartUptime -  mAnrTriggerUptime)
                 /* app_not_responding_duration = */
@@ -369,8 +369,8 @@
 
                 /* anr_queue_size_when_pushed = */
                 + "," + mAnrQueueSize
-                /* dumped_processes_count = */
-                + "," + mDumpedProcessesCount
+                /* dump_stack_traces_io_time = */
+                + "," + (mFirstPidsDumpingStartUptime - mDumpStackTracesStartUptime)
                 + "\n\n";
 
     }
@@ -422,7 +422,7 @@
 
             /* total_duration = */ mEndUptime - mAnrTriggerUptime,
             /* triggering_to_stack_dump_duration = */
-                    mFirstPidsDumpingStart - mAnrTriggerUptime,
+                    mFirstPidsDumpingStartUptime - mAnrTriggerUptime,
             /* triggering_to_app_not_responding_duration = */
                     mAppNotRespondingStartUptime -  mAnrTriggerUptime,
             /* app_not_responding_duration = */
diff --git a/core/java/com/android/internal/os/anr/OWNERS b/core/java/com/android/internal/os/anr/OWNERS
new file mode 100644
index 0000000..9816752
--- /dev/null
+++ b/core/java/com/android/internal/os/anr/OWNERS
@@ -0,0 +1,3 @@
+benmiles@google.com
+gaillard@google.com
+mohamadmahmoud@google.com
\ No newline at end of file
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 25ac1bd..600ae50 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -1279,10 +1279,15 @@
     public static float getBorderLuma(SurfaceControl surfaceControl, int w, int h) {
         final ScreenCapture.ScreenshotHardwareBuffer buffer =
                 ScreenCapture.captureLayers(surfaceControl, new Rect(0, 0, w, h), 1);
-        if (buffer != null) {
-            return getBorderLuma(buffer.getHardwareBuffer(), buffer.getColorSpace());
+        if (buffer == null) {
+            return 0;
         }
-        return 0;
+        final HardwareBuffer hwBuffer = buffer.getHardwareBuffer();
+        final float luma = getBorderLuma(hwBuffer, buffer.getColorSpace());
+        if (hwBuffer != null) {
+            hwBuffer.close();
+        }
+        return luma;
     }
 
     /** Returns the luminance in 0~1. */
diff --git a/core/java/com/android/internal/power/EnergyConsumerStats.java b/core/java/com/android/internal/power/EnergyConsumerStats.java
index 325df57..8cf17cda 100644
--- a/core/java/com/android/internal/power/EnergyConsumerStats.java
+++ b/core/java/com/android/internal/power/EnergyConsumerStats.java
@@ -58,7 +58,8 @@
     public static final int POWER_BUCKET_BLUETOOTH = 5;
     public static final int POWER_BUCKET_GNSS = 6;
     public static final int POWER_BUCKET_MOBILE_RADIO = 7;
-    public static final int NUMBER_STANDARD_POWER_BUCKETS = 8; // Buckets above this are custom.
+    public static final int POWER_BUCKET_CAMERA = 8;
+    public static final int NUMBER_STANDARD_POWER_BUCKETS = 9; // Buckets above this are custom.
 
     @IntDef(prefix = {"POWER_BUCKET_"}, value = {
             POWER_BUCKET_UNKNOWN,
@@ -70,6 +71,7 @@
             POWER_BUCKET_BLUETOOTH,
             POWER_BUCKET_GNSS,
             POWER_BUCKET_MOBILE_RADIO,
+            POWER_BUCKET_CAMERA,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface StandardPowerBucket {
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index db288c0..8fb345b 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -53,7 +53,7 @@
             boolean showImeSwitcher);
     void setWindowState(int display, int window, int state);
 
-    void showRecentApps(boolean triggeredFromAltTab);
+    void showRecentApps(boolean triggeredFromAltTab, boolean forward);
     void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
     void toggleRecentApps();
     void toggleSplitScreen();
diff --git a/core/java/com/android/server/backup/AccountManagerBackupHelper.java b/core/java/com/android/server/backup/AccountManagerBackupHelper.java
index f76dd09..5677699 100644
--- a/core/java/com/android/server/backup/AccountManagerBackupHelper.java
+++ b/core/java/com/android/server/backup/AccountManagerBackupHelper.java
@@ -18,8 +18,8 @@
 
 import android.accounts.AccountManagerInternal;
 import android.app.backup.BlobBackupHelper;
-import android.os.UserHandle;
 import android.util.Slog;
+
 import com.android.server.LocalServices;
 
 /**
@@ -35,8 +35,11 @@
     // key under which the account access grant state blob is committed to backup
     private static final String KEY_ACCOUNT_ACCESS_GRANTS = "account_access_grants";
 
-    public AccountManagerBackupHelper() {
+    private final int mUserId;
+
+    public AccountManagerBackupHelper(int userId) {
         super(STATE_VERSION, KEY_ACCOUNT_ACCESS_GRANTS);
+        mUserId = userId;
     }
 
     @Override
@@ -48,7 +51,7 @@
         try {
             switch (key) {
                 case KEY_ACCOUNT_ACCESS_GRANTS: {
-                    return am.backupAccountAccessPermissions(UserHandle.USER_SYSTEM);
+                    return am.backupAccountAccessPermissions(mUserId);
                 }
 
                 default: {
@@ -71,7 +74,7 @@
         try {
             switch (key) {
                 case KEY_ACCOUNT_ACCESS_GRANTS: {
-                    am.restoreAccountAccessPermissions(payload, UserHandle.USER_SYSTEM);
+                    am.restoreAccountAccessPermissions(payload, mUserId);
                 } break;
 
                 default: {
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 53594e1..a68040d 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -17,7 +17,9 @@
 per-file android_view_KeyCharacterMap.* = file:/services/core/java/com/android/server/input/OWNERS
 per-file android_view_*KeyEvent.* = file:/services/core/java/com/android/server/input/OWNERS
 per-file android_view_*MotionEvent.* = file:/services/core/java/com/android/server/input/OWNERS
+per-file android_view_MotionPredictor.* = file:/services/core/java/com/android/server/input/OWNERS
 per-file android_view_PointerIcon.* = file:/services/core/java/com/android/server/input/OWNERS
+per-file android_view_VelocityTracker.* = file:/services/core/java/com/android/server/input/OWNERS
 
 # WindowManager
 per-file android_graphics_BLASTBufferQueue.cpp = file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/core/proto/android/nfc/nfc_service.proto b/core/proto/android/nfc/nfc_service.proto
index 2df1d5d..1dcd5cc 100644
--- a/core/proto/android/nfc/nfc_service.proto
+++ b/core/proto/android/nfc/nfc_service.proto
@@ -60,7 +60,7 @@
     optional bool secure_nfc_capable = 13;
     optional bool vr_mode_enabled = 14;
     optional DiscoveryParamsProto discovery_params = 15;
-    optional P2pLinkManagerProto p2p_link_manager = 16;
+    reserved 16;
     optional com.android.nfc.cardemulation.CardEmulationManagerProto card_emulation_manager = 17;
     optional NfcDispatcherProto nfc_dispatcher = 18;
     optional string native_crash_logs = 19 [(.android.privacy).dest = DEST_EXPLICIT];
@@ -77,38 +77,6 @@
     optional bool enable_p2p = 5;
 }
 
-// Debugging information for com.android.nfc.P2pLinkManager
-message P2pLinkManagerProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-
-    enum LinkState {
-        LINK_STATE_UNKNOWN = 0;
-        LINK_STATE_DOWN = 1;
-        LINK_STATE_DEBOUNCE = 2;
-        LINK_STATE_UP = 3;
-    }
-
-    enum SendState {
-        SEND_STATE_UNKNOWN = 0;
-        SEND_STATE_NOTHING_TO_SEND = 1;
-        SEND_STATE_NEED_CONFIRMATION = 2;
-        SEND_STATE_SENDING = 3;
-        SEND_STATE_COMPLETE = 4;
-        SEND_STATE_CANCELED = 5;
-    }
-
-    optional int32 default_miu = 1;
-    optional int32 default_rw_size = 2;
-    optional LinkState link_state = 3;
-    optional SendState send_state = 4;
-    optional int32 send_flags = 5;
-    optional bool send_enabled = 6;
-    optional bool receive_enabled = 7;
-    optional string callback_ndef = 8 [(.android.privacy).dest = DEST_EXPLICIT];
-    optional .android.nfc.NdefMessageProto message_to_send = 9;
-    repeated string uris_to_send = 10 [(.android.privacy).dest = DEST_EXPLICIT];
-}
-
 // Debugging information for com.android.nfc.NfcDispatcher
 message NfcDispatcherProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
diff --git a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
index 35aae8f..5a18d9e 100644
--- a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
+++ b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
@@ -31,7 +31,7 @@
     optional string cur_focused_window_soft_input_mode = 6;
     optional .android.view.inputmethod.EditorInfoProto cur_attribute = 7;
     optional string cur_id = 8;
-    optional bool show_requested = 9;
+    reserved 9; // deprecated show_requested
     optional bool show_explicitly_requested = 10;
     optional bool show_forced = 11;
     optional bool input_shown = 12;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 81b3af0..8ee88ad 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6862,6 +6862,15 @@
     <permission android:name="android.permission.READ_HOME_APP_SEARCH_DATA"
         android:protectionLevel="internal|role" />
 
+    <!-- Allows an assistive application to perform actions on behalf of users inside of
+         applications.
+         <p>For now, this permission is only granted to system applications fulfilling the
+         ASSISTANT role.
+         <p>Protection level: internal|role
+    -->
+    <permission android:name="android.permission.EXECUTE_APP_ACTION"
+                android:protectionLevel="internal|role" />
+
     <!-- @SystemApi Allows an application to create virtual devices in VirtualDeviceManager.
          @hide -->
     <permission android:name="android.permission.CREATE_VIRTUAL_DEVICE"
@@ -6994,14 +7003,12 @@
     <permission android:name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE"
                 android:protectionLevel="signature|privileged" />
 
-    <!-- Allows applications to use the long running jobs APIs.
-         <p>This is a special access permission that can be revoked by the system or the user.
-         <p>Apps need to target API {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or above
-         to be able to request this permission.
-         <p>Protection level: appop
+    <!-- Allows applications to use the long running jobs APIs. For more details
+         see {@link android.app.job.JobInfo.Builder#setUserInitiated}.
+         <p>Protection level: normal
      -->
     <permission android:name="android.permission.RUN_LONG_JOBS"
-                android:protectionLevel="normal|appop"/>
+                android:protectionLevel="normal"/>
 
     <!-- Allows an app access to the installer provided app metadata.
         @SystemApi
@@ -7578,7 +7585,7 @@
             </intent-filter>
         </service>
 
-        <service android:name="com.android.server.art.BackgroundDexOptJobService"
+        <service android:name="com.android.server.art.BackgroundDexoptJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index f4d563c..1b6f88f 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5138,6 +5138,15 @@
         <attr name="textLocale" format="string" />
         <!-- Color of the text selection highlight. -->
         <attr name="textColorHighlight" />
+        <!-- Color of search results highlight.
+             This color is typically used when TextView/EditText shows search result in-app text
+             search invoked with Ctrl+F. -->
+        <attr name="searchResultHighlightColor" format="color" />
+        <!-- Color of focused search result highlight.
+             This color is typically used when TextView/EditText shows search result in-app text
+             search invoked with Ctrl+F. -->
+        <attr name="focusedSearchResultHighlightColor" format="color" />
+
         <!-- Color of the hint text. -->
         <attr name="textColorHint" />
         <!-- Color of the links. -->
@@ -5215,6 +5224,14 @@
         <attr name="textColor" />
         <!-- Color of the text selection highlight. -->
         <attr name="textColorHighlight" />
+        <!-- Color of search results highlight.
+             This color is typically used when TextView/EditText shows search result in-app text
+             search invoked with Ctrl+F. -->
+        <attr name="searchResultHighlightColor" format="color" />
+        <!-- Color of focused search result highlight.
+             This color is typically used when TextView/EditText shows search result in-app text
+             search invoked with Ctrl+F. -->
+        <attr name="focusedSearchResultHighlightColor" format="color" />
         <!-- Color of the hint text. -->
         <attr name="textColorHint" />
         <!-- Base text color, typeface, size, and style. -->
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 6047738..f4b49e6 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -126,6 +126,8 @@
     <public name="keyboardLayoutType" />
     <public name="allowUpdateOwnership" />
     <public name="isCredential"/>
+    <public name="searchResultHighlightColor" />
+    <public name="focusedSearchResultHighlightColor" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01cd0000">
diff --git a/core/tests/BroadcastRadioTests/AndroidManifest.xml b/core/tests/BroadcastRadioTests/AndroidManifest.xml
index 869b484..8f655ef 100644
--- a/core/tests/BroadcastRadioTests/AndroidManifest.xml
+++ b/core/tests/BroadcastRadioTests/AndroidManifest.xml
@@ -15,7 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.hardware.radio.tests">
+    package="com.android.frameworks.broadcastradiotests">
 
     <uses-permission android:name="android.permission.ACCESS_BROADCAST_RADIO" />
 
@@ -25,7 +25,7 @@
 
     <instrumentation
         android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.hardware.radio.tests"
+        android:targetPackage="com.android.frameworks.broadcastradiotests"
         android:label="Tests for Broadcast Radio APIs" >
     </instrumentation>
 </manifest>
diff --git a/core/tests/BroadcastRadioTests/AndroidTest.xml b/core/tests/BroadcastRadioTests/AndroidTest.xml
index ed88537..b7e93cd 100644
--- a/core/tests/BroadcastRadioTests/AndroidTest.xml
+++ b/core/tests/BroadcastRadioTests/AndroidTest.xml
@@ -25,7 +25,7 @@
     <option name="test-tag" value="BroadcastRadioTests" />
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.hardware.radio.tests" />
+        <option name="package" value="com.android.frameworks.broadcastradiotests" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="hidden-api-checks" value="false"/>
     </test>
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/DefaultRadioTunerTest.java
similarity index 90%
rename from core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java
rename to core/tests/BroadcastRadioTests/src/android/hardware/radio/DefaultRadioTunerTest.java
index 65e55a2..63de759 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/DefaultRadioTunerTest.java
@@ -14,17 +14,13 @@
  * limitations under the License.
  */
 
-package android.hardware.radio.tests.unittests;
+package android.hardware.radio;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertThrows;
 
 import android.graphics.Bitmap;
-import android.hardware.radio.ProgramList;
-import android.hardware.radio.ProgramSelector;
-import android.hardware.radio.RadioManager;
-import android.hardware.radio.RadioTuner;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -167,9 +163,7 @@
     @Test
     public void setConfigFlag_forRadioTuner_throwsException() {
         UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
-                () -> {
-            DEFAULT_RADIO_TUNER.setConfigFlag(/* flag= */ 1, /* value= */ false);
-        });
+                () -> DEFAULT_RADIO_TUNER.setConfigFlag(/* flag= */ 1, /* value= */ false));
 
         assertWithMessage("Exception for setting config flag on default radio tuner")
                 .that(thrown).hasMessageThat().contains("Setting config flag is not supported");
@@ -178,9 +172,7 @@
     @Test
     public void setParameters_forRadioTuner_throwsException() {
         UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
-                () -> {
-            DEFAULT_RADIO_TUNER.setParameters(Map.of("testKey", "testValue"));
-        });
+                () -> DEFAULT_RADIO_TUNER.setParameters(Map.of("testKey", "testValue")));
 
         assertWithMessage("Exception for setting parameters from default radio tuner")
                 .that(thrown).hasMessageThat().contains("Setting parameters is not supported");
@@ -189,9 +181,7 @@
     @Test
     public void getParameters_forRadioTuner_throwsException() {
         UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
-                () -> {
-            DEFAULT_RADIO_TUNER.getParameters(List.of("testKey"));
-        });
+                () -> DEFAULT_RADIO_TUNER.getParameters(List.of("testKey")));
 
         assertWithMessage("Exception for getting parameters from default radio tuner")
                 .that(thrown).hasMessageThat().contains("Getting parameters is not supported");
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
similarity index 97%
rename from core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
rename to core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
index 9a999e4..f807bad 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.hardware.radio.tests.unittests;
+package android.hardware.radio;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -29,14 +29,6 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
-import android.hardware.radio.IRadioService;
-import android.hardware.radio.ITuner;
-import android.hardware.radio.ITunerCallback;
-import android.hardware.radio.ProgramList;
-import android.hardware.radio.ProgramSelector;
-import android.hardware.radio.RadioManager;
-import android.hardware.radio.RadioMetadata;
-import android.hardware.radio.RadioTuner;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.util.ArraySet;
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
similarity index 99%
rename from core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
rename to core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
index 9399907..ae43a1c 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
@@ -14,15 +14,13 @@
  * limitations under the License.
  */
 
-package android.hardware.radio.tests.unittests;
+package android.hardware.radio;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertThrows;
 
 import android.annotation.Nullable;
-import android.hardware.radio.ProgramSelector;
-import android.hardware.radio.RadioManager;
 import android.os.Parcel;
 
 import org.junit.Test;
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioAnnouncementTest.java
similarity index 96%
rename from core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java
rename to core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioAnnouncementTest.java
index 6e1bb4b4..b0fb26a 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioAnnouncementTest.java
@@ -14,14 +14,12 @@
  * limitations under the License.
  */
 
-package android.hardware.radio.tests.unittests;
+package android.hardware.radio;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertThrows;
 
-import android.hardware.radio.Announcement;
-import android.hardware.radio.ProgramSelector;
 import android.os.Parcel;
 import android.util.ArrayMap;
 
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
similarity index 98%
rename from core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
rename to core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
index afbf8c3..79a6b0d 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.hardware.radio.tests.unittests;
+package android.hardware.radio;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -30,14 +30,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.hardware.radio.Announcement;
-import android.hardware.radio.IAnnouncementListener;
-import android.hardware.radio.ICloseHandle;
-import android.hardware.radio.IRadioService;
-import android.hardware.radio.ProgramSelector;
-import android.hardware.radio.RadioManager;
-import android.hardware.radio.RadioMetadata;
-import android.hardware.radio.RadioTuner;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.RemoteException;
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioMetadataTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
similarity index 98%
rename from core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioMetadataTest.java
rename to core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
index 5771135..e348a51 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioMetadataTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-package android.hardware.radio.tests.unittests;
+package android.hardware.radio;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertThrows;
 
 import android.graphics.Bitmap;
-import android.hardware.radio.RadioMetadata;
 import android.os.Parcel;
 
 import org.junit.Test;
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
similarity index 98%
rename from core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
rename to core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
index c8b4493..487086c 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.hardware.radio.tests.unittests;
+package android.hardware.radio;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -32,13 +32,6 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.graphics.Bitmap;
-import android.hardware.radio.IRadioService;
-import android.hardware.radio.ITuner;
-import android.hardware.radio.ITunerCallback;
-import android.hardware.radio.ProgramSelector;
-import android.hardware.radio.RadioManager;
-import android.hardware.radio.RadioMetadata;
-import android.hardware.radio.RadioTuner;
 import android.os.Build;
 import android.os.RemoteException;
 
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
deleted file mode 100644
index cabeb13..0000000
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * Copyright (C) 2017 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.hardware.radio.tests.functional;
-
-import static org.junit.Assert.*;
-import static org.junit.Assume.*;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.after;
-import static org.mockito.Mockito.atMost;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.testng.Assert.assertThrows;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.hardware.radio.ProgramSelector;
-import android.hardware.radio.RadioManager;
-import android.hardware.radio.RadioTuner;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A test for broadcast radio API.
- */
-@RunWith(MockitoJUnitRunner.class)
-public class RadioTunerTest {
-    private static final String TAG = "BroadcastRadioTests.RadioTuner";
-
-    public final Context mContext = InstrumentationRegistry.getContext();
-
-    private final int kConfigCallbackTimeoutMs = 10000;
-    private final int kCancelTimeoutMs = 1000;
-    private final int kTuneCallbackTimeoutMs = 30000;
-    private final int kFullScanTimeoutMs = 60000;
-
-    private RadioManager mRadioManager;
-    private RadioTuner mRadioTuner;
-    private RadioManager.ModuleProperties mModule;
-    private final List<RadioManager.ModuleProperties> mModules = new ArrayList<>();
-    @Mock private RadioTuner.Callback mCallback;
-
-    RadioManager.AmBandDescriptor mAmBandDescriptor;
-    RadioManager.FmBandDescriptor mFmBandDescriptor;
-
-    RadioManager.BandConfig mAmBandConfig;
-    RadioManager.BandConfig mFmBandConfig;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        // check if radio is supported and skip the test if it's not
-        PackageManager packageManager = mContext.getPackageManager();
-        boolean isRadioSupported = packageManager.hasSystemFeature(
-                PackageManager.FEATURE_BROADCAST_RADIO);
-        assumeTrue(isRadioSupported);
-
-        // Check radio access permission
-        int res = mContext.checkCallingOrSelfPermission(Manifest.permission.ACCESS_BROADCAST_RADIO);
-        assertEquals("ACCESS_BROADCAST_RADIO permission not granted",
-                PackageManager.PERMISSION_GRANTED, res);
-
-        mRadioManager = (RadioManager)mContext.getSystemService(Context.RADIO_SERVICE);
-        assertNotNull(mRadioManager);
-
-        int status = mRadioManager.listModules(mModules);
-        assertEquals(RadioManager.STATUS_OK, status);
-        assertFalse(mModules.isEmpty());
-    }
-
-    @After
-    public void tearDown() {
-        mRadioManager = null;
-        mModules.clear();
-        if (mRadioTuner != null) {
-            mRadioTuner.close();
-            mRadioTuner = null;
-        }
-        resetCallback();
-    }
-
-    private void openTuner() {
-        openTuner(true);
-    }
-
-    private void resetCallback() {
-        verify(mCallback, never()).onError(anyInt());
-        verify(mCallback, never()).onTuneFailed(anyInt(), any());
-        verify(mCallback, never()).onControlChanged(anyBoolean());
-        Mockito.reset(mCallback);
-    }
-
-    private void openTuner(boolean withAudio) {
-        assertNull(mRadioTuner);
-
-        // find FM band and build its config
-        mModule = mModules.get(0);
-
-        for (RadioManager.BandDescriptor band : mModule.getBands()) {
-            Log.d(TAG, "Band: " + band);
-            int bandType = band.getType();
-            if (bandType == RadioManager.BAND_AM || bandType == RadioManager.BAND_AM_HD) {
-                mAmBandDescriptor = (RadioManager.AmBandDescriptor)band;
-            }
-            if (bandType == RadioManager.BAND_FM || bandType == RadioManager.BAND_FM_HD) {
-                mFmBandDescriptor = (RadioManager.FmBandDescriptor)band;
-            }
-        }
-        assertNotNull(mAmBandDescriptor);
-        assertNotNull(mFmBandDescriptor);
-        mAmBandConfig = new RadioManager.AmBandConfig.Builder(mAmBandDescriptor).build();
-        mFmBandConfig = new RadioManager.FmBandConfig.Builder(mFmBandDescriptor).build();
-
-        mRadioTuner = mRadioManager.openTuner(mModule.getId(),
-                mFmBandConfig, withAudio, mCallback, null);
-        if (!withAudio) {
-            // non-audio sessions might not be supported - if so, then skip the test
-            assumeNotNull(mRadioTuner);
-        }
-        assertNotNull(mRadioTuner);
-        verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any());
-        resetCallback();
-
-        boolean isAntennaConnected = mRadioTuner.isAntennaConnected();
-        assertTrue(isAntennaConnected);
-    }
-
-    @Test
-    public void testOpenTuner() {
-        openTuner();
-    }
-
-    @Test
-    public void testReopenTuner() throws Throwable {
-        openTuner();
-        mRadioTuner.close();
-        mRadioTuner = null;
-        Thread.sleep(100);  // TODO(b/36122635): force reopen
-        openTuner();
-    }
-
-    @Test
-    public void testDoubleClose() {
-        openTuner();
-        mRadioTuner.close();
-        mRadioTuner.close();
-    }
-
-    @Test
-    public void testUseAfterClose() {
-        openTuner();
-        mRadioTuner.close();
-        int ret = mRadioTuner.cancel();
-        assertEquals(RadioManager.STATUS_INVALID_OPERATION, ret);
-    }
-
-    @Test
-    public void testSetAndGetConfiguration() {
-        openTuner();
-
-        // set
-        int ret = mRadioTuner.setConfiguration(mAmBandConfig);
-        assertEquals(RadioManager.STATUS_OK, ret);
-        verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any());
-
-        // get
-        RadioManager.BandConfig[] config = new RadioManager.BandConfig[1];
-        ret = mRadioTuner.getConfiguration(config);
-        assertEquals(RadioManager.STATUS_OK, ret);
-
-        assertEquals(mAmBandConfig, config[0]);
-    }
-
-    @Test
-    public void testSetBadConfiguration() throws Throwable {
-        openTuner();
-
-        // set null config
-        int ret = mRadioTuner.setConfiguration(null);
-        assertEquals(RadioManager.STATUS_BAD_VALUE, ret);
-        verify(mCallback, never()).onConfigurationChanged(any());
-
-        // setting good config should recover
-        ret = mRadioTuner.setConfiguration(mAmBandConfig);
-        assertEquals(RadioManager.STATUS_OK, ret);
-        verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any());
-    }
-
-    @Test
-    public void testMute() {
-        openTuner();
-
-        boolean isMuted = mRadioTuner.getMute();
-        assertFalse(isMuted);
-
-        int ret = mRadioTuner.setMute(true);
-        assertEquals(RadioManager.STATUS_OK, ret);
-        isMuted = mRadioTuner.getMute();
-        assertTrue(isMuted);
-
-        ret = mRadioTuner.setMute(false);
-        assertEquals(RadioManager.STATUS_OK, ret);
-        isMuted = mRadioTuner.getMute();
-        assertFalse(isMuted);
-    }
-
-    @Test
-    public void testMuteNoAudio() {
-        openTuner(false);
-
-        int ret = mRadioTuner.setMute(false);
-        assertEquals(RadioManager.STATUS_ERROR, ret);
-
-        boolean isMuted = mRadioTuner.getMute();
-        assertTrue(isMuted);
-    }
-
-    @Test
-    public void testStep() {
-        openTuner();
-
-        int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, true);
-        assertEquals(RadioManager.STATUS_OK, ret);
-        verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any());
-
-        resetCallback();
-
-        ret = mRadioTuner.step(RadioTuner.DIRECTION_UP, false);
-        assertEquals(RadioManager.STATUS_OK, ret);
-        verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any());
-    }
-
-    @Test
-    public void testStepLoop() {
-        openTuner();
-
-        for (int i = 0; i < 10; i++) {
-            Log.d(TAG, "step loop iteration " + (i + 1));
-
-            int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, true);
-            assertEquals(RadioManager.STATUS_OK, ret);
-            verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any());
-
-            resetCallback();
-        }
-    }
-
-    @Test
-    public void testTuneAndGetPI() {
-        openTuner();
-
-        int channel = mFmBandConfig.getLowerLimit() + mFmBandConfig.getSpacing();
-
-        // test tune
-        int ret = mRadioTuner.tune(channel, 0);
-        assertEquals(RadioManager.STATUS_OK, ret);
-        ArgumentCaptor<RadioManager.ProgramInfo> infoc =
-                ArgumentCaptor.forClass(RadioManager.ProgramInfo.class);
-        verify(mCallback, timeout(kTuneCallbackTimeoutMs))
-                .onProgramInfoChanged(infoc.capture());
-        assertEquals(channel, infoc.getValue().getChannel());
-
-        // test getProgramInformation
-        RadioManager.ProgramInfo[] info = new RadioManager.ProgramInfo[1];
-        ret = mRadioTuner.getProgramInformation(info);
-        assertEquals(RadioManager.STATUS_OK, ret);
-        assertNotNull(info[0]);
-        assertEquals(channel, info[0].getChannel());
-        Log.d(TAG, "PI: " + info[0].toString());
-    }
-
-    @Test
-    public void testDummyCancel() {
-        openTuner();
-
-        int ret = mRadioTuner.cancel();
-        assertEquals(RadioManager.STATUS_OK, ret);
-    }
-
-    @Test
-    public void testLateCancel() {
-        openTuner();
-
-        int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, false);
-        assertEquals(RadioManager.STATUS_OK, ret);
-        verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any());
-
-        int cancelRet = mRadioTuner.cancel();
-        assertEquals(RadioManager.STATUS_OK, cancelRet);
-    }
-
-    @Test
-    public void testScanAndCancel() {
-        openTuner();
-
-        /* There is a possible race condition between scan and cancel commands - the scan may finish
-         * before cancel command is issued. Thus we accept both outcomes in this test.
-         */
-        int scanRet = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, true);
-        int cancelRet = mRadioTuner.cancel();
-
-        assertEquals(RadioManager.STATUS_OK, scanRet);
-        assertEquals(RadioManager.STATUS_OK, cancelRet);
-
-        verify(mCallback, after(kCancelTimeoutMs).atMost(1))
-                .onTuneFailed(eq(RadioTuner.TUNER_RESULT_CANCELED), any());
-        verify(mCallback, atMost(1)).onProgramInfoChanged(any());
-        Mockito.reset(mCallback);
-    }
-
-    @Test
-    public void testStartBackgroundScan() {
-        openTuner();
-
-        boolean ret = mRadioTuner.startBackgroundScan();
-        boolean isSupported = mModule.isBackgroundScanningSupported();
-        assertEquals(isSupported, ret);
-    }
-
-    @Test
-    public void testGetProgramList() {
-        openTuner();
-
-        try {
-            Map<String, String> filter = new HashMap<>();
-            filter.put("com.google.dummy", "dummy");
-            List<RadioManager.ProgramInfo> list = mRadioTuner.getProgramList(filter);
-            assertNotNull(list);
-        } catch (IllegalStateException e) {
-            // the list may or may not be ready at this point
-            Log.i(TAG, "Background list is not ready");
-        }
-    }
-
-    @Test
-    public void testTuneFromProgramList() {
-        openTuner();
-
-        List<RadioManager.ProgramInfo> list;
-
-        try {
-            list = mRadioTuner.getProgramList(null);
-            assertNotNull(list);
-        } catch (IllegalStateException e) {
-            Log.i(TAG, "Background list is not ready, trying to fix it");
-
-            boolean success = mRadioTuner.startBackgroundScan();
-            assertTrue(success);
-            verify(mCallback, timeout(kFullScanTimeoutMs)).onBackgroundScanComplete();
-
-            list = mRadioTuner.getProgramList(null);
-            assertNotNull(list);
-        }
-
-        if (list.isEmpty()) {
-            Log.i(TAG, "Program list is empty, can't test tune");
-            return;
-        }
-
-        ProgramSelector sel = list.get(0).getSelector();
-        mRadioTuner.tune(sel);
-        ArgumentCaptor<RadioManager.ProgramInfo> infoc =
-                ArgumentCaptor.forClass(RadioManager.ProgramInfo.class);
-        verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(infoc.capture());
-        assertEquals(sel, infoc.getValue().getSelector());
-    }
-
-    @Test
-    public void testForcedAnalog() {
-        openTuner();
-
-        boolean isSupported = true;
-        boolean isForced;
-        try {
-            isForced = mRadioTuner.isAnalogForced();
-            assertFalse(isForced);
-        } catch (IllegalStateException ex) {
-            Log.i(TAG, "Forced analog switch is not supported by this tuner");
-            isSupported = false;
-        }
-
-        if (isSupported) {
-            mRadioTuner.setAnalogForced(true);
-            isForced = mRadioTuner.isAnalogForced();
-            assertTrue(isForced);
-
-            mRadioTuner.setAnalogForced(false);
-            isForced = mRadioTuner.isAnalogForced();
-            assertFalse(isForced);
-        } else {
-            assertThrows(IllegalStateException.class, () -> mRadioTuner.setAnalogForced(true));
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/internal/os/anr/AnrLatencyTrackerTests.java b/core/tests/coretests/src/android/internal/os/anr/AnrLatencyTrackerTests.java
index ebd78b46..4eea076 100644
--- a/core/tests/coretests/src/android/internal/os/anr/AnrLatencyTrackerTests.java
+++ b/core/tests/coretests/src/android/internal/os/anr/AnrLatencyTrackerTests.java
@@ -57,7 +57,9 @@
             .thenReturn(158L)
             .thenReturn(165L)
             .thenReturn(175L)
-            .thenReturn(188L);
+            .thenReturn(198L)
+            .thenReturn(203L)
+            .thenReturn(209L);
     }
 
     @Test
@@ -109,7 +111,7 @@
         mLatencyTracker.close();
 
         assertThat(mLatencyTracker.dumpAsCommaSeparatedArrayWithHeader())
-            .isEqualTo("DurationsV1: 50,5,25,8,100,2,3,7,8,15,2,7,13,10,3,4\n\n");
+            .isEqualTo("DurationsV2: 50,5,25,8,115,2,3,7,8,15,2,7,23,10,3,6\n\n");
         verify(mLatencyTracker, times(1)).pushAtom();
     }
 
diff --git a/core/tests/coretests/src/android/internal/os/anr/OWNERS b/core/tests/coretests/src/android/internal/os/anr/OWNERS
new file mode 100644
index 0000000..59cc70e
--- /dev/null
+++ b/core/tests/coretests/src/android/internal/os/anr/OWNERS
@@ -0,0 +1 @@
+include /core/java/com/android/internal/os/anr/OWNERS
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index cd61dbb..f6d67d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -47,7 +47,7 @@
 
     private final @NonNull PipBoundsState mPipBoundsState;
     private final PipSnapAlgorithm mSnapAlgorithm;
-    private final PipKeepClearAlgorithm mPipKeepClearAlgorithm;
+    private final PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
 
     private float mDefaultSizePercent;
     private float mMinAspectRatioForMinSize;
@@ -62,7 +62,7 @@
 
     public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState,
             @NonNull PipSnapAlgorithm pipSnapAlgorithm,
-            @NonNull PipKeepClearAlgorithm pipKeepClearAlgorithm) {
+            @NonNull PipKeepClearAlgorithmInterface pipKeepClearAlgorithm) {
         mPipBoundsState = pipBoundsState;
         mSnapAlgorithm = pipSnapAlgorithm;
         mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java
index e3495e1..5045cf9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java
@@ -24,7 +24,7 @@
  * Interface for interacting with keep clear algorithm used to move PiP window out of the way of
  * keep clear areas.
  */
-public interface PipKeepClearAlgorithm {
+public interface PipKeepClearAlgorithmInterface {
 
     /**
      * Adjust the position of picture in picture window based on the registered keep clear areas.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
index 690505e..ed8dc7de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
@@ -26,14 +26,14 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithm;
+import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
 
 import java.util.Set;
 
 /**
  * Calculates the adjusted position that does not occlude keep clear areas.
  */
-public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithm {
+public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithmInterface {
 
     private boolean mKeepClearAreaGravityEnabled =
             SystemProperties.getBoolean(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 3153313..e83854e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -46,8 +46,6 @@
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.UserManager;
 import android.util.Pair;
 import android.util.Size;
 import android.view.DisplayInfo;
@@ -85,7 +83,7 @@
 import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithm;
+import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -137,7 +135,7 @@
     private PipAppOpsListener mAppOpsListener;
     private PipMediaController mMediaController;
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
-    private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
+    private PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
     private PipBoundsState mPipBoundsState;
     private PipMotionHelper mPipMotionHelper;
     private PipTouchHandler mTouchHandler;
@@ -380,7 +378,7 @@
             PipAnimationController pipAnimationController,
             PipAppOpsListener pipAppOpsListener,
             PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipKeepClearAlgorithm pipKeepClearAlgorithm,
+            PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
             PipBoundsState pipBoundsState,
             PipMotionHelper pipMotionHelper,
             PipMediaController pipMediaController,
@@ -419,7 +417,7 @@
             PipAnimationController pipAnimationController,
             PipAppOpsListener pipAppOpsListener,
             PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipKeepClearAlgorithm pipKeepClearAlgorithm,
+            PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
             PipMotionHelper pipMotionHelper,
             PipMediaController pipMediaController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index 31490e4..b042063 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -37,7 +37,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipKeepClearAlgorithm;
+import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -62,7 +62,7 @@
             @NonNull TvPipBoundsState tvPipBoundsState,
             @NonNull PipSnapAlgorithm pipSnapAlgorithm) {
         super(context, tvPipBoundsState, pipSnapAlgorithm,
-                new PipKeepClearAlgorithm() {
+                new PipKeepClearAlgorithmInterface() {
                 });
         this.mTvPipBoundsState = tvPipBoundsState;
         this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 66d0a2a..665267f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -170,6 +170,7 @@
                 if (!isCustomRotate()) {
                     mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer, colorSpace);
                 }
+                hardwareBuffer.close();
             }
 
             t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenCfArm.kt
new file mode 100644
index 0000000..315e021
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenCfArm.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.bubble
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class DismissBubbleScreenCfArm(flicker: FlickerTest) : DismissBubbleScreen(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenCfArm.kt
new file mode 100644
index 0000000..26e6273
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenCfArm.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.bubble
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class ExpandBubbleScreenCfArm(flicker: FlickerTest) : ExpandBubbleScreen(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenCfArm.kt
new file mode 100644
index 0000000..ec6a9c1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenCfArm.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.bubble
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+open class LaunchBubbleScreenCfArm(flicker: FlickerTest) : LaunchBubbleScreen(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
new file mode 100644
index 0000000..69eb06f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.bubble
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+open class MultiBubblesScreenCfArm(flicker: FlickerTest) : MultiBubblesScreen(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 1524b16..5d7003f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -59,12 +59,8 @@
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            setup {
-                pipApp.launchViaIntent(wmHelper)
-            }
-            teardown {
-                pipApp.exit(wmHelper)
-            }
+            setup { pipApp.launchViaIntent(wmHelper) }
+            teardown { pipApp.exit(wmHelper) }
             transitions { pipApp.clickEnterPipButton(wmHelper) }
         }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index 94a16da..02d50f4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -17,6 +17,7 @@
 package com.android.wm.shell.flicker.pip
 
 import android.app.Activity
+import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerBuilder
@@ -28,6 +29,7 @@
 import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
 import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.service.PlatformConsts
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
@@ -163,10 +165,25 @@
     @Presubmit
     @Test
     fun pipAppLayerCoversFullScreenOnStart() {
+        Assume.assumeFalse(tapl.isTablet)
         flicker.assertLayersStart { visibleRegion(pipApp).coversExactly(startingBounds) }
     }
 
     /**
+     * Checks that the visible region of [pipApp] covers the full display area at the start of the
+     * transition
+     */
+    @Postsubmit
+    @Test
+    fun pipAppLayerPlusLetterboxCoversFullScreenOnStartTablet() {
+        Assume.assumeFalse(tapl.isTablet)
+        flicker.assertLayersStart {
+            visibleRegion(pipApp.or(ComponentNameMatcher.LETTERBOX))
+                .coversExactly(startingBounds)
+        }
+    }
+
+    /**
      * Checks that the visible region of [testApp] plus the visible region of [pipApp] cover the
      * full display area at the end of the transition
      */
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index dffbe7e..2c5455f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -57,7 +57,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
+open class ExitPipViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
 
     /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTestCfArm.kt
new file mode 100644
index 0000000..03dfa5b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTestCfArm.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ExitPipViaIntentTestCfArm(flicker: FlickerTest) : ExitPipViaIntentTest(flicker) {
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+         * and navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index 232c025..8a1a31a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -54,7 +54,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipWithDismissButtonTest(flicker: FlickerTest) : ExitPipTransition(flicker) {
+open class ExitPipWithDismissButtonTest(flicker: FlickerTest) : ExitPipTransition(flicker) {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTestCfArm.kt
new file mode 100644
index 0000000..a3f214c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTestCfArm.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class ExitPipWithDismissButtonTestCfArm(flicker: FlickerTest) :
+    ExitPipWithDismissButtonTest(flicker) {
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+         * and navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index dbbfdcc..54ad991 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -54,7 +54,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipWithSwipeDownTest(flicker: FlickerTest) : ExitPipTransition(flicker) {
+open class ExitPipWithSwipeDownTest(flicker: FlickerTest) : ExitPipTransition(flicker) {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTestCfArm.kt
new file mode 100644
index 0000000..9f26018
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTestCfArm.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ExitPipWithSwipeDownTestCfArm(flicker: FlickerTest) : ExitPipWithSwipeDownTest(flicker) {
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+         * and navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index c90c2d4..eff2df8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -54,7 +54,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flicker) {
+open class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flicker) {
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition { transitions { pipApp.doubleClickPipWindow(wmHelper) } }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestCfArm.kt
new file mode 100644
index 0000000..5feb73e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestCfArm.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ExpandPipOnDoubleClickTestCfArm(flicker: FlickerTest) : ExpandPipOnDoubleClickTest(flicker) {
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
index cb2326c..fcb8af4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
@@ -34,7 +34,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicker) {
+open class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicker) {
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition { transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.4f, 30) } }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt
new file mode 100644
index 0000000..30050bf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ExpandPipOnPinchOpenTestCfArm(flicker: FlickerTest) : ExpandPipOnPinchOpenTest(flicker) {
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 737e65c..3939e7f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -56,9 +56,7 @@
                 imeApp.launchViaIntent(wmHelper)
                 setRotation(flicker.scenario.startRotation)
             }
-            teardown {
-                imeApp.exit(wmHelper)
-            }
+            teardown { imeApp.exit(wmHelper) }
             transitions {
                 // open the soft keyboard
                 imeApp.openIME(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestCfArm.kt
new file mode 100644
index 0000000..9e2d27d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestCfArm.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipKeyboardTestCfArm(flicker: FlickerTest) : PipKeyboardTest(flicker) {
+    companion object {
+        private const val TAG_IME_VISIBLE = "imeIsVisible"
+
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestCfArm.kt
new file mode 100644
index 0000000..e72d604
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestCfArm.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipRotationTestCfArm(flicker: FlickerTest) : PipRotationTest(flicker) {
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+         * and navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.rotationTests()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index 1bf1354..ce31aac 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -74,9 +74,7 @@
                 removeAllTasksButHome()
                 pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
             }
-            teardown {
-                pipApp.exit(wmHelper)
-            }
+            teardown { pipApp.exit(wmHelper) }
 
             extraSpec(this)
         }
@@ -87,11 +85,10 @@
     fun hasAtMostOnePipDismissOverlayWindow() {
         val matcher = ComponentNameMatcher("", "pip-dismiss-overlay")
         flicker.assertWm {
-            val overlaysPerState = trace.entries.map { entry ->
-                entry.windowStates.count { window ->
-                    matcher.windowMatchesAnyOf(window)
-                } <= 1
-            }
+            val overlaysPerState =
+                trace.entries.map { entry ->
+                    entry.windowStates.count { window -> matcher.windowMatchesAnyOf(window) } <= 1
+                }
 
             Truth.assertWithMessage("Number of dismiss overlays per state")
                 .that(overlaysPerState)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index 262e429..298d0a6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -64,7 +64,7 @@
         initializeMockResources();
         mPipBoundsState = new PipBoundsState(mContext);
         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
-                new PipSnapAlgorithm(), new PipKeepClearAlgorithm() {});
+                new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {});
 
         mPipBoundsState.setDisplayLayout(
                 new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 9088077..17e7d74 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -98,7 +98,7 @@
         mPipBoundsState = new PipBoundsState(mContext);
         mPipTransitionState = new PipTransitionState();
         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
-                new PipSnapAlgorithm(), new PipKeepClearAlgorithm() {});
+                new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {});
         mMainExecutor = new TestShellExecutor();
         mPipTaskOrganizer = new PipTaskOrganizer(mContext,
                 mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 3bd2ae7..c1993b2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -37,7 +37,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithm;
+import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
@@ -90,8 +90,8 @@
         MockitoAnnotations.initMocks(this);
         mPipBoundsState = new PipBoundsState(mContext);
         final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm();
-        final PipKeepClearAlgorithm pipKeepClearAlgorithm =
-                new PipKeepClearAlgorithm() {};
+        final PipKeepClearAlgorithmInterface pipKeepClearAlgorithm =
+                new PipKeepClearAlgorithmInterface() {};
         final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
                 mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm);
         final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 474d6aa..8ad2932 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -34,7 +34,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithm;
+import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
@@ -106,7 +106,7 @@
         mPipBoundsState = new PipBoundsState(mContext);
         mPipSnapAlgorithm = new PipSnapAlgorithm();
         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
-                new PipKeepClearAlgorithm() {});
+                new PipKeepClearAlgorithmInterface() {});
         PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
                 mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
                 mMockPipTransitionController, mFloatingContentCoordinator);
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 273c7af..4323c73 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -708,12 +708,10 @@
          * It's easier to create it here than in C++.
          */
         try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {
-            native_setup(new WeakReference<MediaPlayer>(this), attributionSourceState.getParcel());
+            native_setup(new WeakReference<>(this), attributionSourceState.getParcel(),
+                    resolvePlaybackSessionId(context, sessionId));
         }
-
-        int effectiveSessionId = resolvePlaybackSessionId(context, sessionId);
-        baseRegisterPlayer(effectiveSessionId);
-        native_setAudioSessionId(effectiveSessionId);
+        baseRegisterPlayer(getAudioSessionId());
     }
 
     private Parcel createPlayerIIdParcel() {
@@ -1022,8 +1020,6 @@
             final AudioAttributes aa = audioAttributes != null ? audioAttributes :
                 new AudioAttributes.Builder().build();
             mp.setAudioAttributes(aa);
-            mp.native_setAudioSessionId(audioSessionId);
-
             mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
             afd.close();
             mp.prepare();
@@ -2521,7 +2517,7 @@
 
     private static native final void native_init();
     private native void native_setup(Object mediaplayerThis,
-            @NonNull Parcel attributionSource);
+            @NonNull Parcel attributionSource, int audioSessionId);
     private native final void native_finalize();
 
     /**
diff --git a/media/java/android/media/projection/MediaProjectionConfig.java b/media/java/android/media/projection/MediaProjectionConfig.java
index 29afaa6..30f34fe 100644
--- a/media/java/android/media/projection/MediaProjectionConfig.java
+++ b/media/java/android/media/projection/MediaProjectionConfig.java
@@ -98,24 +98,12 @@
     }
 
     /**
-     * Returns an instance which restricts the user to capturing a particular display.
-     *
-     * @param displayId The id of the display to capture. Only supports values of
-     *                  {@link android.view.Display#DEFAULT_DISPLAY}.
-     * @throws IllegalArgumentException If the given {@code displayId} is outside the range of
-     * supported values.
+     * Returns an instance which restricts the user to capturing the default display.
      */
     @NonNull
-    public static MediaProjectionConfig createConfigForDisplay(
-            @IntRange(from = DEFAULT_DISPLAY, to = DEFAULT_DISPLAY) int displayId) {
-        if (displayId != DEFAULT_DISPLAY) {
-            throw new IllegalArgumentException(
-                    "A config for capturing the non-default display is not supported; requested "
-                            + "display id "
-                            + displayId);
-        }
+    public static MediaProjectionConfig createConfigForDefaultDisplay() {
         MediaProjectionConfig config = new MediaProjectionConfig(CAPTURE_REGION_FIXED_DISPLAY);
-        config.mDisplayToCapture = displayId;
+        config.mDisplayToCapture = DEFAULT_DISPLAY;
         return config;
     }
 
@@ -279,10 +267,10 @@
     };
 
     @DataClass.Generated(
-            time = 1671030124845L,
+            time = 1673548980960L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/media/java/android/media/projection/MediaProjectionConfig.java",
-            inputSignatures = "public static final  int CAPTURE_REGION_USER_CHOICE\npublic static final  int CAPTURE_REGION_FIXED_DISPLAY\nprivate @android.annotation.IntRange int mDisplayToCapture\nprivate @android.media.projection.MediaProjectionConfig.CaptureRegion int mRegionToCapture\npublic static @android.annotation.NonNull android.media.projection.MediaProjectionConfig createConfigForDisplay(int)\npublic static @android.annotation.NonNull android.media.projection.MediaProjectionConfig createConfigForUserChoice()\nprivate static @android.annotation.NonNull java.lang.String captureRegionToString(int)\npublic @java.lang.Override java.lang.String toString()\nclass MediaProjectionConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genSetters=false, genConstructor=false, genBuilder=false, genToString=false, genHiddenConstDefs=true, genHiddenGetters=true, genConstDefs=false)")
+            inputSignatures = "public static final  int CAPTURE_REGION_USER_CHOICE\npublic static final  int CAPTURE_REGION_FIXED_DISPLAY\nprivate @android.annotation.IntRange int mDisplayToCapture\nprivate @android.media.projection.MediaProjectionConfig.CaptureRegion int mRegionToCapture\npublic static @android.annotation.NonNull android.media.projection.MediaProjectionConfig createConfigForDefaultDisplay()\npublic static @android.annotation.NonNull android.media.projection.MediaProjectionConfig createConfigForUserChoice()\nprivate static @android.annotation.NonNull java.lang.String captureRegionToString(int)\npublic @java.lang.Override java.lang.String toString()\nclass MediaProjectionConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genSetters=false, genConstructor=false, genBuilder=false, genToString=false, genHiddenConstDefs=true, genHiddenGetters=true, genConstDefs=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index d60dfd9..6d65c26 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -109,8 +109,8 @@
      * If {@link MediaProjectionConfig} was created from:
      * <ul>
      *     <li>
-     *         {@link MediaProjectionConfig#createConfigForDisplay(int)}, then creates an
-     *         {@link Intent} for capturing this particular display. The activity limits the user's
+     *         {@link MediaProjectionConfig#createConfigForDefaultDisplay()}, then creates an
+     *         {@link Intent} for capturing the default display. The activity limits the user's
      *         choice to just the display specified.
      *     </li>
      *     <li>
diff --git a/media/java/android/media/tv/TableRequest.java b/media/java/android/media/tv/TableRequest.java
index a1a6b51..a9ea6d3 100644
--- a/media/java/android/media/tv/TableRequest.java
+++ b/media/java/android/media/tv/TableRequest.java
@@ -33,11 +33,54 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({TABLE_NAME_PAT, TABLE_NAME_PMT})
+    @IntDef({TABLE_NAME_PAT, TABLE_NAME_PMT, TABLE_NAME_CAT})
     public @interface TableName {}
 
+    /** Program Association Table */
     public static final int TABLE_NAME_PAT = 0;
+    /** Program Mapping Table */
     public static final int TABLE_NAME_PMT = 1;
+    /**
+     * Conditional Access Table
+     * @hide
+     */
+    public static final int TABLE_NAME_CAT = 2;
+    /**
+     * Network Information Table
+     * @hide
+     */
+    public static final int TABLE_NAME_NIT = 3;
+    /**
+     * Bouquet Association Table
+     * @hide
+     */
+    public static final int TABLE_NAME_BAT = 4;
+    /**
+     * Service Description Table
+     * @hide
+     */
+    public static final int TABLE_NAME_SDT = 5;
+    /**
+     * Event Information Table
+     * @hide
+     */
+    public static final int TABLE_NAME_EIT = 6;
+    /**
+     * Time and Date Table
+     * @hide
+     */
+    public static final int TABLE_NAME_TDT = 7;
+    /**
+     * Time Offset Table
+     * @hide
+     */
+    public static final int TABLE_NAME_TOT = 8;
+    /**
+     * Selection Information Table
+     * @hide
+     */
+    public static final int TABLE_NAME_SIT = 9;
+
 
     public static final @NonNull Parcelable.Creator<TableRequest> CREATOR =
             new Parcelable.Creator<TableRequest>() {
diff --git a/media/java/android/media/tv/TableResponse.java b/media/java/android/media/tv/TableResponse.java
index afc9bee..1c314b0 100644
--- a/media/java/android/media/tv/TableResponse.java
+++ b/media/java/android/media/tv/TableResponse.java
@@ -21,6 +21,7 @@
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.SharedMemory;
 
 /**
  * A response for Table from broadcast signal.
@@ -46,6 +47,8 @@
     private final Uri mTableUri;
     private final int mVersion;
     private final int mSize;
+    private final byte[] mTableByteArray;
+    private final SharedMemory mTableSharedMemory;
 
     static TableResponse createFromParcelBody(Parcel in) {
         return new TableResponse(in);
@@ -54,9 +57,33 @@
     public TableResponse(int requestId, int sequence, @ResponseResult int responseResult,
             @Nullable Uri tableUri, int version, int size) {
         super(RESPONSE_TYPE, requestId, sequence, responseResult);
-        mTableUri = tableUri;
         mVersion = version;
         mSize = size;
+        mTableUri = tableUri;
+        mTableByteArray = null;
+        mTableSharedMemory = null;
+    }
+
+    /** @hide */
+    public TableResponse(int requestId, int sequence, @ResponseResult int responseResult,
+            @NonNull byte[] tableByteArray, int version, int size) {
+        super(RESPONSE_TYPE, requestId, sequence, responseResult);
+        mVersion = version;
+        mSize = size;
+        mTableUri = null;
+        mTableByteArray = tableByteArray;
+        mTableSharedMemory = null;
+    }
+
+    /** @hide */
+    public TableResponse(int requestId, int sequence, @ResponseResult int responseResult,
+            @NonNull SharedMemory tableSharedMemory, int version, int size) {
+        super(RESPONSE_TYPE, requestId, sequence, responseResult);
+        mVersion = version;
+        mSize = size;
+        mTableUri = null;
+        mTableByteArray = null;
+        mTableSharedMemory = tableSharedMemory;
     }
 
     TableResponse(Parcel source) {
@@ -65,6 +92,14 @@
         mTableUri = uriString == null ? null : Uri.parse(uriString);
         mVersion = source.readInt();
         mSize = source.readInt();
+        int arrayLength = source.readInt();
+        if (arrayLength >= 0) {
+            mTableByteArray = new byte[arrayLength];
+            source.readByteArray(mTableByteArray);
+        } else {
+            mTableByteArray = null;
+        }
+        mTableSharedMemory = (SharedMemory) source.readTypedObject(SharedMemory.CREATOR);
     }
 
     /**
@@ -76,6 +111,30 @@
     }
 
     /**
+     * Gets the data of the table as a byte array.
+     *
+     * @return the table data as a byte array, or {@code null} if the data is not stored as a byte
+     *         array.
+     * @hide
+     */
+    @Nullable
+    public byte[] getTableByteArray() {
+        return mTableByteArray;
+    }
+
+    /**
+     * Gets the data of the table as a {@link SharedMemory} object.
+     *
+     * @return the table data as a {@link SharedMemory} object, or {@code null} if the data is not
+     *         stored in shared memory.
+     * @hide
+     */
+    @Nullable
+    public SharedMemory getTableSharedMemory() {
+        return mTableSharedMemory;
+    }
+
+    /**
      * Gets the version number of requested table. If it is null, value will be -1.
      * <p>The consistency of version numbers between request and response depends on
      * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value
@@ -106,5 +165,12 @@
         dest.writeString(uriString);
         dest.writeInt(mVersion);
         dest.writeInt(mSize);
+        if (mTableByteArray != null) {
+            dest.writeInt(mTableByteArray.length);
+            dest.writeByteArray(mTableByteArray);
+        } else {
+            dest.writeInt(-1);
+        }
+        dest.writeTypedObject(mTableSharedMemory, flags);
     }
 }
diff --git a/media/java/android/media/tv/TimelineRequest.java b/media/java/android/media/tv/TimelineRequest.java
index 03c62f0..d04c58a 100644
--- a/media/java/android/media/tv/TimelineRequest.java
+++ b/media/java/android/media/tv/TimelineRequest.java
@@ -42,6 +42,7 @@
             };
 
     private final int mIntervalMillis;
+    private final String mSelector;
 
     static TimelineRequest createFromParcelBody(Parcel in) {
         return new TimelineRequest(in);
@@ -50,11 +51,21 @@
     public TimelineRequest(int requestId, @RequestOption int option, int intervalMillis) {
         super(REQUEST_TYPE, requestId, option);
         mIntervalMillis = intervalMillis;
+        mSelector = null;
+    }
+
+    /** @hide */
+    public TimelineRequest(int requestId, @RequestOption int option, int intervalMillis,
+            @NonNull String selector) {
+        super(REQUEST_TYPE, requestId, option);
+        mIntervalMillis = intervalMillis;
+        mSelector = selector;
     }
 
     TimelineRequest(Parcel source) {
         super(REQUEST_TYPE, source);
         mIntervalMillis = source.readInt();
+        mSelector = source.readString();
     }
 
     /**
@@ -64,6 +75,18 @@
         return mIntervalMillis;
     }
 
+    /**
+     * Gets the timeline selector.
+     * <p>The selector describes the type and location of timeline signalling. For example
+     * {@code urn:dvb:css:timeline:pts} is a selector in DVB standard.
+     *
+     * @return the selector if it's set; {@code null} otherwise.
+     * @hide
+     */
+    public String getSelector() {
+        return mSelector;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -73,5 +96,6 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         dest.writeInt(mIntervalMillis);
+        dest.writeString(mSelector);
     }
 }
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index 537e711..ad9312f 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -41,7 +41,9 @@
     void onTeletextAppStateChanged(int state, int seq);
     void onAdBuffer(in AdBuffer buffer, int seq);
     void onCommandRequest(in String cmdType, in Bundle parameters, int seq);
+    void onTimeShiftCommandRequest(in String cmdType, in Bundle parameters, int seq);
     void onSetVideoBounds(in Rect rect, int seq);
+    void onRequestCurrentVideoBounds(int seq);
     void onRequestCurrentChannelUri(int seq);
     void onRequestCurrentChannelLcn(int seq);
     void onRequestStreamVolume(int seq);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 5a0ac84..c0723f7 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -26,6 +26,7 @@
 import android.media.tv.interactive.ITvInteractiveAppClient;
 import android.media.tv.interactive.ITvInteractiveAppManagerCallback;
 import android.media.tv.interactive.TvInteractiveAppServiceInfo;
+import android.media.PlaybackParams;
 import android.net.Uri;
 import android.os.Bundle;
 import android.view.Surface;
@@ -36,6 +37,7 @@
  */
 interface ITvInteractiveAppManager {
     List<TvInteractiveAppServiceInfo> getTvInteractiveAppServiceList(int userId);
+    List<AppLinkInfo> getAppLinkInfoList(int userId);
     void registerAppLinkInfo(String tiasId, in AppLinkInfo info, int userId);
     void unregisterAppLinkInfo(String tiasId, in AppLinkInfo info, int userId);
     void sendAppLinkCommand(String tiasId, in Bundle command, int userId);
@@ -46,6 +48,7 @@
             in IBinder sessionToken, in Uri biIAppUri, in Bundle params, int userId);
     void destroyBiInteractiveApp(in IBinder sessionToken, in String biIAppId, int userId);
     void setTeletextAppEnabled(in IBinder sessionToken, boolean enable, int userId);
+    void sendCurrentVideoBounds(in IBinder sessionToken, in Rect bounds, int userId);
     void sendCurrentChannelUri(in IBinder sessionToken, in Uri channelUri, int userId);
     void sendCurrentChannelLcn(in IBinder sessionToken, int lcn, int userId);
     void sendStreamVolume(in IBinder sessionToken, float volume, int userId);
@@ -57,6 +60,14 @@
     void sendTvRecordingInfoList(in IBinder sessionToken,
             in List<TvRecordingInfo> recordingInfoList, int userId);
     void notifyError(in IBinder sessionToken, in String errMsg, in Bundle params, int userId);
+    void notifyTimeShiftPlaybackParams(
+            in IBinder sessionToken, in PlaybackParams params, int userId);
+    void notifyTimeShiftStatusChanged(
+            in IBinder sessionToken, in String inputId, int status, int userId);
+    void notifyTimeShiftStartPositionChanged(
+            in IBinder sessionToken, in String inputId, long timeMs, int userId);
+    void notifyTimeShiftCurrentPositionChanged(
+            in IBinder sessionToken, in String inputId, long timeMs, int userId);
     void createSession(in ITvInteractiveAppClient client, in String iAppServiceId, int type,
             int seq, int userId);
     void releaseSession(in IBinder sessionToken, int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 20ba57b..9ae9ca7 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -20,6 +20,7 @@
 import android.media.tv.BroadcastInfoResponse;
 import android.net.Uri;
 import android.media.tv.AdBuffer;
+import android.media.PlaybackParams;
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvTrackInfo;
@@ -39,6 +40,7 @@
     void createBiInteractiveApp(in Uri biIAppUri, in Bundle params);
     void destroyBiInteractiveApp(in String biIAppId);
     void setTeletextAppEnabled(boolean enable);
+    void sendCurrentVideoBounds(in Rect bounds);
     void sendCurrentChannelUri(in Uri channelUri);
     void sendCurrentChannelLcn(int lcn);
     void sendStreamVolume(float volume);
@@ -48,6 +50,10 @@
     void sendTvRecordingInfo(in TvRecordingInfo recordingInfo);
     void sendTvRecordingInfoList(in List<TvRecordingInfo> recordingInfoList);
     void notifyError(in String errMsg, in Bundle params);
+    void notifyTimeShiftPlaybackParams(in PlaybackParams params);
+    void notifyTimeShiftStatusChanged(in String inputId, int status);
+    void notifyTimeShiftStartPositionChanged(in String inputId, long timeMs);
+    void notifyTimeShiftCurrentPositionChanged(in String inputId, long timeMs);
     void release();
     void notifyTuned(in Uri channelUri);
     void notifyTrackSelected(int type, in String trackId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index c5dbd19..d84affd 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -40,7 +40,9 @@
     void onTeletextAppStateChanged(int state);
     void onAdBuffer(in AdBuffer buffer);
     void onCommandRequest(in String cmdType, in Bundle parameters);
+    void onTimeShiftCommandRequest(in String cmdType, in Bundle parameters);
     void onSetVideoBounds(in Rect rect);
+    void onRequestCurrentVideoBounds();
     void onRequestCurrentChannelUri();
     void onRequestCurrentChannelLcn();
     void onRequestStreamVolume();
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index a55e1ac..8a23e65 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Rect;
+import android.media.PlaybackParams;
 import android.media.tv.AdBuffer;
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoResponse;
@@ -89,6 +90,11 @@
     private static final int DO_NOTIFY_TV_MESSAGE = 33;
     private static final int DO_SEND_RECORDING_INFO = 34;
     private static final int DO_SEND_RECORDING_INFO_LIST = 35;
+    private static final int DO_NOTIFY_TIME_SHIFT_PLAYBACK_PARAMS = 36;
+    private static final int DO_NOTIFY_TIME_SHIFT_STATUS_CHANGED = 37;
+    private static final int DO_NOTIFY_TIME_SHIFT_START_POSITION_CHANGED = 38;
+    private static final int DO_NOTIFY_TIME_SHIFT_CURRENT_POSITION_CHANGED = 39;
+    private static final int DO_SEND_CURRENT_VIDEO_BOUNDS = 40;
 
     private final HandlerCaller mCaller;
     private Session mSessionImpl;
@@ -152,6 +158,10 @@
                 mSessionImpl.setTeletextAppEnabled((Boolean) msg.obj);
                 break;
             }
+            case DO_SEND_CURRENT_VIDEO_BOUNDS: {
+                mSessionImpl.sendCurrentVideoBounds((Rect) msg.obj);
+                break;
+            }
             case DO_SEND_CURRENT_CHANNEL_URI: {
                 mSessionImpl.sendCurrentChannelUri((Uri) msg.obj);
                 break;
@@ -277,6 +287,30 @@
                 mSessionImpl.notifyAdBufferConsumed((AdBuffer) msg.obj);
                 break;
             }
+            case DO_NOTIFY_TIME_SHIFT_PLAYBACK_PARAMS: {
+                mSessionImpl.notifyTimeShiftPlaybackParams((PlaybackParams) msg.obj);
+                break;
+            }
+            case DO_NOTIFY_TIME_SHIFT_STATUS_CHANGED: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.notifyTimeShiftStatusChanged((String) args.arg1, (Integer) args.arg2);
+                args.recycle();
+                break;
+            }
+            case DO_NOTIFY_TIME_SHIFT_START_POSITION_CHANGED: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.notifyTimeShiftStartPositionChanged(
+                        (String) args.arg1, (Long) args.arg2);
+                args.recycle();
+                break;
+            }
+            case DO_NOTIFY_TIME_SHIFT_CURRENT_POSITION_CHANGED: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.notifyTimeShiftCurrentPositionChanged(
+                        (String) args.arg1, (Long) args.arg2);
+                args.recycle();
+                break;
+            }
             default: {
                 Log.w(TAG, "Unhandled message code: " + msg.what);
                 break;
@@ -326,6 +360,12 @@
     }
 
     @Override
+    public void sendCurrentVideoBounds(@Nullable Rect bounds) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageO(DO_SEND_CURRENT_VIDEO_BOUNDS, bounds));
+    }
+
+    @Override
     public void sendCurrentChannelUri(@Nullable Uri channelUri) {
         mCaller.executeOrSendMessage(
                 mCaller.obtainMessageO(DO_SEND_CURRENT_CHANNEL_URI, channelUri));
@@ -380,6 +420,30 @@
     }
 
     @Override
+    public void notifyTimeShiftPlaybackParams(@NonNull PlaybackParams params) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageO(DO_NOTIFY_TIME_SHIFT_PLAYBACK_PARAMS, params));
+    }
+
+    @Override
+    public void notifyTimeShiftStatusChanged(@NonNull String inputId, int status) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageOO(DO_NOTIFY_TIME_SHIFT_STATUS_CHANGED, inputId, status));
+    }
+
+    @Override
+    public void notifyTimeShiftStartPositionChanged(@NonNull String inputId, long timeMs) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(
+                DO_NOTIFY_TIME_SHIFT_START_POSITION_CHANGED, inputId, timeMs));
+    }
+
+    @Override
+    public void notifyTimeShiftCurrentPositionChanged(@NonNull String inputId, long timeMs) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(
+                DO_NOTIFY_TIME_SHIFT_CURRENT_POSITION_CHANGED, inputId, timeMs));
+    }
+
+    @Override
     public void release() {
         mSessionImpl.scheduleMediaViewCleanup();
         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index f4847f7..fd3c29b 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -23,6 +23,7 @@
 import android.annotation.SystemService;
 import android.content.Context;
 import android.graphics.Rect;
+import android.media.PlaybackParams;
 import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.AdResponse;
@@ -405,6 +406,21 @@
             }
 
             @Override
+            public void onTimeShiftCommandRequest(
+                    @TvInteractiveAppService.TimeShiftCommandType String cmdType,
+                    Bundle parameters,
+                    int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postTimeShiftCommandRequest(cmdType, parameters);
+                }
+            }
+
+            @Override
             public void onSetVideoBounds(Rect rect, int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -429,6 +445,18 @@
             }
 
             @Override
+            public void onRequestCurrentVideoBounds(int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postRequestCurrentVideoBounds();
+                }
+            }
+
+            @Override
             public void onRequestCurrentChannelUri(int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -849,6 +877,24 @@
     }
 
     /**
+     * Returns a list of available app link information.
+     *
+     * <P>A package must declare its app link info in its manifest using meta-data tag, so the info
+     * can be detected by the system.
+     *
+     * @return List of {@link AppLinkInfo} for each package that deslares its app link information.
+     * @hide
+     */
+    @NonNull
+    public List<AppLinkInfo> getAppLinkInfoList() {
+        try {
+            return mService.getAppLinkInfoList(mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Registers an Android application link info record which can be used to launch the specific
      * Android application by TV interactive App RTE.
      *
@@ -1050,6 +1096,18 @@
             }
         }
 
+        void sendCurrentVideoBounds(@NonNull Rect bounds) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.sendCurrentVideoBounds(mToken, bounds, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         void sendCurrentChannelUri(@Nullable Uri channelUri) {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
@@ -1182,6 +1240,55 @@
             }
         }
 
+        void notifyTimeShiftPlaybackParams(@NonNull PlaybackParams params) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyTimeShiftPlaybackParams(mToken, params, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void notifyTimeShiftStatusChanged(
+                @NonNull String inputId, @TvInputManager.TimeShiftStatus int status) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyTimeShiftStatusChanged(mToken, inputId, status, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void notifyTimeShiftStartPositionChanged(@NonNull String inputId, long timeMs) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyTimeShiftStartPositionChanged(mToken, inputId, timeMs, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void notifyTimeShiftCurrentPositionChanged(@NonNull String inputId, long timeMs) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyTimeShiftCurrentPositionChanged(mToken, inputId, timeMs, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         /**
          * Sets the {@link android.view.Surface} for this session.
          *
@@ -1795,6 +1902,17 @@
             });
         }
 
+        void postTimeShiftCommandRequest(
+                final @TvInteractiveAppService.TimeShiftCommandType String cmdType,
+                final Bundle parameters) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onTimeShiftCommandRequest(mSession, cmdType, parameters);
+                }
+            });
+        }
+
         void postSetVideoBounds(Rect rect) {
             mHandler.post(new Runnable() {
                 @Override
@@ -1804,6 +1922,15 @@
             });
         }
 
+        void postRequestCurrentVideoBounds() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onRequestCurrentVideoBounds(mSession);
+                }
+            });
+        }
+
         void postRequestCurrentChannelUri() {
             mHandler.post(new Runnable() {
                 @Override
@@ -2003,6 +2130,20 @@
         }
 
         /**
+         * This is called when {@link TvInteractiveAppService.Session#requestTimeShiftCommand} is
+         * called.
+         *
+         * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
+         * @param cmdType type of the time shift command.
+         * @param parameters parameters of the command.
+         */
+        public void onTimeShiftCommandRequest(
+                Session session,
+                @TvInteractiveAppService.TimeShiftCommandType String cmdType,
+                Bundle parameters) {
+        }
+
+        /**
          * This is called when {@link TvInteractiveAppService.Session#SetVideoBounds} is called.
          *
          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
@@ -2011,6 +2152,15 @@
         }
 
         /**
+         * This is called when {@link TvInteractiveAppService.Session#RequestCurrentVideoBounds} is
+         * called.
+         *
+         * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
+         */
+        public void onRequestCurrentVideoBounds(Session session) {
+        }
+
+        /**
          * This is called when {@link TvInteractiveAppService.Session#RequestCurrentChannelUri} is
          * called.
          *
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 3ca9f2f..be2c16c 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -17,6 +17,7 @@
 package android.media.tv.interactive;
 
 import android.annotation.CallSuper;
+import android.annotation.IntDef;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -30,6 +31,7 @@
 import android.content.Intent;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.media.PlaybackParams;
 import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.AdResponse;
@@ -138,6 +140,38 @@
      * Playback command type: select the given track.
      */
     public static final String PLAYBACK_COMMAND_TYPE_SELECT_TRACK = "select_track";
+
+
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "PLAYBACK_COMMAND_STOP_MODE_", value = {
+            PLAYBACK_COMMAND_STOP_MODE_BLANK,
+            PLAYBACK_COMMAND_STOP_MODE_FREEZE
+    })
+    public @interface PlaybackCommandStopMode {}
+
+    /**
+     * Playback command stop mode: show a blank screen.
+     * @hide
+     */
+    public static final int PLAYBACK_COMMAND_STOP_MODE_BLANK = 1;
+
+    /**
+     * Playback command stop mode: freeze the video.
+     * @hide
+     */
+    public static final int PLAYBACK_COMMAND_STOP_MODE_FREEZE = 2;
+
+    /**
+     * Playback command parameter: stop mode.
+     * <p>Type: int
+     *
+     * @see #PLAYBACK_COMMAND_TYPE_STOP
+     * @hide
+     */
+    public static final String COMMAND_PARAMETER_KEY_STOP_MODE = "command_stop_mode";
+
     /**
      * Playback command parameter: channel URI.
      * <p>Type: android.net.Uri
@@ -182,6 +216,78 @@
     public static final String COMMAND_PARAMETER_KEY_CHANGE_CHANNEL_QUIETLY =
             "command_change_channel_quietly";
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef(prefix = "TIME_SHIFT_COMMAND_TYPE_", value = {
+            TIME_SHIFT_COMMAND_TYPE_PLAY,
+            TIME_SHIFT_COMMAND_TYPE_PAUSE,
+            TIME_SHIFT_COMMAND_TYPE_RESUME,
+            TIME_SHIFT_COMMAND_TYPE_SEEK_TO,
+            TIME_SHIFT_COMMAND_TYPE_SET_PLAYBACK_PARAMS,
+    })
+    public @interface TimeShiftCommandType {}
+
+    /**
+     * Time shift command type: play.
+     *
+     * @see TvView#timeShiftPlay(String, Uri)
+     * @hide
+     */
+    public static final String TIME_SHIFT_COMMAND_TYPE_PLAY = "play";
+    /**
+     * Time shift command type: pause.
+     *
+     * @see TvView#timeShiftPause()
+     * @hide
+     */
+    public static final String TIME_SHIFT_COMMAND_TYPE_PAUSE = "pause";
+    /**
+     * Time shift command type: resume.
+     *
+     * @see TvView#timeShiftResume()
+     * @hide
+     */
+    public static final String TIME_SHIFT_COMMAND_TYPE_RESUME = "resume";
+    /**
+     * Time shift command type: seek to.
+     *
+     * @see TvView#timeShiftSeekTo(long)
+     * @hide
+     */
+    public static final String TIME_SHIFT_COMMAND_TYPE_SEEK_TO = "seek_to";
+    /**
+     * Time shift command type: set playback params.
+     *
+     * @see TvView#timeShiftSetPlaybackParams(PlaybackParams)
+     * @hide
+     */
+    public static final String TIME_SHIFT_COMMAND_TYPE_SET_PLAYBACK_PARAMS = "set_playback_params";
+
+    /**
+     * Time shift command parameter: program URI.
+     * <p>Type: android.net.Uri
+     *
+     * @see #TIME_SHIFT_COMMAND_TYPE_PLAY
+     * @hide
+     */
+    public static final String COMMAND_PARAMETER_KEY_PROGRAM_URI = "command_program_uri";
+    /**
+     * Time shift command parameter: time position for time shifting, in milliseconds.
+     * <p>Type: long
+     *
+     * @see #TIME_SHIFT_COMMAND_TYPE_SEEK_TO
+     * @hide
+     */
+    public static final String COMMAND_PARAMETER_KEY_TIME_POSITION = "command_time_position";
+    /**
+     * Time shift command parameter: playback params.
+     * <p>Type: android.media.PlaybackParams
+     *
+     * @see #TIME_SHIFT_COMMAND_TYPE_SET_PLAYBACK_PARAMS
+     * @hide
+     */
+    public static final String COMMAND_PARAMETER_KEY_PLAYBACK_PARAMS = "command_playback_params";
+
     private final Handler mServiceHandler = new ServiceHandler();
     private final RemoteCallbackList<ITvInteractiveAppServiceCallback> mCallbacks =
             new RemoteCallbackList<>();
@@ -425,6 +531,13 @@
         }
 
         /**
+         * Receives current video bounds.
+         * @hide
+         */
+        public void onCurrentVideoBounds(@NonNull Rect bounds) {
+        }
+
+        /**
          * Receives current channel URI.
          */
         public void onCurrentChannelUri(@Nullable Uri channelUri) {
@@ -520,6 +633,44 @@
         }
 
         /**
+         * Called when the time shift {@link android.media.PlaybackParams} is set or changed.
+         *
+         * @see TvView#timeShiftSetPlaybackParams(PlaybackParams)
+         * @hide
+         */
+        public void onTimeShiftPlaybackParams(@NonNull PlaybackParams params) {
+        }
+
+        /**
+         * Called when time shift status is changed.
+         *
+         * @see TvView.TvInputCallback#onTimeShiftStatusChanged(String, int)
+         * @see android.media.tv.TvInputService.Session#notifyTimeShiftStatusChanged(int)
+         * @hide
+         */
+        public void onTimeShiftStatusChanged(
+                @NonNull String inputId, @TvInputManager.TimeShiftStatus int status) {
+        }
+
+        /**
+         * Called when time shift start position is changed.
+         *
+         * @see TvView.TimeShiftPositionCallback#onTimeShiftStartPositionChanged(String, long)
+         * @hide
+         */
+        public void onTimeShiftStartPositionChanged(@NonNull String inputId, long timeMs) {
+        }
+
+        /**
+         * Called when time shift current position is changed.
+         *
+         * @see TvView.TimeShiftPositionCallback#onTimeShiftCurrentPositionChanged(String, long)
+         * @hide
+         */
+        public void onTimeShiftCurrentPositionChanged(@NonNull String inputId, long timeMs) {
+        }
+
+        /**
          * Called when the application sets the surface.
          *
          * <p>The TV Interactive App service should render interactive app UI onto the given
@@ -820,6 +971,35 @@
         }
 
         /**
+         * Sends a specific time shift command to be processed by the related TV input.
+         *
+         * @param cmdType type of the specific command
+         * @param parameters parameters of the specific command
+         * @hide
+         */
+        @CallSuper
+        public void sendTimeShiftCommandRequest(
+                @TimeShiftCommandType @NonNull String cmdType, @Nullable Bundle parameters) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "requestTimeShiftCommand (cmdType=" + cmdType
+                                    + ", parameters=" + parameters.toString() + ")");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onTimeShiftCommandRequest(cmdType, parameters);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in requestTimeShiftCommand", e);
+                    }
+                }
+            });
+        }
+
+        /**
          * Sets broadcast video bounds.
          */
         @CallSuper
@@ -843,6 +1023,30 @@
         }
 
         /**
+         * Requests the bounds of the current video.
+         * @hide
+         */
+        @CallSuper
+        public void requestCurrentVideoBounds() {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "requestCurrentVideoBounds");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onRequestCurrentVideoBounds();
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in requestCurrentVideoBounds", e);
+                    }
+                }
+            });
+        }
+
+        /**
          * Requests the URI of the current channel.
          */
         @CallSuper
@@ -1169,6 +1373,10 @@
             onSetTeletextAppEnabled(enable);
         }
 
+        void sendCurrentVideoBounds(@NonNull Rect bounds) {
+            onCurrentVideoBounds(bounds);
+        }
+
         void sendCurrentChannelUri(@Nullable Uri channelUri) {
             onCurrentChannelUri(channelUri);
         }
@@ -1330,6 +1538,34 @@
         }
 
         /**
+         * Calls {@link #onTimeShiftPlaybackParams(PlaybackParams)}.
+         */
+        void notifyTimeShiftPlaybackParams(PlaybackParams params) {
+            onTimeShiftPlaybackParams(params);
+        }
+
+        /**
+         * Calls {@link #onTimeShiftStatusChanged(String, int)}.
+         */
+        void notifyTimeShiftStatusChanged(String inputId, int status) {
+            onTimeShiftStatusChanged(inputId, status);
+        }
+
+        /**
+         * Calls {@link #onTimeShiftStartPositionChanged(String, long)}.
+         */
+        void notifyTimeShiftStartPositionChanged(String inputId, long timeMs) {
+            onTimeShiftStartPositionChanged(inputId, timeMs);
+        }
+
+        /**
+         * Calls {@link #onTimeShiftCurrentPositionChanged(String, long)}.
+         */
+        void notifyTimeShiftCurrentPositionChanged(String inputId, long timeMs) {
+            onTimeShiftCurrentPositionChanged(inputId, timeMs);
+        }
+
+        /**
          * Notifies when the session state is changed.
          *
          * @param state the current session state.
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppServiceInfo.java b/media/java/android/media/tv/interactive/TvInteractiveAppServiceInfo.java
index 3e08852..acc2444 100644
--- a/media/java/android/media/tv/interactive/TvInteractiveAppServiceInfo.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppServiceInfo.java
@@ -57,6 +57,8 @@
             INTERACTIVE_APP_TYPE_HBBTV,
             INTERACTIVE_APP_TYPE_ATSC,
             INTERACTIVE_APP_TYPE_GINGA,
+            INTERACTIVE_APP_TYPE_TARGETED_AD,
+            INTERACTIVE_APP_TYPE_OTHER
     })
     public @interface InteractiveAppType {}
 
@@ -66,10 +68,21 @@
     public static final int INTERACTIVE_APP_TYPE_ATSC = 0x2;
     /** Ginga interactive app type */
     public static final int INTERACTIVE_APP_TYPE_GINGA = 0x4;
+    /**
+     * Targeted Advertisement interactive app type
+     * @hide
+     */
+    public static final int INTERACTIVE_APP_TYPE_TARGETED_AD = 0x8;
+    /**
+     * Other interactive app type
+     * @hide
+     */
+    public static final int INTERACTIVE_APP_TYPE_OTHER = 0x80000000;
 
     private final ResolveInfo mService;
     private final String mId;
     private int mTypes;
+    private final List<String> mExtraTypes = new ArrayList<>();
 
     /**
      * Constructs a TvInteractiveAppServiceInfo object.
@@ -98,18 +111,21 @@
 
         mService = resolveInfo;
         mId = id;
-        mTypes = toTypesFlag(types);
+        toTypesFlag(types);
     }
-    private TvInteractiveAppServiceInfo(ResolveInfo service, String id, int types) {
+    private TvInteractiveAppServiceInfo(
+            ResolveInfo service, String id, int types, List<String> extraTypes) {
         mService = service;
         mId = id;
         mTypes = types;
+        mExtraTypes.addAll(extraTypes);
     }
 
     private TvInteractiveAppServiceInfo(@NonNull Parcel in) {
         mService = ResolveInfo.CREATOR.createFromParcel(in);
         mId = in.readString();
         mTypes = in.readInt();
+        in.readStringList(mExtraTypes);
     }
 
     public static final @NonNull Creator<TvInteractiveAppServiceInfo> CREATOR =
@@ -135,6 +151,7 @@
         mService.writeToParcel(dest, flags);
         dest.writeString(mId);
         dest.writeInt(mTypes);
+        dest.writeStringList(mExtraTypes);
     }
 
     /**
@@ -171,6 +188,17 @@
         return mTypes;
     }
 
+    /**
+     * Gets extra supported interactive app types which are not listed.
+     *
+     * @see #getSupportedTypes()
+     * @hide
+     */
+    @NonNull
+    public List<String> getExtraSupportedTypes() {
+        return mExtraTypes;
+    }
+
     private static String generateInteractiveAppServiceId(ComponentName name) {
         return name.flattenToShortString();
     }
@@ -219,23 +247,27 @@
         }
     }
 
-    private static int toTypesFlag(List<String> types) {
-        int flag = 0;
+    private void toTypesFlag(List<String> types) {
+        mTypes = 0;
+        mExtraTypes.clear();
         for (String type : types) {
             switch (type) {
                 case "hbbtv":
-                    flag |= INTERACTIVE_APP_TYPE_HBBTV;
+                    mTypes |= INTERACTIVE_APP_TYPE_HBBTV;
                     break;
                 case "atsc":
-                    flag |= INTERACTIVE_APP_TYPE_ATSC;
+                    mTypes |= INTERACTIVE_APP_TYPE_ATSC;
                     break;
                 case "ginga":
-                    flag |= INTERACTIVE_APP_TYPE_GINGA;
+                    mTypes |= INTERACTIVE_APP_TYPE_GINGA;
                     break;
+                case "targeted_ad":
+                    mTypes |= INTERACTIVE_APP_TYPE_TARGETED_AD;
                 default:
+                    mTypes |= INTERACTIVE_APP_TYPE_OTHER;
+                    mExtraTypes.add(type);
                     break;
             }
         }
-        return flag;
     }
 }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 6777d1a..af03bb8 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -25,6 +25,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.media.PlaybackParams;
 import android.media.tv.TvInputManager;
 import android.media.tv.TvRecordingInfo;
 import android.media.tv.TvTrackInfo;
@@ -513,6 +514,19 @@
     }
 
     /**
+     * Sends current video bounds to related TV interactive app.
+     * @hide
+     */
+    public void sendCurrentVideoBounds(@NonNull Rect bounds) {
+        if (DEBUG) {
+            Log.d(TAG, "sendCurrentVideoBounds");
+        }
+        if (mSession != null) {
+            mSession.sendCurrentVideoBounds(bounds);
+        }
+    }
+
+    /**
      * Sends current channel URI to related TV interactive app.
      *
      * @param channelUri The current channel URI; {@code null} if there is no currently tuned
@@ -684,6 +698,75 @@
         }
     }
 
+    /**
+     * Notifies the corresponding {@link TvInteractiveAppService} when a time shift
+     * {@link android.media.PlaybackParams} is set or changed.
+     *
+     * @see TvView#timeShiftSetPlaybackParams(PlaybackParams)
+     * @hide
+     */
+    public void notifyTimeShiftPlaybackParams(@NonNull PlaybackParams params) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyTimeShiftPlaybackParams params=" + params);
+        }
+        if (mSession != null) {
+            mSession.notifyTimeShiftPlaybackParams(params);
+        }
+    }
+
+    /**
+     * Notifies the corresponding {@link TvInteractiveAppService} when time shift
+     * status is changed.
+     *
+     * @see TvView.TvInputCallback#onTimeShiftStatusChanged(String, int)
+     * @see android.media.tv.TvInputService.Session#notifyTimeShiftStatusChanged(int)
+     * @hide
+     */
+    public void notifyTimeShiftStatusChanged(
+            @NonNull String inputId, @TvInputManager.TimeShiftStatus int status) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "notifyTimeShiftStatusChanged inputId=" + inputId + "; status=" + status);
+        }
+        if (mSession != null) {
+            mSession.notifyTimeShiftStatusChanged(inputId, status);
+        }
+    }
+
+    /**
+     * Notifies the corresponding {@link TvInteractiveAppService} when time shift
+     * start position is changed.
+     *
+     * @see TvView.TimeShiftPositionCallback#onTimeShiftStartPositionChanged(String, long)
+     * @hide
+     */
+    public void notifyTimeShiftStartPositionChanged(@NonNull String inputId, long timeMs) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyTimeShiftStartPositionChanged inputId=" + inputId
+                    + "; timeMs=" + timeMs);
+        }
+        if (mSession != null) {
+            mSession.notifyTimeShiftStartPositionChanged(inputId, timeMs);
+        }
+    }
+
+    /**
+     * Notifies the corresponding {@link TvInteractiveAppService} when time shift
+     * current position is changed.
+     *
+     * @see TvView.TimeShiftPositionCallback#onTimeShiftCurrentPositionChanged(String, long)
+     * @hide
+     */
+    public void notifyTimeShiftCurrentPositionChanged(@NonNull String inputId, long timeMs) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyTimeShiftCurrentPositionChanged inputId=" + inputId
+                    + "; timeMs=" + timeMs);
+        }
+        if (mSession != null) {
+            mSession.notifyTimeShiftCurrentPositionChanged(inputId, timeMs);
+        }
+    }
+
     private void resetInternal() {
         mSessionCallback = null;
         if (mSession != null) {
@@ -808,6 +891,21 @@
         }
 
         /**
+         * This is called when a time shift command is requested to be processed by the related TV
+         * input.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @param cmdType type of the command
+         * @param parameters parameters of the command
+         * @hide
+         */
+        public void onTimeShiftCommandRequest(
+                @NonNull String iAppServiceId,
+                @NonNull @TvInteractiveAppService.TimeShiftCommandType String cmdType,
+                @NonNull Bundle parameters) {
+        }
+
+        /**
          * This is called when the state of corresponding interactive app is changed.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
@@ -859,6 +957,16 @@
         }
 
         /**
+         * This is called when {@link TvInteractiveAppService.Session#requestCurrentVideoBounds()}
+         * is called.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @hide
+         */
+        public void onRequestCurrentVideoBounds(@NonNull String iAppServiceId) {
+        }
+
+        /**
          * This is called when {@link TvInteractiveAppService.Session#requestCurrentChannelUri()} is
          * called.
          *
@@ -1068,6 +1176,33 @@
         }
 
         @Override
+        public void onTimeShiftCommandRequest(
+                Session session,
+                @TvInteractiveAppService.TimeShiftCommandType String cmdType,
+                Bundle parameters) {
+            if (DEBUG) {
+                Log.d(TAG, "onTimeShiftCommandRequest (cmdType=" + cmdType + ", parameters="
+                        + parameters.toString() + ")");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onTimeShiftCommandRequest - session not created");
+                return;
+            }
+            synchronized (mCallbackLock) {
+                if (mCallbackExecutor != null) {
+                    mCallbackExecutor.execute(() -> {
+                        synchronized (mCallbackLock) {
+                            if (mCallback != null) {
+                                mCallback.onTimeShiftCommandRequest(
+                                        mIAppServiceId, cmdType, parameters);
+                            }
+                        }
+                    });
+                }
+            }
+        }
+
+        @Override
         public void onSessionStateChanged(
                 Session session,
                 @TvInteractiveAppManager.InteractiveAppState int state,
@@ -1153,6 +1288,28 @@
         }
 
         @Override
+        public void onRequestCurrentVideoBounds(Session session) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestCurrentVideoBounds");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestCurrentVideoBounds - session not created");
+                return;
+            }
+            synchronized (mCallbackLock) {
+                if (mCallbackExecutor != null) {
+                    mCallbackExecutor.execute(() -> {
+                        synchronized (mCallbackLock) {
+                            if (mCallback != null) {
+                                mCallback.onRequestCurrentVideoBounds(mIAppServiceId);
+                            }
+                        }
+                    });
+                }
+            }
+        }
+
+        @Override
         public void onRequestCurrentChannelUri(Session session) {
             if (DEBUG) {
                 Log.d(TAG, "onRequestCurrentChannelUri");
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index da920bb..9552200 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -956,14 +956,16 @@
 
 static void
 android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
-                                       jobject jAttributionSource)
+                                       jobject jAttributionSource,
+                                       jint jAudioSessionId)
 {
     ALOGV("native_setup");
 
     Parcel* parcel = parcelForJavaObject(env, jAttributionSource);
     android::content::AttributionSourceState attributionSource;
     attributionSource.readFromParcel(parcel);
-    sp<MediaPlayer> mp = sp<MediaPlayer>::make(attributionSource);
+    sp<MediaPlayer> mp = sp<MediaPlayer>::make(
+        attributionSource, static_cast<audio_session_t>(jAudioSessionId));
     if (mp == NULL) {
         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
         return;
@@ -1419,7 +1421,9 @@
     {"native_setMetadataFilter", "(Landroid/os/Parcel;)I",      (void *)android_media_MediaPlayer_setMetadataFilter},
     {"native_getMetadata", "(ZZLandroid/os/Parcel;)Z",          (void *)android_media_MediaPlayer_getMetadata},
     {"native_init",         "()V",                              (void *)android_media_MediaPlayer_native_init},
-    {"native_setup",        "(Ljava/lang/Object;Landroid/os/Parcel;)V",(void *)android_media_MediaPlayer_native_setup},
+    {"native_setup",
+        "(Ljava/lang/Object;Landroid/os/Parcel;I)V",
+        (void *)android_media_MediaPlayer_native_setup},
     {"native_finalize",     "()V",                              (void *)android_media_MediaPlayer_native_finalize},
     {"getAudioSessionId",   "()I",                              (void *)android_media_MediaPlayer_get_audio_session_id},
     {"native_setAudioSessionId",   "(I)V",                      (void *)android_media_MediaPlayer_set_audio_session_id},
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerUnitTest.java
index f480566..f812d5f 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerUnitTest.java
@@ -23,6 +23,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -56,6 +57,7 @@
         MediaPlayer mediaPlayer = new MediaPlayer(virtualDeviceContext);
 
         assertNotEquals(vdmPlaybackSessionId, mediaPlayer.getAudioSessionId());
+        assertTrue(mediaPlayer.getAudioSessionId() > 0);
     }
 
     @Test
diff --git a/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java b/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java
index a30f2e3..2820606 100644
--- a/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java
+++ b/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java
@@ -22,8 +22,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertThrows;
-
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
 
@@ -44,7 +42,7 @@
 @RunWith(AndroidJUnit4.class)
 public class MediaProjectionConfigTest {
     private static final MediaProjectionConfig DISPLAY_CONFIG =
-            MediaProjectionConfig.createConfigForDisplay(DEFAULT_DISPLAY);
+            MediaProjectionConfig.createConfigForDefaultDisplay();
     private static final MediaProjectionConfig USERS_CHOICE_CONFIG =
             MediaProjectionConfig.createConfigForUserChoice();
 
@@ -60,10 +58,6 @@
 
     @Test
     public void testCreateDisplayConfig() {
-        assertThrows(IllegalArgumentException.class,
-                () -> MediaProjectionConfig.createConfigForDisplay(-1));
-        assertThrows(IllegalArgumentException.class,
-                () -> MediaProjectionConfig.createConfigForDisplay(DEFAULT_DISPLAY + 1));
         assertThat(DISPLAY_CONFIG.getRegionToCapture()).isEqualTo(CAPTURE_REGION_FIXED_DISPLAY);
         assertThat(DISPLAY_CONFIG.getDisplayToCapture()).isEqualTo(DEFAULT_DISPLAY);
     }
@@ -78,7 +72,7 @@
         assertThat(MediaProjectionConfig.createConfigForUserChoice()).isEqualTo(
                 USERS_CHOICE_CONFIG);
         assertThat(DISPLAY_CONFIG).isNotEqualTo(USERS_CHOICE_CONFIG);
-        assertThat(MediaProjectionConfig.createConfigForDisplay(DEFAULT_DISPLAY)).isEqualTo(
+        assertThat(MediaProjectionConfig.createConfigForDefaultDisplay()).isEqualTo(
                 DISPLAY_CONFIG);
     }
 }
diff --git a/media/tests/projection/src/android/media/projection/MediaProjectionManagerTest.java b/media/tests/projection/src/android/media/projection/MediaProjectionManagerTest.java
index a3e4908..00ab150 100644
--- a/media/tests/projection/src/android/media/projection/MediaProjectionManagerTest.java
+++ b/media/tests/projection/src/android/media/projection/MediaProjectionManagerTest.java
@@ -17,7 +17,6 @@
 package android.media.projection;
 
 import static android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION_CONFIG;
-import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
@@ -59,7 +58,7 @@
     private Context mContext;
     private MockitoSession mMockingSession;
     private static final MediaProjectionConfig DISPLAY_CONFIG =
-            MediaProjectionConfig.createConfigForDisplay(DEFAULT_DISPLAY);
+            MediaProjectionConfig.createConfigForDefaultDisplay();
     private static final MediaProjectionConfig USERS_CHOICE_CONFIG =
             MediaProjectionConfig.createConfigForUserChoice();
 
diff --git a/native/android/input.cpp b/native/android/input.cpp
index 5e5ebed..f1c3088 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -299,6 +299,8 @@
             return AMOTION_EVENT_CLASSIFICATION_TWO_FINGER_SWIPE;
         case android::MotionClassification::MULTI_FINGER_SWIPE:
             return AMOTION_EVENT_CLASSIFICATION_MULTI_FINGER_SWIPE;
+        case android::MotionClassification::PINCH:
+            return AMOTION_EVENT_CLASSIFICATION_PINCH;
     }
 }
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
index 8dd691d..1e37f15 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
@@ -268,7 +268,7 @@
                 Context.RECEIVER_EXPORTED);
 
         // Create a matching PendingIntent and use it to generate the IntentSender
-        Intent broadcastIntent = new Intent(action);
+        Intent broadcastIntent = new Intent(action).setPackage(mContext.getPackageName());
         PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, packageName.hashCode(),
                 broadcastIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT
                         | PendingIntent.FLAG_MUTABLE);
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index 468a976..1592094 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -59,6 +59,18 @@
     private Uri mImageUri;
     private Drawable mImageDrawable;
     private View mMiddleGroundView;
+    private OnBindListener mOnBindListener;
+
+    /**
+     * Interface to listen in on when {@link #onBindViewHolder(PreferenceViewHolder)} occurs.
+     */
+    public interface OnBindListener {
+        /**
+         * Called when when {@link #onBindViewHolder(PreferenceViewHolder)} occurs.
+         * @param animationView the animation view for this preference.
+         */
+        void onBind(LottieAnimationView animationView);
+    }
 
     private final Animatable2.AnimationCallback mAnimationCallback =
             new Animatable2.AnimationCallback() {
@@ -133,6 +145,17 @@
         if (IS_ENABLED_LOTTIE_ADAPTIVE_COLOR) {
             ColorUtils.applyDynamicColors(getContext(), illustrationView);
         }
+
+        if (mOnBindListener != null) {
+            mOnBindListener.onBind(illustrationView);
+        }
+    }
+
+    /**
+     * Sets a listener to be notified when the views are binded.
+     */
+    public void setOnBindListener(OnBindListener listener) {
+        mOnBindListener = listener;
     }
 
     /**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
index 29549d9..103512d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
@@ -61,6 +61,8 @@
     private PreferenceViewHolder mViewHolder;
     private FrameLayout mMiddleGroundLayout;
     private final Context mContext = ApplicationProvider.getApplicationContext();
+    private IllustrationPreference.OnBindListener mOnBindListener;
+    private LottieAnimationView mOnBindListenerAnimationView;
 
     @Before
     public void setUp() {
@@ -82,6 +84,12 @@
 
         final AttributeSet attributeSet = Robolectric.buildAttributeSet().build();
         mPreference = new IllustrationPreference(mContext, attributeSet);
+        mOnBindListener = new IllustrationPreference.OnBindListener() {
+            @Override
+            public void onBind(LottieAnimationView animationView) {
+                mOnBindListenerAnimationView = animationView;
+            }
+        };
     }
 
     @Test
@@ -186,4 +194,25 @@
         assertThat(mBackgroundView.getMaxHeight()).isEqualTo(restrictedHeight);
         assertThat(mAnimationView.getMaxHeight()).isEqualTo(restrictedHeight);
     }
+
+    @Test
+    public void setOnBindListener_isNotified() {
+        mOnBindListenerAnimationView = null;
+        mPreference.setOnBindListener(mOnBindListener);
+
+        mPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(mOnBindListenerAnimationView).isNotNull();
+        assertThat(mOnBindListenerAnimationView).isEqualTo(mAnimationView);
+    }
+
+    @Test
+    public void setOnBindListener_notNotified() {
+        mOnBindListenerAnimationView = null;
+        mPreference.setOnBindListener(null);
+
+        mPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(mOnBindListenerAnimationView).isNull();
+    }
 }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 4365a9b..5ee36f3 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -695,6 +695,7 @@
                  Settings.Secure.BACKUP_AUTO_RESTORE,
                  Settings.Secure.BACKUP_ENABLED,
                  Settings.Secure.BACKUP_PROVISIONED,
+                 Settings.Secure.BACKUP_SCHEDULING_ENABLED,
                  Settings.Secure.BACKUP_TRANSPORT,
                  Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT,
                  Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED, // Candidate for backup?
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4e620cd1..90d3488 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -1004,7 +1004,6 @@
                 <action android:name="com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG" />
                 <action android:name="com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG" />
                 <action android:name="com.android.systemui.action.DISMISS_MEDIA_OUTPUT_DIALOG" />
-                <action android:name="android.intent.action.SHOW_OUTPUT_SWITCHER" />
             </intent-filter>
         </receiver>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index af6e646..6d5eb6a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1302,6 +1302,9 @@
     <!-- LOCKSCREEN -> DREAMING transition: Amount to shift lockscreen content on entering -->
     <dimen name="lockscreen_to_dreaming_transition_lockscreen_translation_y">-40dp</dimen>
 
+    <!-- GONE -> DREAMING transition: Amount to shift lockscreen content on entering -->
+    <dimen name="gone_to_dreaming_transition_lockscreen_translation_y">-40dp</dimen>
+
     <!-- LOCKSCREEN -> OCCLUDED transition: Amount to shift lockscreen content on entering -->
     <dimen name="lockscreen_to_occluded_transition_lockscreen_translation_y">-40dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e4f339a..2745202 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2463,10 +2463,10 @@
     <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to play media on the different device. [CHAR LIMIT=75] -->
     <string name="media_move_closer_to_start_cast">Move closer to play on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
     <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to transfer media from the different device and back onto the current device. [CHAR LIMIT=75] -->
-    <string name="media_move_closer_to_end_cast">Move closer to <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g> to play here</string>
+    <string name="media_move_closer_to_end_cast">To play here, move closer to <xliff:g id="deviceName" example="tablet">%1$s</xliff:g></string>
     <!-- Text informing the user that their media is now playing on a different device (deviceName). [CHAR LIMIT=50] -->
     <string name="media_transfer_playing_different_device">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
-    <!-- Text informing the user that the media transfer has failed because something went wrong. [CHAR LIsMIT=50] -->
+    <!-- Text informing the user that the media transfer has failed because something went wrong. [CHAR LIMIT=50] -->
     <string name="media_transfer_failed">Something went wrong. Try again.</string>
     <!-- Text to indicate that a media transfer is currently in-progress, aka loading. [CHAR LIMIT=NONE] -->
     <string name="media_transfer_loading">Loading</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index a71fb56..fa484c7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -37,7 +37,7 @@
     /**
      * Sent when overview is to be shown.
      */
-    void onOverviewShown(boolean triggeredFromAltTab) = 7;
+    void onOverviewShown(boolean triggeredFromAltTab, boolean forward) = 7;
 
     /**
      * Sent when overview is to be hidden.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index d4ca8e3..ea84438 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -29,6 +29,9 @@
 import android.view.View.OnKeyListener;
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
+import android.window.OnBackAnimationCallback;
+
+import androidx.annotation.NonNull;
 
 import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
@@ -394,6 +397,14 @@
     }
 
     /**
+     * @return the {@link OnBackAnimationCallback} to animate this view during a back gesture.
+     */
+    @NonNull
+    public OnBackAnimationCallback getBackCallback() {
+        return mKeyguardSecurityContainerController.getBackCallback();
+    }
+
+    /**
      * Allows the media keys to work when the keyguard is showing.
      * The media keys should be of no interest to the actual keyguard view(s),
      * so intercepting them here should not be of any harm.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 5d7a6f1..e4f85db 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -32,6 +32,7 @@
 import static androidx.constraintlayout.widget.ConstraintSet.TOP;
 import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
 
+import static com.android.systemui.animation.InterpolatorsAndroidX.DECELERATE_QUINT;
 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
 
 import static java.lang.Integer.max;
@@ -73,6 +74,8 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
+import android.window.BackEvent;
+import android.window.OnBackAnimationCallback;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
@@ -135,7 +138,9 @@
     private static final float MIN_DRAG_SIZE = 10;
     // How much to scale the default slop by, to avoid accidental drags.
     private static final float SLOP_SCALE = 4f;
-
+    @VisibleForTesting
+    // How much the view scales down to during back gestures.
+    static final float MIN_BACK_SCALE = 0.9f;
     @VisibleForTesting
     KeyguardSecurityViewFlipper mSecurityViewFlipper;
     private GlobalSettings mGlobalSettings;
@@ -240,6 +245,33 @@
                 }
             };
 
+    private final OnBackAnimationCallback mBackCallback = new OnBackAnimationCallback() {
+        @Override
+        public void onBackCancelled() {
+            // TODO(b/259608500): Remove once back API auto animates progress to 0 on cancel.
+            resetScale();
+        }
+
+        @Override
+        public void onBackInvoked() { }
+
+        @Override
+        public void onBackProgressed(BackEvent event) {
+            float progress = event.getProgress();
+            // TODO(b/263819310): Update the interpolator to match spec.
+            float scale = MIN_BACK_SCALE
+                    +  (1 - MIN_BACK_SCALE) * (1 - DECELERATE_QUINT.getInterpolation(progress));
+            setScale(scale);
+        }
+    };
+    /**
+     * @return the {@link OnBackAnimationCallback} to animate this view during a back gesture.
+     */
+    @NonNull
+    OnBackAnimationCallback getBackCallback() {
+        return mBackCallback;
+    }
+
     // Used to notify the container when something interesting happens.
     public interface SecurityCallback {
         /**
@@ -736,6 +768,15 @@
         mViewMode.onDensityOrFontScaleChanged();
     }
 
+    void resetScale() {
+        setScale(1);
+    }
+
+    private void setScale(float scale) {
+        setScaleX(scale);
+        setScaleY(scale);
+    }
+
     /**
      * Enscapsulates the differences between bouncer modes for the container.
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index a72a484..57bfe54 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -40,7 +40,9 @@
 import android.util.Slog;
 import android.view.MotionEvent;
 import android.view.View;
+import android.window.OnBackAnimationCallback;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -479,6 +481,9 @@
     /** Called when the bouncer changes visibility. */
     public void onBouncerVisibilityChanged(@View.Visibility int visibility) {
         setBouncerVisible(visibility == View.VISIBLE);
+        if (visibility == View.INVISIBLE) {
+            mView.resetScale();
+        }
     }
 
     private void setBouncerVisible(boolean visible) {
@@ -588,6 +593,14 @@
     }
 
     /**
+     * @return the {@link OnBackAnimationCallback} to animate this view during a back gesture.
+     */
+    @NonNull
+    OnBackAnimationCallback getBackCallback() {
+        return mView.getBackCallback();
+    }
+
+    /**
      * Switches to the given security view unless it's already being shown, in which case
      * this is a no-op.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index d040f8f..c880c59 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -200,7 +200,7 @@
     /** A different path for unocclusion transitions back to keyguard */
     // TODO(b/262859270): Tracking Bug
     @JvmField
-    val UNOCCLUSION_TRANSITION = unreleasedFlag(223, "unocclusion_transition", teamfood = false)
+    val UNOCCLUSION_TRANSITION = unreleasedFlag(223, "unocclusion_transition", teamfood = true)
 
     // flag for controlling auto pin confirmation and material u shapes in bouncer
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
index 80c6130..faeb485 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.data
 
 import android.view.KeyEvent
+import android.window.OnBackAnimationCallback
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.ActivityStarter
 import java.lang.ref.WeakReference
@@ -51,4 +52,6 @@
         cancelAction: Runnable?,
     )
     fun willDismissWithActions(): Boolean
+    /** @return the {@link OnBackAnimationCallback} to animate Bouncer during a back gesture. */
+    fun getBackCallback(): OnBackAnimationCallback
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index d14b66a..0c4bca6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -209,7 +209,7 @@
             return
         }
 
-        if (state == TransitionState.FINISHED) {
+        if (state == TransitionState.FINISHED || state == TransitionState.CANCELED) {
             updateTransitionId = null
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 3b09ae7..7134ec0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -56,7 +56,7 @@
         scope.launch {
             // Using isDreamingWithOverlay provides an optimized path to LOCKSCREEN state, which
             // otherwise would have gone through OCCLUDED first
-            keyguardInteractor.isDreamingWithOverlay
+            keyguardInteractor.isAbleToDream
                 .sample(
                     combine(
                         keyguardInteractor.dozeTransitionModel,
@@ -65,8 +65,7 @@
                     ),
                     ::toTriple
                 )
-                .collect { triple ->
-                    val (isDreaming, dozeTransitionModel, lastStartedTransition) = triple
+                .collect { (isDreaming, dozeTransitionModel, lastStartedTransition) ->
                     if (
                         !isDreaming &&
                             isDozeOff(dozeTransitionModel.to) &&
@@ -96,8 +95,7 @@
                     ),
                     ::toTriple
                 )
-                .collect { triple ->
-                    val (isDreaming, isOccluded, lastStartedTransition) = triple
+                .collect { (isDreaming, isOccluded, lastStartedTransition) ->
                     if (
                         isOccluded &&
                             !isDreaming &&
@@ -123,24 +121,18 @@
 
     private fun listenForDreamingToGone() {
         scope.launch {
-            keyguardInteractor.biometricUnlockState
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
-                .collect { pair ->
-                    val (biometricUnlockState, keyguardState) = pair
-                    if (
-                        keyguardState == KeyguardState.DREAMING &&
-                            isWakeAndUnlock(biometricUnlockState)
-                    ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.DREAMING,
-                                KeyguardState.GONE,
-                                getAnimator(),
-                            )
+            keyguardInteractor.biometricUnlockState.collect { biometricUnlockState ->
+                if (biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM) {
+                    keyguardTransitionRepository.startTransition(
+                        TransitionInfo(
+                            name,
+                            KeyguardState.DREAMING,
+                            KeyguardState.GONE,
+                            getAnimator(),
                         )
-                    }
+                    )
                 }
+            }
         }
     }
 
@@ -151,8 +143,7 @@
                     keyguardTransitionInteractor.finishedKeyguardState,
                     ::Pair
                 )
-                .collect { pair ->
-                    val (dozeTransitionModel, keyguardState) = pair
+                .collect { (dozeTransitionModel, keyguardState) ->
                     if (
                         dozeTransitionModel.to == DozeStateModel.DOZE &&
                             keyguardState == KeyguardState.DREAMING
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 64028ce..5674e2a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -48,8 +48,6 @@
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
 ) : TransitionInteractor(FromLockscreenTransitionInteractor::class.simpleName!!) {
 
-    private var transitionId: UUID? = null
-
     override fun start() {
         listenForLockscreenToGone()
         listenForLockscreenToOccluded()
@@ -104,6 +102,7 @@
 
     /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
     private fun listenForLockscreenToBouncerDragging() {
+        var transitionId: UUID? = null
         scope.launch {
             shadeRepository.shadeModel
                 .sample(
@@ -114,25 +113,43 @@
                     ),
                     ::toTriple
                 )
-                .collect { triple ->
-                    val (shadeModel, keyguardState, statusBarState) = triple
-
+                .collect { (shadeModel, keyguardState, statusBarState) ->
                     val id = transitionId
                     if (id != null) {
                         // An existing `id` means a transition is started, and calls to
-                        // `updateTransition` will control it until FINISHED
-                        keyguardTransitionRepository.updateTransition(
-                            id,
-                            1f - shadeModel.expansionAmount,
-                            if (
-                                shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f
-                            ) {
-                                transitionId = null
+                        // `updateTransition` will control it until FINISHED or CANCELED
+                        var nextState =
+                            if (shadeModel.expansionAmount == 0f) {
                                 TransitionState.FINISHED
+                            } else if (shadeModel.expansionAmount == 1f) {
+                                TransitionState.CANCELED
                             } else {
                                 TransitionState.RUNNING
                             }
+                        keyguardTransitionRepository.updateTransition(
+                            id,
+                            1f - shadeModel.expansionAmount,
+                            nextState,
                         )
+
+                        if (
+                            nextState == TransitionState.CANCELED ||
+                                nextState == TransitionState.FINISHED
+                        ) {
+                            transitionId = null
+                        }
+
+                        // If canceled, just put the state back
+                        if (nextState == TransitionState.CANCELED) {
+                            keyguardTransitionRepository.startTransition(
+                                TransitionInfo(
+                                    ownerName = name,
+                                    from = KeyguardState.BOUNCER,
+                                    to = KeyguardState.LOCKSCREEN,
+                                    animator = getAnimator(0.milliseconds)
+                                )
+                            )
+                        }
                     } else {
                         // TODO (b/251849525): Remove statusbarstate check when that state is
                         // integrated into KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 490d22e..4cf56fe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -32,12 +32,15 @@
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.CommandQueue.Callbacks
-import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.merge
 
 /**
@@ -89,15 +92,23 @@
     /**
      * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
      * that doze mode is not running and DREAMING is ok to commence.
+     *
+     * Allow a brief moment to prevent rapidly oscillating between true/false signals.
      */
     val isAbleToDream: Flow<Boolean> =
         merge(isDreaming, isDreamingWithOverlay)
-            .sample(
+            .combine(
                 dozeTransitionModel,
                 { isDreaming, dozeTransitionModel ->
                     isDreaming && isDozeOff(dozeTransitionModel.to)
                 }
             )
+            .flatMapLatest { isAbleToDream ->
+                flow {
+                    delay(50)
+                    emit(isAbleToDream)
+                }
+            }
             .distinctUntilChanged()
 
     /** Whether the keyguard is showing or not. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 9cdbcda..ad6dbea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -22,13 +22,17 @@
 import com.android.systemui.keyguard.shared.model.AnimationParams
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import javax.inject.Inject
+import kotlin.math.max
+import kotlin.math.min
 import kotlin.time.Duration
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.filter
@@ -53,9 +57,16 @@
     val dreamingToLockscreenTransition: Flow<TransitionStep> =
         repository.transition(DREAMING, LOCKSCREEN)
 
+    /** GONE->DREAMING transition information. */
+    val goneToDreamingTransition: Flow<TransitionStep> = repository.transition(GONE, DREAMING)
+
     /** LOCKSCREEN->AOD transition information. */
     val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
 
+    /** LOCKSCREEN->BOUNCER transition information. */
+    val lockscreenToBouncerTransition: Flow<TransitionStep> =
+        repository.transition(LOCKSCREEN, BOUNCER)
+
     /** LOCKSCREEN->DREAMING transition information. */
     val lockscreenToDreamingTransition: Flow<TransitionStep> =
         repository.transition(LOCKSCREEN, DREAMING)
@@ -106,13 +117,23 @@
     ): Flow<Float> {
         val start = (params.startTime / totalDuration).toFloat()
         val chunks = (totalDuration / params.duration).toFloat()
+        var isRunning = false
         return flow
-            // When starting, emit a value of 0f to give animations a chance to set initial state
             .map { step ->
+                val value = (step.value - start) * chunks
                 if (step.transitionState == STARTED) {
-                    0f
+                    // When starting, make sure to always emit. If a transition is started from the
+                    // middle, it is possible this animation is being skipped but we need to inform
+                    // the ViewModels of the last update
+                    isRunning = true
+                    max(0f, min(1f, value))
+                } else if (isRunning && value >= 1f) {
+                    // Always send a final value of 1. Because of rounding, [value] may never be
+                    // exactly 1.
+                    isRunning = false
+                    1f
                 } else {
-                    (step.value - start) * chunks
+                    value
                 }
             }
             .filter { value -> value >= 0f && value <= 1f }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index f772b17..4aaf73f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -19,6 +19,7 @@
 import android.view.KeyEvent
 import android.view.View
 import android.view.ViewGroup
+import android.window.OnBackAnimationCallback
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.internal.policy.SystemBarUtils
@@ -55,6 +56,10 @@
                         mode == KeyguardSecurityModel.SecurityMode.SimPuk
                 }
 
+                override fun getBackCallback(): OnBackAnimationCallback {
+                    return hostViewController.backCallback
+                }
+
                 override fun shouldDismissOnMenuPressed(): Boolean {
                     return hostViewController.shouldEnableMenuKey()
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index e164f5d..6627865 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -22,10 +22,14 @@
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.AnimationParams
+import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
 
 /**
  * Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -49,9 +53,15 @@
 
     /** Lockscreen views y-translation */
     fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
-        return flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
-            -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx)
-        }
+        return merge(
+            flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
+                -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx)
+            },
+            // On end, reset the translation to 0
+            interactor.dreamingToLockscreenTransition
+                .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
+                .map { 0f }
+        )
     }
 
     /** Lockscreen views alpha */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
new file mode 100644
index 0000000..5a47960
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.AnimationParams
+import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/** Breaks down GONE->DREAMING transition into discrete steps for corresponding views to consume. */
+@SysUISingleton
+class GoneToDreamingTransitionViewModel
+@Inject
+constructor(
+    private val interactor: KeyguardTransitionInteractor,
+) {
+
+    /** Lockscreen views y-translation */
+    fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
+        return merge(
+            flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
+                (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
+            },
+            // On end, reset the translation to 0
+            interactor.goneToDreamingTransition
+                .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
+                .map { 0f }
+        )
+    }
+
+    /** Lockscreen views alpha */
+    val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
+
+    private fun flowForAnimation(params: AnimationParams): Flow<Float> {
+        return interactor.transitionStepAnimation(
+            interactor.goneToDreamingTransition,
+            params,
+            totalDuration = TO_DREAMING_DURATION
+        )
+    }
+
+    companion object {
+        val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds)
+        val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
index d48f87d..e05adbd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -21,7 +21,8 @@
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
@@ -48,7 +49,7 @@
             },
             // On end, reset the translation to 0
             interactor.lockscreenToDreamingTransition
-                .filter { step -> step.transitionState == TransitionState.FINISHED }
+                .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
                 .map { 0f }
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
index 899148b..8f1c904 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
@@ -130,7 +130,12 @@
     private var splitShadeContainer: ViewGroup? = null
 
     /** Track the media player setting status on lock screen. */
-    private var allowMediaPlayerOnLockScreen: Boolean = true
+    private var allowMediaPlayerOnLockScreen: Boolean =
+        secureSettings.getBoolForUser(
+            Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
+            true,
+            UserHandle.USER_CURRENT
+        )
     private val lockScreenMediaPlayerUri =
         secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN)
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
index 55fce59..760a42c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
@@ -36,10 +36,6 @@
 ) : BroadcastReceiver() {
     override fun onReceive(context: Context, intent: Intent) {
         when {
-            TextUtils.equals(Intent.ACTION_SHOW_OUTPUT_SWITCHER, intent.action) -> {
-                val packageName: String? = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME)
-                launchMediaOutputDialogIfPossible(packageName)
-            }
             TextUtils.equals(
                 MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG, intent.action) -> {
                 val packageName: String? =
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 e32c301..245a55d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -279,7 +279,6 @@
                 @Override
                 public void triggerBack() {
                     // Notify FalsingManager that an intentional gesture has occurred.
-                    // TODO(b/186519446): use a different method than isFalseTouch
                     mFalsingManager.isFalseTouch(BACK_GESTURE);
                     // Only inject back keycodes when ahead-of-time back dispatching is disabled.
                     if (mBackAnimation == null) {
@@ -955,6 +954,10 @@
                             mThresholdCrossed = true;
                             // Capture inputs
                             mInputMonitor.pilferPointers();
+                            if (mBackAnimation != null) {
+                                // Notify FalsingManager that an intentional gesture has occurred.
+                                mFalsingManager.isFalseTouch(BACK_GESTURE);
+                            }
                             mInputEventReceiver.setBatchingEnabled(true);
                         } else {
                             logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_FAR_FROM_EDGE);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
index 5ea1c0b..c335a6d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
@@ -59,11 +59,11 @@
     }
 
     @Override
-    public void showRecentApps(boolean triggeredFromAltTab) {
+    public void showRecentApps(boolean triggeredFromAltTab, boolean forward) {
         IOverviewProxy overviewProxy = mOverviewProxyService.getProxy();
         if (overviewProxy != null) {
             try {
-                overviewProxy.onOverviewShown(triggeredFromAltTab);
+                overviewProxy.onOverviewShown(triggeredFromAltTab, forward);
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to send overview show event to launcher.", e);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index b041f95..95d6c18 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -65,14 +65,14 @@
     }
 
     @Override
-    public void showRecentApps(boolean triggeredFromAltTab) {
+    public void showRecentApps(boolean triggeredFromAltTab, boolean forward) {
         // Ensure the device has been provisioned before allowing the user to interact with
         // recents
         if (!isUserSetup()) {
             return;
         }
 
-        mImpl.showRecentApps(triggeredFromAltTab);
+        mImpl.showRecentApps(triggeredFromAltTab, forward);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
index 8848dbb..010ceda 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
@@ -31,7 +31,7 @@
 
     default void preloadRecentApps() {}
     default void cancelPreloadRecentApps() {}
-    default void showRecentApps(boolean triggeredFromAltTab) {}
+    default void showRecentApps(boolean triggeredFromAltTab, boolean forward) {}
     default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {}
     default void toggleRecentApps() {}
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 36e50df..964d0b2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -144,6 +144,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
@@ -692,6 +693,7 @@
     private DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
     private OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel;
     private LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel;
+    private GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
     private LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel;
 
     private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -700,6 +702,7 @@
     private int mDreamingToLockscreenTransitionTranslationY;
     private int mOccludedToLockscreenTransitionTranslationY;
     private int mLockscreenToDreamingTransitionTranslationY;
+    private int mGoneToDreamingTransitionTranslationY;
     private int mLockscreenToOccludedTransitionTranslationY;
     private boolean mUnocclusionTransitionFlagEnabled = false;
 
@@ -735,6 +738,12 @@
                     step.getTransitionState() == TransitionState.RUNNING;
             };
 
+    private final Consumer<TransitionStep> mGoneToDreamingTransition =
+            (TransitionStep step) -> {
+                mIsOcclusionTransitionRunning =
+                    step.getTransitionState() == TransitionState.RUNNING;
+            };
+
     private final Consumer<TransitionStep> mLockscreenToOccludedTransition =
             (TransitionStep step) -> {
                 mIsOcclusionTransitionRunning =
@@ -813,6 +822,7 @@
             DreamingToLockscreenTransitionViewModel dreamingToLockscreenTransitionViewModel,
             OccludedToLockscreenTransitionViewModel occludedToLockscreenTransitionViewModel,
             LockscreenToDreamingTransitionViewModel lockscreenToDreamingTransitionViewModel,
+            GoneToDreamingTransitionViewModel goneToDreamingTransitionViewModel,
             LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel,
             @Main CoroutineDispatcher mainDispatcher,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
@@ -834,6 +844,7 @@
         mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
         mOccludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel;
         mLockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel;
+        mGoneToDreamingTransitionViewModel = goneToDreamingTransitionViewModel;
         mLockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@@ -1172,6 +1183,17 @@
                     setTransitionY(mNotificationStackScrollLayoutController),
                     mMainDispatcher);
 
+            // Gone->Dreaming
+            collectFlow(mView, mKeyguardTransitionInteractor.getGoneToDreamingTransition(),
+                    mGoneToDreamingTransition, mMainDispatcher);
+            collectFlow(mView, mGoneToDreamingTransitionViewModel.getLockscreenAlpha(),
+                    setTransitionAlpha(mNotificationStackScrollLayoutController),
+                    mMainDispatcher);
+            collectFlow(mView, mGoneToDreamingTransitionViewModel.lockscreenTranslationY(
+                    mGoneToDreamingTransitionTranslationY),
+                    setTransitionY(mNotificationStackScrollLayoutController),
+                    mMainDispatcher);
+
             // Lockscreen->Occluded
             collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToOccludedTransition(),
                     mLockscreenToOccludedTransition, mMainDispatcher);
@@ -1223,6 +1245,8 @@
                 R.dimen.occluded_to_lockscreen_transition_lockscreen_translation_y);
         mLockscreenToDreamingTransitionTranslationY = mResources.getDimensionPixelSize(
                 R.dimen.lockscreen_to_dreaming_transition_lockscreen_translation_y);
+        mGoneToDreamingTransitionTranslationY = mResources.getDimensionPixelSize(
+                R.dimen.gone_to_dreaming_transition_lockscreen_translation_y);
         mLockscreenToOccludedTransitionTranslationY = mResources.getDimensionPixelSize(
                 R.dimen.lockscreen_to_occluded_transition_lockscreen_translation_y);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 8314ec7..26f8b62 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -321,9 +321,12 @@
                     && !state.mKeyguardFadingAway && !state.mKeyguardGoingAway;
             if (onKeyguard
                     && mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
+                // both max and min display refresh rate must be set to take effect:
                 mLpChanged.preferredMaxDisplayRefreshRate = mKeyguardPreferredRefreshRate;
+                mLpChanged.preferredMinDisplayRefreshRate = mKeyguardPreferredRefreshRate;
             } else {
                 mLpChanged.preferredMaxDisplayRefreshRate = 0;
+                mLpChanged.preferredMinDisplayRefreshRate = 0;
             }
             Trace.setCounter("display_set_preferred_refresh_rate",
                     (long) mKeyguardPreferredRefreshRate);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index bad942f..04adaae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -224,7 +224,7 @@
          */
         default void setImeWindowStatus(int displayId, IBinder token,  int vis,
                 @BackDispositionMode int backDisposition, boolean showImeSwitcher) { }
-        default void showRecentApps(boolean triggeredFromAltTab) { }
+        default void showRecentApps(boolean triggeredFromAltTab, boolean forward) { }
         default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { }
         default void toggleRecentApps() { }
         default void toggleSplitScreen() { }
@@ -686,11 +686,11 @@
         }
     }
 
-    public void showRecentApps(boolean triggeredFromAltTab) {
+    public void showRecentApps(boolean triggeredFromAltTab, boolean forward) {
         synchronized (mLock) {
             mHandler.removeMessages(MSG_SHOW_RECENT_APPS);
-            mHandler.obtainMessage(MSG_SHOW_RECENT_APPS, triggeredFromAltTab ? 1 : 0, 0,
-                    null).sendToTarget();
+            mHandler.obtainMessage(MSG_SHOW_RECENT_APPS, triggeredFromAltTab ? 1 : 0,
+                    forward ? 1 : 0, null).sendToTarget();
         }
     }
 
@@ -1384,7 +1384,7 @@
                     break;
                 case MSG_SHOW_RECENT_APPS:
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).showRecentApps(msg.arg1 != 0);
+                        mCallbacks.get(i).showRecentApps(msg.arg1 != 0, msg.arg2 != 0);
                     }
                     break;
                 case MSG_HIDE_RECENT_APPS:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
index 1e7fc93..197cf56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
@@ -54,7 +54,7 @@
  * their respective views based on the progress of the animator. Interpolation differences TBD
  */
 @SysUISingleton
-class SystemStatusAnimationScheduler @Inject constructor(
+open class SystemStatusAnimationScheduler @Inject constructor(
     private val coordinator: SystemEventCoordinator,
     private val chipAnimationController: SystemEventChipAnimationController,
     private val statusBarWindowController: StatusBarWindowController,
@@ -66,7 +66,7 @@
     companion object {
         private const val PROPERTY_ENABLE_IMMERSIVE_INDICATOR = "enable_immersive_indicator"
     }
-    private fun isImmersiveIndicatorEnabled(): Boolean {
+    public fun isImmersiveIndicatorEnabled(): Boolean {
         return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
                 PROPERTY_ENABLE_IMMERSIVE_INDICATOR, true)
     }
@@ -76,18 +76,22 @@
 
     /** True if the persistent privacy dot should be active */
     var hasPersistentDot = false
-        private set
+        protected set
 
     private var scheduledEvent: StatusEvent? = null
     private var cancelExecutionRunnable: Runnable? = null
     private val listeners = mutableSetOf<SystemStatusAnimationCallback>()
 
+    fun getListeners(): MutableSet<SystemStatusAnimationCallback> {
+        return listeners
+    }
+
     init {
         coordinator.attachScheduler(this)
         dumpManager.registerDumpable(TAG, this)
     }
 
-    fun onStatusEvent(event: StatusEvent) {
+    open fun onStatusEvent(event: StatusEvent) {
         // Ignore any updates until the system is up and running
         if (isTooEarly() || !isImmersiveIndicatorEnabled()) {
             return
@@ -139,7 +143,7 @@
         }
     }
 
-    private fun isTooEarly(): Boolean {
+    public fun isTooEarly(): Boolean {
         return systemClock.uptimeMillis() - Process.getStartUptimeMillis() < MIN_UPTIME
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 61ddf8c..42d78f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -36,7 +36,8 @@
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
 import android.view.WindowManagerGlobal;
-import android.window.OnBackInvokedCallback;
+import android.window.BackEvent;
+import android.window.OnBackAnimationCallback;
 import android.window.OnBackInvokedDispatcher;
 
 import androidx.annotation.NonNull;
@@ -198,11 +199,38 @@
             }
     };
 
-    private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
-        if (DEBUG) {
-            Log.d(TAG, "onBackInvokedCallback() called, invoking onBackPressed()");
+    private final OnBackAnimationCallback mOnBackInvokedCallback = new OnBackAnimationCallback() {
+        @Override
+        public void onBackInvoked() {
+            if (DEBUG) {
+                Log.d(TAG, "onBackInvokedCallback() called, invoking onBackPressed()");
+            }
+            onBackPressed();
+            if (shouldPlayBackAnimation()) {
+                mPrimaryBouncerView.getDelegate().getBackCallback().onBackInvoked();
+            }
         }
-        onBackPressed();
+
+        @Override
+        public void onBackProgressed(BackEvent event) {
+            if (shouldPlayBackAnimation()) {
+                mPrimaryBouncerView.getDelegate().getBackCallback().onBackProgressed(event);
+            }
+        }
+
+        @Override
+        public void onBackCancelled() {
+            if (shouldPlayBackAnimation()) {
+                mPrimaryBouncerView.getDelegate().getBackCallback().onBackCancelled();
+            }
+        }
+
+        @Override
+        public void onBackStarted(BackEvent event) {
+            if (shouldPlayBackAnimation()) {
+                mPrimaryBouncerView.getDelegate().getBackCallback().onBackStarted(event);
+            }
+        }
     };
     private boolean mIsBackCallbackRegistered = false;
 
@@ -256,6 +284,7 @@
     private boolean mIsModernBouncerEnabled;
     private boolean mIsModernAlternateBouncerEnabled;
     private boolean mIsUnoccludeTransitionFlagEnabled;
+    private boolean mIsBackAnimationEnabled;
 
     private OnDismissAction mAfterKeyguardGoneAction;
     private Runnable mKeyguardGoneCancelAction;
@@ -337,6 +366,8 @@
         mIsModernAlternateBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER);
         mAlternateBouncerInteractor = alternateBouncerInteractor;
         mIsUnoccludeTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION);
+        mIsBackAnimationEnabled =
+                featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM);
     }
 
     @Override
@@ -472,6 +503,11 @@
         }
     }
 
+    private boolean shouldPlayBackAnimation() {
+        // Suppress back animation when bouncer shouldn't be dismissed on back invocation.
+        return !needsFullscreenBouncer() && mIsBackAnimationEnabled;
+    }
+
     @Override
     public void onDensityOrFontScaleChanged() {
         hideBouncer(true /* destroyView */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
index 5960387..5562e73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.pipeline.mobile.data.model
 
 import android.telephony.Annotation.NetworkType
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 
 /**
@@ -38,4 +40,12 @@
     data class OverrideNetworkType(
         override val lookupKey: String,
     ) : ResolvedNetworkType
+
+    /** Represents the carrier merged network. See [CarrierMergedConnectionRepository]. */
+    object CarrierMergedNetworkType : ResolvedNetworkType {
+        // Effectively unused since [iconGroupOverride] is used instead.
+        override val lookupKey: String = "cwf"
+
+        val iconGroupOverride: SignalIcon.MobileIconGroup = TelephonyIcons.CARRIER_MERGED_WIFI
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index d04996b..6187f64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -22,7 +22,6 @@
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 
 /**
@@ -50,7 +49,7 @@
      * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
      * listener + model.
      */
-    val connectionInfo: Flow<MobileConnectionModel>
+    val connectionInfo: StateFlow<MobileConnectionModel>
 
     /** The total number of levels. Used with [SignalDrawable]. */
     val numberOfLevels: StateFlow<Int>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 8ac1237..22aca0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -39,7 +39,11 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.CarrierMergedConnectionRepository.Companion.createCarrierMergedConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.MOBILE_CONNECTION_BUFFER_SIZE
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,15 +64,19 @@
 class DemoMobileConnectionsRepository
 @Inject
 constructor(
-    private val dataSource: DemoModeMobileConnectionDataSource,
+    private val mobileDataSource: DemoModeMobileConnectionDataSource,
+    private val wifiDataSource: DemoModeWifiDataSource,
     @Application private val scope: CoroutineScope,
     context: Context,
     private val logFactory: TableLogBufferFactory,
 ) : MobileConnectionsRepository {
 
-    private var demoCommandJob: Job? = null
+    private var mobileDemoCommandJob: Job? = null
+    private var wifiDemoCommandJob: Job? = null
 
-    private var connectionRepoCache = mutableMapOf<Int, DemoMobileConnectionRepository>()
+    private var carrierMergedSubId: Int? = null
+
+    private var connectionRepoCache = mutableMapOf<Int, CacheContainer>()
     private val subscriptionInfoCache = mutableMapOf<Int, SubscriptionModel>()
     val demoModeFinishedEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
 
@@ -144,52 +152,83 @@
     override val defaultMobileNetworkConnectivity = MutableStateFlow(MobileConnectivityModel())
 
     override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository {
-        return connectionRepoCache[subId]
-            ?: createDemoMobileConnectionRepo(subId).also { connectionRepoCache[subId] = it }
+        val current = connectionRepoCache[subId]?.repo
+        if (current != null) {
+            return current
+        }
+
+        val new = createDemoMobileConnectionRepo(subId)
+        connectionRepoCache[subId] = new
+        return new.repo
     }
 
-    private fun createDemoMobileConnectionRepo(subId: Int): DemoMobileConnectionRepository {
-        val tableLogBuffer = logFactory.getOrCreate("DemoMobileConnectionLog [$subId]", 100)
+    private fun createDemoMobileConnectionRepo(subId: Int): CacheContainer {
+        val tableLogBuffer =
+            logFactory.getOrCreate(
+                "DemoMobileConnectionLog [$subId]",
+                MOBILE_CONNECTION_BUFFER_SIZE,
+            )
 
-        return DemoMobileConnectionRepository(
-            subId,
-            tableLogBuffer,
-        )
+        val repo =
+            DemoMobileConnectionRepository(
+                subId,
+                tableLogBuffer,
+            )
+        return CacheContainer(repo, lastMobileState = null)
     }
 
     override val globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
 
     fun startProcessingCommands() {
-        demoCommandJob =
+        mobileDemoCommandJob =
             scope.launch {
-                dataSource.mobileEvents.filterNotNull().collect { event -> processEvent(event) }
+                mobileDataSource.mobileEvents.filterNotNull().collect { event ->
+                    processMobileEvent(event)
+                }
+            }
+        wifiDemoCommandJob =
+            scope.launch {
+                wifiDataSource.wifiEvents.filterNotNull().collect { event ->
+                    processWifiEvent(event)
+                }
             }
     }
 
     fun stopProcessingCommands() {
-        demoCommandJob?.cancel()
+        mobileDemoCommandJob?.cancel()
+        wifiDemoCommandJob?.cancel()
         _subscriptions.value = listOf()
         connectionRepoCache.clear()
         subscriptionInfoCache.clear()
     }
 
-    private fun processEvent(event: FakeNetworkEventModel) {
+    private fun processMobileEvent(event: FakeNetworkEventModel) {
         when (event) {
             is Mobile -> {
                 processEnabledMobileState(event)
             }
             is MobileDisabled -> {
-                processDisabledMobileState(event)
+                maybeRemoveSubscription(event.subId)
             }
         }
     }
 
+    private fun processWifiEvent(event: FakeWifiEventModel) {
+        when (event) {
+            is FakeWifiEventModel.WifiDisabled -> disableCarrierMerged()
+            is FakeWifiEventModel.Wifi -> disableCarrierMerged()
+            is FakeWifiEventModel.CarrierMerged -> processCarrierMergedWifiState(event)
+        }
+    }
+
     private fun processEnabledMobileState(state: Mobile) {
         // get or create the connection repo, and set its values
         val subId = state.subId ?: DEFAULT_SUB_ID
         maybeCreateSubscription(subId)
 
         val connection = getRepoForSubId(subId)
+        connectionRepoCache[subId]?.lastMobileState = state
+
         // This is always true here, because we split out disabled states at the data-source level
         connection.dataEnabled.value = true
         connection.networkName.value = NetworkNameModel.Derived(state.name)
@@ -198,14 +237,36 @@
         connection.connectionInfo.value = state.toMobileConnectionModel()
     }
 
-    private fun processDisabledMobileState(state: MobileDisabled) {
+    private fun processCarrierMergedWifiState(event: FakeWifiEventModel.CarrierMerged) {
+        // The new carrier merged connection is for a different sub ID, so disable carrier merged
+        // for the current (now old) sub
+        if (carrierMergedSubId != event.subscriptionId) {
+            disableCarrierMerged()
+        }
+
+        // get or create the connection repo, and set its values
+        val subId = event.subscriptionId
+        maybeCreateSubscription(subId)
+        carrierMergedSubId = subId
+
+        val connection = getRepoForSubId(subId)
+        // This is always true here, because we split out disabled states at the data-source level
+        connection.dataEnabled.value = true
+        connection.networkName.value = NetworkNameModel.Derived(CARRIER_MERGED_NAME)
+        connection.numberOfLevels.value = event.numberOfLevels
+        connection.cdmaRoaming.value = false
+        connection.connectionInfo.value = event.toMobileConnectionModel()
+        Log.e("CCS", "output connection info = ${connection.connectionInfo.value}")
+    }
+
+    private fun maybeRemoveSubscription(subId: Int?) {
         if (_subscriptions.value.isEmpty()) {
             // Nothing to do here
             return
         }
 
-        val subId =
-            state.subId
+        val finalSubId =
+            subId
                 ?: run {
                     // For sake of usability, we can allow for no subId arg if there is only one
                     // subscription
@@ -223,7 +284,21 @@
                     _subscriptions.value[0].subscriptionId
                 }
 
-        removeSubscription(subId)
+        removeSubscription(finalSubId)
+    }
+
+    private fun disableCarrierMerged() {
+        val currentCarrierMergedSubId = carrierMergedSubId ?: return
+
+        // If this sub ID was previously not carrier merged, we should reset it to its previous
+        // connection.
+        val lastMobileState = connectionRepoCache[carrierMergedSubId]?.lastMobileState
+        if (lastMobileState != null) {
+            processEnabledMobileState(lastMobileState)
+        } else {
+            // Otherwise, just remove the subscription entirely
+            removeSubscription(currentCarrierMergedSubId)
+        }
     }
 
     private fun removeSubscription(subId: Int) {
@@ -251,6 +326,10 @@
         )
     }
 
+    private fun FakeWifiEventModel.CarrierMerged.toMobileConnectionModel(): MobileConnectionModel {
+        return createCarrierMergedConnectionModel(this.level)
+    }
+
     private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType {
         val key = mobileMappingsReverseLookup.value[this] ?: "dis"
         return DefaultNetworkType(key)
@@ -260,9 +339,17 @@
         private const val TAG = "DemoMobileConnectionsRepo"
 
         private const val DEFAULT_SUB_ID = 1
+
+        private const val CARRIER_MERGED_NAME = "Carrier Merged Network"
     }
 }
 
+class CacheContainer(
+    var repo: DemoMobileConnectionRepository,
+    /** The last received [Mobile] event. Used when switching from carrier merged back to mobile. */
+    var lastMobileState: Mobile?,
+)
+
 class DemoMobileConnectionRepository(
     override val subId: Int,
     override val tableLogBuffer: TableLogBuffer,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
new file mode 100644
index 0000000..c783b12
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -0,0 +1,181 @@
+/*
+ * 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.mobile.data.repository.prod
+
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * A repository implementation for a carrier merged (aka VCN) network. A carrier merged network is
+ * delivered to SysUI as a wifi network (see [WifiNetworkModel.CarrierMerged], but is visually
+ * displayed as a mobile network triangle.
+ *
+ * See [android.net.wifi.WifiInfo.isCarrierMerged] for more information.
+ *
+ * See [MobileConnectionRepositoryImpl] for a repository implementation of a typical mobile
+ * connection.
+ */
+class CarrierMergedConnectionRepository(
+    override val subId: Int,
+    override val tableLogBuffer: TableLogBuffer,
+    defaultNetworkName: NetworkNameModel,
+    @Application private val scope: CoroutineScope,
+    val wifiRepository: WifiRepository,
+) : MobileConnectionRepository {
+
+    /**
+     * Outputs the carrier merged network to use, or null if we don't have a valid carrier merged
+     * network.
+     */
+    private val network: Flow<WifiNetworkModel.CarrierMerged?> =
+        combine(
+            wifiRepository.isWifiEnabled,
+            wifiRepository.isWifiDefault,
+            wifiRepository.wifiNetwork,
+        ) { isEnabled, isDefault, network ->
+            when {
+                !isEnabled -> null
+                !isDefault -> null
+                network !is WifiNetworkModel.CarrierMerged -> null
+                network.subscriptionId != subId -> {
+                    Log.w(
+                        TAG,
+                        "Connection repo subId=$subId " +
+                            "does not equal wifi repo subId=${network.subscriptionId}; " +
+                            "not showing carrier merged"
+                    )
+                    null
+                }
+                else -> network
+            }
+        }
+
+    override val connectionInfo: StateFlow<MobileConnectionModel> =
+        network
+            .map { it.toMobileConnectionModel() }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectionModel())
+
+    // TODO(b/238425913): Add logging to this class.
+    // TODO(b/238425913): Make sure SignalStrength.getEmptyState is used when appropriate.
+
+    // Carrier merged is never roaming.
+    override val cdmaRoaming: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
+
+    // TODO(b/238425913): Fetch the carrier merged network name.
+    override val networkName: StateFlow<NetworkNameModel> =
+        flowOf(defaultNetworkName)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
+
+    override val numberOfLevels: StateFlow<Int> =
+        wifiRepository.wifiNetwork
+            .map {
+                if (it is WifiNetworkModel.CarrierMerged) {
+                    it.numberOfLevels
+                } else {
+                    DEFAULT_NUM_LEVELS
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS)
+
+    override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
+
+    private fun WifiNetworkModel.CarrierMerged?.toMobileConnectionModel(): MobileConnectionModel {
+        if (this == null) {
+            return MobileConnectionModel()
+        }
+
+        return createCarrierMergedConnectionModel(level)
+    }
+
+    companion object {
+        /**
+         * Creates an instance of [MobileConnectionModel] that represents a carrier merged network
+         * with the given [level].
+         */
+        fun createCarrierMergedConnectionModel(level: Int): MobileConnectionModel {
+            return MobileConnectionModel(
+                primaryLevel = level,
+                cdmaLevel = level,
+                // A [WifiNetworkModel.CarrierMerged] instance is always connected.
+                // (A [WifiNetworkModel.Inactive] represents a disconnected network.)
+                dataConnectionState = DataConnectionState.Connected,
+                // TODO(b/238425913): This should come from [WifiRepository.wifiActivity].
+                dataActivityDirection =
+                    DataActivityModel(
+                        hasActivityIn = false,
+                        hasActivityOut = false,
+                    ),
+                resolvedNetworkType = ResolvedNetworkType.CarrierMergedNetworkType,
+                // Carrier merged is never roaming
+                isRoaming = false,
+
+                // TODO(b/238425913): Verify that these fields never change for carrier merged.
+                isEmergencyOnly = false,
+                operatorAlphaShort = null,
+                isInService = true,
+                isGsm = false,
+                carrierNetworkChangeActive = false,
+            )
+        }
+    }
+
+    @SysUISingleton
+    class Factory
+    @Inject
+    constructor(
+        @Application private val scope: CoroutineScope,
+        private val wifiRepository: WifiRepository,
+    ) {
+        fun build(
+            subId: Int,
+            mobileLogger: TableLogBuffer,
+            defaultNetworkName: NetworkNameModel,
+        ): MobileConnectionRepository {
+            return CarrierMergedConnectionRepository(
+                subId,
+                mobileLogger,
+                defaultNetworkName,
+                scope,
+                wifiRepository,
+            )
+        }
+    }
+}
+
+private const val TAG = "CarrierMergedConnectionRepository"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
new file mode 100644
index 0000000..0f30ae2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -0,0 +1,179 @@
+/*
+ * 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.mobile.data.repository.prod
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * A repository that fully implements a mobile connection.
+ *
+ * This connection could either be a typical mobile connection (see [MobileConnectionRepositoryImpl]
+ * or a carrier merged connection (see [CarrierMergedConnectionRepository]). This repository
+ * switches between the two types of connections based on whether the connection is currently
+ * carrier merged (see [setIsCarrierMerged]).
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+class FullMobileConnectionRepository(
+    override val subId: Int,
+    startingIsCarrierMerged: Boolean,
+    override val tableLogBuffer: TableLogBuffer,
+    private val defaultNetworkName: NetworkNameModel,
+    private val networkNameSeparator: String,
+    private val globalMobileDataSettingChangedEvent: Flow<Unit>,
+    @Application scope: CoroutineScope,
+    private val mobileRepoFactory: MobileConnectionRepositoryImpl.Factory,
+    private val carrierMergedRepoFactory: CarrierMergedConnectionRepository.Factory,
+) : MobileConnectionRepository {
+    /**
+     * Sets whether this connection is a typical mobile connection or a carrier merged connection.
+     */
+    fun setIsCarrierMerged(isCarrierMerged: Boolean) {
+        _isCarrierMerged.value = isCarrierMerged
+    }
+
+    /**
+     * Returns true if this repo is currently for a carrier merged connection and false otherwise.
+     */
+    @VisibleForTesting fun getIsCarrierMerged() = _isCarrierMerged.value
+
+    private val _isCarrierMerged = MutableStateFlow(startingIsCarrierMerged)
+    private val isCarrierMerged: StateFlow<Boolean> =
+        _isCarrierMerged
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = "isCarrierMerged",
+                initialValue = startingIsCarrierMerged,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), startingIsCarrierMerged)
+
+    private val mobileRepo: MobileConnectionRepository by lazy {
+        mobileRepoFactory.build(
+            subId,
+            tableLogBuffer,
+            defaultNetworkName,
+            networkNameSeparator,
+            globalMobileDataSettingChangedEvent,
+        )
+    }
+
+    private val carrierMergedRepo: MobileConnectionRepository by lazy {
+        carrierMergedRepoFactory.build(subId, tableLogBuffer, defaultNetworkName)
+    }
+
+    @VisibleForTesting
+    internal val activeRepo: StateFlow<MobileConnectionRepository> = run {
+        val initial =
+            if (startingIsCarrierMerged) {
+                carrierMergedRepo
+            } else {
+                mobileRepo
+            }
+
+        this.isCarrierMerged
+            .mapLatest { isCarrierMerged ->
+                if (isCarrierMerged) {
+                    carrierMergedRepo
+                } else {
+                    mobileRepo
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
+    }
+
+    override val cdmaRoaming =
+        activeRepo
+            .flatMapLatest { it.cdmaRoaming }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaRoaming.value)
+
+    override val connectionInfo =
+        activeRepo
+            .flatMapLatest { it.connectionInfo }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.connectionInfo.value)
+
+    override val dataEnabled =
+        activeRepo
+            .flatMapLatest { it.dataEnabled }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.dataEnabled.value)
+
+    override val numberOfLevels =
+        activeRepo
+            .flatMapLatest { it.numberOfLevels }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.numberOfLevels.value)
+
+    override val networkName =
+        activeRepo
+            .flatMapLatest { it.networkName }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value)
+
+    class Factory
+    @Inject
+    constructor(
+        @Application private val scope: CoroutineScope,
+        private val logFactory: TableLogBufferFactory,
+        private val mobileRepoFactory: MobileConnectionRepositoryImpl.Factory,
+        private val carrierMergedRepoFactory: CarrierMergedConnectionRepository.Factory,
+    ) {
+        fun build(
+            subId: Int,
+            startingIsCarrierMerged: Boolean,
+            defaultNetworkName: NetworkNameModel,
+            networkNameSeparator: String,
+            globalMobileDataSettingChangedEvent: Flow<Unit>,
+        ): FullMobileConnectionRepository {
+            val mobileLogger =
+                logFactory.getOrCreate(tableBufferLogName(subId), MOBILE_CONNECTION_BUFFER_SIZE)
+
+            return FullMobileConnectionRepository(
+                subId,
+                startingIsCarrierMerged,
+                mobileLogger,
+                defaultNetworkName,
+                networkNameSeparator,
+                globalMobileDataSettingChangedEvent,
+                scope,
+                mobileRepoFactory,
+                carrierMergedRepoFactory,
+            )
+        }
+
+        companion object {
+            /** The buffer size to use for logging. */
+            const val MOBILE_CONNECTION_BUFFER_SIZE = 100
+
+            /** Returns a log buffer name for a mobile connection with the given [subId]. */
+            fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]"
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 4e42f9b..3f2ce40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -38,7 +38,6 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -70,6 +69,10 @@
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 
+/**
+ * A repository implementation for a typical mobile connection (as opposed to a carrier merged
+ * connection -- see [CarrierMergedConnectionRepository]).
+ */
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 class MobileConnectionRepositoryImpl(
@@ -298,18 +301,16 @@
         private val logger: ConnectivityPipelineLogger,
         private val globalSettings: GlobalSettings,
         private val mobileMappingsProxy: MobileMappingsProxy,
-        private val logFactory: TableLogBufferFactory,
         @Background private val bgDispatcher: CoroutineDispatcher,
         @Application private val scope: CoroutineScope,
     ) {
         fun build(
             subId: Int,
+            mobileLogger: TableLogBuffer,
             defaultNetworkName: NetworkNameModel,
             networkNameSeparator: String,
             globalMobileDataSettingChangedEvent: Flow<Unit>,
         ): MobileConnectionRepository {
-            val mobileLogger = logFactory.getOrCreate(tableBufferLogName(subId), 100)
-
             return MobileConnectionRepositoryImpl(
                 context,
                 subId,
@@ -327,8 +328,4 @@
             )
         }
     }
-
-    companion object {
-        fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]"
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index c88c700..4472e09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -46,11 +46,12 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -85,9 +86,14 @@
     private val context: Context,
     @Background private val bgDispatcher: CoroutineDispatcher,
     @Application private val scope: CoroutineScope,
-    private val mobileConnectionRepositoryFactory: MobileConnectionRepositoryImpl.Factory
+    // Some "wifi networks" should be rendered as a mobile connection, which is why the wifi
+    // repository is an input to the mobile repository.
+    // See [CarrierMergedConnectionRepository] for details.
+    wifiRepository: WifiRepository,
+    private val fullMobileRepoFactory: FullMobileConnectionRepository.Factory,
 ) : MobileConnectionsRepository {
-    private var subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf()
+    private var subIdRepositoryCache: MutableMap<Int, FullMobileConnectionRepository> =
+        mutableMapOf()
 
     private val defaultNetworkName =
         NetworkNameModel.Default(
@@ -97,30 +103,43 @@
     private val networkNameSeparator: String =
         context.getString(R.string.status_bar_network_name_separator)
 
+    private val carrierMergedSubId: StateFlow<Int?> =
+        wifiRepository.wifiNetwork
+            .mapLatest {
+                if (it is WifiNetworkModel.CarrierMerged) {
+                    it.subscriptionId
+                } else {
+                    null
+                }
+            }
+            .distinctUntilChanged()
+            .stateIn(scope, started = SharingStarted.WhileSubscribed(), null)
+
+    private val mobileSubscriptionsChangeEvent: Flow<Unit> = conflatedCallbackFlow {
+        val callback =
+            object : SubscriptionManager.OnSubscriptionsChangedListener() {
+                override fun onSubscriptionsChanged() {
+                    trySend(Unit)
+                }
+            }
+
+        subscriptionManager.addOnSubscriptionsChangedListener(
+            bgDispatcher.asExecutor(),
+            callback,
+        )
+
+        awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
+    }
+
     /**
      * State flow that emits the set of mobile data subscriptions, each represented by its own
-     * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
-     * info object, but for now we keep track of the infos themselves.
+     * [SubscriptionModel].
      */
     override val subscriptions: StateFlow<List<SubscriptionModel>> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : SubscriptionManager.OnSubscriptionsChangedListener() {
-                        override fun onSubscriptionsChanged() {
-                            trySend(Unit)
-                        }
-                    }
-
-                subscriptionManager.addOnSubscriptionsChangedListener(
-                    bgDispatcher.asExecutor(),
-                    callback,
-                )
-
-                awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
-            }
+        merge(mobileSubscriptionsChangeEvent, carrierMergedSubId)
             .mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } }
             .logInputChange(logger, "onSubscriptionsChanged")
-            .onEach { infos -> dropUnusedReposFromCache(infos) }
+            .onEach { infos -> updateRepos(infos) }
             .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
 
     /** StateFlow that keeps track of the current active mobile data subscription */
@@ -173,7 +192,7 @@
             .distinctUntilChanged()
             .logInputChange(logger, "defaultMobileIconGroup")
 
-    override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
+    override fun getRepoForSubId(subId: Int): FullMobileConnectionRepository {
         if (!isValidSubId(subId)) {
             throw IllegalArgumentException(
                 "subscriptionId $subId is not in the list of valid subscriptions"
@@ -251,15 +270,27 @@
 
     @VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
 
-    private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
-        return mobileConnectionRepositoryFactory.build(
+    private fun createRepositoryForSubId(subId: Int): FullMobileConnectionRepository {
+        return fullMobileRepoFactory.build(
             subId,
+            isCarrierMerged(subId),
             defaultNetworkName,
             networkNameSeparator,
             globalMobileDataSettingChangedEvent,
         )
     }
 
+    private fun updateRepos(newInfos: List<SubscriptionModel>) {
+        dropUnusedReposFromCache(newInfos)
+        subIdRepositoryCache.forEach { (subId, repo) ->
+            repo.setIsCarrierMerged(isCarrierMerged(subId))
+        }
+    }
+
+    private fun isCarrierMerged(subId: Int): Boolean {
+        return subId == carrierMergedSubId.value
+    }
+
     private fun dropUnusedReposFromCache(newInfos: List<SubscriptionModel>) {
         // Remove any connection repository from the cache that isn't in the new set of IDs. They
         // will get garbage collected once their subscribers go away
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 9427c6b..003df24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -22,8 +22,8 @@
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -138,7 +138,11 @@
                 defaultMobileIconMapping,
                 defaultMobileIconGroup,
             ) { info, mapping, defaultGroup ->
-                mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup
+                when (info.resolvedNetworkType) {
+                    is ResolvedNetworkType.CarrierMergedNetworkType ->
+                        info.resolvedNetworkType.iconGroupOverride
+                    else -> mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup
+                }
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
index 4251d18..da2daf2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
@@ -16,13 +16,18 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.data.model
 
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.log.table.TableRowLogger
 import com.android.systemui.log.table.Diffable
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
 
 /** Provides information about the current wifi network. */
 sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
 
+    // TODO(b/238425913): Have a better, more unified strategy for diff-logging instead of
+    //   copy-pasting the column names for each sub-object.
+
     /**
      * A model representing that we couldn't fetch any wifi information.
      *
@@ -41,8 +46,43 @@
         override fun logFull(row: TableRowLogger) {
             row.logChange(COL_NETWORK_TYPE, TYPE_UNAVAILABLE)
             row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT)
+            row.logChange(COL_SUB_ID, SUB_ID_DEFAULT)
             row.logChange(COL_VALIDATED, false)
             row.logChange(COL_LEVEL, LEVEL_DEFAULT)
+            row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
+            row.logChange(COL_SSID, null)
+            row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
+            row.logChange(COL_ONLINE_SIGN_UP, false)
+            row.logChange(COL_PASSPOINT_NAME, null)
+        }
+    }
+
+    /**
+     * A model representing that the wifi information we received was invalid in some way.
+     */
+    data class Invalid(
+        /** A description of why the wifi information was invalid. */
+        val invalidReason: String,
+    ) : WifiNetworkModel() {
+        override fun toString() = "WifiNetwork.Invalid[$invalidReason]"
+        override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+            if (prevVal !is Invalid) {
+                logFull(row)
+                return
+            }
+
+            if (invalidReason != prevVal.invalidReason) {
+                row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE $invalidReason")
+            }
+        }
+
+        override fun logFull(row: TableRowLogger) {
+            row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE $invalidReason")
+            row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT)
+            row.logChange(COL_SUB_ID, SUB_ID_DEFAULT)
+            row.logChange(COL_VALIDATED, false)
+            row.logChange(COL_LEVEL, LEVEL_DEFAULT)
+            row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
             row.logChange(COL_SSID, null)
             row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
             row.logChange(COL_ONLINE_SIGN_UP, false)
@@ -59,18 +99,21 @@
                 return
             }
 
-            if (prevVal is CarrierMerged) {
-                // The only difference between CarrierMerged and Inactive is the type
-                row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE)
-                return
-            }
-
-            // When changing from Active to Inactive, we need to log diffs to all the fields.
-            logFullNonActiveNetwork(TYPE_INACTIVE, row)
+            // When changing to Inactive, we need to log diffs to all the fields.
+            logFull(row)
         }
 
         override fun logFull(row: TableRowLogger) {
-            logFullNonActiveNetwork(TYPE_INACTIVE, row)
+            row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE)
+            row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT)
+            row.logChange(COL_SUB_ID, SUB_ID_DEFAULT)
+            row.logChange(COL_VALIDATED, false)
+            row.logChange(COL_LEVEL, LEVEL_DEFAULT)
+            row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
+            row.logChange(COL_SSID, null)
+            row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
+            row.logChange(COL_ONLINE_SIGN_UP, false)
+            row.logChange(COL_PASSPOINT_NAME, null)
         }
     }
 
@@ -80,22 +123,75 @@
      *
      * See [android.net.wifi.WifiInfo.isCarrierMerged] for more information.
      */
-    object CarrierMerged : WifiNetworkModel() {
-        override fun toString() = "WifiNetwork.CarrierMerged"
+    data class CarrierMerged(
+        /**
+         * The [android.net.Network.netId] we received from
+         * [android.net.ConnectivityManager.NetworkCallback] in association with this wifi network.
+         *
+         * Importantly, **not** [android.net.wifi.WifiInfo.getNetworkId].
+         */
+        val networkId: Int,
+
+        /**
+         * The subscription ID that this connection represents.
+         *
+         * Comes from [android.net.wifi.WifiInfo.getSubscriptionId].
+         *
+         * Per that method, this value must not be [INVALID_SUBSCRIPTION_ID] (if it was invalid,
+         * then this is *not* a carrier merged network).
+         */
+        val subscriptionId: Int,
+
+        /**
+         * The signal level, guaranteed to be 0 <= level <= numberOfLevels.
+         */
+        val level: Int,
+
+        /**
+         * The maximum possible level.
+         */
+        val numberOfLevels: Int = DEFAULT_NUM_LEVELS,
+    ) : WifiNetworkModel() {
+        init {
+            require(level in MIN_VALID_LEVEL..numberOfLevels) {
+                "0 <= wifi level <= $numberOfLevels required; level was $level"
+            }
+            require(subscriptionId != INVALID_SUBSCRIPTION_ID) {
+                "subscription ID cannot be invalid"
+            }
+        }
 
         override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
-            if (prevVal is CarrierMerged) {
+            if (prevVal !is CarrierMerged) {
+                logFull(row)
                 return
             }
 
-            if (prevVal is Inactive) {
-                // The only difference between CarrierMerged and Inactive is the type.
-                row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED)
-                return
+            if (prevVal.networkId != networkId) {
+                row.logChange(COL_NETWORK_ID, networkId)
             }
+            if (prevVal.subscriptionId != subscriptionId) {
+                row.logChange(COL_SUB_ID, subscriptionId)
+            }
+            if (prevVal.level != level) {
+                row.logChange(COL_LEVEL, level)
+            }
+            if (prevVal.numberOfLevels != numberOfLevels) {
+                row.logChange(COL_NUM_LEVELS, numberOfLevels)
+            }
+        }
 
-            // When changing from Active to CarrierMerged, we need to log diffs to all the fields.
-            logFullNonActiveNetwork(TYPE_CARRIER_MERGED, row)
+        override fun logFull(row: TableRowLogger) {
+            row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED)
+            row.logChange(COL_NETWORK_ID, networkId)
+            row.logChange(COL_SUB_ID, subscriptionId)
+            row.logChange(COL_VALIDATED, true)
+            row.logChange(COL_LEVEL, level)
+            row.logChange(COL_NUM_LEVELS, numberOfLevels)
+            row.logChange(COL_SSID, null)
+            row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
+            row.logChange(COL_ONLINE_SIGN_UP, false)
+            row.logChange(COL_PASSPOINT_NAME, null)
         }
     }
 
@@ -137,38 +233,50 @@
 
         override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
             if (prevVal !is Active) {
-                row.logChange(COL_NETWORK_TYPE, TYPE_ACTIVE)
+                logFull(row)
+                return
             }
 
-            if (prevVal !is Active || prevVal.networkId != networkId) {
+            if (prevVal.networkId != networkId) {
                 row.logChange(COL_NETWORK_ID, networkId)
             }
-            if (prevVal !is Active || prevVal.isValidated != isValidated) {
+            if (prevVal.isValidated != isValidated) {
                 row.logChange(COL_VALIDATED, isValidated)
             }
-            if (prevVal !is Active || prevVal.level != level) {
+            if (prevVal.level != level) {
                 row.logChange(COL_LEVEL, level)
             }
-            if (prevVal !is Active || prevVal.ssid != ssid) {
+            if (prevVal.ssid != ssid) {
                 row.logChange(COL_SSID, ssid)
             }
 
             // TODO(b/238425913): The passpoint-related values are frequently never used, so it
             //   would be great to not log them when they're not used.
-            if (prevVal !is Active || prevVal.isPasspointAccessPoint != isPasspointAccessPoint) {
+            if (prevVal.isPasspointAccessPoint != isPasspointAccessPoint) {
                 row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint)
             }
-            if (prevVal !is Active ||
-                prevVal.isOnlineSignUpForPasspointAccessPoint !=
+            if (prevVal.isOnlineSignUpForPasspointAccessPoint !=
                 isOnlineSignUpForPasspointAccessPoint) {
                 row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint)
             }
-            if (prevVal !is Active ||
-                prevVal.passpointProviderFriendlyName != passpointProviderFriendlyName) {
+            if (prevVal.passpointProviderFriendlyName != passpointProviderFriendlyName) {
                 row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName)
             }
         }
 
+        override fun logFull(row: TableRowLogger) {
+            row.logChange(COL_NETWORK_TYPE, TYPE_ACTIVE)
+            row.logChange(COL_NETWORK_ID, networkId)
+            row.logChange(COL_SUB_ID, null)
+            row.logChange(COL_VALIDATED, isValidated)
+            row.logChange(COL_LEVEL, level)
+            row.logChange(COL_NUM_LEVELS, null)
+            row.logChange(COL_SSID, ssid)
+            row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint)
+            row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint)
+            row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName)
+        }
+
         override fun toString(): String {
             // Only include the passpoint-related values in the string if we have them. (Most
             // networks won't have them so they'll be mostly clutter.)
@@ -189,21 +297,13 @@
 
         companion object {
             @VisibleForTesting
-            internal const val MIN_VALID_LEVEL = 0
-            @VisibleForTesting
             internal const val MAX_VALID_LEVEL = 4
         }
     }
 
-    internal fun logFullNonActiveNetwork(type: String, row: TableRowLogger) {
-        row.logChange(COL_NETWORK_TYPE, type)
-        row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT)
-        row.logChange(COL_VALIDATED, false)
-        row.logChange(COL_LEVEL, LEVEL_DEFAULT)
-        row.logChange(COL_SSID, null)
-        row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
-        row.logChange(COL_ONLINE_SIGN_UP, false)
-        row.logChange(COL_PASSPOINT_NAME, null)
+    companion object {
+        @VisibleForTesting
+        internal const val MIN_VALID_LEVEL = 0
     }
 }
 
@@ -214,12 +314,16 @@
 
 const val COL_NETWORK_TYPE = "type"
 const val COL_NETWORK_ID = "networkId"
+const val COL_SUB_ID = "subscriptionId"
 const val COL_VALIDATED = "isValidated"
 const val COL_LEVEL = "level"
+const val COL_NUM_LEVELS = "maxLevel"
 const val COL_SSID = "ssid"
 const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint"
 const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint"
 const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName"
 
 val LEVEL_DEFAULT: String? = null
+val NUM_LEVELS_DEFAULT: String? = null
 val NETWORK_ID_DEFAULT: String? = null
+val SUB_ID_DEFAULT: String? = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
index c588945..caac8fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
 import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -43,10 +44,10 @@
 
     private fun Bundle.toWifiEvent(): FakeWifiEventModel? {
         val wifi = getString("wifi") ?: return null
-        return if (wifi == "show") {
-            activeWifiEvent()
-        } else {
-            FakeWifiEventModel.WifiDisabled
+        return when (wifi) {
+            "show" -> activeWifiEvent()
+            "carriermerged" -> carrierMergedWifiEvent()
+            else -> FakeWifiEventModel.WifiDisabled
         }
     }
 
@@ -64,6 +65,14 @@
         )
     }
 
+    private fun Bundle.carrierMergedWifiEvent(): FakeWifiEventModel.CarrierMerged {
+        val subId = getString("slot")?.toInt() ?: DEFAULT_CARRIER_MERGED_SUB_ID
+        val level = getString("level")?.toInt() ?: 0
+        val numberOfLevels = getString("numlevels")?.toInt() ?: DEFAULT_NUM_LEVELS
+
+        return FakeWifiEventModel.CarrierMerged(subId, level, numberOfLevels)
+    }
+
     private fun String.toActivity(): Int =
         when (this) {
             "inout" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT
@@ -71,4 +80,8 @@
             "out" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT
             else -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE
         }
+
+    companion object {
+        const val DEFAULT_CARRIER_MERGED_SUB_ID = 10
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
index be3d7d4..e161b3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -66,6 +66,7 @@
     private fun processEvent(event: FakeWifiEventModel) =
         when (event) {
             is FakeWifiEventModel.Wifi -> processEnabledWifiState(event)
+            is FakeWifiEventModel.CarrierMerged -> processCarrierMergedWifiState(event)
             is FakeWifiEventModel.WifiDisabled -> processDisabledWifiState()
         }
 
@@ -85,6 +86,14 @@
         _wifiNetwork.value = event.toWifiNetworkModel()
     }
 
+    private fun processCarrierMergedWifiState(event: FakeWifiEventModel.CarrierMerged) {
+        _isWifiEnabled.value = true
+        _isWifiDefault.value = true
+        // TODO(b/238425913): Support activity in demo mode.
+        _wifiActivity.value = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+        _wifiNetwork.value = event.toCarrierMergedModel()
+    }
+
     private fun FakeWifiEventModel.Wifi.toWifiNetworkModel(): WifiNetworkModel =
         WifiNetworkModel.Active(
             networkId = DEMO_NET_ID,
@@ -99,6 +108,14 @@
             passpointProviderFriendlyName = null,
         )
 
+    private fun FakeWifiEventModel.CarrierMerged.toCarrierMergedModel(): WifiNetworkModel =
+        WifiNetworkModel.CarrierMerged(
+            networkId = DEMO_NET_ID,
+            subscriptionId = subscriptionId,
+            level = level,
+            numberOfLevels = numberOfLevels,
+        )
+
     companion object {
         private const val DEMO_NET_ID = 1234
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
index 2353fb8..518f8ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
@@ -29,5 +29,11 @@
         val validated: Boolean?,
     ) : FakeWifiEventModel
 
+    data class CarrierMerged(
+        val subscriptionId: Int,
+        val level: Int,
+        val numberOfLevels: Int,
+    ) : FakeWifiEventModel
+
     object WifiDisabled : FakeWifiEventModel
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index c47c20d..d26499c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -29,6 +29,7 @@
 import android.net.wifi.WifiInfo
 import android.net.wifi.WifiManager
 import android.net.wifi.WifiManager.TrafficStateCallback
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import com.android.settingslib.Utils
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -269,7 +270,19 @@
             wifiManager: WifiManager,
         ): WifiNetworkModel {
             return if (wifiInfo.isCarrierMerged) {
-                WifiNetworkModel.CarrierMerged
+                if (wifiInfo.subscriptionId == INVALID_SUBSCRIPTION_ID) {
+                    WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
+                } else {
+                    WifiNetworkModel.CarrierMerged(
+                        networkId = network.getNetId(),
+                        subscriptionId = wifiInfo.subscriptionId,
+                        level = wifiManager.calculateSignalLevel(wifiInfo.rssi),
+                        // The WiFi signal level returned by WifiManager#calculateSignalLevel start
+                        // from 0, so WifiManager#getMaxSignalLevel + 1 represents the total level
+                        // buckets count.
+                        numberOfLevels = wifiManager.maxSignalLevel + 1,
+                    )
+                }
             } else {
                 WifiNetworkModel.Active(
                     network.getNetId(),
@@ -302,6 +315,9 @@
                 .build()
 
         private const val WIFI_NETWORK_CALLBACK_NAME = "wifiNetworkModel"
+
+        private const val CARRIER_MERGED_INVALID_SUB_ID_REASON =
+            "Wifi network was carrier merged but had invalid sub ID"
     }
 
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index 980560a..86dcd18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -66,6 +66,7 @@
     override val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
         when (info) {
             is WifiNetworkModel.Unavailable -> null
+            is WifiNetworkModel.Invalid -> null
             is WifiNetworkModel.Inactive -> null
             is WifiNetworkModel.CarrierMerged -> null
             is WifiNetworkModel.Active -> when {
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 824b597..95431af 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
@@ -83,6 +83,7 @@
     private fun WifiNetworkModel.icon(): WifiIcon {
         return when (this) {
             is WifiNetworkModel.Unavailable -> WifiIcon.Hidden
+            is WifiNetworkModel.Invalid -> WifiIcon.Hidden
             is WifiNetworkModel.CarrierMerged -> WifiIcon.Hidden
             is WifiNetworkModel.Inactive -> WifiIcon.Visible(
                 res = WIFI_NO_NETWORK,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 84f6d91..075ef9d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -405,6 +405,13 @@
     }
 
     @Test
+    public void onBouncerVisibilityChanged_resetsScale() {
+        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.INVISIBLE);
+
+        verify(mView).resetScale();
+    }
+
+    @Test
     public void onStartingToHide_sideFpsHintShown_sideFpsHintHidden() {
         setupGetSecurityView();
         setupConditionsToEnableSideFpsHint();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 36ed669..1bbc199 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -49,6 +49,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
+import android.window.BackEvent;
+import android.window.OnBackAnimationCallback;
 
 import androidx.constraintlayout.widget.ConstraintSet;
 import androidx.test.filters.SmallTest;
@@ -357,6 +359,27 @@
         assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
     }
 
+    @Test
+    public void testPlayBackAnimation() {
+        OnBackAnimationCallback backCallback = mKeyguardSecurityContainer.getBackCallback();
+        backCallback.onBackStarted(createBackEvent(0, 0));
+        mKeyguardSecurityContainer.getBackCallback().onBackProgressed(
+                createBackEvent(0, 1));
+        assertThat(mKeyguardSecurityContainer.getScaleX()).isEqualTo(
+                KeyguardSecurityContainer.MIN_BACK_SCALE);
+        assertThat(mKeyguardSecurityContainer.getScaleY()).isEqualTo(
+                KeyguardSecurityContainer.MIN_BACK_SCALE);
+
+        // reset scale
+        mKeyguardSecurityContainer.resetScale();
+        assertThat(mKeyguardSecurityContainer.getScaleX()).isEqualTo(1);
+        assertThat(mKeyguardSecurityContainer.getScaleY()).isEqualTo(1);
+    }
+
+    private BackEvent createBackEvent(float touchX, float progress) {
+        return new BackEvent(0, 0, progress, BackEvent.EDGE_LEFT);
+    }
+
     private Configuration configuration(@Configuration.Orientation int orientation) {
         Configuration config = new Configuration();
         config.orientation = orientation;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index f8f2a56..32cec09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -168,6 +168,25 @@
         assertThat(wtfHandler.failed).isTrue()
     }
 
+    @Test
+    fun `Attempt to manually update transition after CANCELED state throws exception`() {
+        val uuid =
+            underTest.startTransition(
+                TransitionInfo(
+                    ownerName = OWNER_NAME,
+                    from = AOD,
+                    to = LOCKSCREEN,
+                    animator = null,
+                )
+            )
+
+        checkNotNull(uuid).let {
+            underTest.updateTransition(it, 0.2f, TransitionState.CANCELED)
+            underTest.updateTransition(it, 0.5f, TransitionState.RUNNING)
+        }
+        assertThat(wtfHandler.failed).isTrue()
+    }
+
     private fun listWithStep(
         step: BigDecimal,
         start: BigDecimal = BigDecimal.ZERO,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index b3cee22..a1b6d47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -38,6 +38,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.cancelChildren
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -177,7 +178,7 @@
             keyguardRepository.setDreamingWithOverlay(false)
             // AND occluded has stopped
             keyguardRepository.setKeyguardOccluded(false)
-            runCurrent()
+            advanceUntilIdle()
 
             val info =
                 withArgCaptor<TransitionInfo> {
@@ -506,7 +507,7 @@
                 withArgCaptor<TransitionInfo> {
                     verify(mockTransitionRepository).startTransition(capture())
                 }
-            // THEN a transition to DOZING should occur
+            // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
             assertThat(info.from).isEqualTo(KeyguardState.GONE)
             assertThat(info.to).isEqualTo(KeyguardState.AOD)
@@ -515,6 +516,49 @@
             coroutineContext.cancelChildren()
         }
 
+    @Test
+    fun `GONE to DREAMING`() =
+        testScope.runTest {
+            // GIVEN a device that is not dreaming or dozing
+            keyguardRepository.setDreamingWithOverlay(false)
+            keyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+            )
+            runCurrent()
+
+            // GIVEN a prior transition has run to GONE
+            runner.startTransition(
+                testScope,
+                TransitionInfo(
+                    ownerName = "",
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    animator =
+                        ValueAnimator().apply {
+                            duration = 10
+                            interpolator = Interpolators.LINEAR
+                        },
+                )
+            )
+            reset(mockTransitionRepository)
+
+            // WHEN the device begins to dream
+            keyguardRepository.setDreamingWithOverlay(true)
+            advanceUntilIdle()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(mockTransitionRepository).startTransition(capture())
+                }
+            // THEN a transition to DREAMING should occur
+            assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.GONE)
+            assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
     private fun startingToWake() =
         WakefulnessModel(
             WakefulnessState.STARTING_TO_WAKE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
new file mode 100644
index 0000000..7fa204b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.AnimationParams
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA
+import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class GoneToDreamingTransitionViewModelTest : SysuiTestCase() {
+    private lateinit var underTest: GoneToDreamingTransitionViewModel
+    private lateinit var repository: FakeKeyguardTransitionRepository
+
+    @Before
+    fun setUp() {
+        repository = FakeKeyguardTransitionRepository()
+        val interactor = KeyguardTransitionInteractor(repository)
+        underTest = GoneToDreamingTransitionViewModel(interactor)
+    }
+
+    @Test
+    fun lockscreenFadeOut() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<Float>()
+
+            val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
+
+            // Should start running here...
+            repository.sendTransitionStep(step(0f))
+            repository.sendTransitionStep(step(0.1f))
+            repository.sendTransitionStep(step(0.2f))
+            // ...up to here
+            repository.sendTransitionStep(step(0.3f))
+            repository.sendTransitionStep(step(1f))
+
+            // Only three values should be present, since the dream overlay runs for a small
+            // fraction
+            // of the overall animation time
+            assertThat(values.size).isEqualTo(3)
+            assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
+            assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
+            assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA))
+
+            job.cancel()
+        }
+
+    @Test
+    fun lockscreenTranslationY() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<Float>()
+
+            val pixels = 100
+            val job =
+                underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
+
+            // Should start running here...
+            repository.sendTransitionStep(step(0f))
+            repository.sendTransitionStep(step(0.3f))
+            repository.sendTransitionStep(step(0.5f))
+            // ...up to here
+            repository.sendTransitionStep(step(1f))
+            // And a final reset event on CANCEL
+            repository.sendTransitionStep(step(0.8f, TransitionState.CANCELED))
+
+            assertThat(values.size).isEqualTo(4)
+            assertThat(values[0])
+                .isEqualTo(
+                    EMPHASIZED_ACCELERATE.getInterpolation(
+                        animValue(0f, LOCKSCREEN_TRANSLATION_Y)
+                    ) * pixels
+                )
+            assertThat(values[1])
+                .isEqualTo(
+                    EMPHASIZED_ACCELERATE.getInterpolation(
+                        animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
+                    ) * pixels
+                )
+            assertThat(values[2])
+                .isEqualTo(
+                    EMPHASIZED_ACCELERATE.getInterpolation(
+                        animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
+                    ) * pixels
+                )
+            assertThat(values[3]).isEqualTo(0f)
+            job.cancel()
+        }
+
+    private fun animValue(stepValue: Float, params: AnimationParams): Float {
+        val totalDuration = TO_DREAMING_DURATION
+        val startValue = (params.startTime / totalDuration).toFloat()
+
+        val multiplier = (totalDuration / params.duration).toFloat()
+        return (stepValue - startValue) * multiplier
+    }
+
+    private fun step(
+        value: Float,
+        state: TransitionState = TransitionState.RUNNING
+    ): TransitionStep {
+        return TransitionStep(
+            from = KeyguardState.GONE,
+            to = KeyguardState.DREAMING,
+            value = value,
+            transitionState = state,
+            ownerName = "GoneToDreamingTransitionViewModelTest"
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index 7390591..539fc2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -67,8 +67,7 @@
             repository.sendTransitionStep(step(1f))
 
             // Only three values should be present, since the dream overlay runs for a small
-            // fraction
-            // of the overall animation time
+            // fraction of the overall animation time
             assertThat(values.size).isEqualTo(3)
             assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
             assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
@@ -92,8 +91,10 @@
             repository.sendTransitionStep(step(0.5f))
             // ...up to here
             repository.sendTransitionStep(step(1f))
+            // And a final reset event on FINISHED
+            repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
 
-            assertThat(values.size).isEqualTo(3)
+            assertThat(values.size).isEqualTo(4)
             assertThat(values[0])
                 .isEqualTo(
                     EMPHASIZED_ACCELERATE.getInterpolation(
@@ -112,6 +113,8 @@
                         animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
                     ) * pixels
                 )
+            assertThat(values[3]).isEqualTo(0f)
+
             job.cancel()
         }
 
@@ -123,12 +126,15 @@
         return (stepValue - startValue) * multiplier
     }
 
-    private fun step(value: Float): TransitionStep {
+    private fun step(
+        value: Float,
+        state: TransitionState = TransitionState.RUNNING
+    ): TransitionStep {
         return TransitionStep(
             from = KeyguardState.LOCKSCREEN,
             to = KeyguardState.DREAMING,
             value = value,
-            transitionState = TransitionState.RUNNING,
+            transitionState = state,
             ownerName = "LockscreenToDreamingTransitionViewModelTest"
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index 771b986..3d5dba3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -54,36 +54,6 @@
     }
 
     @Test
-    public void showOutputSwitcher_ExtraPackageName_DialogFactoryCalled() {
-        Intent intent = new Intent(Intent.ACTION_SHOW_OUTPUT_SWITCHER);
-        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, getContext().getPackageName());
-        mMediaOutputDialogReceiver.onReceive(getContext(), intent);
-
-        verify(mMockMediaOutputDialogFactory, times(1))
-                .create(getContext().getPackageName(), false, null);
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
-    }
-
-    @Test
-    public void showOutputSwitcher_WrongExtraKey_DialogFactoryNotCalled() {
-        Intent intent = new Intent(Intent.ACTION_SHOW_OUTPUT_SWITCHER);
-        intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
-        mMediaOutputDialogReceiver.onReceive(getContext(), intent);
-
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
-    }
-
-    @Test
-    public void showOutputSwitcher_NoExtra_DialogFactoryNotCalled() {
-        Intent intent = new Intent(Intent.ACTION_SHOW_OUTPUT_SWITCHER);
-        mMediaOutputDialogReceiver.onReceive(getContext(), intent);
-
-        verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
-        verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
-    }
-
-    @Test
     public void launchMediaOutputDialog_ExtraPackageName_DialogFactoryCalled() {
         Intent intent = new Intent(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
         intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index a1e9a27..6dd2d61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -107,6 +107,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
@@ -299,6 +300,7 @@
     @Mock private OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel;
     @Mock private LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel;
     @Mock private LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel;
+    @Mock private GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
 
     @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     @Mock private CoroutineDispatcher mMainDispatcher;
@@ -522,6 +524,7 @@
                 mDreamingToLockscreenTransitionViewModel,
                 mOccludedToLockscreenTransitionViewModel,
                 mLockscreenToDreamingTransitionViewModel,
+                mGoneToDreamingTransitionViewModel,
                 mLockscreenToOccludedTransitionViewModel,
                 mMainDispatcher,
                 mKeyguardTransitionInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 08a9c96..526dc8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -46,11 +46,14 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.colorextraction.ColorExtractor;
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -68,6 +71,8 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
 
+import java.util.List;
+
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 @SmallTest
@@ -91,13 +96,21 @@
     @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
     @Mock private ShadeWindowLogger mShadeWindowLogger;
     @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
+    @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
 
     private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
-
+    private float mPreferredRefreshRate = -1;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        // Preferred refresh rate is equal to the first displayMode's refresh rate
+        mPreferredRefreshRate = mContext.getDisplay().getSupportedModes()[0].getRefreshRate();
+        overrideResource(
+                R.integer.config_keyguardRefreshRate,
+                (int) mPreferredRefreshRate
+        );
+
         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
@@ -117,6 +130,7 @@
 
         mNotificationShadeWindowController.attach();
         verify(mWindowManager).addView(eq(mNotificationShadeWindowView), any());
+        verify(mStatusBarStateController).addCallback(mStateListener.capture(), anyInt());
     }
 
     @Test
@@ -334,4 +348,59 @@
         assertThat(mLayoutParameters.getValue().screenOrientation)
                 .isEqualTo(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
     }
+
+    @Test
+    public void udfpsEnrolled_minAndMaxRefreshRateSetToPreferredRefreshRate() {
+        // GIVEN udfps is enrolled
+        when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
+
+        // WHEN keyguard is showing
+        setKeyguardShowing();
+
+        // THEN min and max refresh rate is set to the preferredRefreshRate
+        verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture());
+        final List<WindowManager.LayoutParams> lpList = mLayoutParameters.getAllValues();
+        final WindowManager.LayoutParams lp = lpList.get(lpList.size() - 1);
+        assertThat(lp.preferredMaxDisplayRefreshRate).isEqualTo(mPreferredRefreshRate);
+        assertThat(lp.preferredMinDisplayRefreshRate).isEqualTo(mPreferredRefreshRate);
+    }
+
+    @Test
+    public void udfpsNotEnrolled_refreshRateUnset() {
+        // GIVEN udfps is NOT enrolled
+        when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
+
+        // WHEN keyguard is showing
+        setKeyguardShowing();
+
+        // THEN min and max refresh rate aren't set (set to 0)
+        verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture());
+        final List<WindowManager.LayoutParams> lpList = mLayoutParameters.getAllValues();
+        final WindowManager.LayoutParams lp = lpList.get(lpList.size() - 1);
+        assertThat(lp.preferredMaxDisplayRefreshRate).isEqualTo(0);
+        assertThat(lp.preferredMinDisplayRefreshRate).isEqualTo(0);
+    }
+
+    @Test
+    public void keyguardNotShowing_refreshRateUnset() {
+        // GIVEN UDFPS is enrolled
+        when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
+
+        // WHEN keyguard is NOT showing
+        mNotificationShadeWindowController.setKeyguardShowing(false);
+
+        // THEN min and max refresh rate aren't set (set to 0)
+        verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture());
+        final List<WindowManager.LayoutParams> lpList = mLayoutParameters.getAllValues();
+        final WindowManager.LayoutParams lp = lpList.get(lpList.size() - 1);
+        assertThat(lp.preferredMaxDisplayRefreshRate).isEqualTo(0);
+        assertThat(lp.preferredMinDisplayRefreshRate).isEqualTo(0);
+    }
+
+    private void setKeyguardShowing() {
+        mNotificationShadeWindowController.setKeyguardShowing(true);
+        mNotificationShadeWindowController.setKeyguardGoingAway(false);
+        mNotificationShadeWindowController.setKeyguardFadingAway(false);
+        mStateListener.getValue().onStateChanged(StatusBarState.KEYGUARD);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 0000c32..fc7cd89 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -209,9 +209,9 @@
 
     @Test
     public void testShowRecentApps() {
-        mCommandQueue.showRecentApps(true);
+        mCommandQueue.showRecentApps(true, false);
         waitForIdleSync();
-        verify(mCallbacks).showRecentApps(eq(true));
+        verify(mCallbacks).showRecentApps(eq(true), eq(false));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 04a6700..7f86327 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -37,6 +37,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
+import android.window.BackEvent;
+import android.window.OnBackAnimationCallback;
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
 import android.window.WindowOnBackInvokedDispatcher;
@@ -54,6 +56,7 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.data.BouncerView;
 import com.android.systemui.keyguard.data.BouncerViewDelegate;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
@@ -118,16 +121,19 @@
     @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock private BouncerView mBouncerView;
     @Mock private BouncerViewDelegate mBouncerViewDelegate;
+    @Mock private OnBackAnimationCallback mBouncerViewDelegateBackCallback;
 
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback;
     private FakeKeyguardStateController mKeyguardStateController =
             spy(new FakeKeyguardStateController());
 
-    @Mock private ViewRootImpl mViewRootImpl;
-    @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
+    @Mock
+    private ViewRootImpl mViewRootImpl;
+    @Mock
+    private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
     @Captor
-    private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
+    private ArgumentCaptor<OnBackInvokedCallback> mBackCallbackCaptor;
 
 
     @Before
@@ -138,6 +144,10 @@
         when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
                 .thenReturn(mKeyguardMessageAreaController);
         when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
+        when(mBouncerViewDelegate.getBackCallback()).thenReturn(mBouncerViewDelegateBackCallback);
+        when(mFeatureFlags
+                .isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM))
+                .thenReturn(true);
 
         when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(true);
 
@@ -191,7 +201,8 @@
     @Test
     public void dismissWithAction_AfterKeyguardGoneSetToFalse() {
         OnDismissAction action = () -> false;
-        Runnable cancelAction = () -> {};
+        Runnable cancelAction = () -> {
+        };
         mStatusBarKeyguardViewManager.dismissWithAction(
                 action, cancelAction, false /* afterKeyguardGone */);
         verify(mPrimaryBouncerInteractor).setDismissAction(eq(action), eq(cancelAction));
@@ -539,12 +550,12 @@
         mBouncerExpansionCallback.onVisibilityChanged(true);
         verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
                 eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
-                mOnBackInvokedCallback.capture());
+                mBackCallbackCaptor.capture());
 
         /* verify that the same callback is unregistered when the bouncer becomes invisible */
         mBouncerExpansionCallback.onVisibilityChanged(false);
         verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(
-                eq(mOnBackInvokedCallback.getValue()));
+                eq(mBackCallbackCaptor.getValue()));
     }
 
     @Test
@@ -553,18 +564,63 @@
         /* capture the predictive back callback during registration */
         verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
                 eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
-                mOnBackInvokedCallback.capture());
+                mBackCallbackCaptor.capture());
 
         when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
         when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true);
         /* invoke the back callback directly */
-        mOnBackInvokedCallback.getValue().onBackInvoked();
+        mBackCallbackCaptor.getValue().onBackInvoked();
 
         /* verify that the bouncer will be hidden as a result of the invocation */
         verify(mCentralSurfaces).setBouncerShowing(eq(false));
     }
 
     @Test
+    public void testPredictiveBackCallback_noBackAnimationForFullScreenBouncer() {
+        when(mKeyguardSecurityModel.getSecurityMode(anyInt()))
+                .thenReturn(KeyguardSecurityModel.SecurityMode.SimPin);
+        mBouncerExpansionCallback.onVisibilityChanged(true);
+        /* capture the predictive back callback during registration */
+        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+                mBackCallbackCaptor.capture());
+        assertTrue(mBackCallbackCaptor.getValue() instanceof OnBackAnimationCallback);
+
+        OnBackAnimationCallback backCallback =
+                (OnBackAnimationCallback) mBackCallbackCaptor.getValue();
+
+        BackEvent event = new BackEvent(0, 0, 0, BackEvent.EDGE_LEFT);
+        backCallback.onBackStarted(event);
+        verify(mBouncerViewDelegateBackCallback, never()).onBackStarted(any());
+    }
+
+    @Test
+    public void testPredictiveBackCallback_forwardsBackDispatches() {
+        mBouncerExpansionCallback.onVisibilityChanged(true);
+        /* capture the predictive back callback during registration */
+        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+                mBackCallbackCaptor.capture());
+        assertTrue(mBackCallbackCaptor.getValue() instanceof OnBackAnimationCallback);
+
+        OnBackAnimationCallback backCallback =
+                (OnBackAnimationCallback) mBackCallbackCaptor.getValue();
+
+        BackEvent event = new BackEvent(0, 0, 0, BackEvent.EDGE_LEFT);
+        backCallback.onBackStarted(event);
+        verify(mBouncerViewDelegateBackCallback).onBackStarted(eq(event));
+
+        backCallback.onBackProgressed(event);
+        verify(mBouncerViewDelegateBackCallback).onBackProgressed(eq(event));
+
+        backCallback.onBackInvoked();
+        verify(mBouncerViewDelegateBackCallback).onBackInvoked();
+
+        backCallback.onBackCancelled();
+        verify(mBouncerViewDelegateBackCallback).onBackCancelled();
+    }
+
+    @Test
     public void testReportBouncerOnDreamWhenVisible() {
         mBouncerExpansionCallback.onVisibilityChanged(true);
         verify(mCentralSurfaces).setBouncerShowingOverDream(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 5d377a8..0859d14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -34,6 +34,8 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.kotlinArgumentCaptor
 import com.android.systemui.util.mockito.mock
@@ -71,8 +73,10 @@
     private lateinit var underTest: MobileRepositorySwitcher
     private lateinit var realRepo: MobileConnectionsRepositoryImpl
     private lateinit var demoRepo: DemoMobileConnectionsRepository
-    private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
+    private lateinit var mobileDataSource: DemoModeMobileConnectionDataSource
+    private lateinit var wifiDataSource: DemoModeWifiDataSource
     private lateinit var logFactory: TableLogBufferFactory
+    private lateinit var wifiRepository: FakeWifiRepository
 
     @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var subscriptionManager: SubscriptionManager
@@ -96,10 +100,15 @@
         // Never start in demo mode
         whenever(demoModeController.isInDemoMode).thenReturn(false)
 
-        mockDataSource =
+        mobileDataSource =
             mock<DemoModeMobileConnectionDataSource>().also {
                 whenever(it.mobileEvents).thenReturn(fakeNetworkEventsFlow)
             }
+        wifiDataSource =
+            mock<DemoModeWifiDataSource>().also {
+                whenever(it.wifiEvents).thenReturn(MutableStateFlow(null))
+            }
+        wifiRepository = FakeWifiRepository()
 
         realRepo =
             MobileConnectionsRepositoryImpl(
@@ -113,12 +122,14 @@
                 context,
                 IMMEDIATE,
                 scope,
+                wifiRepository,
                 mock(),
             )
 
         demoRepo =
             DemoMobileConnectionsRepository(
-                dataSource = mockDataSource,
+                mobileDataSource = mobileDataSource,
+                wifiDataSource = wifiDataSource,
                 scope = scope,
                 context = context,
                 logFactory = logFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index 2102085..6989b514 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -29,6 +29,8 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
@@ -63,10 +65,12 @@
     private val testScope = TestScope(testDispatcher)
 
     private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
+    private val fakeWifiEventFlow = MutableStateFlow<FakeWifiEventModel?>(null)
 
     private lateinit var connectionsRepo: DemoMobileConnectionsRepository
     private lateinit var underTest: DemoMobileConnectionRepository
     private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
+    private lateinit var mockWifiDataSource: DemoModeWifiDataSource
 
     @Before
     fun setUp() {
@@ -75,10 +79,15 @@
             mock<DemoModeMobileConnectionDataSource>().also {
                 whenever(it.mobileEvents).thenReturn(fakeNetworkEventFlow)
             }
+        mockWifiDataSource =
+            mock<DemoModeWifiDataSource>().also {
+                whenever(it.wifiEvents).thenReturn(fakeWifiEventFlow)
+            }
 
         connectionsRepo =
             DemoMobileConnectionsRepository(
-                dataSource = mockDataSource,
+                mobileDataSource = mockDataSource,
+                wifiDataSource = mockWifiDataSource,
                 scope = testScope.backgroundScope,
                 context = context,
                 logFactory = logFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index cdbe75e..9d16b7fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -32,6 +32,8 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
@@ -57,21 +59,28 @@
     private val testScope = TestScope(testDispatcher)
 
     private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
+    private val fakeWifiEventFlow = MutableStateFlow<FakeWifiEventModel?>(null)
 
     private lateinit var underTest: DemoMobileConnectionsRepository
-    private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
+    private lateinit var mobileDataSource: DemoModeMobileConnectionDataSource
+    private lateinit var wifiDataSource: DemoModeWifiDataSource
 
     @Before
     fun setUp() {
         // The data source only provides one API, so we can mock it with a flow here for convenience
-        mockDataSource =
+        mobileDataSource =
             mock<DemoModeMobileConnectionDataSource>().also {
                 whenever(it.mobileEvents).thenReturn(fakeNetworkEventFlow)
             }
+        wifiDataSource =
+            mock<DemoModeWifiDataSource>().also {
+                whenever(it.wifiEvents).thenReturn(fakeWifiEventFlow)
+            }
 
         underTest =
             DemoMobileConnectionsRepository(
-                dataSource = mockDataSource,
+                mobileDataSource = mobileDataSource,
+                wifiDataSource = wifiDataSource,
                 scope = testScope.backgroundScope,
                 context = context,
                 logFactory = logFactory,
@@ -97,6 +106,22 @@
         }
 
     @Test
+    fun `wifi carrier merged event - create new subscription`() =
+        testScope.runTest {
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEmpty()
+
+            fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5)
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].subscriptionId).isEqualTo(5)
+
+            job.cancel()
+        }
+
+    @Test
     fun `network event - reuses subscription when same Id`() =
         testScope.runTest {
             var latest: List<SubscriptionModel>? = null
@@ -119,6 +144,28 @@
         }
 
     @Test
+    fun `wifi carrier merged event - reuses subscription when same Id`() =
+        testScope.runTest {
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEmpty()
+
+            fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5, level = 1)
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].subscriptionId).isEqualTo(5)
+
+            // Second network event comes in with the same subId, does not create a new subscription
+            fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5, level = 2)
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].subscriptionId).isEqualTo(5)
+
+            job.cancel()
+        }
+
+    @Test
     fun `multiple subscriptions`() =
         testScope.runTest {
             var latest: List<SubscriptionModel>? = null
@@ -133,6 +180,35 @@
         }
 
     @Test
+    fun `mobile subscription and carrier merged subscription`() =
+        testScope.runTest {
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1)
+            fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5)
+
+            assertThat(latest).hasSize(2)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `multiple mobile subscriptions and carrier merged subscription`() =
+        testScope.runTest {
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1)
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 2)
+            fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 3)
+
+            assertThat(latest).hasSize(3)
+
+            job.cancel()
+        }
+
+    @Test
     fun `mobile disabled event - disables connection - subId specified - single conn`() =
         testScope.runTest {
             var latest: List<SubscriptionModel>? = null
@@ -194,6 +270,112 @@
             job.cancel()
         }
 
+    @Test
+    fun `wifi network updates to disabled - carrier merged connection removed`() =
+        testScope.runTest {
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+            fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 1)
+
+            assertThat(latest).hasSize(1)
+
+            fakeWifiEventFlow.value = FakeWifiEventModel.WifiDisabled
+
+            assertThat(latest).isEmpty()
+
+            job.cancel()
+        }
+
+    @Test
+    fun `wifi network updates to active - carrier merged connection removed`() =
+        testScope.runTest {
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+            fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 1)
+
+            assertThat(latest).hasSize(1)
+
+            fakeWifiEventFlow.value =
+                FakeWifiEventModel.Wifi(
+                    level = 1,
+                    activity = 0,
+                    ssid = null,
+                    validated = true,
+                )
+
+            assertThat(latest).isEmpty()
+
+            job.cancel()
+        }
+
+    @Test
+    fun `mobile sub updates to carrier merged - only one connection`() =
+        testScope.runTest {
+            var latestSubsList: List<SubscriptionModel>? = null
+            var connections: List<DemoMobileConnectionRepository>? = null
+            val job =
+                underTest.subscriptions
+                    .onEach { latestSubsList = it }
+                    .onEach { infos ->
+                        connections =
+                            infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) }
+                    }
+                    .launchIn(this)
+
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 3, level = 2)
+            assertThat(latestSubsList).hasSize(1)
+
+            val carrierMergedEvent = validCarrierMergedEvent(subId = 3, level = 1)
+            fakeWifiEventFlow.value = carrierMergedEvent
+            assertThat(latestSubsList).hasSize(1)
+            val connection = connections!!.find { it.subId == 3 }!!
+            assertCarrierMergedConnection(connection, carrierMergedEvent)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `mobile sub updates to carrier merged then back - has old mobile data`() =
+        testScope.runTest {
+            var latestSubsList: List<SubscriptionModel>? = null
+            var connections: List<DemoMobileConnectionRepository>? = null
+            val job =
+                underTest.subscriptions
+                    .onEach { latestSubsList = it }
+                    .onEach { infos ->
+                        connections =
+                            infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) }
+                    }
+                    .launchIn(this)
+
+            val mobileEvent = validMobileEvent(subId = 3, level = 2)
+            fakeNetworkEventFlow.value = mobileEvent
+            assertThat(latestSubsList).hasSize(1)
+
+            val carrierMergedEvent = validCarrierMergedEvent(subId = 3, level = 1)
+            fakeWifiEventFlow.value = carrierMergedEvent
+            assertThat(latestSubsList).hasSize(1)
+            var connection = connections!!.find { it.subId == 3 }!!
+            assertCarrierMergedConnection(connection, carrierMergedEvent)
+
+            // WHEN the carrier merged is removed
+            fakeWifiEventFlow.value =
+                FakeWifiEventModel.Wifi(
+                    level = 4,
+                    activity = 0,
+                    ssid = null,
+                    validated = true,
+                )
+
+            // THEN the subId=3 connection goes back to the mobile information
+            connection = connections!!.find { it.subId == 3 }!!
+            assertConnection(connection, mobileEvent)
+
+            job.cancel()
+        }
+
     /** Regression test for b/261706421 */
     @Test
     fun `multiple connections - remove all - does not throw`() =
@@ -289,6 +471,51 @@
             job.cancel()
         }
 
+    @Test
+    fun `demo connection - two connections - update carrier merged - no affect on first`() =
+        testScope.runTest {
+            var currentEvent1 = validMobileEvent(subId = 1)
+            var connection1: DemoMobileConnectionRepository? = null
+            var currentEvent2 = validCarrierMergedEvent(subId = 2)
+            var connection2: DemoMobileConnectionRepository? = null
+            var connections: List<DemoMobileConnectionRepository>? = null
+            val job =
+                underTest.subscriptions
+                    .onEach { infos ->
+                        connections =
+                            infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) }
+                    }
+                    .launchIn(this)
+
+            fakeNetworkEventFlow.value = currentEvent1
+            fakeWifiEventFlow.value = currentEvent2
+            assertThat(connections).hasSize(2)
+            connections!!.forEach {
+                when (it.subId) {
+                    1 -> connection1 = it
+                    2 -> connection2 = it
+                    else -> Assert.fail("Unexpected subscription")
+                }
+            }
+
+            assertConnection(connection1!!, currentEvent1)
+            assertCarrierMergedConnection(connection2!!, currentEvent2)
+
+            // WHEN the event changes for connection 2, it updates, and connection 1 stays the same
+            currentEvent2 = validCarrierMergedEvent(subId = 2, level = 4)
+            fakeWifiEventFlow.value = currentEvent2
+            assertConnection(connection1!!, currentEvent1)
+            assertCarrierMergedConnection(connection2!!, currentEvent2)
+
+            // and vice versa
+            currentEvent1 = validMobileEvent(subId = 1, inflateStrength = true)
+            fakeNetworkEventFlow.value = currentEvent1
+            assertConnection(connection1!!, currentEvent1)
+            assertCarrierMergedConnection(connection2!!, currentEvent2)
+
+            job.cancel()
+        }
+
     private fun assertConnection(
         conn: DemoMobileConnectionRepository,
         model: FakeNetworkEventModel
@@ -315,6 +542,21 @@
             else -> {}
         }
     }
+
+    private fun assertCarrierMergedConnection(
+        conn: DemoMobileConnectionRepository,
+        model: FakeWifiEventModel.CarrierMerged,
+    ) {
+        val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
+        assertThat(conn.subId).isEqualTo(model.subscriptionId)
+        assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
+        assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
+        assertThat(connectionInfo.carrierNetworkChangeActive).isEqualTo(false)
+        assertThat(connectionInfo.isRoaming).isEqualTo(false)
+        assertThat(connectionInfo.isEmergencyOnly).isFalse()
+        assertThat(connectionInfo.isGsm).isFalse()
+        assertThat(connectionInfo.dataConnectionState).isEqualTo(DataConnectionState.Connected)
+    }
 }
 
 /** Convenience to create a valid fake network event with minimal params */
@@ -339,3 +581,14 @@
         roaming = roaming,
         name = "demo name",
     )
+
+fun validCarrierMergedEvent(
+    subId: Int = 1,
+    level: Int = 1,
+    numberOfLevels: Int = 4,
+): FakeWifiEventModel.CarrierMerged =
+    FakeWifiEventModel.CarrierMerged(
+        subscriptionId = subId,
+        level = level,
+        numberOfLevels = numberOfLevels,
+    )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
new file mode 100644
index 0000000..ea90150
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
@@ -0,0 +1,251 @@
+/*
+ * 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.mobile.data.repository.prod
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
+
+    private lateinit var underTest: CarrierMergedConnectionRepository
+
+    private lateinit var wifiRepository: FakeWifiRepository
+    @Mock private lateinit var logger: TableLogBuffer
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        wifiRepository = FakeWifiRepository()
+
+        underTest =
+            CarrierMergedConnectionRepository(
+                SUB_ID,
+                logger,
+                NetworkNameModel.Default("name"),
+                testScope.backgroundScope,
+                wifiRepository,
+            )
+    }
+
+    @Test
+    fun connectionInfo_inactiveWifi_isDefault() =
+        testScope.runTest {
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+
+            assertThat(latest).isEqualTo(MobileConnectionModel())
+
+            job.cancel()
+        }
+
+    @Test
+    fun connectionInfo_activeWifi_isDefault() =
+        testScope.runTest {
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = NET_ID, level = 1))
+
+            assertThat(latest).isEqualTo(MobileConnectionModel())
+
+            job.cancel()
+        }
+
+    @Test
+    fun connectionInfo_carrierMergedWifi_isValidAndFieldsComeFromWifiNetwork() =
+        testScope.runTest {
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+            wifiRepository.setIsWifiEnabled(true)
+            wifiRepository.setIsWifiDefault(true)
+
+            wifiRepository.setWifiNetwork(
+                WifiNetworkModel.CarrierMerged(
+                    networkId = NET_ID,
+                    subscriptionId = SUB_ID,
+                    level = 3,
+                )
+            )
+
+            val expected =
+                MobileConnectionModel(
+                    primaryLevel = 3,
+                    cdmaLevel = 3,
+                    dataConnectionState = DataConnectionState.Connected,
+                    dataActivityDirection =
+                        DataActivityModel(
+                            hasActivityIn = false,
+                            hasActivityOut = false,
+                        ),
+                    resolvedNetworkType = ResolvedNetworkType.CarrierMergedNetworkType,
+                    isRoaming = false,
+                    isEmergencyOnly = false,
+                    operatorAlphaShort = null,
+                    isInService = true,
+                    isGsm = false,
+                    carrierNetworkChangeActive = false,
+                )
+            assertThat(latest).isEqualTo(expected)
+
+            job.cancel()
+        }
+
+    @Test
+    fun connectionInfo_carrierMergedWifi_wrongSubId_isDefault() =
+        testScope.runTest {
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+            wifiRepository.setWifiNetwork(
+                WifiNetworkModel.CarrierMerged(
+                    networkId = NET_ID,
+                    subscriptionId = SUB_ID + 10,
+                    level = 3,
+                )
+            )
+
+            assertThat(latest).isEqualTo(MobileConnectionModel())
+            assertThat(latest!!.primaryLevel).isNotEqualTo(3)
+            assertThat(latest!!.resolvedNetworkType)
+                .isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
+
+            job.cancel()
+        }
+
+    // This scenario likely isn't possible, but write a test for it anyway
+    @Test
+    fun connectionInfo_carrierMergedButNotEnabled_isDefault() =
+        testScope.runTest {
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+            wifiRepository.setWifiNetwork(
+                WifiNetworkModel.CarrierMerged(
+                    networkId = NET_ID,
+                    subscriptionId = SUB_ID,
+                    level = 3,
+                )
+            )
+            wifiRepository.setIsWifiEnabled(false)
+
+            assertThat(latest).isEqualTo(MobileConnectionModel())
+
+            job.cancel()
+        }
+
+    // This scenario likely isn't possible, but write a test for it anyway
+    @Test
+    fun connectionInfo_carrierMergedButWifiNotDefault_isDefault() =
+        testScope.runTest {
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+            wifiRepository.setWifiNetwork(
+                WifiNetworkModel.CarrierMerged(
+                    networkId = NET_ID,
+                    subscriptionId = SUB_ID,
+                    level = 3,
+                )
+            )
+            wifiRepository.setIsWifiDefault(false)
+
+            assertThat(latest).isEqualTo(MobileConnectionModel())
+
+            job.cancel()
+        }
+
+    @Test
+    fun numberOfLevels_comesFromCarrierMerged() =
+        testScope.runTest {
+            var latest: Int? = null
+            val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
+
+            wifiRepository.setWifiNetwork(
+                WifiNetworkModel.CarrierMerged(
+                    networkId = NET_ID,
+                    subscriptionId = SUB_ID,
+                    level = 1,
+                    numberOfLevels = 6,
+                )
+            )
+
+            assertThat(latest).isEqualTo(6)
+
+            job.cancel()
+        }
+
+    @Test
+    fun dataEnabled_matchesWifiEnabled() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
+
+            wifiRepository.setIsWifiEnabled(true)
+            assertThat(latest).isTrue()
+
+            wifiRepository.setIsWifiEnabled(false)
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun cdmaRoaming_alwaysFalse() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job = underTest.cdmaRoaming.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    private companion object {
+        const val SUB_ID = 123
+        const val NET_ID = 456
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
new file mode 100644
index 0000000..c02a4df
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2023 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.mobile.data.repository.prod
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+/**
+ * This repo acts as a dispatcher to either the `typical` or `carrier merged` versions of the
+ * repository interface it's switching on. These tests just need to verify that the entire interface
+ * properly switches over when the value of `isCarrierMerged` changes.
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class FullMobileConnectionRepositoryTest : SysuiTestCase() {
+    private lateinit var underTest: FullMobileConnectionRepository
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+    private val mobileMappings = FakeMobileMappingsProxy()
+    private val tableLogBuffer = mock<TableLogBuffer>()
+    private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>()
+    private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>()
+
+    private lateinit var connectionsRepo: FakeMobileConnectionsRepository
+    private val globalMobileDataSettingChangedEvent: Flow<Unit>
+        get() = connectionsRepo.globalMobileDataSettingChangedEvent
+
+    private lateinit var mobileRepo: FakeMobileConnectionRepository
+    private lateinit var carrierMergedRepo: FakeMobileConnectionRepository
+
+    @Before
+    fun setUp() {
+        connectionsRepo = FakeMobileConnectionsRepository(mobileMappings, tableLogBuffer)
+
+        mobileRepo = FakeMobileConnectionRepository(SUB_ID, tableLogBuffer)
+        carrierMergedRepo = FakeMobileConnectionRepository(SUB_ID, tableLogBuffer)
+
+        whenever(
+                mobileFactory.build(
+                    eq(SUB_ID),
+                    any(),
+                    eq(DEFAULT_NAME),
+                    eq(SEP),
+                    eq(globalMobileDataSettingChangedEvent),
+                )
+            )
+            .thenReturn(mobileRepo)
+        whenever(carrierMergedFactory.build(eq(SUB_ID), any(), eq(DEFAULT_NAME)))
+            .thenReturn(carrierMergedRepo)
+    }
+
+    @Test
+    fun startingIsCarrierMerged_usesCarrierMergedInitially() =
+        testScope.runTest {
+            val carrierMergedConnectionInfo =
+                MobileConnectionModel(
+                    operatorAlphaShort = "Carrier Merged Operator",
+                )
+            carrierMergedRepo.setConnectionInfo(carrierMergedConnectionInfo)
+
+            initializeRepo(startingIsCarrierMerged = true)
+
+            assertThat(underTest.activeRepo.value).isEqualTo(carrierMergedRepo)
+            assertThat(underTest.connectionInfo.value).isEqualTo(carrierMergedConnectionInfo)
+            verify(mobileFactory, never())
+                .build(
+                    SUB_ID,
+                    tableLogBuffer,
+                    DEFAULT_NAME,
+                    SEP,
+                    globalMobileDataSettingChangedEvent
+                )
+        }
+
+    @Test
+    fun startingNotCarrierMerged_usesTypicalInitially() =
+        testScope.runTest {
+            val mobileConnectionInfo =
+                MobileConnectionModel(
+                    operatorAlphaShort = "Typical Operator",
+                )
+            mobileRepo.setConnectionInfo(mobileConnectionInfo)
+
+            initializeRepo(startingIsCarrierMerged = false)
+
+            assertThat(underTest.activeRepo.value).isEqualTo(mobileRepo)
+            assertThat(underTest.connectionInfo.value).isEqualTo(mobileConnectionInfo)
+            verify(carrierMergedFactory, never()).build(SUB_ID, tableLogBuffer, DEFAULT_NAME)
+        }
+
+    @Test
+    fun activeRepo_matchesIsCarrierMerged() =
+        testScope.runTest {
+            initializeRepo(startingIsCarrierMerged = false)
+            var latest: MobileConnectionRepository? = null
+            val job = underTest.activeRepo.onEach { latest = it }.launchIn(this)
+
+            underTest.setIsCarrierMerged(true)
+
+            assertThat(latest).isEqualTo(carrierMergedRepo)
+
+            underTest.setIsCarrierMerged(false)
+
+            assertThat(latest).isEqualTo(mobileRepo)
+
+            underTest.setIsCarrierMerged(true)
+
+            assertThat(latest).isEqualTo(carrierMergedRepo)
+
+            job.cancel()
+        }
+
+    @Test
+    fun connectionInfo_getsUpdatesFromRepo_carrierMerged() =
+        testScope.runTest {
+            initializeRepo(startingIsCarrierMerged = false)
+
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+            underTest.setIsCarrierMerged(true)
+
+            val info1 =
+                MobileConnectionModel(
+                    operatorAlphaShort = "Carrier Merged Operator",
+                    primaryLevel = 1,
+                )
+            carrierMergedRepo.setConnectionInfo(info1)
+
+            assertThat(latest).isEqualTo(info1)
+
+            val info2 =
+                MobileConnectionModel(
+                    operatorAlphaShort = "Carrier Merged Operator #2",
+                    primaryLevel = 2,
+                )
+            carrierMergedRepo.setConnectionInfo(info2)
+
+            assertThat(latest).isEqualTo(info2)
+
+            val info3 =
+                MobileConnectionModel(
+                    operatorAlphaShort = "Carrier Merged Operator #3",
+                    primaryLevel = 3,
+                )
+            carrierMergedRepo.setConnectionInfo(info3)
+
+            assertThat(latest).isEqualTo(info3)
+
+            job.cancel()
+        }
+
+    @Test
+    fun connectionInfo_getsUpdatesFromRepo_mobile() =
+        testScope.runTest {
+            initializeRepo(startingIsCarrierMerged = false)
+
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+            underTest.setIsCarrierMerged(false)
+
+            val info1 =
+                MobileConnectionModel(
+                    operatorAlphaShort = "Typical Merged Operator",
+                    primaryLevel = 1,
+                )
+            mobileRepo.setConnectionInfo(info1)
+
+            assertThat(latest).isEqualTo(info1)
+
+            val info2 =
+                MobileConnectionModel(
+                    operatorAlphaShort = "Typical Merged Operator #2",
+                    primaryLevel = 2,
+                )
+            mobileRepo.setConnectionInfo(info2)
+
+            assertThat(latest).isEqualTo(info2)
+
+            val info3 =
+                MobileConnectionModel(
+                    operatorAlphaShort = "Typical Merged Operator #3",
+                    primaryLevel = 3,
+                )
+            mobileRepo.setConnectionInfo(info3)
+
+            assertThat(latest).isEqualTo(info3)
+
+            job.cancel()
+        }
+
+    @Test
+    fun connectionInfo_updatesWhenCarrierMergedUpdates() =
+        testScope.runTest {
+            initializeRepo(startingIsCarrierMerged = false)
+
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+            val carrierMergedInfo =
+                MobileConnectionModel(
+                    operatorAlphaShort = "Carrier Merged Operator",
+                    primaryLevel = 4,
+                )
+            carrierMergedRepo.setConnectionInfo(carrierMergedInfo)
+
+            val mobileInfo =
+                MobileConnectionModel(
+                    operatorAlphaShort = "Typical Operator",
+                    primaryLevel = 2,
+                )
+            mobileRepo.setConnectionInfo(mobileInfo)
+
+            // Start with the mobile info
+            assertThat(latest).isEqualTo(mobileInfo)
+
+            // WHEN isCarrierMerged is set to true
+            underTest.setIsCarrierMerged(true)
+
+            // THEN the carrier merged info is used
+            assertThat(latest).isEqualTo(carrierMergedInfo)
+
+            val newCarrierMergedInfo =
+                MobileConnectionModel(
+                    operatorAlphaShort = "New CM Operator",
+                    primaryLevel = 0,
+                )
+            carrierMergedRepo.setConnectionInfo(newCarrierMergedInfo)
+
+            assertThat(latest).isEqualTo(newCarrierMergedInfo)
+
+            // WHEN isCarrierMerged is set to false
+            underTest.setIsCarrierMerged(false)
+
+            // THEN the typical info is used
+            assertThat(latest).isEqualTo(mobileInfo)
+
+            val newMobileInfo =
+                MobileConnectionModel(
+                    operatorAlphaShort = "New Mobile Operator",
+                    primaryLevel = 3,
+                )
+            mobileRepo.setConnectionInfo(newMobileInfo)
+
+            assertThat(latest).isEqualTo(newMobileInfo)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `factory - reuses log buffers for same connection`() =
+        testScope.runTest {
+            val realLoggerFactory = TableLogBufferFactory(mock(), FakeSystemClock())
+
+            val factory =
+                FullMobileConnectionRepository.Factory(
+                    scope = testScope.backgroundScope,
+                    realLoggerFactory,
+                    mobileFactory,
+                    carrierMergedFactory,
+                )
+
+            // Create two connections for the same subId. Similar to if the connection appeared
+            // and disappeared from the connectionFactory's perspective
+            val connection1 =
+                factory.build(
+                    SUB_ID,
+                    startingIsCarrierMerged = false,
+                    DEFAULT_NAME,
+                    SEP,
+                    globalMobileDataSettingChangedEvent,
+                )
+
+            val connection1Repeat =
+                factory.build(
+                    SUB_ID,
+                    startingIsCarrierMerged = false,
+                    DEFAULT_NAME,
+                    SEP,
+                    globalMobileDataSettingChangedEvent,
+                )
+
+            assertThat(connection1.tableLogBuffer)
+                .isSameInstanceAs(connection1Repeat.tableLogBuffer)
+        }
+
+    @Test
+    fun `factory - reuses log buffers for same sub ID even if carrier merged`() =
+        testScope.runTest {
+            val realLoggerFactory = TableLogBufferFactory(mock(), FakeSystemClock())
+
+            val factory =
+                FullMobileConnectionRepository.Factory(
+                    scope = testScope.backgroundScope,
+                    realLoggerFactory,
+                    mobileFactory,
+                    carrierMergedFactory,
+                )
+
+            val connection1 =
+                factory.build(
+                    SUB_ID,
+                    startingIsCarrierMerged = false,
+                    DEFAULT_NAME,
+                    SEP,
+                    globalMobileDataSettingChangedEvent,
+                )
+
+            // WHEN a connection with the same sub ID but carrierMerged = true is created
+            val connection1Repeat =
+                factory.build(
+                    SUB_ID,
+                    startingIsCarrierMerged = true,
+                    DEFAULT_NAME,
+                    SEP,
+                    globalMobileDataSettingChangedEvent,
+                )
+
+            // THEN the same table is re-used
+            assertThat(connection1.tableLogBuffer)
+                .isSameInstanceAs(connection1Repeat.tableLogBuffer)
+        }
+
+    // TODO(b/238425913): Verify that the logging switches correctly (once the carrier merged repo
+    //   implements logging).
+
+    private fun initializeRepo(startingIsCarrierMerged: Boolean) {
+        underTest =
+            FullMobileConnectionRepository(
+                SUB_ID,
+                startingIsCarrierMerged,
+                tableLogBuffer,
+                DEFAULT_NAME,
+                SEP,
+                globalMobileDataSettingChangedEvent,
+                testScope.backgroundScope,
+                mobileFactory,
+                carrierMergedFactory,
+            )
+    }
+
+    private companion object {
+        const val SUB_ID = 42
+        private val DEFAULT_NAME = NetworkNameModel.Default("default name")
+        private const val SEP = "-"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 0958970..813b0ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -37,17 +37,18 @@
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
-import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
-import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -74,6 +75,9 @@
     private lateinit var underTest: MobileConnectionsRepositoryImpl
 
     private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
+    private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
+    private lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory
+    private lateinit var wifiRepository: FakeWifiRepository
     @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var subscriptionManager: SubscriptionManager
     @Mock private lateinit var telephonyManager: TelephonyManager
@@ -100,6 +104,8 @@
             mock<TableLogBuffer>()
         }
 
+        wifiRepository = FakeWifiRepository()
+
         connectionFactory =
             MobileConnectionRepositoryImpl.Factory(
                 fakeBroadcastDispatcher,
@@ -110,7 +116,18 @@
                 logger = logger,
                 mobileMappingsProxy = mobileMappings,
                 scope = scope,
+            )
+        carrierMergedFactory =
+            CarrierMergedConnectionRepository.Factory(
+                scope,
+                wifiRepository,
+            )
+        fullConnectionFactory =
+            FullMobileConnectionRepository.Factory(
+                scope = scope,
                 logFactory = logBufferFactory,
+                mobileRepoFactory = connectionFactory,
+                carrierMergedRepoFactory = carrierMergedFactory,
             )
 
         underTest =
@@ -125,7 +142,8 @@
                 context,
                 IMMEDIATE,
                 scope,
-                connectionFactory,
+                wifiRepository,
+                fullConnectionFactory,
             )
     }
 
@@ -180,6 +198,40 @@
         }
 
     @Test
+    fun testSubscriptions_carrierMergedOnly_listHasCarrierMerged() =
+        runBlocking(IMMEDIATE) {
+            var latest: List<SubscriptionModel>? = null
+
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_CM))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            assertThat(latest).isEqualTo(listOf(MODEL_CM))
+
+            job.cancel()
+        }
+
+    @Test
+    fun testSubscriptions_carrierMergedAndOther_listHasBothWithCarrierMergedLast() =
+        runBlocking(IMMEDIATE) {
+            var latest: List<SubscriptionModel>? = null
+
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2, MODEL_CM))
+
+            job.cancel()
+        }
+
+    @Test
     fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
         runBlocking(IMMEDIATE) {
             assertThat(underTest.activeMobileDataSubscriptionId.value)
@@ -219,6 +271,96 @@
         }
 
     @Test
+    fun testConnectionRepository_carrierMergedSubId_isCached() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.subscriptions.launchIn(this)
+
+            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_CM))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            val repo1 = underTest.getRepoForSubId(SUB_CM_ID)
+            val repo2 = underTest.getRepoForSubId(SUB_CM_ID)
+
+            assertThat(repo1).isSameInstanceAs(repo2)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testConnectionRepository_carrierMergedAndMobileSubs_usesCorrectRepos() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.subscriptions.launchIn(this)
+
+            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_CM))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
+            val mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
+            assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
+            assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun testSubscriptions_subNoLongerCarrierMerged_repoUpdates() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.subscriptions.launchIn(this)
+
+            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_CM))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
+            var mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
+            assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
+            assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
+
+            // WHEN the wifi network updates to be not carrier merged
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 4, level = 1))
+
+            // THEN the repos update
+            val noLongerCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
+            mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
+            assertThat(noLongerCarrierMergedRepo.getIsCarrierMerged()).isFalse()
+            assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun testSubscriptions_subBecomesCarrierMerged_repoUpdates() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.subscriptions.launchIn(this)
+
+            wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_CM))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            val notYetCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
+            var mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
+            assertThat(notYetCarrierMergedRepo.getIsCarrierMerged()).isFalse()
+            assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
+
+            // WHEN the wifi network updates to be carrier merged
+            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+
+            // THEN the repos update
+            val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
+            mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
+            assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
+            assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
     fun testConnectionCache_clearsInvalidSubscriptions() =
         runBlocking(IMMEDIATE) {
             val job = underTest.subscriptions.launchIn(this)
@@ -244,6 +386,34 @@
             job.cancel()
         }
 
+    @Test
+    fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.subscriptions.launchIn(this)
+
+            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // Get repos to trigger caching
+            val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+            val repo2 = underTest.getRepoForSubId(SUB_2_ID)
+            val repoCarrierMerged = underTest.getRepoForSubId(SUB_CM_ID)
+
+            assertThat(underTest.getSubIdRepoCache())
+                .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2, SUB_CM_ID, repoCarrierMerged)
+
+            // SUB_2 and SUB_CM disappear
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
+
+            job.cancel()
+        }
+
     /** Regression test for b/261706421 */
     @Test
     fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() =
@@ -295,13 +465,13 @@
             underTest.getRepoForSubId(SUB_1_ID)
             verify(logBufferFactory)
                 .getOrCreate(
-                    eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_1_ID)),
+                    eq(tableBufferLogName(SUB_1_ID)),
                     anyInt(),
                 )
             underTest.getRepoForSubId(SUB_2_ID)
             verify(logBufferFactory)
                 .getOrCreate(
-                    eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_2_ID)),
+                    eq(tableBufferLogName(SUB_2_ID)),
                     anyInt(),
                 )
 
@@ -309,46 +479,6 @@
         }
 
     @Test
-    fun `connection repository factory - reuses log buffers for same connection`() =
-        runBlocking(IMMEDIATE) {
-            val realLoggerFactory = TableLogBufferFactory(mock(), FakeSystemClock())
-
-            connectionFactory =
-                MobileConnectionRepositoryImpl.Factory(
-                    fakeBroadcastDispatcher,
-                    context = context,
-                    telephonyManager = telephonyManager,
-                    bgDispatcher = IMMEDIATE,
-                    globalSettings = globalSettings,
-                    logger = logger,
-                    mobileMappingsProxy = mobileMappings,
-                    scope = scope,
-                    logFactory = realLoggerFactory,
-                )
-
-            // Create two connections for the same subId. Similar to if the connection appeared
-            // and disappeared from the connectionFactory's perspective
-            val connection1 =
-                connectionFactory.build(
-                    1,
-                    NetworkNameModel.Default("default_name"),
-                    "-",
-                    underTest.globalMobileDataSettingChangedEvent,
-                )
-
-            val connection1_repeat =
-                connectionFactory.build(
-                    1,
-                    NetworkNameModel.Default("default_name"),
-                    "-",
-                    underTest.globalMobileDataSettingChangedEvent,
-                )
-
-            assertThat(connection1.tableLogBuffer)
-                .isSameInstanceAs(connection1_repeat.tableLogBuffer)
-        }
-
-    @Test
     fun mobileConnectivity_default() {
         assertThat(underTest.defaultMobileNetworkConnectivity.value)
             .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
@@ -461,7 +591,8 @@
                     context,
                     IMMEDIATE,
                     scope,
-                    connectionFactory,
+                    wifiRepository,
+                    fullConnectionFactory,
                 )
 
             var latest: MobileMappings.Config? = null
@@ -571,5 +702,16 @@
 
         private const val NET_ID = 123
         private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }
+
+        private const val SUB_CM_ID = 5
+        private val SUB_CM =
+            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_CM_ID) }
+        private val MODEL_CM = SubscriptionModel(subscriptionId = SUB_CM_ID)
+        private val WIFI_NETWORK_CM =
+            WifiNetworkModel.CarrierMerged(
+                networkId = 3,
+                subscriptionId = SUB_CM_ID,
+                level = 1,
+            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 61e13b8..e6be7f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.CarrierMergedNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
@@ -271,6 +272,23 @@
         }
 
     @Test
+    fun iconGroup_carrierMerged_usesOverride() =
+        runBlocking(IMMEDIATE) {
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(
+                    resolvedNetworkType = CarrierMergedNetworkType,
+                ),
+            )
+
+            var latest: MobileIconGroup? = null
+            val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(CarrierMergedNetworkType.iconGroupOverride)
+
+            job.cancel()
+        }
+
+    @Test
     fun alwaysShowDataRatIcon_matchesParent() =
         runBlocking(IMMEDIATE) {
             var latest: Boolean? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
index 30ac8d4..824cebd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
@@ -16,11 +16,12 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.data.model
 
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.table.TableRowLogger
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MIN_VALID_LEVEL
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Companion.MIN_VALID_LEVEL
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 
@@ -44,9 +45,53 @@
         WifiNetworkModel.Active(NETWORK_ID, level = MAX_VALID_LEVEL + 1)
     }
 
+    @Test(expected = IllegalArgumentException::class)
+    fun carrierMerged_invalidSubId_exceptionThrown() {
+        WifiNetworkModel.CarrierMerged(NETWORK_ID, INVALID_SUBSCRIPTION_ID, 1)
+    }
+
     // Non-exhaustive logDiffs test -- just want to make sure the logging logic isn't totally broken
 
     @Test
+    fun logDiffs_carrierMergedToInactive_resetsAllFields() {
+        val logger = TestLogger()
+        val prevVal =
+            WifiNetworkModel.CarrierMerged(
+                networkId = 5,
+                subscriptionId = 3,
+                level = 1,
+            )
+
+        WifiNetworkModel.Inactive.logDiffs(prevVal, logger)
+
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_INACTIVE))
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString()))
+        assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
+        assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
+        assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
+    }
+
+    @Test
+    fun logDiffs_inactiveToCarrierMerged_logsAllFields() {
+        val logger = TestLogger()
+        val carrierMerged =
+            WifiNetworkModel.CarrierMerged(
+                networkId = 6,
+                subscriptionId = 3,
+                level = 2,
+            )
+
+        carrierMerged.logDiffs(prevVal = WifiNetworkModel.Inactive, logger)
+
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED))
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "6"))
+        assertThat(logger.changes).contains(Pair(COL_SUB_ID, "3"))
+        assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
+        assertThat(logger.changes).contains(Pair(COL_LEVEL, "2"))
+        assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
+    }
+
+    @Test
     fun logDiffs_inactiveToActive_logsAllActiveFields() {
         val logger = TestLogger()
         val activeNetwork =
@@ -95,8 +140,14 @@
                 level = 3,
                 ssid = "Test SSID"
             )
+        val prevVal =
+            WifiNetworkModel.CarrierMerged(
+                networkId = 5,
+                subscriptionId = 3,
+                level = 1,
+            )
 
-        activeNetwork.logDiffs(prevVal = WifiNetworkModel.CarrierMerged, logger)
+        activeNetwork.logDiffs(prevVal, logger)
 
         assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE))
         assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5"))
@@ -105,7 +156,7 @@
         assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
     }
     @Test
-    fun logDiffs_activeToCarrierMerged_resetsAllActiveFields() {
+    fun logDiffs_activeToCarrierMerged_logsAllFields() {
         val logger = TestLogger()
         val activeNetwork =
             WifiNetworkModel.Active(
@@ -114,13 +165,20 @@
                 level = 3,
                 ssid = "Test SSID"
             )
+        val carrierMerged =
+            WifiNetworkModel.CarrierMerged(
+                networkId = 6,
+                subscriptionId = 3,
+                level = 2,
+            )
 
-        WifiNetworkModel.CarrierMerged.logDiffs(prevVal = activeNetwork, logger)
+        carrierMerged.logDiffs(prevVal = activeNetwork, logger)
 
         assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED))
-        assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString()))
-        assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
-        assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
+        assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "6"))
+        assertThat(logger.changes).contains(Pair(COL_SUB_ID, "3"))
+        assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
+        assertThat(logger.changes).contains(Pair(COL_LEVEL, "2"))
         assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 8f07615..87ce8fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -26,6 +26,7 @@
 import android.net.wifi.WifiInfo
 import android.net.wifi.WifiManager
 import android.net.wifi.WifiManager.TrafficStateCallback
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -340,7 +341,6 @@
             .launchIn(this)
 
         val wifiInfo = mock<WifiInfo>().apply {
-            whenever(this.ssid).thenReturn(SSID)
             whenever(this.isPrimary).thenReturn(true)
             whenever(this.isCarrierMerged).thenReturn(true)
         }
@@ -353,6 +353,67 @@
     }
 
     @Test
+    fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() =
+        runBlocking(IMMEDIATE) {
+            var latest: WifiNetworkModel? = null
+            val job = underTest
+                .wifiNetwork
+                .onEach { latest = it }
+                .launchIn(this)
+
+            val wifiInfo = mock<WifiInfo>().apply {
+                whenever(this.isPrimary).thenReturn(true)
+                whenever(this.isCarrierMerged).thenReturn(true)
+                whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
+            }
+
+            getNetworkCallback().onCapabilitiesChanged(
+                NETWORK,
+                createWifiNetworkCapabilities(wifiInfo),
+            )
+
+            assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java)
+
+            job.cancel()
+        }
+
+    @Test
+    fun wifiNetwork_isCarrierMerged_getsCorrectValues() =
+        runBlocking(IMMEDIATE) {
+            var latest: WifiNetworkModel? = null
+            val job = underTest
+                .wifiNetwork
+                .onEach { latest = it }
+                .launchIn(this)
+
+            val rssi = -57
+            val wifiInfo = mock<WifiInfo>().apply {
+                whenever(this.isPrimary).thenReturn(true)
+                whenever(this.isCarrierMerged).thenReturn(true)
+                whenever(this.rssi).thenReturn(rssi)
+                whenever(this.subscriptionId).thenReturn(567)
+            }
+
+            whenever(wifiManager.calculateSignalLevel(rssi)).thenReturn(2)
+            whenever(wifiManager.maxSignalLevel).thenReturn(5)
+
+            getNetworkCallback().onCapabilitiesChanged(
+                NETWORK,
+                createWifiNetworkCapabilities(wifiInfo),
+            )
+
+            assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+            val latestCarrierMerged = latest as WifiNetworkModel.CarrierMerged
+            assertThat(latestCarrierMerged.networkId).isEqualTo(NETWORK_ID)
+            assertThat(latestCarrierMerged.subscriptionId).isEqualTo(567)
+            assertThat(latestCarrierMerged.level).isEqualTo(2)
+            // numberOfLevels = maxSignalLevel + 1
+            assertThat(latestCarrierMerged.numberOfLevels).isEqualTo(6)
+
+            job.cancel()
+        }
+
+    @Test
     fun wifiNetwork_notValidated_networkNotValidated() = runBlocking(IMMEDIATE) {
         var latest: WifiNetworkModel? = null
         val job = underTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
index 01d59f9..089a170 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
@@ -84,7 +84,9 @@
 
     @Test
     fun ssid_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
-        wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
+        wifiRepository.setWifiNetwork(
+            WifiNetworkModel.CarrierMerged(networkId = 1, subscriptionId = 2, level = 1)
+        )
 
         var latest: String? = "default"
         val job = underTest
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 726e813..b932837 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
@@ -206,7 +206,8 @@
                 // Enabled = false => no networks shown
                 TestCase(
                     enabled = false,
-                    network = WifiNetworkModel.CarrierMerged,
+                    network =
+                        WifiNetworkModel.CarrierMerged(NETWORK_ID, subscriptionId = 1, level = 1),
                     expected = null,
                 ),
                 TestCase(
@@ -228,7 +229,8 @@
                 // forceHidden = true => no networks shown
                 TestCase(
                     forceHidden = true,
-                    network = WifiNetworkModel.CarrierMerged,
+                    network =
+                        WifiNetworkModel.CarrierMerged(NETWORK_ID, subscriptionId = 1, level = 1),
                     expected = null,
                 ),
                 TestCase(
@@ -369,7 +371,8 @@
 
                 // network = CarrierMerged => not shown
                 TestCase(
-                    network = WifiNetworkModel.CarrierMerged,
+                    network =
+                        WifiNetworkModel.CarrierMerged(NETWORK_ID, subscriptionId = 1, level = 1),
                     expected = null,
                 ),
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 010189a..b28ab7a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3248,7 +3248,7 @@
         if (targetName.equals(MAGNIFICATION_CONTROLLER_NAME)) {
             final boolean enabled =
                     !getMagnificationController().getFullScreenMagnificationController()
-                            .isMagnifying(displayId);
+                            .isActivated(displayId);
             logAccessibilityShortcutActivated(mContext, MAGNIFICATION_COMPONENT_NAME, shortcutType,
                     enabled);
             sendAccessibilityButtonToInputFilter(displayId);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index 6cfbfb8..de7184c 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -126,8 +126,6 @@
         private boolean mUnregisterPending;
         private boolean mDeleteAfterUnregister;
 
-        private boolean mForceShowMagnifiableBounds;
-
         private final int mDisplayId;
 
         private int mIdOfLastServiceToMagnify = INVALID_SERVICE_ID;
@@ -213,8 +211,8 @@
             return mRegistered;
         }
 
-        boolean isMagnifying() {
-            return mCurrentMagnificationSpec.scale > 1.0f;
+        boolean isActivated() {
+            return mMagnificationActivated;
         }
 
         float getScale() {
@@ -370,12 +368,6 @@
         @GuardedBy("mLock")
         void onMagnificationChangedLocked() {
             final float scale = getScale();
-            final boolean lastMagnificationActivated = mMagnificationActivated;
-            mMagnificationActivated = scale > 1.0f;
-            if (mMagnificationActivated != lastMagnificationActivated) {
-                mMagnificationInfoChangedCallback.onFullScreenMagnificationActivationState(
-                        mDisplayId, mMagnificationActivated);
-            }
 
             final MagnificationConfig config = new MagnificationConfig.Builder()
                     .setMode(MAGNIFICATION_MODE_FULLSCREEN)
@@ -384,7 +376,7 @@
                     .setCenterY(getCenterY()).build();
             mMagnificationInfoChangedCallback.onFullScreenMagnificationChanged(mDisplayId,
                     mMagnificationRegion, config);
-            if (mUnregisterPending && !isMagnifying()) {
+            if (mUnregisterPending && !isActivated()) {
                 unregister(mDeleteAfterUnregister);
             }
         }
@@ -476,21 +468,22 @@
         }
 
         @GuardedBy("mLock")
-        void setForceShowMagnifiableBounds(boolean show) {
-            if (mRegistered) {
-                mForceShowMagnifiableBounds = show;
-                if (traceEnabled()) {
-                    logTrace("setForceShowMagnifiableBounds",
-                            "displayID=" + mDisplayId + ";show=" + show);
-                }
-                mControllerCtx.getWindowManager().setForceShowMagnifiableBounds(
-                        mDisplayId, show);
+        private boolean setActivated(boolean activated) {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "setActivated(activated = " + activated + ")");
             }
-        }
 
-        @GuardedBy("mLock")
-        boolean isForceShowMagnifiableBounds() {
-            return mRegistered && mForceShowMagnifiableBounds;
+            final boolean changed = (mMagnificationActivated != activated);
+
+            if (changed) {
+                mMagnificationActivated = activated;
+                mMagnificationInfoChangedCallback.onFullScreenMagnificationActivationState(
+                        mDisplayId, mMagnificationActivated);
+                mControllerCtx.getWindowManager().setForceShowMagnifiableBounds(
+                        mDisplayId, activated);
+            }
+
+            return changed;
         }
 
         @GuardedBy("mLock")
@@ -504,13 +497,13 @@
                 return false;
             }
             final MagnificationSpec spec = mCurrentMagnificationSpec;
-            final boolean changed = !spec.isNop();
+            final boolean changed = isActivated();
+            setActivated(false);
             if (changed) {
                 spec.clear();
                 onMagnificationChangedLocked();
             }
             mIdOfLastServiceToMagnify = INVALID_SERVICE_ID;
-            mForceShowMagnifiableBounds = false;
             sendSpecToAnimation(spec, animationCallback);
             return changed;
         }
@@ -554,9 +547,10 @@
                                 + ", centerY = " + centerY + ", endCallback = "
                                 + animationCallback + ", id = " + id + ")");
             }
-            final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
+            boolean changed = setActivated(true);
+            changed |= updateMagnificationSpecLocked(scale, centerX, centerY);
             sendSpecToAnimation(mCurrentMagnificationSpec, animationCallback);
-            if (isMagnifying() && (id != INVALID_SERVICE_ID)) {
+            if (isActivated() && (id != INVALID_SERVICE_ID)) {
                 mIdOfLastServiceToMagnify = id;
                 mMagnificationInfoChangedCallback.onRequestMagnificationSpec(mDisplayId,
                         mIdOfLastServiceToMagnify);
@@ -779,7 +773,7 @@
             if (display == null) {
                 return;
             }
-            if (!display.isMagnifying()) {
+            if (!display.isActivated()) {
                 return;
             }
             final Rect magnifiedRegionBounds = mTempRect;
@@ -831,16 +825,16 @@
 
     /**
      * @param displayId The logical display id.
-     * @return {@code true} if magnification is active, e.g. the scale
-     *         is > 1, {@code false} otherwise
+     * @return {@code true} if magnification is activated,
+     *         {@code false} otherwise
      */
-    public boolean isMagnifying(int displayId) {
+    public boolean isActivated(int displayId) {
         synchronized (mLock) {
             final DisplayMagnification display = mDisplays.get(displayId);
             if (display == null) {
                 return false;
             }
-            return display.isMagnifying();
+            return display.isActivated();
         }
     }
 
@@ -1166,6 +1160,9 @@
      */
     public void persistScale(int displayId) {
         final float scale = getScale(Display.DEFAULT_DISPLAY);
+        if (scale < 2.0f) {
+            return;
+        }
         mScaleProvider.putScale(scale, displayId);
     }
 
@@ -1177,7 +1174,8 @@
      *         scale if none is available
      */
     public float getPersistedScale(int displayId) {
-        return mScaleProvider.getScale(displayId);
+        return MathUtils.constrain(mScaleProvider.getScale(displayId),
+                2.0f, MagnificationScaleProvider.MAX_SCALE);
     }
 
     /**
@@ -1198,12 +1196,12 @@
      *
      * @param displayId The logical display id.
      * @param animate whether the animate the transition
-     * @return whether was {@link #isMagnifying(int) magnifying}
+     * @return whether was {@link #isActivated(int)}  activated}
      */
     boolean resetIfNeeded(int displayId, boolean animate) {
         synchronized (mLock) {
             final DisplayMagnification display = mDisplays.get(displayId);
-            if (display == null || !display.isMagnifying()) {
+            if (display == null || !display.isActivated()) {
                 return false;
             }
             display.reset(animate);
@@ -1221,7 +1219,7 @@
     boolean resetIfNeeded(int displayId, int connectionId) {
         synchronized (mLock) {
             final DisplayMagnification display = mDisplays.get(displayId);
-            if (display == null || !display.isMagnifying()
+            if (display == null || !display.isActivated()
                     || connectionId != display.getIdOfLastServiceToMagnify()) {
                 return false;
             }
@@ -1230,16 +1228,6 @@
         }
     }
 
-    void setForceShowMagnifiableBounds(int displayId, boolean show) {
-        synchronized (mLock) {
-            final DisplayMagnification display = mDisplays.get(displayId);
-            if (display == null) {
-                return;
-            }
-            display.setForceShowMagnifiableBounds(show);
-        }
-    }
-
     /**
      * Notifies that the IME window visibility changed.
      *
@@ -1251,21 +1239,6 @@
         mMagnificationInfoChangedCallback.onImeWindowVisibilityChanged(displayId, shown);
     }
 
-    /**
-     * Returns {@code true} if the magnifiable regions of the display is forced to be shown.
-     *
-     * @param displayId The logical display id.
-     */
-    public boolean isForceShowMagnifiableBounds(int displayId) {
-        synchronized (mLock) {
-            final DisplayMagnification display = mDisplays.get(displayId);
-            if (display == null) {
-                return false;
-            }
-            return display.isForceShowMagnifiableBounds();
-        }
-    }
-
     private void onScreenTurnedOff() {
         final Message m = PooledLambda.obtainMessage(
                 FullScreenMagnificationController::resetAllIfNeeded, this, false);
@@ -1295,7 +1268,7 @@
             }
             return;
         }
-        if (!display.isMagnifying()) {
+        if (!display.isActivated()) {
             display.unregister(delete);
         } else {
             display.unregisterPending(delete);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index dc39b01..6bf37a1 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -122,7 +122,7 @@
     // The MIN_SCALE is different from MagnificationScaleProvider.MIN_SCALE due
     // to AccessibilityService.MagnificationController#setScale() has
     // different scale range
-    private static final float MIN_SCALE = 2.0f;
+    private static final float MIN_SCALE = 1.0f;
     private static final float MAX_SCALE = MagnificationScaleProvider.MAX_SCALE;
 
     @VisibleForTesting final FullScreenMagnificationController mFullScreenMagnificationController;
@@ -220,14 +220,19 @@
 
     @Override
     public void handleShortcutTriggered() {
-        boolean wasMagnifying = mFullScreenMagnificationController.resetIfNeeded(mDisplayId,
-                /* animate */ true);
-        if (wasMagnifying) {
+        final boolean isActivated = mFullScreenMagnificationController.isActivated(mDisplayId);
+
+        if (isActivated) {
+            zoomOff();
             clearAndTransitionToStateDetecting();
         } else {
-            mPromptController.showNotificationIfNeeded();
             mDetectingState.toggleShortcutTriggered();
         }
+
+        if (mDetectingState.isShortcutTriggered()) {
+            mPromptController.showNotificationIfNeeded();
+            zoomToScale(1.0f, Float.NaN, Float.NaN);
+        }
     }
 
     @Override
@@ -441,7 +446,12 @@
     final class ViewportDraggingState implements State {
 
         /** Whether to disable zoom after dragging ends */
-        boolean mZoomedInBeforeDrag;
+        @VisibleForTesting boolean mActivatedBeforeDrag;
+        /** Whether to restore scale after dragging ends */
+        private boolean mZoomedInTemporary;
+        /** The cached scale for recovering after dragging ends */
+        private float mScaleBeforeZoomedInTemporary;
+
         private boolean mLastMoveOutsideMagnifiedRegion;
 
         @Override
@@ -474,7 +484,13 @@
 
                 case ACTION_UP:
                 case ACTION_CANCEL: {
-                    if (!mZoomedInBeforeDrag) zoomOff();
+                    if (mActivatedBeforeDrag) {
+                        if (mZoomedInTemporary) {
+                            zoomToScale(mScaleBeforeZoomedInTemporary, event.getX(), event.getY());
+                        }
+                    } else {
+                        zoomOff();
+                    }
                     clear();
                     transitionTo(mDetectingState);
                 }
@@ -488,15 +504,27 @@
             }
         }
 
+        public void prepareForZoomInTemporary() {
+            mViewportDraggingState.mActivatedBeforeDrag =
+                    mFullScreenMagnificationController.isActivated(mDisplayId);
+
+            mViewportDraggingState.mZoomedInTemporary = true;
+            mViewportDraggingState.mScaleBeforeZoomedInTemporary =
+                    mFullScreenMagnificationController.getScale(mDisplayId);
+        }
+
         @Override
         public void clear() {
             mLastMoveOutsideMagnifiedRegion = false;
+
+            mZoomedInTemporary = false;
+            mScaleBeforeZoomedInTemporary = 1.0f;
         }
 
         @Override
         public String toString() {
             return "ViewportDraggingState{"
-                    + "mZoomedInBeforeDrag=" + mZoomedInBeforeDrag
+                    + "mActivatedBeforeDrag=" + mActivatedBeforeDrag
                     + ", mLastMoveOutsideMagnifiedRegion=" + mLastMoveOutsideMagnifiedRegion
                     + '}';
         }
@@ -625,10 +653,10 @@
                         transitionToDelegatingStateAndClear();
 
                     } else if (mDetectTripleTap
-                            // If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay
+                            // If activated, delay an ACTION_DOWN for mMultiTapMaxDelay
                             // to ensure reachability of
                             // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
-                            || mFullScreenMagnificationController.isMagnifying(mDisplayId)) {
+                            || isActivated()) {
 
                         afterMultiTapTimeoutTransitionToDelegatingState();
 
@@ -640,8 +668,7 @@
                 }
                 break;
                 case ACTION_POINTER_DOWN: {
-                    if (mFullScreenMagnificationController.isMagnifying(mDisplayId)
-                            && event.getPointerCount() == 2) {
+                    if (isActivated() && event.getPointerCount() == 2) {
                         storeSecondPointerDownLocation(event);
                         mHandler.sendEmptyMessageDelayed(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE,
                                 ViewConfiguration.getTapTimeout());
@@ -665,13 +692,13 @@
                         // (which is a rare combo to be used aside from magnification)
                         if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) {
                             transitionToViewportDraggingStateAndClear(event);
-                        } else if (isMagnifying() && event.getPointerCount() == 2) {
+                        } else if (isActivated() && event.getPointerCount() == 2) {
                             //Primary pointer is swiping, so transit to PanningScalingState
                             transitToPanningScalingStateAndClear();
                         } else {
                             transitionToDelegatingStateAndClear();
                         }
-                    } else if (isMagnifying() && secondPointerDownValid()
+                    } else if (isActivated() && secondPointerDownValid()
                             && distanceClosestPointerToPoint(
                             mSecondPointerDownLocation, /* move */ event) > mSwipeMinDistance) {
                         //Second pointer is swiping, so transit to PanningScalingState
@@ -734,7 +761,7 @@
 
             // Only log the triple tap event, use numTaps to filter.
             if (multitapTriggered && numTaps > 2) {
-                final boolean enabled = mFullScreenMagnificationController.isMagnifying(mDisplayId);
+                final boolean enabled = isActivated();
                 logMagnificationTripleTap(enabled);
             }
             return multitapTriggered;
@@ -862,24 +889,33 @@
             mSecondPointerDownLocation.set(Float.NaN, Float.NaN);
         }
 
+        /**
+         * This method could be triggered by both 2 cases.
+         *      1. direct three tap gesture
+         *      2. one tap while shortcut triggered (it counts as two taps).
+         */
         private void onTripleTap(MotionEvent up) {
             if (DEBUG_DETECTING) {
                 Slog.i(mLogTag, "onTripleTap(); delayed: "
                         + MotionEventInfo.toString(mDelayedEventQueue));
             }
-            clear();
 
-            // Toggle zoom
-            if (mFullScreenMagnificationController.isMagnifying(mDisplayId)) {
-                zoomOff();
-            } else {
+            // We put mShortcutTriggered into conditions.
+            // The reason is when the shortcut is triggered,
+            //   the magnifier is activated and keeps in scale 1.0,
+            //   and in this case, we still want to zoom on the magnifier.
+            if (!isActivated() || mShortcutTriggered) {
                 mPromptController.showNotificationIfNeeded();
                 zoomOn(up.getX(), up.getY());
+            } else {
+                zoomOff();
             }
+
+            clear();
         }
 
-        private boolean isMagnifying() {
-            return mFullScreenMagnificationController.isMagnifying(mDisplayId);
+        private boolean isActivated() {
+            return mFullScreenMagnificationController.isActivated(mDisplayId);
         }
 
         void transitionToViewportDraggingStateAndClear(MotionEvent down) {
@@ -887,14 +923,13 @@
             if (DEBUG_DETECTING) Slog.i(mLogTag, "onTripleTapAndHold()");
             clear();
 
-            mViewportDraggingState.mZoomedInBeforeDrag =
-                    mFullScreenMagnificationController.isMagnifying(mDisplayId);
-
             // Triple tap and hold also belongs to triple tap event.
-            final boolean enabled = !mViewportDraggingState.mZoomedInBeforeDrag;
+            final boolean enabled = !isActivated();
             logMagnificationTripleTap(enabled);
 
-            zoomOn(down.getX(), down.getY());
+            mViewportDraggingState.prepareForZoomInTemporary();
+
+            zoomInTemporary(down.getX(), down.getY());
 
             transitionTo(mViewportDraggingState);
         }
@@ -919,7 +954,10 @@
             if (DEBUG_DETECTING) Slog.i(mLogTag, "setShortcutTriggered(" + state + ")");
 
             mShortcutTriggered = state;
-            mFullScreenMagnificationController.setForceShowMagnifiableBounds(mDisplayId, state);
+        }
+
+        private boolean isShortcutTriggered() {
+            return mShortcutTriggered;
         }
 
         /**
@@ -948,12 +986,29 @@
         }
     }
 
+    private void zoomInTemporary(float centerX, float centerY) {
+        final float currentScale = mFullScreenMagnificationController.getScale(mDisplayId);
+        final float persistedScale = MathUtils.constrain(
+                mFullScreenMagnificationController.getPersistedScale(mDisplayId),
+                MIN_SCALE, MAX_SCALE);
+
+        final float scale = MathUtils.constrain(Math.max(currentScale + 1.0f, persistedScale),
+                MIN_SCALE, MAX_SCALE);
+
+        zoomToScale(scale, centerX, centerY);
+    }
+
     private void zoomOn(float centerX, float centerY) {
         if (DEBUG_DETECTING) Slog.i(mLogTag, "zoomOn(" + centerX + ", " + centerY + ")");
 
         final float scale = MathUtils.constrain(
                 mFullScreenMagnificationController.getPersistedScale(mDisplayId),
                 MIN_SCALE, MAX_SCALE);
+        zoomToScale(scale, centerX, centerY);
+    }
+
+    private void zoomToScale(float scale, float centerX, float centerY) {
+        scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
         mFullScreenMagnificationController.setScaleAndCenter(mDisplayId,
                 scale, centerX, centerY,
                 /* animate */ true,
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 02e810f..129bc16 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -254,13 +254,15 @@
         final DisableMagnificationCallback animationEndCallback =
                 new DisableMagnificationCallback(transitionCallBack, displayId, targetMode,
                         scale, currentCenter, true);
+
+        setDisableMagnificationCallbackLocked(displayId, animationEndCallback);
+
         if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
             screenMagnificationController.reset(displayId, animationEndCallback);
         } else {
             windowMagnificationMgr.disableWindowMagnification(displayId, false,
                     animationEndCallback);
         }
-        setDisableMagnificationCallbackLocked(displayId, animationEndCallback);
     }
 
     /**
@@ -481,17 +483,17 @@
      */
     private boolean shouldNotifyMagnificationChange(int displayId, int changeMode) {
         synchronized (mLock) {
-            final boolean fullScreenMagnifying = mFullScreenMagnificationController != null
-                    && mFullScreenMagnificationController.isMagnifying(displayId);
+            final boolean fullScreenActivated = mFullScreenMagnificationController != null
+                    && mFullScreenMagnificationController.isActivated(displayId);
             final boolean windowEnabled = mWindowMagnificationMgr != null
                     && mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId);
             final Integer transitionMode = mTransitionModes.get(displayId);
-            if (((changeMode == MAGNIFICATION_MODE_FULLSCREEN && fullScreenMagnifying)
+            if (((changeMode == MAGNIFICATION_MODE_FULLSCREEN && fullScreenActivated)
                     || (changeMode == MAGNIFICATION_MODE_WINDOW && windowEnabled))
                     && (transitionMode == null)) {
                 return true;
             }
-            if ((!fullScreenMagnifying && !windowEnabled)
+            if ((!fullScreenActivated && !windowEnabled)
                     && (transitionMode == null)) {
                 return true;
             }
@@ -742,7 +744,7 @@
                     mWindowMagnificationMgr.getCenterY(displayId));
         } else {
             if (mFullScreenMagnificationController == null
-                    || !mFullScreenMagnificationController.isMagnifying(displayId)) {
+                    || !mFullScreenMagnificationController.isActivated(displayId)) {
                 return null;
             }
             mTempPoint.set(mFullScreenMagnificationController.getCenterX(displayId),
@@ -766,9 +768,7 @@
                 if (mFullScreenMagnificationController == null) {
                     return false;
                 }
-                isActivated = mFullScreenMagnificationController.isMagnifying(displayId)
-                        || mFullScreenMagnificationController.isForceShowMagnifiableBounds(
-                        displayId);
+                isActivated = mFullScreenMagnificationController.isActivated(displayId);
             }
         } else if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
             synchronized (mLock) {
@@ -829,7 +829,7 @@
                     final FullScreenMagnificationController screenMagnificationController =
                             getFullScreenMagnificationController();
                     if (mCurrentMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
-                            && !screenMagnificationController.isMagnifying(mDisplayId)) {
+                            && !screenMagnificationController.isActivated(mDisplayId)) {
                         MagnificationConfig.Builder configBuilder =
                                 new MagnificationConfig.Builder();
                         Region region = new Region();
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index a356ae6..75fe026 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -313,13 +313,13 @@
     }
 
     /**
-     * {@link FullScreenMagnificationController#isMagnifying(int)}
+     * {@link FullScreenMagnificationController#isActivated(int)}
      * {@link WindowMagnificationManager#isWindowMagnifierEnabled(int)}
      */
     public boolean isMagnifying(int displayId) {
         int mode = getControllingMode(displayId);
         if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
-            return mController.getFullScreenMagnificationController().isMagnifying(displayId);
+            return mController.getFullScreenMagnificationController().isActivated(displayId);
         } else if (mode == MAGNIFICATION_MODE_WINDOW) {
             return mController.getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId);
         }
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 8c09dcd..dc475f6 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -25,7 +25,6 @@
 import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.backup.BackupManager;
-import android.app.backup.BackupRestoreEventLogger;
 import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.app.backup.IBackupManager;
 import android.app.backup.IBackupManagerMonitor;
@@ -723,6 +722,17 @@
     }
 
     @Override
+    public void setFrameworkSchedulingEnabledForUser(int userId, boolean isEnabled) {
+        UserBackupManagerService userBackupManagerService =
+                getServiceForUserIfCallerHasPermission(userId,
+                        "setFrameworkSchedulingEnabledForUser()");
+
+        if (userBackupManagerService != null) {
+            userBackupManagerService.setFrameworkSchedulingEnabled(isEnabled);
+        }
+    }
+
+    @Override
     public void setBackupEnabledForUser(@UserIdInt int userId, boolean isEnabled)
             throws RemoteException {
         if (isUserReadyForBackup(userId)) {
diff --git a/services/backup/java/com/android/server/backup/FullBackupJob.java b/services/backup/java/com/android/server/backup/FullBackupJob.java
index 0bb25e3..fe0e1c6 100644
--- a/services/backup/java/com/android/server/backup/FullBackupJob.java
+++ b/services/backup/java/com/android/server/backup/FullBackupJob.java
@@ -45,9 +45,12 @@
     private final SparseArray<JobParameters> mParamsForUser = new SparseArray<>();
 
     public static void schedule(int userId, Context ctx, long minDelay,
-            BackupManagerConstants constants) {
+            UserBackupManagerService userBackupManagerService) {
+        if (!userBackupManagerService.isFrameworkSchedulingEnabled()) return;
+
         JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
         JobInfo.Builder builder = new JobInfo.Builder(getJobIdForUserId(userId), sIdleService);
+        final BackupManagerConstants constants = userBackupManagerService.getConstants();
         synchronized (constants) {
             builder.setRequiresDeviceIdle(true)
                     .setRequiredNetworkType(constants.getFullBackupRequiredNetworkType())
diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
index 058dcae..164bbea 100644
--- a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
+++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
@@ -64,14 +64,16 @@
     @VisibleForTesting
     public static final int MAX_JOB_ID = 52418896;
 
-    public static void schedule(int userId, Context ctx, BackupManagerConstants constants) {
-        schedule(userId, ctx, 0, constants);
+    public static void schedule(int userId, Context ctx,
+            UserBackupManagerService userBackupManagerService) {
+        schedule(userId, ctx, 0, userBackupManagerService);
     }
 
     public static void schedule(int userId, Context ctx, long delay,
-            BackupManagerConstants constants) {
+            UserBackupManagerService userBackupManagerService) {
         synchronized (KeyValueBackupJob.class) {
-            if (sScheduledForUserId.get(userId)) {
+            if (sScheduledForUserId.get(userId)
+                    || !userBackupManagerService.isFrameworkSchedulingEnabled()) {
                 return;
             }
 
@@ -80,6 +82,7 @@
             final int networkType;
             final boolean needsCharging;
 
+            final BackupManagerConstants constants = userBackupManagerService.getConstants();
             synchronized (constants) {
                 interval = constants.getKeyValueBackupIntervalMilliseconds();
                 fuzz = constants.getKeyValueBackupFuzzMilliseconds();
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index a4ea698..05327dc 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -156,14 +156,16 @@
             } catch (IllegalArgumentException ex) {
                 // packageName doesn't exist: likely due to a race with it being uninstalled.
                 if (MORE_DEBUG) {
-                    Slog.d(TAG, "Package " + packageName + " was changed, but no longer exists.");
+                    Slog.d(TAG, addUserIdToLogMessage(mUserId, "Package " + packageName
+                            + " was changed, but no longer exists."));
                 }
                 return;
             }
             switch (enabled) {
                 case COMPONENT_ENABLED_STATE_ENABLED: {
                     if (MORE_DEBUG) {
-                        Slog.d(TAG, "Package " + packageName + " was enabled.");
+                        Slog.d(TAG, addUserIdToLogMessage(mUserId, "Package " + packageName
+                                + " was enabled."));
                     }
                     onPackageEnabled(packageName);
                     return;
@@ -173,28 +175,31 @@
                     // Unless explicitly specified in manifest, the default enabled state
                     // is 'enabled'. Here, we assume that default state always means enabled.
                     if (MORE_DEBUG) {
-                        Slog.d(TAG, "Package " + packageName
-                                + " was put in default enabled state.");
+                        Slog.d(TAG, addUserIdToLogMessage(mUserId, "Package " + packageName
+                                + " was put in default enabled state."));
                     }
                     onPackageEnabled(packageName);
                     return;
                 }
                 case COMPONENT_ENABLED_STATE_DISABLED: {
                     if (MORE_DEBUG) {
-                        Slog.d(TAG, "Package " + packageName + " was disabled.");
+                        Slog.d(TAG, addUserIdToLogMessage(mUserId, "Package " + packageName
+                                + " was disabled."));
                     }
                     onPackageDisabled(packageName);
                     return;
                 }
                 case COMPONENT_ENABLED_STATE_DISABLED_USER: {
                     if (MORE_DEBUG) {
-                        Slog.d(TAG, "Package " + packageName + " was disabled by user.");
+                        Slog.d(TAG, addUserIdToLogMessage(mUserId, "Package " + packageName
+                                + " was disabled by user."));
                     }
                     onPackageDisabled(packageName);
                     return;
                 }
                 default: {
-                    Slog.w(TAG, "Package " + packageName + " enabled setting: " + enabled);
+                    Slog.w(TAG, addUserIdToLogMessage(mUserId, "Package " + packageName
+                            + " enabled setting: " + enabled));
                     return;
                 }
             }
@@ -405,7 +410,8 @@
             TransportDescription description =
                     mRegisteredTransportsDescriptionMap.get(transportComponent);
             if (description == null) {
-                Slog.e(TAG, "Transport " + name + " not registered tried to change description");
+                Slog.e(TAG, addUserIdToLogMessage(mUserId, "Transport " + name
+                        + " not registered tried to change description"));
                 return;
             }
             description.name = name;
@@ -413,7 +419,8 @@
             description.currentDestinationString = currentDestinationString;
             description.dataManagementIntent = dataManagementIntent;
             description.dataManagementLabel = dataManagementLabel;
-            Slog.d(TAG, "Transport " + name + " updated its attributes");
+            Slog.d(TAG, addUserIdToLogMessage(mUserId, "Transport " + name
+                    + " updated its attributes"));
         }
     }
 
@@ -493,7 +500,8 @@
         try {
             return getTransportClientOrThrow(transportName, caller);
         } catch (TransportNotRegisteredException e) {
-            Slog.w(TAG, "Transport " + transportName + " not registered");
+            Slog.w(TAG, addUserIdToLogMessage(mUserId, "Transport " + transportName
+                    + " not registered"));
             return null;
         }
     }
@@ -620,7 +628,7 @@
                 selectTransport(getTransportName(transportComponent));
                 return BackupManager.SUCCESS;
             } catch (TransportNotRegisteredException e) {
-                Slog.wtf(TAG, "Transport got unregistered");
+                Slog.wtf(TAG, addUserIdToLogMessage(mUserId, "Transport got unregistered"));
                 return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
             }
         }
@@ -637,7 +645,8 @@
         try {
             mPackageManager.getPackageInfoAsUser(packageName, 0, mUserId);
         } catch (PackageManager.NameNotFoundException e) {
-            Slog.e(TAG, "Trying to register transports from package not found " + packageName);
+            Slog.e(TAG, addUserIdToLogMessage(mUserId,
+                    "Trying to register transports from package not found " + packageName));
             return;
         }
 
@@ -668,7 +677,8 @@
         if (!mTransportWhitelist.contains(transport)) {
             Slog.w(
                     TAG,
-                    "BackupTransport " + transport.flattenToShortString() + " not whitelisted.");
+                    addUserIdToLogMessage(mUserId, "BackupTransport "
+                            + transport.flattenToShortString() + " not whitelisted."));
             return false;
         }
         try {
@@ -676,11 +686,12 @@
                     mPackageManager.getPackageInfoAsUser(transport.getPackageName(), 0, mUserId);
             if ((packInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
                     == 0) {
-                Slog.w(TAG, "Transport package " + transport.getPackageName() + " not privileged");
+                Slog.w(TAG, addUserIdToLogMessage(mUserId, "Transport package "
+                        + transport.getPackageName() + " not privileged"));
                 return false;
             }
         } catch (PackageManager.NameNotFoundException e) {
-            Slog.w(TAG, "Package not found.", e);
+            Slog.w(TAG, addUserIdToLogMessage(mUserId, "Package not found."), e);
             return false;
         }
         return true;
@@ -716,7 +727,8 @@
         try {
             transport = transportConnection.connectOrThrow(callerLogString);
         } catch (TransportNotAvailableException e) {
-            Slog.e(TAG, "Couldn't connect to transport " + transportString + " for registration");
+            Slog.e(TAG, addUserIdToLogMessage(mUserId, "Couldn't connect to transport "
+                    + transportString + " for registration"));
             mTransportConnectionManager.disposeOfTransportClient(transportConnection,
                     callerLogString);
             return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
@@ -728,11 +740,13 @@
             String transportDirName = transport.transportDirName();
             registerTransport(transportComponent, transport);
             // If registerTransport() hasn't thrown...
-            Slog.d(TAG, "Transport " + transportString + " registered");
+            Slog.d(TAG, addUserIdToLogMessage(mUserId, "Transport " + transportString
+                    + " registered"));
             mOnTransportRegisteredListener.onTransportRegistered(transportName, transportDirName);
             result = BackupManager.SUCCESS;
         } catch (RemoteException e) {
-            Slog.e(TAG, "Transport " + transportString + " died while registering");
+            Slog.e(TAG, addUserIdToLogMessage(mUserId, "Transport " + transportString
+                    + " died while registering"));
             result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
         }
 
@@ -798,4 +812,8 @@
             this.dataManagementLabel = dataManagementLabel;
         }
     }
+
+    private static String addUserIdToLogMessage(int userId, String message) {
+        return "[UserID:" + userId + "] " + message;
+    }
 }
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 6ba01d7..40a4af9 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -1958,8 +1958,8 @@
             }
             // We don't want the backup jobs to kick in any time soon.
             // Reschedules them to run in the distant future.
-            KeyValueBackupJob.schedule(mUserId, mContext, BUSY_BACKOFF_MIN_MILLIS, mConstants);
-            FullBackupJob.schedule(mUserId, mContext, 2 * BUSY_BACKOFF_MIN_MILLIS, mConstants);
+            KeyValueBackupJob.schedule(mUserId, mContext, BUSY_BACKOFF_MIN_MILLIS, this);
+            FullBackupJob.schedule(mUserId, mContext, 2 * BUSY_BACKOFF_MIN_MILLIS, this);
         } finally {
             Binder.restoreCallingIdentity(oldToken);
         }
@@ -2088,7 +2088,7 @@
                 final long interval = mConstants.getFullBackupIntervalMilliseconds();
                 final long appLatency = (timeSinceLast < interval) ? (interval - timeSinceLast) : 0;
                 final long latency = Math.max(transportMinLatency, appLatency);
-                FullBackupJob.schedule(mUserId, mContext, latency, mConstants);
+                FullBackupJob.schedule(mUserId, mContext, latency, this);
             } else {
                 if (DEBUG_SCHEDULING) {
                     Slog.i(
@@ -2226,7 +2226,7 @@
                         addUserIdToLogMessage(
                                 mUserId, "Deferring scheduled full backups in battery saver mode"));
             }
-            FullBackupJob.schedule(mUserId, mContext, keyValueBackupInterval, mConstants);
+            FullBackupJob.schedule(mUserId, mContext, keyValueBackupInterval, this);
             return false;
         }
 
@@ -2392,7 +2392,7 @@
                                             + "operation; rescheduling +" + latency));
                 }
                 final long deferTime = latency;     // pin for the closure
-                FullBackupJob.schedule(mUserId, mContext, deferTime, mConstants);
+                FullBackupJob.schedule(mUserId, mContext, deferTime, this);
                 return false;
             }
 
@@ -2495,7 +2495,7 @@
         }
 
         // ...and schedule a backup pass if necessary
-        KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
+        KeyValueBackupJob.schedule(mUserId, mContext, this);
     }
 
     // Note: packageName is currently unused, but may be in the future
@@ -2730,7 +2730,7 @@
                                     mUserId, "Not running backup while in battery save mode"));
                 }
                 // Try again in several hours.
-                KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
+                KeyValueBackupJob.schedule(mUserId, mContext, this);
             } else {
                 if (DEBUG) {
                     Slog.v(TAG, addUserIdToLogMessage(mUserId, "Scheduling immediate backup pass"));
@@ -3208,12 +3208,49 @@
         }
     }
 
+    synchronized void setFrameworkSchedulingEnabled(boolean isEnabled) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+                "setFrameworkSchedulingEnabled");
+
+        boolean wasEnabled = isFrameworkSchedulingEnabled();
+        if (wasEnabled == isEnabled) return;
+
+        Slog.i(TAG, addUserIdToLogMessage(mUserId,
+                (isEnabled ? "Enabling" : "Disabling") + " backup scheduling"));
+
+        final long oldId = Binder.clearCallingIdentity();
+        try {
+            // TODO(b/264889098): Consider at a later point if we should us a sentinel file as
+            // setBackupEnabled.
+            Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.BACKUP_SCHEDULING_ENABLED, isEnabled ? 1 : 0, mUserId);
+
+            if (!isEnabled) {
+                KeyValueBackupJob.cancel(mUserId, mContext);
+                FullBackupJob.cancel(mUserId, mContext);
+            } else {
+                KeyValueBackupJob.schedule(mUserId, mContext, this);
+                scheduleNextFullBackupJob(/* transportMinLatency */ 0);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(oldId);
+        }
+    }
+
+    synchronized boolean isFrameworkSchedulingEnabled() {
+        // By default scheduling is enabled
+        final int defaultSetting = 1;
+        int isEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.BACKUP_SCHEDULING_ENABLED, defaultSetting, mUserId);
+        return isEnabled == 1;
+    }
+
     @VisibleForTesting
     void updateStateOnBackupEnabled(boolean wasEnabled, boolean enable) {
         synchronized (mQueueLock) {
             if (enable && !wasEnabled && mSetupComplete) {
                 // if we've just been enabled, start scheduling backup passes
-                KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
+                KeyValueBackupJob.schedule(mUserId, mContext, this);
                 scheduleNextFullBackupJob(0);
             } else if (!enable) {
                 // No longer enabled, so stop running backups
@@ -4127,6 +4164,8 @@
             pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled"));
             if (mBackupRunning) pw.println("Backup currently running");
             pw.println(isBackupOperationInProgress() ? "Backup in progress" : "No backups running");
+            pw.println("Framework scheduling is "
+                    + (isFrameworkSchedulingEnabled() ? "enabled" : "disabled"));
             pw.println("Last backup pass started: " + mLastBackupPass
                     + " (now = " + System.currentTimeMillis() + ')');
             pw.println("  next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId));
diff --git a/services/backup/java/com/android/server/backup/internal/SetupObserver.java b/services/backup/java/com/android/server/backup/internal/SetupObserver.java
index c5e912e..f399fe9 100644
--- a/services/backup/java/com/android/server/backup/internal/SetupObserver.java
+++ b/services/backup/java/com/android/server/backup/internal/SetupObserver.java
@@ -23,7 +23,6 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.os.Handler;
-import android.provider.Settings;
 import android.util.Slog;
 
 import com.android.server.backup.KeyValueBackupJob;
@@ -78,7 +77,7 @@
                     Slog.d(TAG, "Setup complete so starting backups");
                 }
                 KeyValueBackupJob.schedule(mUserBackupManagerService.getUserId(), mContext,
-                        mUserBackupManagerService.getConstants());
+                        mUserBackupManagerService);
                 mUserBackupManagerService.scheduleNextFullBackupJob(0);
             }
         }
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index ca92b69..41e8092 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -1246,7 +1246,7 @@
             delay = 0;
         }
         KeyValueBackupJob.schedule(mBackupManagerService.getUserId(),
-                mBackupManagerService.getContext(), delay, mBackupManagerService.getConstants());
+                mBackupManagerService.getContext(), delay, mBackupManagerService);
 
         for (String packageName : mOriginalQueue) {
             mBackupManagerService.dataChangedImpl(packageName);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 17b6f5d..97e9d26 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -101,6 +101,8 @@
     defaults: ["platform_service_defaults"],
     srcs: [
         ":android.hardware.biometrics.face-V3-java-source",
+        ":android.hardware.tv.hdmi.connection-V1-java-source",
+        ":android.hardware.tv.hdmi.earc-V1-java-source",
         ":statslog-art-java-gen",
         ":statslog-contexthub-java-gen",
         ":services.core-sources",
@@ -160,6 +162,7 @@
         "android.hardware.tv.cec-V1.1-java",
         "android.hardware.tv.hdmi.cec-V1-java",
         "android.hardware.tv.hdmi.connection-V1-java",
+        "android.hardware.tv.hdmi.earc-V1-java",
         "android.hardware.weaver-V1.0-java",
         "android.hardware.weaver-V2-java",
         "android.hardware.biometrics.face-V1.0-java",
@@ -170,7 +173,7 @@
         "android.hardware.ir-V1-java",
         "android.hardware.rebootescrow-V1-java",
         "android.hardware.soundtrigger-V2.3-java",
-        "android.hardware.power.stats-V1-java",
+        "android.hardware.power.stats-V2-java",
         "android.hardware.power-V4-java",
         "android.hidl.manager-V1.2-java",
         "icu4j_calendar_astronomer",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index f101e73..f8e79e5 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -112,11 +112,11 @@
     /** Observer called whenever the list of packages changes */
     public interface PackageListObserver {
         /** A package was added to the system. */
-        void onPackageAdded(@NonNull String packageName, int uid);
+        default void onPackageAdded(@NonNull String packageName, int uid) {}
         /** A package was changed - either installed for a specific user or updated. */
         default void onPackageChanged(@NonNull String packageName, int uid) {}
         /** A package was removed from the system. */
-        void onPackageRemoved(@NonNull String packageName, int uid);
+        default void onPackageRemoved(@NonNull String packageName, int uid) {}
     }
 
     /**
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 2fc0712..ff75796 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -1476,6 +1476,8 @@
             addFeature(PackageManager.FEATURE_IPSEC_TUNNELS, 0);
         }
 
+        enableIpSecTunnelMigrationOnVsrUAndAbove();
+
         if (isErofsSupported()) {
             if (isKernelVersionAtLeast(5, 10)) {
                 addFeature(PackageManager.FEATURE_EROFS, 0);
@@ -1489,6 +1491,18 @@
         }
     }
 
+    // This method only enables a new Android feature added in U and will not have impact on app
+    // compatibility
+    @SuppressWarnings("AndroidFrameworkCompatChange")
+    private void enableIpSecTunnelMigrationOnVsrUAndAbove() {
+        final int vsrApi =
+                SystemProperties.getInt(
+                        "ro.vendor.api_level", Build.VERSION.DEVICE_INITIAL_SDK_INT);
+        if (vsrApi > Build.VERSION_CODES.TIRAMISU) {
+            addFeature(PackageManager.FEATURE_IPSEC_TUNNEL_MIGRATION, 0);
+        }
+    }
+
     private void addFeature(String name, int version) {
         FeatureInfo fi = mAvailableFeatures.get(name);
         if (fi == null) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index ece7254..9154231 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -112,6 +112,7 @@
 import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.ForegroundServiceDelegationOptions;
 import android.app.ForegroundServiceStartNotAllowedException;
 import android.app.ForegroundServiceTypePolicy;
 import android.app.ForegroundServiceTypePolicy.ForegroundServicePolicyCheckCode;
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index fa0972a..abaa8c7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -17,7 +17,6 @@
 package com.android.server.am;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.content.Context;
@@ -118,28 +117,4 @@
      *        sandbox. This is obtained using {@link Context#getIApplicationThreadBinder()}.
      */
     void killSdkSandboxClientAppProcess(@NonNull IBinder clientApplicationThreadBinder);
-
-    /**
-     * Start a foreground service delegate.
-     * @param options foreground service delegate options.
-     * @param connection a service connection served as callback to caller.
-     * @return true if delegate is started successfully, false otherwise.
-     * @hide
-     */
-    boolean startForegroundServiceDelegate(@NonNull ForegroundServiceDelegationOptions options,
-            @Nullable ServiceConnection connection);
-
-    /**
-     * Stop a foreground service delegate.
-     * @param options the foreground service delegate options.
-     * @hide
-     */
-    void stopForegroundServiceDelegate(@NonNull ForegroundServiceDelegationOptions options);
-
-    /**
-     * Stop a foreground service delegate by service connection.
-     * @param connection service connection used to start delegate previously.
-     * @hide
-     */
-    void stopForegroundServiceDelegate(@NonNull ServiceConnection connection);
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a386baf..338bd38 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -186,6 +186,7 @@
 import android.app.BroadcastOptions;
 import android.app.ComponentOptions;
 import android.app.ContentProviderHolder;
+import android.app.ForegroundServiceDelegationOptions;
 import android.app.IActivityController;
 import android.app.IActivityManager;
 import android.app.IApplicationThread;
@@ -18604,10 +18605,10 @@
                 for (int i = delegates.size() - 1; i >= 0; i--) {
                     final ForegroundServiceDelegationOptions options = delegates.get(i);
                     if (isStart) {
-                        ((ActivityManagerLocal) mInternal).startForegroundServiceDelegate(options,
+                        mInternal.startForegroundServiceDelegate(options,
                                 null /* connection */);
                     } else {
-                        ((ActivityManagerLocal) mInternal).stopForegroundServiceDelegate(options);
+                        mInternal.stopForegroundServiceDelegate(options);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 788c81c..4b7857f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -46,6 +46,7 @@
 import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.AppGlobals;
 import android.app.BroadcastOptions;
+import android.app.ForegroundServiceDelegationOptions;
 import android.app.IActivityController;
 import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
diff --git a/services/core/java/com/android/server/am/ForegroundServiceDelegation.java b/services/core/java/com/android/server/am/ForegroundServiceDelegation.java
index a051d17..a17848e 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceDelegation.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceDelegation.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ForegroundServiceDelegationOptions;
 import android.content.ServiceConnection;
 import android.os.Binder;
 import android.os.IBinder;
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index 1b20e43..59d8afa 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -84,7 +84,8 @@
     private static final String WALLPAPER_IMAGE_KEY = WallpaperBackupHelper.WALLPAPER_IMAGE_KEY;
 
     private static final Set<String> sEligibleForMultiUser = Sets.newArraySet(
-            PERMISSION_HELPER, NOTIFICATION_HELPER, SYNC_SETTINGS_HELPER, APP_LOCALES_HELPER);
+            PERMISSION_HELPER, NOTIFICATION_HELPER, SYNC_SETTINGS_HELPER, APP_LOCALES_HELPER,
+            ACCOUNT_MANAGER_HELPER);
 
     private int mUserId = UserHandle.USER_SYSTEM;
 
@@ -100,7 +101,7 @@
         addHelper(PERMISSION_HELPER, new PermissionBackupHelper(mUserId));
         addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
         addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
-        addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper());
+        addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper(mUserId));
         addHelper(SLICES_HELPER, new SliceBackupHelper(this));
         addHelper(PEOPLE_HELPER, new PeopleBackupHelper(mUserId));
         addHelper(APP_LOCALES_HELPER, new AppSpecificLocalesBackupHelper(mUserId));
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index bde14ee..dcc98e1 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -49,7 +49,6 @@
 import android.content.IntentFilter;
 import android.content.PeriodicSync;
 import android.content.ServiceConnection;
-import android.content.SharedPreferences;
 import android.content.SyncActivityTooManyDeletes;
 import android.content.SyncAdapterType;
 import android.content.SyncAdaptersCache;
@@ -206,6 +205,13 @@
      */
     private static final long SYNC_DELAY_ON_CONFLICT = 10*1000; // 10 seconds
 
+    /**
+     * Generate job ids in the range [MIN_SYNC_JOB_ID, MAX_SYNC_JOB_ID) to avoid conflicts with
+     * other jobs scheduled by the system process.
+     */
+    private static final int MIN_SYNC_JOB_ID = 100000;
+    private static final int MAX_SYNC_JOB_ID = 110000;
+
     private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*/";
     private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
     private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock";
@@ -223,9 +229,6 @@
     private static final int SYNC_ADAPTER_CONNECTION_FLAGS = Context.BIND_AUTO_CREATE
             | Context.BIND_NOT_FOREGROUND | Context.BIND_ALLOW_OOM_MANAGEMENT;
 
-    private static final String PREF_KEY_SYNC_JOB_NAMESPACE_MIGRATED =
-            "sync_job_namespace_migrated";
-
     /** Singleton instance. */
     @GuardedBy("SyncManager.class")
     private static SyncManager sInstance;
@@ -239,11 +242,12 @@
 
     volatile private PowerManager.WakeLock mSyncManagerWakeLock;
     volatile private boolean mDataConnectionIsConnected = false;
-    private volatile int mNextJobId = 0;
+    private volatile int mNextJobIdOffset = 0;
 
     private final NotificationManager mNotificationMgr;
     private final IBatteryStats mBatteryStats;
     private JobScheduler mJobScheduler;
+    private JobSchedulerInternal mJobSchedulerInternal;
 
     private SyncStorageEngine mSyncStorageEngine;
 
@@ -277,19 +281,24 @@
     }
 
     private int getUnusedJobIdH() {
-        final List<JobInfo> pendingJobs = mJobScheduler.getAllPendingJobs();
-        while (isJobIdInUseLockedH(mNextJobId, pendingJobs)) {
-            // SyncManager jobs are placed in their own namespace. Since there's no chance of
-            // conflicting with other parts of the system, we can just keep incrementing until
-            // we find an unused ID.
-            mNextJobId++;
+        final int maxNumSyncJobIds = MAX_SYNC_JOB_ID - MIN_SYNC_JOB_ID;
+        final List<JobInfo> pendingJobs = mJobSchedulerInternal.getSystemScheduledPendingJobs();
+        for (int i = 0; i < maxNumSyncJobIds; ++i) {
+            int newJobId = MIN_SYNC_JOB_ID + ((mNextJobIdOffset + i) % maxNumSyncJobIds);
+            if (!isJobIdInUseLockedH(newJobId, pendingJobs)) {
+                mNextJobIdOffset = (mNextJobIdOffset + i + 1) % maxNumSyncJobIds;
+                return newJobId;
+            }
         }
-        return mNextJobId;
+        // We've used all 10,000 intended job IDs.... We're probably in a world of pain right now :/
+        Slog.wtf(TAG, "All " + maxNumSyncJobIds + " possible sync job IDs are taken :/");
+        mNextJobIdOffset = (mNextJobIdOffset + 1) % maxNumSyncJobIds;
+        return MIN_SYNC_JOB_ID + mNextJobIdOffset;
     }
 
     private List<SyncOperation> getAllPendingSyncs() {
         verifyJobScheduler();
-        List<JobInfo> pendingJobs = mJobScheduler.getAllPendingJobs();
+        List<JobInfo> pendingJobs = mJobSchedulerInternal.getSystemScheduledPendingJobs();
         final int numJobs = pendingJobs.size();
         final List<SyncOperation> pendingSyncs = new ArrayList<>(numJobs);
         for (int i = 0; i < numJobs; ++i) {
@@ -297,8 +306,6 @@
             SyncOperation op = SyncOperation.maybeCreateFromJobExtras(job.getExtras());
             if (op != null) {
                 pendingSyncs.add(op);
-            } else {
-                Slog.wtf(TAG, "Non-sync job inside of SyncManager's namespace");
             }
         }
         return pendingSyncs;
@@ -484,31 +491,6 @@
         });
     }
 
-    /**
-     * Migrate syncs from the default job namespace to SyncManager's namespace if they haven't been
-     * migrated already.
-     */
-    private void migrateSyncJobNamespaceIfNeeded() {
-        final SharedPreferences prefs = mContext.getSharedPreferences(
-                mSyncStorageEngine.getSyncDir(), Context.MODE_PRIVATE);
-        if (prefs.getBoolean(PREF_KEY_SYNC_JOB_NAMESPACE_MIGRATED, false)) {
-            return;
-        }
-        final List<JobInfo> pendingJobs = getJobSchedulerInternal().getSystemScheduledPendingJobs();
-        final JobScheduler jobSchedulerDefaultNamespace =
-                mContext.getSystemService(JobScheduler.class);
-        for (int i = pendingJobs.size() - 1; i >= 0; --i) {
-            final JobInfo job = pendingJobs.get(i);
-            final SyncOperation op = SyncOperation.maybeCreateFromJobExtras(job.getExtras());
-            if (op != null) {
-                // This is a sync. Move it over to SyncManager's namespace.
-                mJobScheduler.schedule(job);
-                jobSchedulerDefaultNamespace.cancel(job.getId());
-            }
-        }
-        prefs.edit().putBoolean(PREF_KEY_SYNC_JOB_NAMESPACE_MIGRATED, true).apply();
-    }
-
     private synchronized void verifyJobScheduler() {
         if (mJobScheduler != null) {
             return;
@@ -518,12 +500,10 @@
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.d(TAG, "initializing JobScheduler object.");
             }
-            // Use a dedicated namespace to avoid conflicts with other jobs
-            // scheduled by the system process.
-            mJobScheduler = mContext.getSystemService(JobScheduler.class)
-                    .forNamespace("SyncManager");
-            migrateSyncJobNamespaceIfNeeded();
-            // Get all persisted syncs from JobScheduler in the SyncManager namespace.
+            mJobScheduler = (JobScheduler) mContext.getSystemService(
+                    Context.JOB_SCHEDULER_SERVICE);
+            mJobSchedulerInternal = getJobSchedulerInternal();
+            // Get all persisted syncs from JobScheduler
             List<JobInfo> pendingJobs = mJobScheduler.getAllPendingJobs();
 
             int numPersistedPeriodicSyncs = 0;
@@ -539,8 +519,6 @@
                         // shown on the settings activity.
                         mSyncStorageEngine.markPending(op.target, true);
                     }
-                } else {
-                    Slog.wtf(TAG, "Non-sync job inside of SyncManager namespace");
                 }
             }
             final String summary = "Loaded persisted syncs: "
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index f7468fc..9c1cf38 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -21,7 +21,6 @@
 import android.accounts.Account;
 import android.accounts.AccountAndUser;
 import android.accounts.AccountManager;
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.backup.BackupManager;
 import android.content.ComponentName;
@@ -575,11 +574,6 @@
         return sSyncStorageEngine;
     }
 
-    @NonNull
-    File getSyncDir() {
-        return mSyncDir;
-    }
-
     protected void setOnSyncRequestListener(OnSyncRequestListener listener) {
         if (mSyncRequestListener == null) {
             mSyncRequestListener = listener;
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 59aa3f9..6d1af3b 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -19,6 +19,8 @@
 import android.annotation.IntDef;
 import android.annotation.StringDef;
 import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.tv.hdmi.connection.HpdSignal;
+import android.hardware.tv.hdmi.earc.IEArcStatus;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -599,10 +601,13 @@
     })
     @interface RcProfileSource {}
 
-    static final int HDMI_EARC_STATUS_IDLE = 0;             // IDLE1
-    static final int HDMI_EARC_STATUS_EARC_PENDING = 1;     // DISC1 and DISC2
-    static final int HDMI_EARC_STATUS_ARC_PENDING = 2;      // IDLE2 for ARC
-    static final int HDMI_EARC_STATUS_EARC_CONNECTED = 3;   // eARC connected
+    static final int HDMI_EARC_STATUS_IDLE = IEArcStatus.STATUS_IDLE; // IDLE1
+    static final int HDMI_EARC_STATUS_EARC_PENDING =
+            IEArcStatus.STATUS_EARC_PENDING; // DISC1 and DISC2
+    static final int HDMI_EARC_STATUS_ARC_PENDING = IEArcStatus.STATUS_ARC_PENDING; // IDLE2 for ARC
+    static final int HDMI_EARC_STATUS_EARC_CONNECTED =
+            IEArcStatus.STATUS_EARC_CONNECTED; // eARC connected
+
     @IntDef({
             HDMI_EARC_STATUS_IDLE,
             HDMI_EARC_STATUS_EARC_PENDING,
@@ -611,8 +616,11 @@
             })
     @interface EarcStatus {}
 
-    static final int HDMI_HPD_TYPE_PHYSICAL = 0;   // Default. Physical hotplug signal.
-    static final int HDMI_HPD_TYPE_STATUS_BIT = 1; // HDMI_HPD status bit.
+    static final int HDMI_HPD_TYPE_PHYSICAL =
+            HpdSignal.HDMI_HPD_PHYSICAL; // Default. Physical hotplug signal.
+    static final int HDMI_HPD_TYPE_STATUS_BIT =
+            HpdSignal.HDMI_HPD_STATUS_BIT; // HDMI_HPD status bit.
+
     @IntDef({
             HDMI_HPD_TYPE_PHYSICAL,
             HDMI_HPD_TYPE_STATUS_BIT
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 7ac8fd0..14b9121 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -931,6 +931,11 @@
     }
 
     @VisibleForTesting
+    void setEarcController(HdmiEarcController earcController) {
+        mEarcController = earcController;
+    }
+
+    @VisibleForTesting
     void setHdmiCecNetwork(HdmiCecNetwork hdmiCecNetwork) {
         mHdmiCecNetwork = hdmiCecNetwork;
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiEarcController.java b/services/core/java/com/android/server/hdmi/HdmiEarcController.java
index 8522509..2bb9ffb 100644
--- a/services/core/java/com/android/server/hdmi/HdmiEarcController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiEarcController.java
@@ -16,8 +16,14 @@
 
 package com.android.server.hdmi;
 
+import android.hardware.tv.hdmi.earc.IEArc;
+import android.hardware.tv.hdmi.earc.IEArcCallback;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -29,9 +35,109 @@
 
     private final HdmiControlService mService;
 
+    private EArcNativeWrapper mEArcNativeWrapperImpl;
+
+    protected interface EArcNativeWrapper {
+        boolean nativeInit();
+        void nativeSetEArcEnabled(boolean enabled);
+        boolean nativeIsEArcEnabled();
+        void nativeSetCallback(EarcAidlCallback callback);
+        byte nativeGetState(int portId);
+        byte[] nativeGetLastReportedAudioCapabilities(int portId);
+    }
+
+    private static final class EArcNativeWrapperImpl implements EArcNativeWrapper,
+            IBinder.DeathRecipient {
+        private IEArc mEArc;
+        private EarcAidlCallback mEArcCallback;
+
+        @Override
+        public void binderDied() {
+            mEArc.asBinder().unlinkToDeath(this, 0);
+            connectToHal();
+            if (mEArcCallback != null) {
+                nativeSetCallback(mEArcCallback);
+            }
+        }
+
+        boolean connectToHal() {
+            mEArc =
+                    IEArc.Stub.asInterface(
+                            ServiceManager.getService(IEArc.DESCRIPTOR + "/default"));
+            if (mEArc == null) {
+                return false;
+            }
+            try {
+                mEArc.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                HdmiLogger.error("Couldn't link callback object: ", e);
+            }
+            return true;
+        }
+
+        @Override
+        public boolean nativeInit() {
+            return connectToHal();
+        }
+
+        @Override
+        public void nativeSetEArcEnabled(boolean enabled) {
+            try {
+                mEArc.setEArcEnabled(enabled);
+            } catch (ServiceSpecificException sse) {
+                HdmiLogger.error(
+                        "Could not set eARC enabled to " + enabled + ". Error: ", sse.errorCode);
+            } catch (RemoteException re) {
+                HdmiLogger.error("Could not set eARC enabled to " + enabled + ":. Exception: ", re);
+            }
+        }
+
+        @Override
+        public boolean nativeIsEArcEnabled() {
+            try {
+                return mEArc.isEArcEnabled();
+            } catch (RemoteException re) {
+                HdmiLogger.error("Could not read if eARC is enabled. Exception: ", re);
+                return false;
+            }
+        }
+
+        @Override
+        public void nativeSetCallback(EarcAidlCallback callback) {
+            mEArcCallback = callback;
+            try {
+                mEArc.setCallback(callback);
+            } catch (RemoteException re) {
+                HdmiLogger.error("Could not set callback. Exception: ", re);
+            }
+        }
+
+        @Override
+        public byte nativeGetState(int portId) {
+            try {
+                return mEArc.getState(portId);
+            } catch (RemoteException re) {
+                HdmiLogger.error("Could not get eARC state. Exception: ", re);
+                return -1;
+            }
+        }
+
+        @Override
+        public byte[] nativeGetLastReportedAudioCapabilities(int portId) {
+            try {
+                return mEArc.getLastReportedAudioCapabilities(portId);
+            } catch (RemoteException re) {
+                HdmiLogger.error(
+                        "Could not read last reported audio capabilities. Exception: ", re);
+                return null;
+            }
+        }
+    }
+
     // Private constructor. Use HdmiEarcController.create().
-    private HdmiEarcController(HdmiControlService service) {
+    private HdmiEarcController(HdmiControlService service, EArcNativeWrapper nativeWrapper) {
         mService = service;
+        mEArcNativeWrapperImpl = nativeWrapper;
     }
 
     /**
@@ -45,14 +151,29 @@
      *         returns {@code null}.
      */
     static HdmiEarcController create(HdmiControlService service) {
-        // TODO add the native wrapper and return null if eARC HAL is not present.
-        HdmiEarcController controller = new HdmiEarcController(service);
-        controller.init();
+        return createWithNativeWrapper(service, new EArcNativeWrapperImpl());
+    }
+
+    /**
+     * A factory method with injection of native methods for testing.
+     */
+    static HdmiEarcController createWithNativeWrapper(HdmiControlService service,
+            EArcNativeWrapper nativeWrapper) {
+        HdmiEarcController controller = new HdmiEarcController(service, nativeWrapper);
+        if (!controller.init(nativeWrapper)) {
+            HdmiLogger.warning("Could not connect to eARC AIDL HAL.");
+            return null;
+        }
         return controller;
     }
 
-    private void init() {
-        mControlHandler = new Handler(mService.getServiceLooper());
+    private boolean init(EArcNativeWrapper nativeWrapper) {
+        if (nativeWrapper.nativeInit()) {
+            mControlHandler = new Handler(mService.getServiceLooper());
+            mEArcNativeWrapperImpl.nativeSetCallback(new EarcAidlCallback());
+            return true;
+        }
+        return false;
     }
 
     private void assertRunOnServiceThread() {
@@ -73,9 +194,7 @@
     @HdmiAnnotations.ServiceThreadOnly
     void setEarcEnabled(boolean enabled) {
         assertRunOnServiceThread();
-        // Stub.
-        // TODO: bind to native.
-        // TODO: handle error return values here, with logging.
+        mEArcNativeWrapperImpl.nativeSetEArcEnabled(enabled);
     }
 
     /**
@@ -86,23 +205,21 @@
     @HdmiAnnotations.ServiceThreadOnly
     @Constants.EarcStatus
     int getState(int portId) {
-        // Stub.
-        // TODO: bind to native.
-        return Constants.HDMI_EARC_STATUS_IDLE;
+        return mEArcNativeWrapperImpl.nativeGetState(portId);
     }
 
-     /**
+    /**
      * Ask the HAL to report the last eARC capabilities that the connected audio system reported.
+     *
      * @return the raw eARC capabilities
      */
     @HdmiAnnotations.ServiceThreadOnly
-    byte[] getLastReportedCaps() {
-        // Stub. TODO: bind to native.
-        return new byte[] {};
+    byte[] getLastReportedCaps(int portId) {
+        return mEArcNativeWrapperImpl.nativeGetLastReportedAudioCapabilities(portId);
     }
 
-    final class EarcCallback {
-        public void onStateChange(@Constants.EarcStatus int status, int portId) {
+    final class EarcAidlCallback extends IEArcCallback.Stub {
+        public void onStateChange(@Constants.EarcStatus byte status, int portId) {
             runOnServiceThread(
                     () -> mService.handleEarcStateChange(status, portId));
         }
@@ -111,7 +228,15 @@
             runOnServiceThread(
                     () -> mService.handleEarcCapabilitiesReported(rawCapabilities, portId));
         }
-    }
 
-    // TODO: bind to native.
+        @Override
+        public synchronized String getInterfaceHash() throws RemoteException {
+            return IEArcCallback.Stub.HASH;
+        }
+
+        @Override
+        public int getInterfaceVersion() throws RemoteException {
+            return IEArcCallback.Stub.VERSION;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index ca42614..4d03e44 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -213,4 +213,10 @@
      * @param enabled When true, stylus buttons will not be reported through motion events.
      */
     public abstract void setStylusButtonMotionEventsEnabled(boolean enabled);
+
+    /**
+     * Notify whether any user activity occurred. This includes any input activity on any
+     * display, external peripherals, fingerprint sensor, etc.
+     */
+    public abstract void notifyUserActivity();
 }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index ea7f0bb..be4373a 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -70,6 +70,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.os.vibrator.StepSegment;
@@ -158,6 +159,11 @@
     private static final AdditionalDisplayInputProperties
             DEFAULT_ADDITIONAL_DISPLAY_INPUT_PROPERTIES = new AdditionalDisplayInputProperties();
 
+    // To disable Keyboard backlight control via Framework, run:
+    // 'adb shell setprop persist.input.keyboard_backlight_control.enabled false' (requires restart)
+    private static final boolean KEYBOARD_BACKLIGHT_CONTROL_ENABLED = SystemProperties.getBoolean(
+            "persist.input.keyboard.backlight_control.enabled", true);
+
     private final NativeInputManagerService mNative;
 
     private final Context mContext;
@@ -305,7 +311,7 @@
     private final BatteryController mBatteryController;
 
     // Manages Keyboard backlight
-    private final KeyboardBacklightController mKeyboardBacklightController;
+    private final KeyboardBacklightControllerInterface mKeyboardBacklightController;
 
     // Manages Keyboard modifier keys remapping
     private final KeyRemapper mKeyRemapper;
@@ -422,8 +428,10 @@
         mKeyboardLayoutManager = new KeyboardLayoutManager(mContext, mNative, mDataStore,
                 injector.getLooper());
         mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());
-        mKeyboardBacklightController = new KeyboardBacklightController(mContext, mNative,
-                mDataStore, injector.getLooper());
+        mKeyboardBacklightController =
+                KEYBOARD_BACKLIGHT_CONTROL_ENABLED ? new KeyboardBacklightController(mContext,
+                        mNative, mDataStore, injector.getLooper())
+                        : new KeyboardBacklightControllerInterface() {};
         mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
 
         mUseDevInputEventForAudioJack =
@@ -3263,6 +3271,7 @@
         public void setInteractive(boolean interactive) {
             mNative.setInteractive(interactive);
             mBatteryController.onInteractiveChanged(interactive);
+            mKeyboardBacklightController.onInteractiveChanged(interactive);
         }
 
         @Override
@@ -3346,10 +3355,12 @@
         public void onInputMethodSubtypeChangedForKeyboardLayoutMapping(@UserIdInt int userId,
                 @Nullable InputMethodSubtypeHandle subtypeHandle,
                 @Nullable InputMethodSubtype subtype) {
-            if (DEBUG) {
-                Slog.i(TAG, "InputMethodSubtype changed: userId=" + userId
-                        + " subtypeHandle=" + subtypeHandle);
-            }
+            mKeyboardLayoutManager.onInputMethodSubtypeChanged(userId, subtypeHandle, subtype);
+        }
+
+        @Override
+        public void notifyUserActivity() {
+            mKeyboardBacklightController.notifyUserActivity();
         }
 
         @Override
@@ -3478,4 +3489,15 @@
             applyAdditionalDisplayInputPropertiesLocked(properties);
         }
     }
+
+    interface KeyboardBacklightControllerInterface {
+        default void incrementKeyboardBacklight(int deviceId) {}
+        default void decrementKeyboardBacklight(int deviceId) {}
+        default void registerKeyboardBacklightListener(IKeyboardBacklightListener l, int pid) {}
+        default void unregisterKeyboardBacklightListener(IKeyboardBacklightListener l, int pid) {}
+        default void onInteractiveChanged(boolean isInteractive) {}
+        default void notifyUserActivity() {}
+        default void systemRunning() {}
+        default void dump(PrintWriter pw) {}
+    }
 }
diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
index 77b0d4f..e1e3dd9 100644
--- a/services/core/java/com/android/server/input/KeyboardBacklightController.java
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -17,7 +17,6 @@
 package com.android.server.input;
 
 import android.annotation.BinderThread;
-import android.annotation.ColorInt;
 import android.content.Context;
 import android.graphics.Color;
 import android.hardware.input.IKeyboardBacklightListener;
@@ -29,6 +28,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Slog;
@@ -39,15 +39,17 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
+import java.time.Duration;
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.OptionalInt;
-import java.util.TreeSet;
 
 /**
  * A thread-safe component of {@link InputManagerService} responsible for managing the keyboard
  * backlight for supported keyboards.
  */
-final class KeyboardBacklightController implements InputManager.InputDeviceListener {
+final class KeyboardBacklightController implements
+        InputManagerService.KeyboardBacklightControllerInterface, InputManager.InputDeviceListener {
 
     private static final String TAG = "KbdBacklightController";
 
@@ -58,12 +60,20 @@
     private enum Direction {
         DIRECTION_UP, DIRECTION_DOWN
     }
-    private static final int MSG_INCREMENT_KEYBOARD_BACKLIGHT = 1;
-    private static final int MSG_DECREMENT_KEYBOARD_BACKLIGHT = 2;
+    private static final int MSG_UPDATE_EXISTING_DEVICES = 1;
+    private static final int MSG_INCREMENT_KEYBOARD_BACKLIGHT = 2;
+    private static final int MSG_DECREMENT_KEYBOARD_BACKLIGHT = 3;
+    private static final int MSG_NOTIFY_USER_ACTIVITY = 4;
+    private static final int MSG_NOTIFY_USER_INACTIVITY = 5;
+    private static final int MSG_INTERACTIVE_STATE_CHANGED = 6;
     private static final int MAX_BRIGHTNESS = 255;
     private static final int NUM_BRIGHTNESS_CHANGE_STEPS = 10;
+
     @VisibleForTesting
-    static final TreeSet<Integer> BRIGHTNESS_LEVELS = new TreeSet<>();
+    static final long USER_INACTIVITY_THRESHOLD_MILLIS = Duration.ofSeconds(30).toMillis();
+
+    @VisibleForTesting
+    static final int[] BRIGHTNESS_VALUE_FOR_LEVEL = new int[NUM_BRIGHTNESS_CHANGE_STEPS + 1];
 
     private final Context mContext;
     private final NativeInputManagerService mNative;
@@ -71,7 +81,12 @@
     @GuardedBy("mDataStore")
     private final PersistentDataStore mDataStore;
     private final Handler mHandler;
-    private final SparseArray<Light> mKeyboardBacklights = new SparseArray<>();
+    // Always access on handler thread or need to lock this for synchronization.
+    private final SparseArray<KeyboardBacklightState> mKeyboardBacklights = new SparseArray<>(1);
+    // Maintains state if all backlights should be on or turned off
+    private boolean mIsBacklightOn = false;
+    // Maintains state if currently the device is interactive or not
+    private boolean mIsInteractive = true;
 
     // List of currently registered keyboard backlight listeners
     @GuardedBy("mKeyboardBacklightListenerRecords")
@@ -83,8 +98,8 @@
         // device brightness range to [0-255]
         // Levels are: 0, 25, 51, ..., 255
         for (int i = 0; i <= NUM_BRIGHTNESS_CHANGE_STEPS; i++) {
-            BRIGHTNESS_LEVELS.add(
-                    (int) Math.floor(((float) i * MAX_BRIGHTNESS) / NUM_BRIGHTNESS_CHANGE_STEPS));
+            BRIGHTNESS_VALUE_FOR_LEVEL[i] = (int) Math.floor(
+                    ((float) i * MAX_BRIGHTNESS) / NUM_BRIGHTNESS_CHANGE_STEPS);
         }
     }
 
@@ -96,57 +111,64 @@
         mHandler = new Handler(looper, this::handleMessage);
     }
 
-    void systemRunning() {
+    @Override
+    public void systemRunning() {
         InputManager inputManager = Objects.requireNonNull(
                 mContext.getSystemService(InputManager.class));
         inputManager.registerInputDeviceListener(this, mHandler);
-        // Circle through all the already added input devices
-        for (int deviceId : inputManager.getInputDeviceIds()) {
-            onInputDeviceAdded(deviceId);
-        }
+
+        Message msg = Message.obtain(mHandler, MSG_UPDATE_EXISTING_DEVICES,
+                inputManager.getInputDeviceIds());
+        mHandler.sendMessage(msg);
     }
 
+    @Override
     public void incrementKeyboardBacklight(int deviceId) {
         Message msg = Message.obtain(mHandler, MSG_INCREMENT_KEYBOARD_BACKLIGHT, deviceId);
         mHandler.sendMessage(msg);
     }
 
+    @Override
     public void decrementKeyboardBacklight(int deviceId) {
         Message msg = Message.obtain(mHandler, MSG_DECREMENT_KEYBOARD_BACKLIGHT, deviceId);
         mHandler.sendMessage(msg);
     }
 
+    @Override
+    public void notifyUserActivity() {
+        Message msg = Message.obtain(mHandler, MSG_NOTIFY_USER_ACTIVITY);
+        mHandler.sendMessage(msg);
+    }
+
+    @Override
+    public void onInteractiveChanged(boolean isInteractive) {
+        Message msg = Message.obtain(mHandler, MSG_INTERACTIVE_STATE_CHANGED, isInteractive);
+        mHandler.sendMessage(msg);
+    }
+
     private void updateKeyboardBacklight(int deviceId, Direction direction) {
         InputDevice inputDevice = getInputDevice(deviceId);
-        Light keyboardBacklight = mKeyboardBacklights.get(deviceId);
-        if (inputDevice == null || keyboardBacklight == null) {
+        KeyboardBacklightState state = mKeyboardBacklights.get(deviceId);
+        if (inputDevice == null || state == null) {
             return;
         }
+        Light keyboardBacklight = state.mLight;
         // Follow preset levels of brightness defined in BRIGHTNESS_LEVELS
-        int currBrightness = BRIGHTNESS_LEVELS.floor(Color.alpha(
-                mNative.getLightColor(deviceId, keyboardBacklight.getId())));
-        int newBrightness;
+        final int currBrightnessLevel = state.mBrightnessLevel;
+        final int newBrightnessLevel;
         if (direction == Direction.DIRECTION_UP) {
-            newBrightness = currBrightness != MAX_BRIGHTNESS ? BRIGHTNESS_LEVELS.higher(
-                    currBrightness) : currBrightness;
+            newBrightnessLevel = Math.min(currBrightnessLevel + 1, NUM_BRIGHTNESS_CHANGE_STEPS);
         } else {
-            newBrightness = currBrightness != 0 ? BRIGHTNESS_LEVELS.lower(currBrightness)
-                    : currBrightness;
+            newBrightnessLevel = Math.max(currBrightnessLevel - 1, 0);
         }
-        @ColorInt int newColor = Color.argb(newBrightness, 0, 0, 0);
-        mNative.setLightColor(deviceId, keyboardBacklight.getId(), newColor);
-        if (DEBUG) {
-            Slog.d(TAG, "Changing brightness from " + currBrightness + " to " + newBrightness);
-        }
-
-        notifyKeyboardBacklightChanged(deviceId, BRIGHTNESS_LEVELS.headSet(newBrightness).size(),
-                true/* isTriggeredByKeyPress */);
+        updateBacklightState(deviceId, keyboardBacklight, newBrightnessLevel,
+                true /* isTriggeredByKeyPress */);
 
         synchronized (mDataStore) {
             try {
                 mDataStore.setKeyboardBacklightBrightness(inputDevice.getDescriptor(),
                         keyboardBacklight.getId(),
-                        newBrightness);
+                        BRIGHTNESS_VALUE_FOR_LEVEL[newBrightnessLevel]);
             } finally {
                 mDataStore.saveIfNeeded();
             }
@@ -159,23 +181,83 @@
             brightness = mDataStore.getKeyboardBacklightBrightness(
                     inputDevice.getDescriptor(), keyboardBacklight.getId());
         }
-        if (!brightness.isEmpty()) {
-            mNative.setLightColor(inputDevice.getId(), keyboardBacklight.getId(),
-                    Color.argb(brightness.getAsInt(), 0, 0, 0));
+        if (brightness.isPresent()) {
+            int brightnessValue = Math.max(0, Math.min(MAX_BRIGHTNESS, brightness.getAsInt()));
+            int brightnessLevel = Arrays.binarySearch(BRIGHTNESS_VALUE_FOR_LEVEL, brightnessValue);
+            updateBacklightState(inputDevice.getId(), keyboardBacklight, brightnessLevel,
+                    false /* isTriggeredByKeyPress */);
             if (DEBUG) {
                 Slog.d(TAG, "Restoring brightness level " + brightness.getAsInt());
             }
         }
     }
 
+    private void handleUserActivity() {
+        // Ignore user activity if device is not interactive. When device becomes interactive, we
+        // will send another user activity to turn backlight on.
+        if (!mIsInteractive) {
+            return;
+        }
+        if (!mIsBacklightOn) {
+            mIsBacklightOn = true;
+            for (int i = 0; i < mKeyboardBacklights.size(); i++) {
+                int deviceId = mKeyboardBacklights.keyAt(i);
+                KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
+                updateBacklightState(deviceId, state.mLight, state.mBrightnessLevel,
+                        false /* isTriggeredByKeyPress */);
+            }
+        }
+        mHandler.removeMessages(MSG_NOTIFY_USER_INACTIVITY);
+        mHandler.sendEmptyMessageAtTime(MSG_NOTIFY_USER_INACTIVITY,
+                SystemClock.uptimeMillis() + USER_INACTIVITY_THRESHOLD_MILLIS);
+    }
+
+    private void handleUserInactivity() {
+        if (mIsBacklightOn) {
+            mIsBacklightOn = false;
+            for (int i = 0; i < mKeyboardBacklights.size(); i++) {
+                int deviceId = mKeyboardBacklights.keyAt(i);
+                KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
+                updateBacklightState(deviceId, state.mLight, state.mBrightnessLevel,
+                        false /* isTriggeredByKeyPress */);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    public void handleInteractiveStateChange(boolean isInteractive) {
+        // Interactive state changes should force the keyboard to turn on/off irrespective of
+        // whether time out occurred or not.
+        mIsInteractive = isInteractive;
+        if (isInteractive) {
+            handleUserActivity();
+        } else {
+            handleUserInactivity();
+        }
+    }
+
     private boolean handleMessage(Message msg) {
         switch (msg.what) {
+            case MSG_UPDATE_EXISTING_DEVICES:
+                for (int deviceId : (int[]) msg.obj) {
+                    onInputDeviceAdded(deviceId);
+                }
+                return true;
             case MSG_INCREMENT_KEYBOARD_BACKLIGHT:
                 updateKeyboardBacklight((int) msg.obj, Direction.DIRECTION_UP);
                 return true;
             case MSG_DECREMENT_KEYBOARD_BACKLIGHT:
                 updateKeyboardBacklight((int) msg.obj, Direction.DIRECTION_DOWN);
                 return true;
+            case MSG_NOTIFY_USER_ACTIVITY:
+                handleUserActivity();
+                return true;
+            case MSG_NOTIFY_USER_INACTIVITY:
+                handleUserInactivity();
+                return true;
+            case MSG_INTERACTIVE_STATE_CHANGED:
+                handleInteractiveStateChange((boolean) msg.obj);
+                return true;
         }
         return false;
     }
@@ -204,12 +286,12 @@
             mKeyboardBacklights.remove(deviceId);
             return;
         }
-        final Light oldBacklight = mKeyboardBacklights.get(deviceId);
-        if (oldBacklight != null && oldBacklight.getId() == keyboardBacklight.getId()) {
+        KeyboardBacklightState state = mKeyboardBacklights.get(deviceId);
+        if (state != null && state.mLight.getId() == keyboardBacklight.getId()) {
             return;
         }
         // The keyboard backlight was added or changed.
-        mKeyboardBacklights.put(deviceId, keyboardBacklight);
+        mKeyboardBacklights.put(deviceId, new KeyboardBacklightState(keyboardBacklight));
         restoreBacklightBrightness(inputDevice, keyboardBacklight);
     }
 
@@ -232,6 +314,7 @@
 
     /** Register the keyboard backlight listener for a process. */
     @BinderThread
+    @Override
     public void registerKeyboardBacklightListener(IKeyboardBacklightListener listener,
             int pid) {
         synchronized (mKeyboardBacklightListenerRecords) {
@@ -252,6 +335,7 @@
 
     /** Unregister the keyboard backlight listener for a process. */
     @BinderThread
+    @Override
     public void unregisterKeyboardBacklightListener(IKeyboardBacklightListener listener,
             int pid) {
         synchronized (mKeyboardBacklightListenerRecords) {
@@ -269,13 +353,29 @@
         }
     }
 
-    private void notifyKeyboardBacklightChanged(int deviceId, int currentBacklightLevel,
+    private void updateBacklightState(int deviceId, Light light, int brightnessLevel,
             boolean isTriggeredByKeyPress) {
+        KeyboardBacklightState state = mKeyboardBacklights.get(deviceId);
+        if (state == null) {
+            return;
+        }
+
+        mNative.setLightColor(deviceId, light.getId(),
+                mIsBacklightOn ? Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[brightnessLevel], 0, 0, 0)
+                        : 0);
+        if (DEBUG) {
+            Slog.d(TAG, "Changing state from " + state.mBrightnessLevel + " to " + brightnessLevel
+                    + "(isBacklightOn = " + mIsBacklightOn + ")");
+        }
+        state.mBrightnessLevel = brightnessLevel;
+
         synchronized (mKeyboardBacklightListenerRecords) {
             for (int i = 0; i < mKeyboardBacklightListenerRecords.size(); i++) {
+                IKeyboardBacklightState callbackState = new IKeyboardBacklightState();
+                callbackState.brightnessLevel = brightnessLevel;
+                callbackState.maxBrightnessLevel = NUM_BRIGHTNESS_CHANGE_STEPS;
                 mKeyboardBacklightListenerRecords.valueAt(i).notifyKeyboardBacklightChanged(
-                        deviceId, new KeyboardBacklightState(currentBacklightLevel),
-                        isTriggeredByKeyPress);
+                        deviceId, callbackState, isTriggeredByKeyPress);
             }
         }
     }
@@ -286,13 +386,17 @@
         }
     }
 
-    void dump(PrintWriter pw) {
+    @Override
+    public void dump(PrintWriter pw) {
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
-        ipw.println(TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights");
+        ipw.println(
+                TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights, isBacklightOn = "
+                        + mIsBacklightOn);
+
         ipw.increaseIndent();
         for (int i = 0; i < mKeyboardBacklights.size(); i++) {
-            Light light = mKeyboardBacklights.get(i);
-            ipw.println(i + ": { id: " + light.getId() + ", name: " + light.getName() + " }");
+            KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
+            ipw.println(i + ": " + state.toString());
         }
         ipw.decreaseIndent();
     }
@@ -327,17 +431,18 @@
         }
     }
 
-    private static class KeyboardBacklightState extends IKeyboardBacklightState {
+    private static class KeyboardBacklightState {
+        private final Light mLight;
+        private int mBrightnessLevel;
 
-        KeyboardBacklightState(int brightnessLevel) {
-            this.brightnessLevel = brightnessLevel;
-            this.maxBrightnessLevel = NUM_BRIGHTNESS_CHANGE_STEPS;
+        KeyboardBacklightState(Light light) {
+            mLight = light;
         }
 
         @Override
         public String toString() {
-            return "KeyboardBacklightState{brightnessLevel=" + brightnessLevel
-                    + ", maxBrightnessLevel=" + maxBrightnessLevel
+            return "KeyboardBacklightState{Light=" + mLight.getId()
+                    + ", BrightnessLevel=" + mBrightnessLevel
                     + "}";
         }
     }
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index b8eb901..9e8b9f1 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -37,6 +37,8 @@
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
 import android.hardware.input.KeyboardLayout;
+import android.icu.lang.UScript;
+import android.icu.util.ULocale;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.LocaleList;
@@ -45,6 +47,8 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.view.InputDevice;
@@ -54,6 +58,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.XmlUtils;
@@ -63,10 +68,12 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Objects;
 import java.util.stream.Stream;
 
@@ -94,10 +101,18 @@
     @GuardedBy("mDataStore")
     private final PersistentDataStore mDataStore;
     private final Handler mHandler;
+
     private final List<InputDevice> mKeyboardsWithMissingLayouts = new ArrayList<>();
     private boolean mKeyboardLayoutNotificationShown = false;
     private Toast mSwitchedKeyboardLayoutToast;
 
+    // This cache stores "best-matched" layouts so that we don't need to run the matching
+    // algorithm repeatedly.
+    @GuardedBy("mKeyboardLayoutCache")
+    private final Map<String, String> mKeyboardLayoutCache = new ArrayMap<>();
+    @Nullable
+    private ImeInfo mCurrentImeInfo;
+
     KeyboardLayoutManager(Context context, NativeInputManagerService nativeService,
             PersistentDataStore dataStore, Looper looper) {
         mContext = context;
@@ -139,8 +154,10 @@
 
     @Override
     public void onInputDeviceRemoved(int deviceId) {
-        mKeyboardsWithMissingLayouts.removeIf(device -> device.getId() == deviceId);
-        maybeUpdateNotification();
+        if (!useNewSettingsUi()) {
+            mKeyboardsWithMissingLayouts.removeIf(device -> device.getId() == deviceId);
+            maybeUpdateNotification();
+        }
     }
 
     @Override
@@ -149,18 +166,21 @@
         if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
             return;
         }
-        synchronized (mDataStore) {
-            String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
-            if (layout == null) {
-                layout = getDefaultKeyboardLayout(inputDevice);
-                if (layout != null) {
-                    setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
-                } else {
-                    mKeyboardsWithMissingLayouts.add(inputDevice);
+        if (!useNewSettingsUi()) {
+            synchronized (mDataStore) {
+                String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
+                if (layout == null) {
+                    layout = getDefaultKeyboardLayout(inputDevice);
+                    if (layout != null) {
+                        setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
+                    } else {
+                        mKeyboardsWithMissingLayouts.add(inputDevice);
+                    }
                 }
+                maybeUpdateNotification();
             }
-            maybeUpdateNotification();
         }
+        // TODO(b/259530132): Show notification for new Settings UI
     }
 
     private String getDefaultKeyboardLayout(final InputDevice inputDevice) {
@@ -244,6 +264,12 @@
             }
         }
 
+        synchronized (mKeyboardLayoutCache) {
+            // Invalidate the cache: With packages being installed/removed, existing cache of
+            // auto-selected layout might not be the best layouts anymore.
+            mKeyboardLayoutCache.clear();
+        }
+
         // Reload keyboard layouts.
         reloadKeyboardLayouts();
     }
@@ -256,6 +282,9 @@
 
     public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
             final InputDeviceIdentifier identifier) {
+        if (useNewSettingsUi()) {
+            return new KeyboardLayout[0];
+        }
         final String[] enabledLayoutDescriptors =
                 getEnabledKeyboardLayoutsForInputDevice(identifier);
         final ArrayList<KeyboardLayout> enabledLayouts =
@@ -296,6 +325,7 @@
                 KeyboardLayout[]::new);
     }
 
+    @Nullable
     public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
         Objects.requireNonNull(keyboardLayoutDescriptor,
                 "keyboardLayoutDescriptor must not be null");
@@ -434,21 +464,25 @@
         return LocaleList.forLanguageTags(languageTags.replace('|', ','));
     }
 
-    /**
-     * Builds a layout descriptor for the vendor/product. This returns the
-     * descriptor for ids that aren't useful (such as the default 0, 0).
-     */
-    private String getLayoutDescriptor(InputDeviceIdentifier identifier) {
+    private static String getLayoutDescriptor(@NonNull InputDeviceIdentifier identifier) {
         Objects.requireNonNull(identifier, "identifier must not be null");
         Objects.requireNonNull(identifier.getDescriptor(), "descriptor must not be null");
 
         if (identifier.getVendorId() == 0 && identifier.getProductId() == 0) {
             return identifier.getDescriptor();
         }
+        // If vendor id and product id is available, use it as keys. This allows us to have the
+        // same setup for all keyboards with same product and vendor id. i.e. User can swap 2
+        // identical keyboards and still get the same setup.
         return "vendor:" + identifier.getVendorId() + ",product:" + identifier.getProductId();
     }
 
+    @Nullable
     public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
+        if (useNewSettingsUi()) {
+            Slog.e(TAG, "getCurrentKeyboardLayoutForInputDevice API not supported");
+            return null;
+        }
         String key = getLayoutDescriptor(identifier);
         synchronized (mDataStore) {
             String layout;
@@ -468,9 +502,13 @@
 
     public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
+        if (useNewSettingsUi()) {
+            Slog.e(TAG, "setCurrentKeyboardLayoutForInputDevice API not supported");
+            return;
+        }
+
         Objects.requireNonNull(keyboardLayoutDescriptor,
                 "keyboardLayoutDescriptor must not be null");
-
         String key = getLayoutDescriptor(identifier);
         synchronized (mDataStore) {
             try {
@@ -489,6 +527,10 @@
     }
 
     public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
+        if (useNewSettingsUi()) {
+            Slog.e(TAG, "getEnabledKeyboardLayoutsForInputDevice API not supported");
+            return new String[0];
+        }
         String key = getLayoutDescriptor(identifier);
         synchronized (mDataStore) {
             String[] layouts = mDataStore.getKeyboardLayouts(key);
@@ -502,6 +544,10 @@
 
     public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
+        if (useNewSettingsUi()) {
+            Slog.e(TAG, "addKeyboardLayoutForInputDevice API not supported");
+            return;
+        }
         Objects.requireNonNull(keyboardLayoutDescriptor,
                 "keyboardLayoutDescriptor must not be null");
 
@@ -525,6 +571,10 @@
 
     public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
+        if (useNewSettingsUi()) {
+            Slog.e(TAG, "removeKeyboardLayoutForInputDevice API not supported");
+            return;
+        }
         Objects.requireNonNull(keyboardLayoutDescriptor,
                 "keyboardLayoutDescriptor must not be null");
 
@@ -551,31 +601,11 @@
         }
     }
 
-    public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
-            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @Nullable InputMethodSubtype imeSubtype) {
-        // TODO(b/259530132): Implement the new keyboard layout API: Returning non-IME specific
-        //  layout for now.
-        return getCurrentKeyboardLayoutForInputDevice(identifier);
-    }
-
-    public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
-            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @Nullable InputMethodSubtype imeSubtype, String keyboardLayoutDescriptor) {
-        // TODO(b/259530132): Implement the new keyboard layout API: setting non-IME specific
-        //  layout for now.
-        setCurrentKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor);
-    }
-
-    public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
-            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @Nullable InputMethodSubtype imeSubtype) {
-        // TODO(b/259530132): Implement the new keyboard layout API: Returning list of all
-        //  layouts for now.
-        return getKeyboardLayouts();
-    }
-
     public void switchKeyboardLayout(int deviceId, int direction) {
+        if (useNewSettingsUi()) {
+            Slog.e(TAG, "switchKeyboardLayout API not supported");
+            return;
+        }
         mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
     }
 
@@ -616,8 +646,21 @@
         }
     }
 
+    @Nullable
     public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
-        String keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
+        String keyboardLayoutDescriptor;
+        if (useNewSettingsUi()) {
+            if (mCurrentImeInfo == null) {
+                // Haven't received onInputMethodSubtypeChanged() callback from IMMS. Will reload
+                // keyboard layouts once we receive the callback.
+                return null;
+            }
+
+            keyboardLayoutDescriptor = getKeyboardLayoutForInputDeviceInternal(identifier,
+                    mCurrentImeInfo);
+        } else {
+            keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
+        }
         if (keyboardLayoutDescriptor == null) {
             return null;
         }
@@ -640,6 +683,287 @@
         return result;
     }
 
+    @Nullable
+    public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+            @Nullable InputMethodSubtype imeSubtype) {
+        if (!useNewSettingsUi()) {
+            Slog.e(TAG, "getKeyboardLayoutForInputDevice() API not supported");
+            return null;
+        }
+        InputMethodSubtypeHandle subtypeHandle = InputMethodSubtypeHandle.of(imeInfo, imeSubtype);
+        String layout = getKeyboardLayoutForInputDeviceInternal(identifier,
+                new ImeInfo(userId, subtypeHandle, imeSubtype));
+        if (DEBUG) {
+            Slog.d(TAG, "getKeyboardLayoutForInputDevice() " + identifier.toString() + ", userId : "
+                    + userId + ", subtypeHandle = " + subtypeHandle + " -> " + layout);
+        }
+        return layout;
+    }
+
+    public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+            @Nullable InputMethodSubtype imeSubtype,
+            String keyboardLayoutDescriptor) {
+        if (!useNewSettingsUi()) {
+            Slog.e(TAG, "setKeyboardLayoutForInputDevice() API not supported");
+            return;
+        }
+        Objects.requireNonNull(keyboardLayoutDescriptor,
+                "keyboardLayoutDescriptor must not be null");
+        String key = createLayoutKey(identifier, userId,
+                InputMethodSubtypeHandle.of(imeInfo, imeSubtype));
+        synchronized (mDataStore) {
+            try {
+                // Key for storing into data store = <device descriptor>,<userId>,<subtypeHandle>
+                if (mDataStore.setKeyboardLayout(getLayoutDescriptor(identifier), key,
+                        keyboardLayoutDescriptor)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "setKeyboardLayoutForInputDevice() " + identifier
+                                + " key: " + key
+                                + " keyboardLayoutDescriptor: " + keyboardLayoutDescriptor);
+                    }
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+    }
+
+    public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
+            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+            @Nullable InputMethodSubtype imeSubtype) {
+        if (!useNewSettingsUi()) {
+            Slog.e(TAG, "getKeyboardLayoutListForInputDevice() API not supported");
+            return new KeyboardLayout[0];
+        }
+        return getKeyboardLayoutListForInputDeviceInternal(identifier, new ImeInfo(userId,
+                InputMethodSubtypeHandle.of(imeInfo, imeSubtype), imeSubtype));
+    }
+
+    private KeyboardLayout[] getKeyboardLayoutListForInputDeviceInternal(
+            InputDeviceIdentifier identifier, ImeInfo imeInfo) {
+        String key = createLayoutKey(identifier, imeInfo.mUserId, imeInfo.mImeSubtypeHandle);
+
+        // Fetch user selected layout and always include it in layout list.
+        String userSelectedLayout;
+        synchronized (mDataStore) {
+            userSelectedLayout = mDataStore.getKeyboardLayout(getLayoutDescriptor(identifier), key);
+        }
+
+        final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>();
+        String imeLanguageTag;
+        if (imeInfo.mImeSubtype == null) {
+            imeLanguageTag = "";
+        } else {
+            ULocale imeLocale = imeInfo.mImeSubtype.getPhysicalKeyboardHintLanguageTag();
+            imeLanguageTag = imeLocale != null ? imeLocale.toLanguageTag()
+                    : imeInfo.mImeSubtype.getCanonicalizedLanguageTag();
+        }
+
+        visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
+            boolean mDeviceSpecificLayoutAvailable;
+
+            @Override
+            public void visitKeyboardLayout(Resources resources,
+                    int keyboardLayoutResId, KeyboardLayout layout) {
+                // Next find any potential layouts that aren't yet enabled for the device. For
+                // devices that have special layouts we assume there's a reason that the generic
+                // layouts don't work for them, so we don't want to return them since it's likely
+                // to result in a poor user experience.
+                if (layout.getVendorId() == identifier.getVendorId()
+                        && layout.getProductId() == identifier.getProductId()) {
+                    if (!mDeviceSpecificLayoutAvailable) {
+                        mDeviceSpecificLayoutAvailable = true;
+                        potentialLayouts.clear();
+                    }
+                    potentialLayouts.add(layout);
+                } else if (layout.getVendorId() == -1 && layout.getProductId() == -1
+                        && !mDeviceSpecificLayoutAvailable && isLayoutCompatibleWithLanguageTag(
+                        layout, imeLanguageTag)) {
+                    potentialLayouts.add(layout);
+                } else if (layout.getDescriptor().equals(userSelectedLayout)) {
+                    potentialLayouts.add(layout);
+                }
+            }
+        });
+        // Sort the Keyboard layouts. This is done first by priority then by label. So, system
+        // layouts will come above 3rd party layouts.
+        Collections.sort(potentialLayouts);
+        return potentialLayouts.toArray(new KeyboardLayout[0]);
+    }
+
+    public void onInputMethodSubtypeChanged(@UserIdInt int userId,
+            @Nullable InputMethodSubtypeHandle subtypeHandle,
+            @Nullable InputMethodSubtype subtype) {
+        if (!useNewSettingsUi()) {
+            Slog.e(TAG, "onInputMethodSubtypeChanged() API not supported");
+            return;
+        }
+        if (subtypeHandle == null) {
+            if (DEBUG) {
+                Slog.d(TAG, "No InputMethod is running, ignoring change");
+            }
+            return;
+        }
+        if (mCurrentImeInfo == null || !subtypeHandle.equals(mCurrentImeInfo.mImeSubtypeHandle)
+                || mCurrentImeInfo.mUserId != userId) {
+            mCurrentImeInfo = new ImeInfo(userId, subtypeHandle, subtype);
+            mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+            if (DEBUG) {
+                Slog.d(TAG, "InputMethodSubtype changed: userId=" + userId
+                        + " subtypeHandle=" + subtypeHandle);
+            }
+        }
+    }
+
+    @Nullable
+    private String getKeyboardLayoutForInputDeviceInternal(InputDeviceIdentifier identifier,
+            ImeInfo imeInfo) {
+        InputDevice inputDevice = getInputDevice(identifier);
+        if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
+            return null;
+        }
+        String key = createLayoutKey(identifier, imeInfo.mUserId, imeInfo.mImeSubtypeHandle);
+        String layout;
+        synchronized (mDataStore) {
+            layout = mDataStore.getKeyboardLayout(getLayoutDescriptor(identifier), key);
+        }
+        if (layout == null) {
+            synchronized (mKeyboardLayoutCache) {
+                // Check Auto-selected layout cache to see if layout had been previously selected
+                if (mKeyboardLayoutCache.containsKey(key)) {
+                    layout = mKeyboardLayoutCache.get(key);
+                } else {
+                    // NOTE: This list is already filtered based on IME Script code
+                    KeyboardLayout[] layoutList = getKeyboardLayoutListForInputDeviceInternal(
+                            identifier, imeInfo);
+                    // Call auto-matching algorithm to find the best matching layout
+                    layout = getDefaultKeyboardLayoutBasedOnImeInfo(inputDevice, imeInfo,
+                            layoutList);
+                    mKeyboardLayoutCache.put(key, layout);
+                }
+            }
+        }
+        return layout;
+    }
+
+    @Nullable
+    private static String getDefaultKeyboardLayoutBasedOnImeInfo(InputDevice inputDevice,
+            ImeInfo imeInfo, KeyboardLayout[] layoutList) {
+        if (imeInfo.mImeSubtypeHandle == null) {
+            return null;
+        }
+
+        Arrays.sort(layoutList);
+
+        // Check <VendorID, ProductID> matching for explicitly declared custom KCM files.
+        for (KeyboardLayout layout : layoutList) {
+            if (layout.getVendorId() == inputDevice.getVendorId()
+                    && layout.getProductId() == inputDevice.getProductId()) {
+                if (DEBUG) {
+                    Slog.d(TAG,
+                            "getDefaultKeyboardLayoutBasedOnImeInfo() : Layout found based on "
+                                    + "vendor and product Ids. " + inputDevice.getIdentifier()
+                                    + " : " + layout.getDescriptor());
+                }
+                return layout.getDescriptor();
+            }
+        }
+
+        // Check layout type, language tag information from InputDevice for matching
+        String inputLanguageTag = inputDevice.getKeyboardLanguageTag();
+        if (inputLanguageTag != null) {
+            String layoutDesc = getMatchingLayoutForProvidedLanguageTagAndLayoutType(layoutList,
+                    inputLanguageTag, inputDevice.getKeyboardLayoutType());
+
+            if (layoutDesc != null) {
+                if (DEBUG) {
+                    Slog.d(TAG,
+                            "getDefaultKeyboardLayoutBasedOnImeInfo() : Layout found based on "
+                                    + "HW information (Language tag and Layout type). "
+                                    + inputDevice.getIdentifier() + " : " + layoutDesc);
+                }
+                return layoutDesc;
+            }
+        }
+
+        InputMethodSubtype subtype = imeInfo.mImeSubtype;
+        // Can't auto select layout based on IME if subtype or language tag is null
+        if (subtype == null) {
+            return null;
+        }
+
+        // Check layout type, language tag information from IME for matching
+        ULocale pkLocale = subtype.getPhysicalKeyboardHintLanguageTag();
+        String pkLanguageTag =
+                pkLocale != null ? pkLocale.toLanguageTag() : subtype.getCanonicalizedLanguageTag();
+        String layoutDesc = getMatchingLayoutForProvidedLanguageTagAndLayoutType(layoutList,
+                pkLanguageTag, subtype.getPhysicalKeyboardHintLayoutType());
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "getDefaultKeyboardLayoutBasedOnImeInfo() : Layout found based on "
+                            + "IME locale matching. " + inputDevice.getIdentifier() + " : "
+                            + layoutDesc);
+        }
+        return layoutDesc;
+    }
+
+    @Nullable
+    private static String getMatchingLayoutForProvidedLanguageTagAndLayoutType(
+            KeyboardLayout[] layoutList, @NonNull String languageTag, @Nullable String layoutType) {
+        if (layoutType == null || !KeyboardLayout.isLayoutTypeValid(layoutType)) {
+            layoutType = KeyboardLayout.LAYOUT_TYPE_UNDEFINED;
+        }
+        List<KeyboardLayout> layoutsFilteredByLayoutType = new ArrayList<>();
+        for (KeyboardLayout layout : layoutList) {
+            if (layout.getLayoutType().equals(layoutType)) {
+                layoutsFilteredByLayoutType.add(layout);
+            }
+        }
+        String layoutDesc = getMatchingLayoutForProvidedLanguageTag(layoutsFilteredByLayoutType,
+                languageTag);
+        if (layoutDesc != null) {
+            return layoutDesc;
+        }
+
+        return getMatchingLayoutForProvidedLanguageTag(Arrays.asList(layoutList), languageTag);
+    }
+
+    @Nullable
+    private static String getMatchingLayoutForProvidedLanguageTag(List<KeyboardLayout> layoutList,
+            @NonNull String languageTag) {
+        Locale locale = Locale.forLanguageTag(languageTag);
+        String layoutMatchingLanguage = null;
+        String layoutMatchingLanguageAndCountry = null;
+
+        for (KeyboardLayout layout : layoutList) {
+            final LocaleList locales = layout.getLocales();
+            for (int i = 0; i < locales.size(); i++) {
+                final Locale l = locales.get(i);
+                if (l == null) {
+                    continue;
+                }
+                if (l.getLanguage().equals(locale.getLanguage())) {
+                    if (layoutMatchingLanguage == null) {
+                        layoutMatchingLanguage = layout.getDescriptor();
+                    }
+                    if (l.getCountry().equals(locale.getCountry())) {
+                        if (layoutMatchingLanguageAndCountry == null) {
+                            layoutMatchingLanguageAndCountry = layout.getDescriptor();
+                        }
+                        if (l.getVariant().equals(locale.getVariant())) {
+                            return layout.getDescriptor();
+                        }
+                    }
+                }
+            }
+        }
+        return layoutMatchingLanguageAndCountry != null
+                    ? layoutMatchingLanguageAndCountry : layoutMatchingLanguage;
+    }
+
     private void reloadKeyboardLayouts() {
         if (DEBUG) {
             Slog.d(TAG, "Reloading keyboard layouts.");
@@ -734,11 +1058,65 @@
         }
     }
 
+    private boolean useNewSettingsUi() {
+        return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI);
+    }
+
+    @Nullable
     private InputDevice getInputDevice(int deviceId) {
         InputManager inputManager = mContext.getSystemService(InputManager.class);
         return inputManager != null ? inputManager.getInputDevice(deviceId) : null;
     }
 
+    @Nullable
+    private InputDevice getInputDevice(InputDeviceIdentifier identifier) {
+        InputManager inputManager = mContext.getSystemService(InputManager.class);
+        return inputManager != null ? inputManager.getInputDeviceByDescriptor(
+                identifier.getDescriptor()) : null;
+    }
+
+    private static String createLayoutKey(InputDeviceIdentifier identifier, int userId,
+            @NonNull InputMethodSubtypeHandle subtypeHandle) {
+        Objects.requireNonNull(subtypeHandle, "subtypeHandle must not be null");
+        return "layoutDescriptor:" + getLayoutDescriptor(identifier) + ",userId:" + userId
+                + ",subtypeHandle:" + subtypeHandle.toStringHandle();
+    }
+
+    private static boolean isLayoutCompatibleWithLanguageTag(KeyboardLayout layout,
+            @NonNull String languageTag) {
+        final int[] scriptsFromLanguageTag = UScript.getCode(Locale.forLanguageTag(languageTag));
+        if (scriptsFromLanguageTag.length == 0) {
+            // If no scripts inferred from languageTag then allowing the layout
+            return true;
+        }
+        LocaleList locales = layout.getLocales();
+        if (locales.isEmpty()) {
+            // KCM file doesn't have an associated language tag. This can be from
+            // a 3rd party app so need to include it as a potential layout.
+            return true;
+        }
+        for (int i = 0; i < locales.size(); i++) {
+            final Locale locale = locales.get(i);
+            if (locale == null) {
+                continue;
+            }
+            int[] scripts = UScript.getCode(locale);
+            if (scripts != null && haveCommonValue(scripts, scriptsFromLanguageTag)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean haveCommonValue(int[] arr1, int[] arr2) {
+        for (int a1 : arr1) {
+            for (int a2 : arr2) {
+                if (a1 == a2) return true;
+            }
+        }
+        return false;
+    }
+
     private static final class KeyboardLayoutDescriptor {
         public String packageName;
         public String receiverName;
@@ -767,6 +1145,19 @@
         }
     }
 
+    private static class ImeInfo {
+        @UserIdInt int mUserId;
+        @NonNull InputMethodSubtypeHandle mImeSubtypeHandle;
+        @Nullable InputMethodSubtype mImeSubtype;
+
+        ImeInfo(@UserIdInt int userId, @NonNull InputMethodSubtypeHandle imeSubtypeHandle,
+                @Nullable InputMethodSubtype imeSubtype) {
+            mUserId = userId;
+            mImeSubtypeHandle = imeSubtypeHandle;
+            mImeSubtype = imeSubtype;
+        }
+    }
+
     private interface KeyboardLayoutVisitor {
         void visitKeyboardLayout(Resources resources,
                 int keyboardLayoutResId, KeyboardLayout layout);
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index 375377a7..a2b18362 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.hardware.input.TouchCalibration;
+import android.util.ArrayMap;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseIntArray;
@@ -42,6 +43,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.OptionalInt;
@@ -121,6 +123,7 @@
         return false;
     }
 
+    @Nullable
     public String getCurrentKeyboardLayout(String inputDeviceDescriptor) {
         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
         return state != null ? state.getCurrentKeyboardLayout() : null;
@@ -136,6 +139,22 @@
         return false;
     }
 
+    @Nullable
+    public String getKeyboardLayout(String inputDeviceDescriptor, String key) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
+        return state != null ? state.getKeyboardLayout(key) : null;
+    }
+
+    public boolean setKeyboardLayout(String inputDeviceDescriptor, String key,
+            String keyboardLayoutDescriptor) {
+        InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
+        if (state.setKeyboardLayout(key, keyboardLayoutDescriptor)) {
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
     public String[] getKeyboardLayouts(String inputDeviceDescriptor) {
         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
         if (state == null) {
@@ -387,6 +406,8 @@
         private final ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
         private final SparseIntArray mKeyboardBacklightBrightnessMap = new SparseIntArray();
 
+        private final Map<String, String> mKeyboardLayoutMap = new ArrayMap<>();
+
         public TouchCalibration getTouchCalibration(int surfaceRotation) {
             try {
                 return mTouchCalibration[surfaceRotation];
@@ -410,6 +431,15 @@
         }
 
         @Nullable
+        public String getKeyboardLayout(String key) {
+            return mKeyboardLayoutMap.get(key);
+        }
+
+        public boolean setKeyboardLayout(String key, String keyboardLayout) {
+            return !Objects.equals(mKeyboardLayoutMap.put(key, keyboardLayout), keyboardLayout);
+        }
+
+        @Nullable
         public String getCurrentKeyboardLayout() {
             return mCurrentKeyboardLayout;
         }
@@ -507,6 +537,18 @@
                     changed = true;
                 }
             }
+            List<String> removedEntries = new ArrayList<>();
+            for (String key : mKeyboardLayoutMap.keySet()) {
+                if (!availableKeyboardLayouts.contains(mKeyboardLayoutMap.get(key))) {
+                    removedEntries.add(key);
+                }
+            }
+            if (!removedEntries.isEmpty()) {
+                for (String key : removedEntries) {
+                    mKeyboardLayoutMap.remove(key);
+                }
+                changed = true;
+            }
             return changed;
         }
 
@@ -534,6 +576,18 @@
                         }
                         mCurrentKeyboardLayout = descriptor;
                     }
+                } else if (parser.getName().equals("keyed-keyboard-layout")) {
+                    String key = parser.getAttributeValue(null, "key");
+                    if (key == null) {
+                        throw new XmlPullParserException(
+                                "Missing key attribute on keyed-keyboard-layout.");
+                    }
+                    String layout = parser.getAttributeValue(null, "layout");
+                    if (layout == null) {
+                        throw new XmlPullParserException(
+                                "Missing layout attribute on keyed-keyboard-layout.");
+                    }
+                    mKeyboardLayoutMap.put(key, layout);
                 } else if (parser.getName().equals("light-info")) {
                     int lightId = parser.getAttributeInt(null, "light-id");
                     int lightBrightness = parser.getAttributeInt(null, "light-brightness");
@@ -607,6 +661,13 @@
                 serializer.endTag(null, "keyboard-layout");
             }
 
+            for (String key : mKeyboardLayoutMap.keySet()) {
+                serializer.startTag(null, "keyed-keyboard-layout");
+                serializer.attribute(null, "key", key);
+                serializer.attribute(null, "layout", mKeyboardLayoutMap.get(key));
+                serializer.endTag(null, "keyed-keyboard-layout");
+            }
+
             for (int i = 0; i < mKeyboardBacklightBrightnessMap.size(); i++) {
                 serializer.startTag(null, "light-info");
                 serializer.attributeInt(null, "light-id", mKeyboardBacklightBrightnessMap.keyAt(i));
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index 86a0857..e21895a 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -21,7 +21,10 @@
 import static com.android.server.EventLogTags.IMF_HIDE_IME;
 import static com.android.server.EventLogTags.IMF_SHOW_IME;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_IMPLICIT;
 
 import android.annotation.Nullable;
 import android.os.Binder;
@@ -30,6 +33,7 @@
 import android.util.EventLog;
 import android.util.Slog;
 import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.inputmethod.InputMethodDebug;
@@ -124,6 +128,12 @@
     @Override
     public void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
             @ImeVisibilityStateComputer.VisibilityState int state) {
+        applyImeVisibility(windowToken, statsToken, state, -1 /* ignore reason */);
+    }
+
+    @GuardedBy("ImfLock.class")
+    void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+            @ImeVisibilityStateComputer.VisibilityState int state, int reason) {
         switch (state) {
             case STATE_SHOW_IME:
                 ImeTracker.get().onProgress(statsToken,
@@ -148,6 +158,17 @@
                             ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                 }
                 break;
+            case STATE_HIDE_IME_EXPLICIT:
+                mService.hideCurrentInputLocked(windowToken, statsToken, 0, null, reason);
+                break;
+            case STATE_HIDE_IME_NOT_ALWAYS:
+                mService.hideCurrentInputLocked(windowToken, statsToken,
+                        InputMethodManager.HIDE_NOT_ALWAYS, null, reason);
+                break;
+            case STATE_SHOW_IME_IMPLICIT:
+                mService.showCurrentInputLocked(windowToken, statsToken,
+                        InputMethodManager.SHOW_IMPLICIT, null, reason);
+                break;
             default:
                 throw new IllegalArgumentException("Invalid IME visibility state: " + state);
         }
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index a2655f4..795e4bf 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -18,11 +18,13 @@
 
 import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
 import static android.server.inputmethod.InputMethodManagerServiceProto.ACCESSIBILITY_REQUESTING_NO_SOFT_KEYBOARD;
+import static android.server.inputmethod.InputMethodManagerServiceProto.INPUT_SHOWN;
 import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_EXPLICITLY_REQUESTED;
 import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_FORCED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
 import static android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 
@@ -32,6 +34,7 @@
 import android.accessibilityservice.AccessibilityService;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.content.res.Configuration;
 import android.os.IBinder;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
@@ -42,6 +45,8 @@
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodManager;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.server.LocalServices;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -88,6 +93,11 @@
      */
     boolean mShowForced;
 
+    /**
+     * Set if we last told the input method to show itself.
+     */
+    private boolean mInputShown;
+
     /** Represent the invalid IME visibility state */
     public static final int STATE_INVALID = -1;
 
@@ -105,6 +115,12 @@
 
     /** State to handle showing an IME preview surface during the app was loosing the IME focus */
     public static final int STATE_SHOW_IME_SNAPSHOT = 4;
+
+    public static final int STATE_HIDE_IME_EXPLICIT = 5;
+
+    public static final int STATE_HIDE_IME_NOT_ALWAYS = 6;
+
+    public static final int STATE_SHOW_IME_IMPLICIT = 7;
     @IntDef({
             STATE_INVALID,
             STATE_HIDE_IME,
@@ -112,6 +128,9 @@
             STATE_SHOW_IME_ABOVE_OVERLAY,
             STATE_SHOW_IME_BEHIND_OVERLAY,
             STATE_SHOW_IME_SNAPSHOT,
+            STATE_HIDE_IME_EXPLICIT,
+            STATE_HIDE_IME_NOT_ALWAYS,
+            STATE_SHOW_IME_IMPLICIT,
     })
     @interface VisibilityState {}
 
@@ -120,11 +139,38 @@
      */
     private final ImeVisibilityPolicy mPolicy;
 
-    public ImeVisibilityStateComputer(InputMethodManagerService service) {
+    public ImeVisibilityStateComputer(@NonNull InputMethodManagerService service) {
+        this(service,
+                LocalServices.getService(WindowManagerInternal.class),
+                LocalServices.getService(WindowManagerInternal.class)::getDisplayImePolicy,
+                new ImeVisibilityPolicy());
+    }
+
+    @VisibleForTesting
+    public ImeVisibilityStateComputer(@NonNull InputMethodManagerService service,
+            @NonNull Injector injector) {
+        this(service, injector.getWmService(), injector.getImeValidator(),
+                new ImeVisibilityPolicy());
+    }
+
+    interface Injector {
+        default WindowManagerInternal getWmService() {
+            return null;
+        }
+
+        default InputMethodManagerService.ImeDisplayValidator getImeValidator() {
+            return null;
+        }
+    }
+
+    private ImeVisibilityStateComputer(InputMethodManagerService service,
+            WindowManagerInternal wmService,
+            InputMethodManagerService.ImeDisplayValidator imeDisplayValidator,
+            ImeVisibilityPolicy imePolicy) {
         mService = service;
-        mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
-        mImeDisplayValidator = mWindowManagerInternal::getDisplayImePolicy;
-        mPolicy = new ImeVisibilityPolicy();
+        mWindowManagerInternal = wmService;
+        mImeDisplayValidator = imeDisplayValidator;
+        mPolicy = imePolicy;
     }
 
     /**
@@ -187,6 +233,7 @@
     void clearImeShowFlags() {
         mRequestedShowExplicitly = false;
         mShowForced = false;
+        mInputShown = false;
     }
 
     int computeImeDisplayId(@NonNull ImeTargetWindowState state, int displayId) {
@@ -207,15 +254,22 @@
      *                            {@link #STATE_HIDE_IME}.
      */
     void requestImeVisibility(IBinder windowToken, boolean showIme) {
-        final ImeTargetWindowState state = getOrCreateWindowState(windowToken);
-        state.setRequestedImeVisible(showIme);
-        setWindowState(windowToken, state);
+        ImeTargetWindowState state = getOrCreateWindowState(windowToken);
+        if (!mPolicy.mPendingA11yRequestingHideKeyboard) {
+            state.setRequestedImeVisible(showIme);
+        } else {
+            // As A11y requests no IME is just a temporary, so we don't change the requested IME
+            // visible in case the last visibility state goes wrong after leaving from the a11y
+            // policy.
+            mPolicy.mPendingA11yRequestingHideKeyboard = false;
+        }
+        setWindowStateInner(windowToken, state);
     }
 
     ImeTargetWindowState getOrCreateWindowState(IBinder windowToken) {
         ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
         if (state == null) {
-            state = new ImeTargetWindowState(SOFT_INPUT_STATE_UNSPECIFIED, false, false);
+            state = new ImeTargetWindowState(SOFT_INPUT_STATE_UNSPECIFIED, 0, false, false, false);
         }
         return state;
     }
@@ -229,16 +283,176 @@
         ImeTargetWindowState state = getWindowStateOrNull(windowToken);
         if (state != null) {
             state.setRequestImeToken(token);
-            setWindowState(windowToken, state);
+            setWindowStateInner(windowToken, state);
         }
     }
 
-    void setWindowState(IBinder windowToken, ImeTargetWindowState newState) {
-        if (DEBUG) Slog.d(TAG, "setWindowState, windowToken=" + windowToken
+    void setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
+        final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
+        if (state != null && newState.hasEdiorFocused()) {
+            // Inherit the last requested IME visible state when the target window is still
+            // focused with an editor.
+            newState.setRequestedImeVisible(state.mRequestedImeVisible);
+        }
+        setWindowStateInner(windowToken, newState);
+    }
+
+    private void setWindowStateInner(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
+        if (DEBUG) Slog.d(TAG, "setWindowStateInner, windowToken=" + windowToken
                 + ", state=" + newState);
         mRequestWindowStateMap.put(windowToken, newState);
     }
 
+    static class ImeVisibilityResult {
+        private final @VisibilityState int mState;
+        private final @SoftInputShowHideReason int mReason;
+
+        ImeVisibilityResult(@VisibilityState int state, @SoftInputShowHideReason int reason) {
+            mState = state;
+            mReason = reason;
+        }
+
+        @VisibilityState int getState() {
+            return mState;
+        }
+
+        @SoftInputShowHideReason int getReason() {
+            return mReason;
+        }
+    }
+
+    ImeVisibilityResult computeState(ImeTargetWindowState state, boolean allowVisible) {
+        // TODO: Output the request IME visibility state according to the requested window state
+        final int softInputVisibility = state.mSoftInputModeState & SOFT_INPUT_MASK_STATE;
+        // Should we auto-show the IME even if the caller has not
+        // specified what should be done with it?
+        // We only do this automatically if the window can resize
+        // to accommodate the IME (so what the user sees will give
+        // them good context without input information being obscured
+        // by the IME) or if running on a large screen where there
+        // is more room for the target window + IME.
+        final boolean doAutoShow =
+                (state.mSoftInputModeState & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
+                        == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
+                        || mService.mRes.getConfiguration().isLayoutSizeAtLeast(
+                        Configuration.SCREENLAYOUT_SIZE_LARGE);
+        final boolean isForwardNavigation = (state.mSoftInputModeState
+                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0;
+
+        // We shows the IME when the system allows the IME focused target window to restore the
+        // IME visibility (e.g. switching to the app task when last time the IME is visible).
+        // Note that we don't restore IME visibility for some cases (e.g. when the soft input
+        // state is ALWAYS_HIDDEN or STATE_HIDDEN with forward navigation).
+        // Because the app might leverage these flags to hide soft-keyboard with showing their own
+        // UI for input.
+        if (state.hasEdiorFocused() && shouldRestoreImeVisibility(state)) {
+            if (DEBUG) Slog.v(TAG, "Will show input to restore visibility");
+            // Inherit the last requested IME visible state when the target window is still
+            // focused with an editor.
+            state.setRequestedImeVisible(true);
+            setWindowStateInner(getWindowTokenFrom(state), state);
+            return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
+                    SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY);
+        }
+
+        switch (softInputVisibility) {
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
+                if (state.hasImeFocusChanged() && (!state.hasEdiorFocused() || !doAutoShow)) {
+                    if (WindowManager.LayoutParams.mayUseInputMethod(state.getWindowFlags())) {
+                        // There is no focus view, and this window will
+                        // be behind any soft input window, so hide the
+                        // soft input window if it is shown.
+                        if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
+                        return new ImeVisibilityResult(STATE_HIDE_IME_NOT_ALWAYS,
+                                SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW);
+                    }
+                } else if (state.hasEdiorFocused() && doAutoShow && isForwardNavigation) {
+                    // There is a focus view, and we are navigating forward
+                    // into the window, so show the input window for the user.
+                    // We only do this automatically if the window can resize
+                    // to accommodate the IME (so what the user sees will give
+                    // them good context without input information being obscured
+                    // by the IME) or if running on a large screen where there
+                    // is more room for the target window + IME.
+                    if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
+                    return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
+                            SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV);
+                }
+                break;
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
+                // Do nothing.
+                break;
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
+                if (isForwardNavigation) {
+                    if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
+                    return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
+                            SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV);
+                }
+                break;
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
+                if (state.hasImeFocusChanged()) {
+                    if (DEBUG) Slog.v(TAG, "Window asks to hide input");
+                    return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
+                            SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE);
+                }
+                break;
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
+                if (isForwardNavigation) {
+                    if (allowVisible) {
+                        if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
+                        return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
+                                SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV);
+                    } else {
+                        Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
+                                + " there is no focused view that also returns true from"
+                                + " View#onCheckIsTextEditor()");
+                    }
+                }
+                break;
+            case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
+                if (DEBUG) Slog.v(TAG, "Window asks to always show input");
+                if (allowVisible) {
+                    if (state.hasImeFocusChanged()) {
+                        return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
+                                SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE);
+                    }
+                } else {
+                    Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because"
+                            + " there is no focused view that also returns true from"
+                            + " View#onCheckIsTextEditor()");
+                }
+                break;
+        }
+
+        if (!state.hasImeFocusChanged()) {
+            // On previous platforms, when Dialogs re-gained focus, the Activity behind
+            // would briefly gain focus first, and dismiss the IME.
+            // On R that behavior has been fixed, but unfortunately apps have come
+            // to rely on this behavior to hide the IME when the editor no longer has focus
+            // To maintain compatibility, we are now hiding the IME when we don't have
+            // an editor upon refocusing a window.
+            if (state.isStartInputByGainFocus()) {
+                if (DEBUG) Slog.v(TAG, "Same window without editor will hide input");
+                return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
+                        SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
+            }
+        }
+        if (!state.hasEdiorFocused() && mInputShown && state.isStartInputByGainFocus()
+                && mService.mInputMethodDeviceConfigs.shouldHideImeWhenNoEditorFocus()) {
+            // Hide the soft-keyboard when the system do nothing for softInputModeState
+            // of the window being gained focus without an editor. This behavior benefits
+            // to resolve some unexpected IME visible cases while that window with following
+            // configurations being switched from an IME shown window:
+            // 1) SOFT_INPUT_STATE_UNCHANGED state without an editor
+            // 2) SOFT_INPUT_STATE_VISIBLE state without an editor
+            // 3) SOFT_INPUT_STATE_ALWAYS_VISIBLE state without an editor
+            if (DEBUG) Slog.v(TAG, "Window without editor will hide input");
+            return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
+                    SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR);
+        }
+        return null;
+    }
+
     IBinder getWindowTokenFrom(IBinder requestImeToken) {
         for (IBinder windowToken : mRequestWindowStateMap.keySet()) {
             final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
@@ -273,11 +487,20 @@
         return mWindowManagerInternal.shouldRestoreImeVisibility(getWindowTokenFrom(state));
     }
 
+    boolean isInputShown() {
+        return mInputShown;
+    }
+
+    void setInputShown(boolean inputShown) {
+        mInputShown = inputShown;
+    }
+
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
         proto.write(SHOW_EXPLICITLY_REQUESTED, mRequestedShowExplicitly);
         proto.write(SHOW_FORCED, mShowForced);
         proto.write(ACCESSIBILITY_REQUESTING_NO_SOFT_KEYBOARD,
                 mPolicy.isA11yRequestNoSoftKeyboard());
+        proto.write(INPUT_SHOWN, mInputShown);
     }
 
     void dump(PrintWriter pw) {
@@ -285,6 +508,7 @@
         p.println(" mRequestedShowExplicitly=" + mRequestedShowExplicitly
                 + " mShowForced=" + mShowForced);
         p.println("  mImeHiddenByDisplayPolicy=" + mPolicy.isImeHiddenByDisplayPolicy());
+        p.println("  mInputShown=" + mInputShown);
     }
 
     /**
@@ -309,6 +533,14 @@
          */
         private boolean mA11yRequestingNoSoftKeyboard;
 
+        /**
+         * Used when A11y request to hide IME temporary when receiving
+         * {@link AccessibilityService#SHOW_MODE_HIDDEN} from
+         * {@link android.provider.Settings.Secure#ACCESSIBILITY_SOFT_KEYBOARD_MODE} without
+         * changing the requested IME visible state.
+         */
+        private boolean mPendingA11yRequestingHideKeyboard;
+
         void setImeHiddenByDisplayPolicy(boolean hideIme) {
             mImeHiddenByDisplayPolicy = hideIme;
         }
@@ -320,6 +552,9 @@
         void setA11yRequestNoSoftKeyboard(int keyboardShowMode) {
             mA11yRequestingNoSoftKeyboard =
                     (keyboardShowMode & AccessibilityService.SHOW_MODE_MASK) == SHOW_MODE_HIDDEN;
+            if (mA11yRequestingNoSoftKeyboard) {
+                mPendingA11yRequestingHideKeyboard = true;
+            }
         }
 
         boolean isA11yRequestNoSoftKeyboard() {
@@ -335,11 +570,14 @@
      * A class that represents the current state of the IME target window.
      */
     static class ImeTargetWindowState {
-        ImeTargetWindowState(@SoftInputModeFlags int softInputModeState, boolean imeFocusChanged,
-                boolean hasFocusedEditor) {
+        ImeTargetWindowState(@SoftInputModeFlags int softInputModeState, int windowFlags,
+                boolean imeFocusChanged, boolean hasFocusedEditor,
+                boolean isStartInputByGainFocus) {
             mSoftInputModeState = softInputModeState;
+            mWindowFlags = windowFlags;
             mImeFocusChanged = imeFocusChanged;
             mHasFocusedEditor = hasFocusedEditor;
+            mIsStartInputByGainFocus = isStartInputByGainFocus;
         }
 
         /**
@@ -347,6 +585,8 @@
          */
         private final @SoftInputModeFlags int mSoftInputModeState;
 
+        private final int mWindowFlags;
+
         /**
          * {@code true} means the IME focus changed from the previous window, {@code false}
          * otherwise.
@@ -358,6 +598,8 @@
          */
         private final boolean mHasFocusedEditor;
 
+        private final boolean mIsStartInputByGainFocus;
+
         /**
          * Set if the client has asked for the input method to be shown.
          */
@@ -382,10 +624,18 @@
             return mHasFocusedEditor;
         }
 
+        boolean isStartInputByGainFocus() {
+            return mIsStartInputByGainFocus;
+        }
+
         int getSoftInputModeState() {
             return mSoftInputModeState;
         }
 
+        int getWindowFlags() {
+            return mWindowFlags;
+        }
+
         private void setImeDisplayId(int imeDisplayId) {
             mImeDisplayId = imeDisplayId;
         }
@@ -418,6 +668,7 @@
                     + " requestedImeVisible " + mRequestedImeVisible
                     + " imeDisplayId " + mImeDisplayId
                     + " softInputModeState " + softInputModeToString(mSoftInputModeState)
+                    + " isStartInputByGainFocus " + mIsStartInputByGainFocus
                     + "}";
         }
     }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 079234c..187de930 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -350,7 +350,7 @@
                     // should now try to restart the service for us.
                     mLastBindTime = SystemClock.uptimeMillis();
                     clearCurMethodAndSessions();
-                    mService.clearInputShowRequestLocked();
+                    mService.clearInputShownLocked();
                     mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME);
                 }
             }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2dbbb10..a94c90c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -33,13 +33,11 @@
 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_TOKEN_DISPLAY_ID;
 import static android.server.inputmethod.InputMethodManagerServiceProto.HAVE_CONNECTION;
 import static android.server.inputmethod.InputMethodManagerServiceProto.IME_WINDOW_VISIBILITY;
-import static android.server.inputmethod.InputMethodManagerServiceProto.INPUT_SHOWN;
 import static android.server.inputmethod.InputMethodManagerServiceProto.IN_FULLSCREEN_MODE;
 import static android.server.inputmethod.InputMethodManagerServiceProto.IS_INTERACTIVE;
 import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_IME_TARGET_WINDOW_NAME;
 import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_SWITCH_USER_ID;
 import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_IME_WITH_HARD_KEYBOARD;
-import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_REQUESTED;
 import static android.server.inputmethod.InputMethodManagerServiceProto.SYSTEM_READY;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
@@ -47,6 +45,7 @@
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
 import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
 import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed;
 
@@ -77,7 +76,6 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Matrix;
@@ -624,16 +622,6 @@
         return mBindingController.hasConnection();
     }
 
-    /**
-     * Set if the client has asked for the input method to be shown.
-     */
-    private boolean mShowRequested;
-
-    /**
-     * Set if we last told the input method to show itself.
-     */
-    private boolean mInputShown;
-
     /** The token tracking the current IME request or {@code null} otherwise. */
     @Nullable
     private ImeTracker.Token mCurStatsToken;
@@ -689,7 +677,7 @@
      * The display ID of the input method indicates the fallback display which returned by
      * {@link #computeImeDisplayIdForTarget}.
      */
-    private static final int FALLBACK_DISPLAY_ID = DEFAULT_DISPLAY;
+    static final int FALLBACK_DISPLAY_ID = DEFAULT_DISPLAY;
 
     /**
      * If non-null, this is the input method service we are currently connected
@@ -1174,12 +1162,10 @@
                     mVisibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard(
                             accessibilitySoftKeyboardSetting);
                     if (mVisibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) {
-                        final boolean showRequested = mShowRequested;
                         hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
                                 0 /* flags */, null /* resultReceiver */,
                                 SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE);
-                        mShowRequested = showRequested;
-                    } else if (mShowRequested) {
+                    } else if (isShowRequestedForCurrentWindow()) {
                         showCurrentInputImplicitLocked(mCurFocusedWindow,
                                 SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE);
                     }
@@ -2299,9 +2285,20 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void clearInputShowRequestLocked() {
-        mShowRequested = mInputShown;
-        mInputShown = false;
+    void clearInputShownLocked() {
+        mVisibilityStateComputer.setInputShown(false);
+    }
+
+    @GuardedBy("ImfLock.class")
+    private boolean isInputShown() {
+        return mVisibilityStateComputer.isInputShown();
+    }
+
+    @GuardedBy("ImfLock.class")
+    private boolean isShowRequestedForCurrentWindow() {
+        final ImeTargetWindowState state = mVisibilityStateComputer.getWindowStateOrNull(
+                mCurFocusedWindow);
+        return state != null && state.isRequestedImeVisible();
     }
 
     @GuardedBy("ImfLock.class")
@@ -2340,7 +2337,7 @@
         setEnabledSessionLocked(session);
         session.mMethod.startInput(startInputToken, mCurInputConnection, mCurEditorInfo, restarting,
                 navButtonFlags, mCurImeDispatcher);
-        if (mShowRequested) {
+        if (isShowRequestedForCurrentWindow()) {
             if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
             // Re-use current statsToken, if it exists.
             final ImeTracker.Token statsToken = mCurStatsToken;
@@ -2559,7 +2556,7 @@
         if (!mPreventImeStartupUnlessTextEditor) {
             return false;
         }
-        if (mShowRequested) {
+        if (isShowRequestedForCurrentWindow()) {
             return false;
         }
         if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) {
@@ -3370,9 +3367,6 @@
                     ImeTracker.ORIGIN_SERVER_START_INPUT, reason);
         }
 
-        // TODO(b/246309664): make mShowRequested as per-window state.
-        mShowRequested = true;
-
         if (!mVisibilityStateComputer.onImeShowFlags(statsToken, flags)) {
             return false;
         }
@@ -3398,8 +3392,7 @@
             }
             mVisibilityApplier.performShowIme(windowToken, statsToken,
                     mVisibilityStateComputer.getImeShowFlags(), resultReceiver, reason);
-            // TODO(b/246309664): make mInputShown tracked by the Ime visibility computer.
-            mInputShown = true;
+            mVisibilityStateComputer.setInputShown(true);
             return true;
         } else {
             ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
@@ -3417,7 +3410,7 @@
                 "InputMethodManagerService#hideSoftInput");
         synchronized (ImfLock.class) {
             if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken)) {
-                if (mInputShown) {
+                if (isInputShown()) {
                     ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
                 } else {
                     ImeTracker.get().onCancelled(statsToken,
@@ -3468,10 +3461,10 @@
         // application process as a valid request, and have even promised such a behavior with CTS
         // since Android Eclair.  That's why we need to accept IMM#hideSoftInput() even when only
         // IMMS#InputShown indicates that the software keyboard is shown.
-        // TODO(b/246309664): Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
+        // TODO(b/246309664): Clean up IMMS#mImeWindowVis
         IInputMethodInvoker curMethod = getCurMethodLocked();
-        final boolean shouldHideSoftInput = (curMethod != null)
-                && (mInputShown || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
+        final boolean shouldHideSoftInput = curMethod != null
+                && (isInputShown() || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
 
         mVisibilityStateComputer.requestImeVisibility(windowToken, false);
         if (shouldHideSoftInput) {
@@ -3486,8 +3479,6 @@
         }
         mBindingController.setCurrentMethodNotVisible();
         mVisibilityStateComputer.clearImeShowFlags();
-        mInputShown = false;
-        mShowRequested = false;
         // Cancel existing statsToken for show IME as we got a hide request.
         ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
         mCurStatsToken = null;
@@ -3666,8 +3657,8 @@
 
         // Init the focused window state (e.g. whether the editor has focused or IME focus has
         // changed from another window).
-        final ImeTargetWindowState windowState = new ImeTargetWindowState(
-                softInputMode, !sameWindowFocused, isTextEditor);
+        final ImeTargetWindowState windowState = new ImeTargetWindowState(softInputMode,
+                windowFlags, !sameWindowFocused, isTextEditor, startInputByWinGainedFocus);
         mVisibilityStateComputer.setWindowState(windowToken, windowState);
 
         if (sameWindowFocused && isTextEditor) {
@@ -3692,74 +3683,21 @@
         mCurFocusedWindowClient = cs;
         mCurPerceptible = true;
 
-        // Should we auto-show the IME even if the caller has not
-        // specified what should be done with it?
-        // We only do this automatically if the window can resize
-        // to accommodate the IME (so what the user sees will give
-        // them good context without input information being obscured
-        // by the IME) or if running on a large screen where there
-        // is more room for the target window + IME.
-        final boolean doAutoShow =
-                (softInputMode & LayoutParams.SOFT_INPUT_MASK_ADJUST)
-                        == LayoutParams.SOFT_INPUT_ADJUST_RESIZE
-                        || mRes.getConfiguration().isLayoutSizeAtLeast(
-                        Configuration.SCREENLAYOUT_SIZE_LARGE);
-
         // We want to start input before showing the IME, but after closing
         // it.  We want to do this after closing it to help the IME disappear
         // more quickly (not get stuck behind it initializing itself for the
         // new focused input, even if its window wants to hide the IME).
         boolean didStart = false;
-
         InputBindResult res = null;
-        // We show the IME when the system allows the IME focused target window to restore the
-        // IME visibility (e.g. switching to the app task when last time the IME is visible).
-        // Note that we don't restore IME visibility for some cases (e.g. when the soft input
-        // state is ALWAYS_HIDDEN or STATE_HIDDEN with forward navigation).
-        // Because the app might leverage these flags to hide soft-keyboard with showing their own
-        // UI for input.
-        if (isTextEditor && editorInfo != null
-                && mVisibilityStateComputer.shouldRestoreImeVisibility(windowState)) {
-            if (DEBUG) Slog.v(TAG, "Will show input to restore visibility");
-            res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection,
-                    editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion,
-                    imeDispatcher);
-            showCurrentInputImplicitLocked(windowToken,
-                    SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY);
-            return res;
-        }
 
-        switch (softInputMode & LayoutParams.SOFT_INPUT_MASK_STATE) {
-            case LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
-                if (!sameWindowFocused && (!isTextEditor || !doAutoShow)) {
-                    if (LayoutParams.mayUseInputMethod(windowFlags)) {
-                        // There is no focus view, and this window will
-                        // be behind any soft input window, so hide the
-                        // soft input window if it is shown.
-                        if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
-                        hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
-                                InputMethodManager.HIDE_NOT_ALWAYS, null /* resultReceiver */,
-                                SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW);
-
-                        // If focused display changed, we should unbind current method
-                        // to make app window in previous display relayout after Ime
-                        // window token removed.
-                        // Note that we can trust client's display ID as long as it matches
-                        // to the display ID obtained from the window.
-                        if (cs.mSelfReportedDisplayId != mCurTokenDisplayId) {
-                            mBindingController.unbindCurrentMethod();
-                        }
-                    }
-                } else if (isTextEditor && doAutoShow
-                        && (softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
-                    // There is a focus view, and we are navigating forward
-                    // into the window, so show the input window for the user.
-                    // We only do this automatically if the window can resize
-                    // to accommodate the IME (so what the user sees will give
-                    // them good context without input information being obscured
-                    // by the IME) or if running on a large screen where there
-                    // is more room for the target window + IME.
-                    if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
+        final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.computeState(windowState,
+                isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags));
+        if (imeVisRes != null) {
+            switch (imeVisRes.getReason()) {
+                case SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY:
+                case SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV:
+                case SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV:
+                case SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE:
                     if (editorInfo != null) {
                         res = startInputUncheckedLocked(cs, inputContext,
                                 remoteAccessibilityInputConnection, editorInfo, startInputFlags,
@@ -3767,106 +3705,25 @@
                                 imeDispatcher);
                         didStart = true;
                     }
-                    showCurrentInputImplicitLocked(windowToken,
-                            SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV);
-                }
-                break;
-            case LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
-                if (DEBUG) {
-                    Slog.v(TAG, "Window asks to keep the input in whatever state it was last in");
-                }
-                // Do nothing.
-                break;
-            case LayoutParams.SOFT_INPUT_STATE_HIDDEN:
-                if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
-                    if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
-                    hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
-                            null /* resultReceiver */,
-                            SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV);
-                }
-                break;
-            case LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
-                if (!sameWindowFocused) {
-                    if (DEBUG) Slog.v(TAG, "Window asks to hide input");
-                    hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
-                            null /* resultReceiver */,
-                            SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE);
-                }
-                break;
-            case LayoutParams.SOFT_INPUT_STATE_VISIBLE:
-                if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
-                    if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
-                    if (isSoftInputModeStateVisibleAllowed(
-                            unverifiedTargetSdkVersion, startInputFlags)) {
-                        if (editorInfo != null) {
-                            res = startInputUncheckedLocked(cs, inputContext,
-                                    remoteAccessibilityInputConnection, editorInfo, startInputFlags,
-                                    startInputReason, unverifiedTargetSdkVersion,
-                                    imeDispatcher);
-                            didStart = true;
-                        }
-                        showCurrentInputImplicitLocked(windowToken,
-                                SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV);
-                    } else {
-                        Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
-                                + " there is no focused view that also returns true from"
-                                + " View#onCheckIsTextEditor()");
-                    }
-                }
-                break;
-            case LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
-                if (DEBUG) Slog.v(TAG, "Window asks to always show input");
-                if (isSoftInputModeStateVisibleAllowed(
-                        unverifiedTargetSdkVersion, startInputFlags)) {
-                    if (!sameWindowFocused) {
-                        if (editorInfo != null) {
-                            res = startInputUncheckedLocked(cs, inputContext,
-                                    remoteAccessibilityInputConnection, editorInfo, startInputFlags,
-                                    startInputReason, unverifiedTargetSdkVersion,
-                                    imeDispatcher);
-                            didStart = true;
-                        }
-                        showCurrentInputImplicitLocked(windowToken,
-                                SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE);
-                    }
-                } else {
-                    Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because"
-                            + " there is no focused view that also returns true from"
-                            + " View#onCheckIsTextEditor()");
-                }
-                break;
-        }
+                    break;
+            }
 
+            mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, null /* statsToken */,
+                    imeVisRes.getState(), imeVisRes.getReason());
+
+            if (imeVisRes.getReason() == SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW) {
+                // If focused display changed, we should unbind current method
+                // to make app window in previous display relayout after Ime
+                // window token removed.
+                // Note that we can trust client's display ID as long as it matches
+                // to the display ID obtained from the window.
+                if (cs.mSelfReportedDisplayId != mCurTokenDisplayId) {
+                    mBindingController.unbindCurrentMethod();
+                }
+            }
+        }
         if (!didStart) {
             if (editorInfo != null) {
-                if (sameWindowFocused) {
-                    // On previous platforms, when Dialogs re-gained focus, the Activity behind
-                    // would briefly gain focus first, and dismiss the IME.
-                    // On R that behavior has been fixed, but unfortunately apps have come
-                    // to rely on this behavior to hide the IME when the editor no longer has focus
-                    // To maintain compatibility, we are now hiding the IME when we don't have
-                    // an editor upon refocusing a window.
-                    if (startInputByWinGainedFocus) {
-                        if (DEBUG) Slog.v(TAG, "Same window without editor will hide input");
-                        hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
-                                0 /* flags */, null /* resultReceiver */,
-                                SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
-                    }
-                }
-                if (!isTextEditor && mInputShown && startInputByWinGainedFocus
-                        && mInputMethodDeviceConfigs.shouldHideImeWhenNoEditorFocus()) {
-                    // Hide the soft-keyboard when the system do nothing for softInputModeState
-                    // of the window being gained focus without an editor. This behavior benefits
-                    // to resolve some unexpected IME visible cases while that window with following
-                    // configurations being switched from an IME shown window:
-                    // 1) SOFT_INPUT_STATE_UNCHANGED state without an editor
-                    // 2) SOFT_INPUT_STATE_VISIBLE state without an editor
-                    // 3) SOFT_INPUT_STATE_ALWAYS_VISIBLE state without an editor
-                    if (DEBUG) Slog.v(TAG, "Window without editor will hide input");
-                    hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
-                            null /* resultReceiver */,
-                            SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR);
-                }
                 res = startInputUncheckedLocked(cs, inputContext,
                         remoteAccessibilityInputConnection, editorInfo, startInputFlags,
                         startInputReason, unverifiedTargetSdkVersion,
@@ -4639,9 +4496,7 @@
                 mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE);
             }
             proto.write(CUR_ID, getCurIdLocked());
-            proto.write(SHOW_REQUESTED, mShowRequested);
             mVisibilityStateComputer.dumpDebug(proto, fieldId);
-            proto.write(INPUT_SHOWN, mInputShown);
             proto.write(IN_FULLSCREEN_MODE, mInFullscreenMode);
             proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked()));
             proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId);
@@ -4785,6 +4640,16 @@
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
+    @VisibleForTesting
+    ImeVisibilityStateComputer getVisibilityStateComputer() {
+        return mVisibilityStateComputer;
+    }
+
+    @VisibleForTesting
+    ImeVisibilityApplier getVisibilityApplier() {
+        return mVisibilityApplier;
+    }
+
     @GuardedBy("ImfLock.class")
     void setEnabledSessionLocked(SessionState session) {
         if (mEnabledSession != session) {
@@ -4847,7 +4712,9 @@
                         // This is undocumented so far, but IMM#showInputMethodPicker() has been
                         // implemented so that auxiliary subtypes will be excluded when the soft
                         // keyboard is invisible.
-                        showAuxSubtypes = mInputShown;
+                        synchronized (ImfLock.class) {
+                            showAuxSubtypes = isInputShown();
+                        }
                         break;
                     case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES:
                         showAuxSubtypes = true;
@@ -4876,7 +4743,7 @@
                 synchronized (ImfLock.class) {
                     try {
                         if (mEnabledSession != null && mEnabledSession.mSession != null
-                                && !mShowRequested) {
+                                && !isShowRequestedForCurrentWindow()) {
                             mEnabledSession.mSession.removeImeSurface();
                         }
                     } catch (RemoteException e) {
@@ -5864,7 +5731,6 @@
             method = getCurMethodLocked();
             p.println("  mCurMethod=" + getCurMethodLocked());
             p.println("  mEnabledSession=" + mEnabledSession);
-            p.println("  mShowRequested=" + mShowRequested + " mInputShown=" + mInputShown);
             mVisibilityStateComputer.dump(pw);
             p.println("  mInFullscreenMode=" + mInFullscreenMode);
             p.println("  mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 121b7c8..31b8ef2 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1156,18 +1156,21 @@
     @Override
     public void setBoolean(String key, boolean value, int userId) {
         checkWritePermission();
+        Objects.requireNonNull(key);
         mStorage.setBoolean(key, value, userId);
     }
 
     @Override
     public void setLong(String key, long value, int userId) {
         checkWritePermission();
+        Objects.requireNonNull(key);
         mStorage.setLong(key, value, userId);
     }
 
     @Override
     public void setString(String key, String value, int userId) {
         checkWritePermission();
+        Objects.requireNonNull(key);
         mStorage.setString(key, value, userId);
     }
 
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 2c28af1..434c0d7 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -62,6 +62,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Storage for the lock settings service.
@@ -886,12 +887,15 @@
                 if (!(obj instanceof CacheKey))
                     return false;
                 CacheKey o = (CacheKey) obj;
-                return userId == o.userId && type == o.type && key.equals(o.key);
+                return userId == o.userId && type == o.type && Objects.equals(key, o.key);
             }
 
             @Override
             public int hashCode() {
-                return key.hashCode() ^ userId ^ type;
+                int hashCode = Objects.hashCode(key);
+                hashCode = 31 * hashCode + userId;
+                hashCode = 31 * hashCode + type;
+                return hashCode;
             }
         }
     }
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index c90554d..ab59f87 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -27,6 +27,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothLeAudio;
+import android.bluetooth.BluetoothManager;
 import android.bluetooth.BluetoothProfile;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -70,11 +71,13 @@
     private final BluetoothAdapter mBluetoothAdapter;
     private final BluetoothRoutesUpdatedListener mListener;
     private final AudioManager mAudioManager;
-    private final Map<String, BluetoothEventReceiver> mEventReceiverMap = new HashMap<>();
-    private final IntentFilter mIntentFilter = new IntentFilter();
-    private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
     private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
 
+    private final AdapterStateChangedReceiver mAdapterStateChangedReceiver =
+            new AdapterStateChangedReceiver();
+    private final DeviceStateChangedReceiver mDeviceStateChangedReceiver =
+            new DeviceStateChangedReceiver();
+
     private BluetoothA2dp mA2dpProfile;
     private BluetoothHearingAid mHearingAidProfile;
     private BluetoothLeAudio mLeAudioProfile;
@@ -89,7 +92,9 @@
         Objects.requireNonNull(context);
         Objects.requireNonNull(listener);
 
-        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+        BluetoothManager bluetoothManager = (BluetoothManager)
+                context.getSystemService(Context.BLUETOOTH_SERVICE);
+        BluetoothAdapter btAdapter = bluetoothManager.getAdapter();
         if (btAdapter == null) {
             return null;
         }
@@ -105,32 +110,45 @@
         buildBluetoothRoutes();
     }
 
+    /**
+     * Registers listener to bluetooth status changes as the provided user.
+     *
+     * The registered receiver listens to {@link BluetoothA2dp#ACTION_ACTIVE_DEVICE_CHANGED} and
+     * {@link BluetoothA2dp#ACTION_CONNECTION_STATE_CHANGED } events for {@link BluetoothProfile#A2DP},
+     * {@link BluetoothProfile#HEARING_AID}, and {@link BluetoothProfile#LE_AUDIO} bluetooth profiles.
+     *
+     * @param user {@code UserHandle} as which receiver is registered
+     */
     void start(UserHandle user) {
         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.LE_AUDIO);
 
-        // Bluetooth on/off broadcasts
-        addEventReceiver(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedReceiver());
+        IntentFilter adapterStateChangedIntentFilter = new IntentFilter();
 
-        DeviceStateChangedReceiver deviceStateChangedReceiver = new DeviceStateChangedReceiver();
-        addEventReceiver(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, deviceStateChangedReceiver);
-        addEventReceiver(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, deviceStateChangedReceiver);
-        addEventReceiver(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
-                deviceStateChangedReceiver);
-        addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED,
-                deviceStateChangedReceiver);
-        addEventReceiver(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED,
-                deviceStateChangedReceiver);
-        addEventReceiver(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED,
-                deviceStateChangedReceiver);
+        adapterStateChangedIntentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+        mContext.registerReceiverAsUser(mAdapterStateChangedReceiver, user,
+                adapterStateChangedIntentFilter, null, null);
 
-        mContext.registerReceiverAsUser(mBroadcastReceiver, user,
-                mIntentFilter, null, null);
+        IntentFilter deviceStateChangedIntentFilter = new IntentFilter();
+
+        deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+        deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        deviceStateChangedIntentFilter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
+        deviceStateChangedIntentFilter.addAction(
+                BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
+        deviceStateChangedIntentFilter.addAction(
+                BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
+        deviceStateChangedIntentFilter.addAction(
+                BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
+
+        mContext.registerReceiverAsUser(mDeviceStateChangedReceiver, user,
+                deviceStateChangedIntentFilter, null, null);
     }
 
     void stop() {
-        mContext.unregisterReceiver(mBroadcastReceiver);
+        mContext.unregisterReceiver(mAdapterStateChangedReceiver);
+        mContext.unregisterReceiver(mDeviceStateChangedReceiver);
     }
 
     /**
@@ -158,6 +176,18 @@
         }
     }
 
+    private BluetoothRouteInfo findBluetoothRouteWithRouteId(String routeId) {
+        if (routeId == null) {
+            return null;
+        }
+        for (BluetoothRouteInfo btRouteInfo : mBluetoothRoutes.values()) {
+            if (TextUtils.equals(btRouteInfo.mRoute.getId(), routeId)) {
+                return btRouteInfo;
+            }
+        }
+        return null;
+    }
+
     /**
      * Clears the active device for all known profiles.
      */
@@ -167,11 +197,6 @@
         }
     }
 
-    private void addEventReceiver(String action, BluetoothEventReceiver eventReceiver) {
-        mEventReceiverMap.put(action, eventReceiver);
-        mIntentFilter.addAction(action);
-    }
-
     private void buildBluetoothRoutes() {
         mBluetoothRoutes.clear();
         Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
@@ -225,18 +250,6 @@
         return routes;
     }
 
-    BluetoothRouteInfo findBluetoothRouteWithRouteId(String routeId) {
-        if (routeId == null) {
-            return null;
-        }
-        for (BluetoothRouteInfo btRouteInfo : mBluetoothRoutes.values()) {
-            if (TextUtils.equals(btRouteInfo.mRoute.getId(), routeId)) {
-                return btRouteInfo;
-            }
-        }
-        return null;
-    }
-
     /**
      * Updates the volume for {@link AudioManager#getDevicesForStream(int) devices}.
      *
@@ -401,21 +414,6 @@
             }
         }
     }
-    private void addActiveHearingAidDevices(BluetoothDevice device) {
-        if (DEBUG) {
-            Log.d(TAG, "Setting active hearing aid devices. device=" + device);
-        }
-
-        addActiveDevices(device);
-    }
-
-    private void addActiveLeAudioDevices(BluetoothDevice device) {
-        if (DEBUG) {
-            Log.d(TAG, "Setting active le audio devices. device=" + device);
-        }
-
-        addActiveDevices(device);
-    }
 
     interface BluetoothRoutesUpdatedListener {
         void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes);
@@ -495,26 +493,9 @@
         }
     }
 
-    private class BluetoothBroadcastReceiver extends BroadcastReceiver {
+    private class AdapterStateChangedReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, android.bluetooth.BluetoothDevice.class);
-
-            BluetoothEventReceiver receiver = mEventReceiverMap.get(action);
-            if (receiver != null) {
-                receiver.onReceive(context, intent, device);
-            }
-        }
-    }
-
-    private interface BluetoothEventReceiver {
-        void onReceive(Context context, Intent intent, BluetoothDevice device);
-    }
-
-    private class AdapterStateChangedReceiver implements BluetoothEventReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
             if (state == BluetoothAdapter.STATE_OFF
                     || state == BluetoothAdapter.STATE_TURNING_OFF) {
@@ -529,9 +510,12 @@
         }
     }
 
-    private class DeviceStateChangedReceiver implements BluetoothEventReceiver {
+    private class DeviceStateChangedReceiver extends BroadcastReceiver {
         @Override
-        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+        public void onReceive(Context context, Intent intent) {
+            BluetoothDevice device = intent.getParcelableExtra(
+                    BluetoothDevice.EXTRA_DEVICE, android.bluetooth.BluetoothDevice.class);
+
             switch (intent.getAction()) {
                 case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
                     clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
@@ -543,14 +527,22 @@
                 case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
                     clearActiveRoutesWithType(MediaRoute2Info.TYPE_HEARING_AID);
                     if (device != null) {
-                        addActiveHearingAidDevices(device);
+                        if (DEBUG) {
+                            Log.d(TAG, "Setting active hearing aid devices. device=" + device);
+                        }
+
+                        addActiveDevices(device);
                     }
                     notifyBluetoothRoutesUpdated();
                     break;
                 case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED:
                     clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLE_HEADSET);
                     if (device != null) {
-                        addActiveLeAudioDevices(device);
+                        if (DEBUG) {
+                            Log.d(TAG, "Setting active le audio devices. device=" + device);
+                        }
+
+                        addActiveDevices(device);
                     }
                     notifyBluetoothRoutesUpdated();
                     break;
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 9466a6f5..ed76dec 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -63,6 +63,10 @@
     private static final String TAG = "MR2SystemProvider";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    private static final ComponentName COMPONENT_NAME = new ComponentName(
+            SystemMediaRoute2Provider.class.getPackage().getName(),
+            SystemMediaRoute2Provider.class.getName());
+
     static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
     static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE";
     static final String SYSTEM_SESSION_ID = "SYSTEM_SESSION";
@@ -74,17 +78,12 @@
     private final UserHandle mUser;
     private final BluetoothRouteProvider mBtRouteProvider;
 
-    private static ComponentName sComponentName = new ComponentName(
-            SystemMediaRoute2Provider.class.getPackage().getName(),
-            SystemMediaRoute2Provider.class.getName());
-
     private String mSelectedRouteId;
     // For apps without MODIFYING_AUDIO_ROUTING permission.
     // This should be the currently selected route.
     MediaRoute2Info mDefaultRoute;
     MediaRoute2Info mDeviceRoute;
     RoutingSessionInfo mDefaultSessionInfo;
-    final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
     int mDeviceVolume;
 
     private final AudioManagerBroadcastReceiver mAudioReceiver =
@@ -108,7 +107,7 @@
     };
 
     SystemMediaRoute2Provider(Context context, UserHandle user) {
-        super(sComponentName);
+        super(COMPONENT_NAME);
 
         mIsSystemRouteProvider = true;
         mContext = context;
@@ -275,7 +274,6 @@
         int name = R.string.default_audio_route_name;
         int type = TYPE_BUILTIN_SPEAKER;
         if (newRoutes != null) {
-            mCurAudioRoutesInfo.mainType = newRoutes.mainType;
             if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) {
                 type = TYPE_WIRED_HEADPHONES;
                 name = com.android.internal.R.string.default_audio_route_name_headphones;
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 71593e1..5e62b56 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -138,7 +138,7 @@
             this.activeApexChanged = activeApexChanged;
         }
 
-        private ActiveApexInfo(ApexInfo apexInfo) {
+        public ActiveApexInfo(ApexInfo apexInfo) {
             this(
                     apexInfo.moduleName,
                     new File(Environment.getApexDirectory() + File.separator
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 9785d47..f85d6af 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -405,11 +405,15 @@
                                     new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_PACKAGE_MANAGER);
                             tr.traceBegin("jobExecution");
                             boolean completed = false;
+                            boolean fatalError = false;
                             try {
                                 completed = runIdleOptimization(
                                         pm, pkgs, params.getJobId() == JOB_POST_BOOT_UPDATE);
                             } catch (LegacyDexoptDisabledException e) {
                                 Slog.wtf(TAG, e);
+                            } catch (RuntimeException e) {
+                                fatalError = true;
+                                throw e;
                             } finally { // Those cleanup should be done always.
                                 tr.traceEnd();
                                 Slog.i(TAG,
@@ -422,12 +426,10 @@
                                     if (completed) {
                                         markPostBootUpdateCompleted(params);
                                     }
-                                    // Reschedule when cancelled
-                                    job.jobFinished(params, !completed);
-                                } else {
-                                    // Periodic job
-                                    job.jobFinished(params, false /* reschedule */);
                                 }
+                                // Reschedule when cancelled. No need to reschedule when failed with
+                                // fatal error because it's likely to fail again.
+                                job.jobFinished(params, !completed && !fatalError);
                                 markDexOptCompleted();
                             }
                         }));
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index db3a343..69436da 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IBackgroundInstallControlService;
+import android.content.pm.InstallSourceInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
@@ -183,10 +184,12 @@
             return;
         }
 
-        String installerPackageName = null;
+        String installerPackageName;
+        String initiatingPackageName;
         try {
-            installerPackageName = mPackageManager
-                    .getInstallSourceInfo(packageName).getInstallingPackageName();
+            final InstallSourceInfo installInfo = mPackageManager.getInstallSourceInfo(packageName);
+            installerPackageName = installInfo.getInstallingPackageName();
+            initiatingPackageName = installInfo.getInitiatingPackageName();
         } catch (PackageManager.NameNotFoundException e) {
             Slog.w(TAG, "Package's installer not found " + packageName);
             return;
@@ -196,7 +199,8 @@
         final long installTimestamp = System.currentTimeMillis()
                 - (SystemClock.uptimeMillis() - appInfo.createTimestamp);
 
-        if (wasForegroundInstallation(installerPackageName, userId, installTimestamp)) {
+        if (installedByAdb(initiatingPackageName)
+                || wasForegroundInstallation(installerPackageName, userId, installTimestamp)) {
             return;
         }
 
@@ -205,6 +209,12 @@
         writeBackgroundInstalledPackagesToDisk();
     }
 
+    // ADB sets installerPackageName to null, this creates a loophole to bypass BIC which will be
+    // addressed with b/265203007
+    private boolean installedByAdb(String initiatingPackageName) {
+        return initiatingPackageName == null;
+    }
+
     private boolean wasForegroundInstallation(String installerPackageName,
             int userId, long installTimestamp) {
         TreeSet<BackgroundInstallControlService.ForegroundTimeFrame> foregroundTimeFrames =
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index c6700e6..2752104 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -1755,6 +1755,7 @@
         forwardingResolveInfo.isDefault = true;
         forwardingResolveInfo.filter = new IntentFilter(filter.getIntentFilter());
         forwardingResolveInfo.targetUserId = targetUserId;
+        forwardingResolveInfo.userHandle = UserHandle.of(sourceUserId);
         return forwardingResolveInfo;
     }
 
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilterHelper.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilterHelper.java
index e682586..51214fa 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentFilterHelper.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilterHelper.java
@@ -54,7 +54,8 @@
             UserProperties currentUserProperties = mUserManagerInternal
                     .getUserProperties(userInfo.id);
 
-            if (currentUserProperties.getUpdateCrossProfileIntentFiltersOnOTA()) {
+            if (currentUserProperties != null
+                    && currentUserProperties.getUpdateCrossProfileIntentFiltersOnOTA()) {
                 int parentUserId = mUserManagerInternal.getProfileParentId(userInfo.id);
                 if (parentUserId != userInfo.id) {
                     clearCrossProfileIntentFilters(userInfo.id,
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index cf447a7..9da319d 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -65,8 +65,8 @@
 import com.android.server.art.ArtManagerLocal;
 import com.android.server.art.DexUseManagerLocal;
 import com.android.server.art.model.ArtFlags;
-import com.android.server.art.model.OptimizeParams;
-import com.android.server.art.model.OptimizeResult;
+import com.android.server.art.model.DexoptParams;
+import com.android.server.art.model.DexoptResult;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.PackageDexOptimizer.DexOptResult;
@@ -502,7 +502,7 @@
      *     necessary to fall back to the legacy code paths.
      */
     private Optional<Integer> performDexOptWithArtService(DexoptOptions options,
-            /*@OptimizeFlags*/ int extraFlags) {
+            /*@DexoptFlags*/ int extraFlags) {
         ArtManagerLocal artManager = getArtManagerLocal();
         if (artManager == null) {
             return Optional.empty();
@@ -522,14 +522,14 @@
                 return Optional.of(PackageDexOptimizer.DEX_OPT_SKIPPED);
             }
 
-            OptimizeParams params = options.convertToOptimizeParams(extraFlags);
+            DexoptParams params = options.convertToDexoptParams(extraFlags);
             if (params == null) {
                 return Optional.empty();
             }
 
-            OptimizeResult result;
+            DexoptResult result;
             try {
-                result = artManager.optimizePackage(snapshot, options.getPackageName(), params);
+                result = artManager.dexoptPackage(snapshot, options.getPackageName(), params);
             } catch (UnsupportedOperationException e) {
                 reportArtManagerFallback(options.getPackageName(), e.toString());
                 return Optional.empty();
@@ -954,22 +954,21 @@
         }
     }
 
-    private static class OptimizePackageDoneHandler
-            implements ArtManagerLocal.OptimizePackageDoneCallback {
+    private static class DexoptDoneHandler implements ArtManagerLocal.DexoptDoneCallback {
         @NonNull private final PackageManagerService mPm;
 
-        OptimizePackageDoneHandler(@NonNull PackageManagerService pm) { mPm = pm; }
+        DexoptDoneHandler(@NonNull PackageManagerService pm) { mPm = pm; }
 
         /**
-         * Called after every package optimization operation done by {@link ArtManagerLocal}.
+         * Called after every package dexopt operation done by {@link ArtManagerLocal}.
          */
         @Override
-        public void onOptimizePackageDone(@NonNull OptimizeResult result) {
-            for (OptimizeResult.PackageOptimizeResult pkgRes : result.getPackageOptimizeResults()) {
+        public void onDexoptDone(@NonNull DexoptResult result) {
+            for (DexoptResult.PackageDexoptResult pkgRes : result.getPackageDexoptResults()) {
                 CompilerStats.PackageStats stats =
                         mPm.getOrCreateCompilerPackageStats(pkgRes.getPackageName());
-                for (OptimizeResult.DexContainerFileOptimizeResult dexRes :
-                        pkgRes.getDexContainerFileOptimizeResults()) {
+                for (DexoptResult.DexContainerFileDexoptResult dexRes :
+                        pkgRes.getDexContainerFileDexoptResults()) {
                     stats.setCompileTime(
                             dexRes.getDexContainerFile(), dexRes.getDex2oatWallTimeMillis());
                 }
@@ -994,13 +993,13 @@
         ArtManagerLocal artManager = new ArtManagerLocal(systemContext);
         // There doesn't appear to be any checks that @NonNull is heeded, so use requireNonNull
         // below to ensure we don't store away a null that we'll fail on later.
-        artManager.addOptimizePackageDoneCallback(false /* onlyIncludeUpdates */,
-                Runnable::run, new OptimizePackageDoneHandler(Objects.requireNonNull(pm)));
+        artManager.addDexoptDoneCallback(false /* onlyIncludeUpdates */,
+                Runnable::run, new DexoptDoneHandler(Objects.requireNonNull(pm)));
         LocalManagerRegistry.addManager(ArtManagerLocal.class, artManager);
     }
 
     /**
-     * Returns {@link ArtManagerLocal} if ART Service should be used for package optimization.
+     * Returns {@link ArtManagerLocal} if ART Service should be used for package dexopt.
      */
     private static @Nullable ArtManagerLocal getArtManagerLocal() {
         if (!useArtService()) {
@@ -1014,25 +1013,25 @@
     }
 
     /**
-     * Converts an ART Service {@link OptimizeResult} to {@link DexOptResult}.
+     * Converts an ART Service {@link DexoptResult} to {@link DexOptResult}.
      *
      * For interfacing {@link ArtManagerLocal} with legacy dex optimization code in PackageManager.
      */
     @DexOptResult
-    private static int convertToDexOptResult(OptimizeResult result) {
-        /*@OptimizeStatus*/ int status = result.getFinalStatus();
+    private static int convertToDexOptResult(DexoptResult result) {
+        /*@DexoptResultStatus*/ int status = result.getFinalStatus();
         switch (status) {
-            case OptimizeResult.OPTIMIZE_SKIPPED:
+            case DexoptResult.DEXOPT_SKIPPED:
                 return PackageDexOptimizer.DEX_OPT_SKIPPED;
-            case OptimizeResult.OPTIMIZE_FAILED:
+            case DexoptResult.DEXOPT_FAILED:
                 return PackageDexOptimizer.DEX_OPT_FAILED;
-            case OptimizeResult.OPTIMIZE_PERFORMED:
+            case DexoptResult.DEXOPT_PERFORMED:
                 return PackageDexOptimizer.DEX_OPT_PERFORMED;
-            case OptimizeResult.OPTIMIZE_CANCELLED:
+            case DexoptResult.DEXOPT_CANCELLED:
                 return PackageDexOptimizer.DEX_OPT_CANCELLED;
             default:
-                throw new IllegalArgumentException("OptimizeResult for "
-                        + result.getPackageOptimizeResults().get(0).getPackageName()
+                throw new IllegalArgumentException("DexoptResult for "
+                        + result.getPackageDexoptResults().get(0).getPackageName()
                         + " has unsupported status " + status);
         }
     }
diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java
index 3385a09..fcaaa90 100644
--- a/services/core/java/com/android/server/pm/DumpHelper.java
+++ b/services/core/java/com/android/server/pm/DumpHelper.java
@@ -111,6 +111,8 @@
                 dumpState.setOptionEnabled(DumpState.OPTION_DUMP_ALL_COMPONENTS);
             } else if ("-f".equals(opt)) {
                 dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS);
+            } else if ("--include-apex".equals(opt)) {
+                dumpState.setOptionEnabled(DumpState.OPTION_INCLUDE_APEX);
             } else if ("--proto".equals(opt)) {
                 dumpProto(snapshot, fd);
                 return;
diff --git a/services/core/java/com/android/server/pm/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java
index 6225753..0bdce21 100644
--- a/services/core/java/com/android/server/pm/DumpState.java
+++ b/services/core/java/com/android/server/pm/DumpState.java
@@ -51,6 +51,7 @@
     public static final int OPTION_SHOW_FILTERS = 1 << 0;
     public static final int OPTION_DUMP_ALL_COMPONENTS = 1 << 1;
     public static final int OPTION_SKIP_PERMISSIONS = 1 << 2;
+    public static final int OPTION_INCLUDE_APEX = 1 << 3;
 
     private int mTypes;
 
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 6825dd7..5c4447e 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -21,11 +21,9 @@
 import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME;
 import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_SYSTEM_APP_AVG_SCAN_TIME;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_APK_IN_APEX;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_FACTORY;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_PRIVILEGED;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM;
 import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
-import static com.android.server.pm.PackageManagerService.SCAN_DROP_CACHE;
 import static com.android.server.pm.PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE;
 import static com.android.server.pm.PackageManagerService.SCAN_INITIAL;
 import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX;
@@ -147,14 +145,7 @@
                     sp.getFolder().getAbsolutePath())
                     || apexInfo.preInstalledApexPath.getAbsolutePath().startsWith(
                     sp.getFolder().getAbsolutePath() + File.separator)) {
-                int additionalScanFlag = SCAN_AS_APK_IN_APEX;
-                if (apexInfo.isFactory) {
-                    additionalScanFlag |= SCAN_AS_FACTORY;
-                }
-                if (apexInfo.activeApexChanged) {
-                    additionalScanFlag |= SCAN_DROP_CACHE;
-                }
-                return new ScanPartition(apexInfo.apexDirectory, sp, additionalScanFlag);
+                return new ScanPartition(apexInfo.apexDirectory, sp, apexInfo);
             }
         }
         return null;
@@ -266,7 +257,7 @@
         }
 
         scanDirTracedLI(mPm.getAppInstallDir(), 0,
-                mScanFlags | SCAN_REQUIRE_KNOWN, packageParser, mExecutorService);
+                mScanFlags | SCAN_REQUIRE_KNOWN, packageParser, mExecutorService, null);
 
         List<Runnable> unfinishedTasks = mExecutorService.shutdownNow();
         if (!unfinishedTasks.isEmpty()) {
@@ -335,12 +326,12 @@
             }
             scanDirTracedLI(partition.getOverlayFolder(),
                     mSystemParseFlags, mSystemScanFlags | partition.scanFlag,
-                    packageParser, executorService);
+                    packageParser, executorService, partition.apexInfo);
         }
 
         scanDirTracedLI(frameworkDir,
                 mSystemParseFlags, mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED,
-                packageParser, executorService);
+                packageParser, executorService, null);
         if (!mPm.mPackages.containsKey("android")) {
             throw new IllegalStateException(
                     "Failed to load frameworks package; check log for warnings");
@@ -352,11 +343,11 @@
                 scanDirTracedLI(partition.getPrivAppFolder(),
                         mSystemParseFlags,
                         mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag,
-                        packageParser, executorService);
+                        packageParser, executorService, partition.apexInfo);
             }
             scanDirTracedLI(partition.getAppFolder(),
                     mSystemParseFlags, mSystemScanFlags | partition.scanFlag,
-                    packageParser, executorService);
+                    packageParser, executorService, partition.apexInfo);
         }
     }
 
@@ -373,7 +364,8 @@
 
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
     private void scanDirTracedLI(File scanDir, int parseFlags, int scanFlags,
-            PackageParser2 packageParser, ExecutorService executorService) {
+            PackageParser2 packageParser, ExecutorService executorService,
+            @Nullable ApexManager.ActiveApexInfo apexInfo) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
         try {
             if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
@@ -381,7 +373,7 @@
                 parseFlags |= PARSE_APK_IN_APEX;
             }
             mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags,
-                    scanFlags, packageParser, executorService);
+                    scanFlags, packageParser, executorService, apexInfo);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
diff --git a/services/core/java/com/android/server/pm/InstallArgs.java b/services/core/java/com/android/server/pm/InstallArgs.java
index ced547c..9cf9122 100644
--- a/services/core/java/com/android/server/pm/InstallArgs.java
+++ b/services/core/java/com/android/server/pm/InstallArgs.java
@@ -59,7 +59,7 @@
     final boolean mForceQueryableOverride;
     final int mDataLoaderType;
     final int mPackageSource;
-    final boolean mKeepApplicationEnabledSetting;
+    final boolean mApplicationEnabledSettingPersistent;
 
     // The list of instruction sets supported by this app. This is currently
     // only used during the rmdex() phase to clean up resources. We can get rid of this
@@ -74,7 +74,7 @@
             int autoRevokePermissionsMode, String traceMethod, int traceCookie,
             SigningDetails signingDetails, int installReason, int installScenario,
             boolean forceQueryableOverride, int dataLoaderType, int packageSource,
-            boolean keepApplicationEnabledSetting) {
+            boolean applicationEnabledSettingPersistent) {
         mOriginInfo = originInfo;
         mMoveInfo = moveInfo;
         mInstallFlags = installFlags;
@@ -95,7 +95,7 @@
         mForceQueryableOverride = forceQueryableOverride;
         mDataLoaderType = dataLoaderType;
         mPackageSource = packageSource;
-        mKeepApplicationEnabledSetting = keepApplicationEnabledSetting;
+        mApplicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index ac4da2e..f0f23cd 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -367,10 +367,11 @@
 
         if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
             boolean isFactory = (scanFlags & SCAN_AS_FACTORY) != 0;
-            pkgSetting.getPkgState().setApkInApex(true);
             pkgSetting.getPkgState().setApkInUpdatedApex(!isFactory);
         }
 
+        pkgSetting.getPkgState().setApexModuleName(request.getApexModuleName());
+
         // TODO(toddke): Consider a method specifically for modifying the Package object
         // post scan; or, moving this stuff out of the Package object since it has nothing
         // to do with the package on disk.
@@ -1146,7 +1147,7 @@
             if (onExternal) {
                 Slog.i(TAG, "Static shared libs can only be installed on internal storage.");
                 throw new PrepareFailure(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
-                        "Packages declaring static-shared libs cannot be updated");
+                        "Static shared libs can only be installed on internal storage.");
             }
         }
 
@@ -1716,6 +1717,7 @@
                                 + ", old=" + oldPackage);
                     }
                     request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
+                    request.setApexModuleName(oldPackageState.getApexModuleName());
                     targetParseFlags = systemParseFlags;
                     targetScanFlags = systemScanFlags;
                 } else { // non system replace
@@ -2127,7 +2129,7 @@
                     }
                     // Enable system package for requested users
                     if (installedForUsers != null
-                            && !installRequest.isKeepApplicationEnabledSetting()) {
+                            && !installRequest.isApplicationEnabledSettingPersistent()) {
                         for (int origUserId : installedForUsers) {
                             if (userId == UserHandle.USER_ALL || userId == origUserId) {
                                 ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT,
@@ -2180,7 +2182,7 @@
                     // be installed and enabled. The caller, however, can explicitly specify to
                     // keep the existing enabled state.
                     ps.setInstalled(true, userId);
-                    if (!installRequest.isKeepApplicationEnabledSetting()) {
+                    if (!installRequest.isApplicationEnabledSettingPersistent()) {
                         ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId,
                                 installerPackageName);
                     }
@@ -2189,7 +2191,7 @@
                     // Thus, updating the settings to install the app for all users.
                     for (int currentUserId : allUsers) {
                         ps.setInstalled(true, currentUserId);
-                        if (!installRequest.isKeepApplicationEnabledSetting()) {
+                        if (!installRequest.isApplicationEnabledSettingPersistent()) {
                             ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, currentUserId,
                                     installerPackageName);
                         }
@@ -2291,7 +2293,7 @@
                 }
             }
             installRequest.setName(pkgName);
-            installRequest.setUid(pkg.getUid());
+            installRequest.setAppId(pkg.getUid());
             installRequest.setPkg(pkg);
             installRequest.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
             //to update install status
@@ -2776,7 +2778,7 @@
             }
 
             Bundle extras = new Bundle();
-            extras.putInt(Intent.EXTRA_UID, request.getUid());
+            extras.putInt(Intent.EXTRA_UID, request.getAppId());
             if (update) {
                 extras.putBoolean(Intent.EXTRA_REPLACING, true);
             }
@@ -2799,7 +2801,7 @@
 
                 // Send PACKAGE_ADDED broadcast for users that see the package for the first time
                 // sendPackageAddedForNewUsers also deals with system apps
-                int appId = UserHandle.getAppId(request.getUid());
+                int appId = UserHandle.getAppId(request.getAppId());
                 boolean isSystem = request.isInstallSystem();
                 mPm.sendPackageAddedForNewUsers(mPm.snapshotComputer(), packageName,
                         isSystem || virtualPreload, virtualPreload /*startReceiver*/, appId,
@@ -2944,9 +2946,9 @@
             }
 
             if (allNewUsers && !update) {
-                mPm.notifyPackageAdded(packageName, request.getUid());
+                mPm.notifyPackageAdded(packageName, request.getAppId());
             } else {
-                mPm.notifyPackageChanged(packageName, request.getUid());
+                mPm.notifyPackageChanged(packageName, request.getAppId());
             }
 
             // Log current value of "unknown sources" setting
@@ -3172,7 +3174,7 @@
         final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
         removePackageHelper.removePackage(stubPkg, true /*chatty*/);
         try {
-            return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags);
+            return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags, null);
         } catch (PackageManagerException e) {
             Slog.w(TAG, "Failed to install compressed system package:" + stubPkg.getPackageName(),
                     e);
@@ -3304,7 +3306,7 @@
                         | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
         @PackageManagerService.ScanFlags int scanFlags = mPm.getSystemPackageScanFlags(codePath);
         final AndroidPackage pkg = scanSystemPackageTracedLI(
-                codePath, parseFlags, scanFlags);
+                codePath, parseFlags, scanFlags, null);
 
         synchronized (mPm.mLock) {
             PackageSetting pkgSetting = mPm.mSettings.getPackageLPr(pkg.getPackageName());
@@ -3484,7 +3486,7 @@
                 try {
                     final File codePath = new File(pkg.getPath());
                     synchronized (mPm.mInstallLock) {
-                        scanSystemPackageTracedLI(codePath, 0, scanFlags);
+                        scanSystemPackageTracedLI(codePath, 0, scanFlags, null);
                     }
                 } catch (PackageManagerException e) {
                     Slog.e(TAG, "Failed to parse updated, ex-system package: "
@@ -3563,7 +3565,8 @@
 
             if (throwable == null) {
                 try {
-                    addForInitLI(parseResult.parsedPackage, newParseFlags, newScanFlags, null);
+                    addForInitLI(parseResult.parsedPackage, newParseFlags, newScanFlags, null,
+                            new ApexManager.ActiveApexInfo(ai));
                     AndroidPackage pkg = parseResult.parsedPackage.hideAsFinal();
                     if (ai.isFactory && !ai.isActive) {
                         disableSystemPackageLPw(pkg);
@@ -3585,8 +3588,8 @@
 
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
     public void installPackagesFromDir(File scanDir, int parseFlags,
-            int scanFlags, PackageParser2 packageParser,
-            ExecutorService executorService) {
+            int scanFlags, PackageParser2 packageParser, ExecutorService executorService,
+            @Nullable ApexManager.ActiveApexInfo apexInfo) {
         final File[] files = scanDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
             Log.d(TAG, "No files in app dir " + scanDir);
@@ -3634,7 +3637,7 @@
                 }
                 try {
                     addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,
-                            new UserHandle(UserHandle.USER_SYSTEM));
+                            new UserHandle(UserHandle.USER_SYSTEM), apexInfo);
                 } catch (PackageManagerException e) {
                     errorCode = e.error;
                     errorMsg = "Failed to scan " + parseResult.scanFile + ": " + e.getMessage();
@@ -3697,7 +3700,7 @@
             try {
                 synchronized (mPm.mInstallLock) {
                     final AndroidPackage newPkg = scanSystemPackageTracedLI(
-                            scanFile, reparseFlags, rescanFlags);
+                            scanFile, reparseFlags, rescanFlags, null);
                     // We rescanned a stub, add it to the list of stubbed system packages
                     if (newPkg.isStub()) {
                         stubSystemApps.add(packageName);
@@ -3716,10 +3719,11 @@
      */
     @GuardedBy("mPm.mInstallLock")
     public AndroidPackage scanSystemPackageTracedLI(File scanFile, final int parseFlags,
-            int scanFlags) throws PackageManagerException {
+            int scanFlags, @Nullable ApexManager.ActiveApexInfo apexInfo)
+            throws PackageManagerException {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage [" + scanFile.toString() + "]");
         try {
-            return scanSystemPackageLI(scanFile, parseFlags, scanFlags);
+            return scanSystemPackageLI(scanFile, parseFlags, scanFlags, apexInfo);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
@@ -3730,7 +3734,8 @@
      *  Returns {@code null} in case of errors and the error code is stored in mLastScanError
      */
     @GuardedBy("mPm.mInstallLock")
-    private AndroidPackage scanSystemPackageLI(File scanFile, int parseFlags, int scanFlags)
+    private AndroidPackage scanSystemPackageLI(File scanFile, int parseFlags, int scanFlags,
+            @Nullable ApexManager.ActiveApexInfo apexInfo)
             throws PackageManagerException {
         if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
 
@@ -3748,7 +3753,7 @@
         }
 
         return addForInitLI(parsedPackage, parseFlags, scanFlags,
-                new UserHandle(UserHandle.USER_SYSTEM));
+                new UserHandle(UserHandle.USER_SYSTEM), apexInfo);
     }
 
     /**
@@ -3768,7 +3773,26 @@
     private AndroidPackage addForInitLI(ParsedPackage parsedPackage,
             @ParsingPackageUtils.ParseFlags int parseFlags,
             @PackageManagerService.ScanFlags int scanFlags,
-            @Nullable UserHandle user) throws PackageManagerException {
+            @Nullable UserHandle user, @Nullable ApexManager.ActiveApexInfo activeApexInfo)
+            throws PackageManagerException {
+        PackageSetting disabledPkgSetting;
+        synchronized (mPm.mLock) {
+            disabledPkgSetting =
+                    mPm.mSettings.getDisabledSystemPkgLPr(parsedPackage.getPackageName());
+            if (activeApexInfo != null && disabledPkgSetting != null) {
+                // When a disabled system package is scanned, its final PackageSetting is actually
+                // skipped and not added to any data structures, instead relying on the disabled
+                // setting read from the persisted Settings XML file. This persistence does not
+                // include the APEX module name, so here, re-set it from the active APEX info.
+                //
+                // This also has the (beneficial) side effect where if a package disappears from an
+                // APEX, leaving only a /data copy, it will lose its apexModuleName.
+                //
+                // This must be done before scanSystemPackageLI as that will throw in the case of a
+                // system -> data package.
+                disabledPkgSetting.setApexModuleName(activeApexInfo.apexModuleName);
+            }
+        }
 
         final Pair<ScanResult, Boolean> scanResultPair = scanSystemPackageLI(
                 parsedPackage, parseFlags, scanFlags, user);
@@ -3777,6 +3801,24 @@
         final InstallRequest installRequest = new InstallRequest(
                 parsedPackage, parseFlags, scanFlags, user, scanResult);
 
+        String existingApexModuleName = null;
+        synchronized (mPm.mLock) {
+            var existingPkgSetting = mPm.mSettings.getPackageLPr(parsedPackage.getPackageName());
+            if (existingPkgSetting != null) {
+                existingApexModuleName = existingPkgSetting.getApexModuleName();
+            }
+        }
+
+        if (activeApexInfo != null) {
+            installRequest.setApexModuleName(activeApexInfo.apexModuleName);
+        } else {
+            if (disabledPkgSetting != null) {
+                installRequest.setApexModuleName(disabledPkgSetting.getApexModuleName());
+            } else if (existingApexModuleName != null) {
+                installRequest.setApexModuleName(existingApexModuleName);
+            }
+        }
+
         synchronized (mPm.mLock) {
             boolean appIdCreated = false;
             try {
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index c6cdc4c..a9c5773 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -44,6 +44,7 @@
 
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
@@ -81,7 +82,7 @@
     /** Package Installed Info */
     @Nullable
     private String mName;
-    private int mUid = INVALID_UID;
+    private int mAppId = INVALID_UID;
     // The set of users that originally had this package installed.
     @Nullable
     private int[] mOrigUsers;
@@ -107,6 +108,12 @@
     @Nullable
     private ApexInfo mApexInfo;
 
+    /**
+     * For tracking {@link PackageState#getApexModuleName()}.
+     */
+    @Nullable
+    private String mApexModuleName;
+
     @Nullable
     private ScanResult mScanResult;
 
@@ -129,7 +136,7 @@
                 params.mTraceMethod, params.mTraceCookie, params.mSigningDetails,
                 params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride,
                 params.mDataLoaderType, params.mPackageSource,
-                params.mKeepApplicationEnabledSetting);
+                params.mApplicationEnabledSettingPersistent);
         mPackageMetrics = new PackageMetrics(this);
         mIsInstallInherit = params.mIsInherit;
         mSessionId = params.mSessionId;
@@ -158,7 +165,7 @@
             mUserId = user.getIdentifier();
         } else {
             // APEX
-            mUserId = INVALID_UID;
+            mUserId = UserHandle.USER_SYSTEM;
         }
         mInstallArgs = null;
         mParsedPackage = parsedPackage;
@@ -348,6 +355,11 @@
     }
 
     @Nullable
+    public String getApexModuleName() {
+        return mApexModuleName;
+    }
+
+    @Nullable
     public String getSourceInstallerPackageName() {
         return mInstallArgs.mInstallSource.mInstallerPackageName;
     }
@@ -367,8 +379,8 @@
         return mOrigUsers;
     }
 
-    public int getUid() {
-        return mUid;
+    public int getAppId() {
+        return mAppId;
     }
 
     @Nullable
@@ -499,8 +511,8 @@
         return mScanResult.mChangedAbiCodePath;
     }
 
-    public boolean isKeepApplicationEnabledSetting() {
-        return mInstallArgs == null ? false : mInstallArgs.mKeepApplicationEnabledSetting;
+    public boolean isApplicationEnabledSettingPersistent() {
+        return mInstallArgs == null ? false : mInstallArgs.mApplicationEnabledSettingPersistent;
     }
 
     public boolean isForceQueryableOverride() {
@@ -644,12 +656,16 @@
         mApexInfo = apexInfo;
     }
 
+    public void setApexModuleName(@Nullable String apexModuleName) {
+        mApexModuleName = apexModuleName;
+    }
+
     public void setPkg(AndroidPackage pkg) {
         mPkg = pkg;
     }
 
-    public void setUid(int uid) {
-        mUid = uid;
+    public void setAppId(int appId) {
+        mAppId = appId;
     }
 
     public void setNewUsers(int[] newUsers) {
@@ -773,10 +789,10 @@
         }
     }
 
-    public void onInstallCompleted(int userId) {
+    public void onInstallCompleted() {
         if (getReturnCode() == INSTALL_SUCCEEDED) {
             if (mPackageMetrics != null) {
-                mPackageMetrics.onInstallSucceed(userId);
+                mPackageMetrics.onInstallSucceed();
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index eb3b29c..7b759e3 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -97,7 +97,7 @@
     final boolean mIsInherit;
     final int mSessionId;
     final int mRequireUserAction;
-    final boolean mKeepApplicationEnabledSetting;
+    final boolean mApplicationEnabledSettingPersistent;
 
     // For move install
     InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
@@ -130,7 +130,7 @@
         mIsInherit = false;
         mSessionId = -1;
         mRequireUserAction = USER_ACTION_UNSPECIFIED;
-        mKeepApplicationEnabledSetting = false;
+        mApplicationEnabledSettingPersistent = false;
     }
 
     InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer,
@@ -164,7 +164,7 @@
         mIsInherit = sessionParams.mode == MODE_INHERIT_EXISTING;
         mSessionId = sessionId;
         mRequireUserAction = sessionParams.requireUserAction;
-        mKeepApplicationEnabledSetting = sessionParams.keepApplicationEnabledSetting;
+        mApplicationEnabledSettingPersistent = sessionParams.applicationEnabledSettingPersistent;
     }
 
     @Override
@@ -535,7 +535,7 @@
             mInstallPackageHelper.installPackagesTraced(installRequests);
 
             for (InstallRequest request : installRequests) {
-                request.onInstallCompleted(mUser.getIdentifier());
+                request.onInstallCompleted();
                 doPostInstall(request);
             }
         }
@@ -609,6 +609,7 @@
                 // processApkInstallRequests() fails. Need a way to keep info stored in apexd
                 // and PMS in sync in the face of install failures.
                 request.setApexInfo(apexInfo);
+                request.setApexModuleName(apexInfo.moduleName);
                 mPm.mHandler.post(() -> processApkInstallRequests(true, requests));
                 return;
             }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 350f5ef..47e18f1 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -278,8 +278,8 @@
     private static final String ATTR_SIGNATURE = "signature";
     private static final String ATTR_CHECKSUM_KIND = "checksumKind";
     private static final String ATTR_CHECKSUM_VALUE = "checksumValue";
-    private static final String ATTR_KEEP_APPLICATION_ENABLED_SETTING =
-            "keepApplicationEnabledSetting";
+    private static final String ATTR_APPLICATION_ENABLED_SETTING_PERSISTENT =
+            "applicationEnabledSettingPersistent";
 
     private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
     private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT;
@@ -1163,7 +1163,7 @@
             info.requireUserAction = params.requireUserAction;
             info.installerUid = mInstallerUid;
             info.packageSource = params.packageSource;
-            info.keepApplicationEnabledSetting = params.keepApplicationEnabledSetting;
+            info.applicationEnabledSettingPersistent = params.applicationEnabledSettingPersistent;
             info.pendingUserActionReason = userActionRequirementToReason(mUserActionRequirement);
         }
         return info;
@@ -4553,8 +4553,8 @@
     }
 
     @Override
-    public boolean isKeepApplicationEnabledSetting() {
-        return params.keepApplicationEnabledSetting;
+    public boolean isApplicationEnabledSettingPersistent() {
+        return params.applicationEnabledSettingPersistent;
     }
 
     @Override
@@ -4945,8 +4945,8 @@
             writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
             writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
             out.attributeInt(null, ATTR_INSTALL_REASON, params.installReason);
-            writeBooleanAttribute(out, ATTR_KEEP_APPLICATION_ENABLED_SETTING,
-                    params.keepApplicationEnabledSetting);
+            writeBooleanAttribute(out, ATTR_APPLICATION_ENABLED_SETTING_PERSISTENT,
+                    params.applicationEnabledSettingPersistent);
 
             final boolean isDataLoader = params.dataLoaderParams != null;
             writeBooleanAttribute(out, ATTR_IS_DATALOADER, isDataLoader);
@@ -5110,8 +5110,8 @@
         params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
         params.installReason = in.getAttributeInt(null, ATTR_INSTALL_REASON);
         params.packageSource = in.getAttributeInt(null, ATTR_PACKAGE_SOURCE);
-        params.keepApplicationEnabledSetting = in.getAttributeBoolean(null,
-                ATTR_KEEP_APPLICATION_ENABLED_SETTING, false);
+        params.applicationEnabledSettingPersistent = in.getAttributeBoolean(null,
+                ATTR_APPLICATION_ENABLED_SETTING_PERSISTENT, false);
 
         if (in.getAttributeBoolean(null, ATTR_IS_DATALOADER, false)) {
             params.dataLoaderParams = new DataLoaderParams(
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 92bbb7e..9cc0334 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -716,7 +716,7 @@
      * The list of all system partitions that may contain packages in ascending order of
      * specificity (the more generic, the earlier in the list a partition appears).
      */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @VisibleForTesting(visibility = Visibility.PACKAGE)
     public static final List<ScanPartition> SYSTEM_PARTITIONS = Collections.unmodifiableList(
             PackagePartitions.getOrderedPartitions(ScanPartition::new));
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 849cbeb..12841a4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3291,7 +3291,7 @@
                     sessionParams.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
                     break;
                 case "--skip-enable":
-                    sessionParams.setKeepApplicationEnabledSetting();
+                    sessionParams.setApplicationEnabledSettingPersistent();
                     break;
                 case "--bypass-low-target-sdk-block":
                     sessionParams.installFlags |=
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 8252a9fa..d4c1256 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -19,9 +19,11 @@
 import static android.os.Process.INVALID_UID;
 
 import android.annotation.IntDef;
+import android.app.ActivityManager;
 import android.app.admin.SecurityLog;
 import android.content.pm.PackageManager;
 import android.content.pm.parsing.ApkLiteParseUtils;
+import android.os.UserHandle;
 import android.util.Pair;
 import android.util.SparseArray;
 
@@ -68,8 +70,8 @@
         mInstallRequest = installRequest;
     }
 
-    public void onInstallSucceed(int userId) {
-        reportInstallationToSecurityLog(userId);
+    public void onInstallSucceed() {
+        reportInstallationToSecurityLog(mInstallRequest.getUserId());
         reportInstallationStats(true /* success */);
     }
 
@@ -110,10 +112,11 @@
             }
         }
 
+
         FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED,
                 mInstallRequest.getSessionId() /* session_id */,
                 packageName /* package_name */,
-                mInstallRequest.getUid() /* uid */,
+                getUid(mInstallRequest.getAppId(), mInstallRequest.getUserId()) /* uid */,
                 newUsers /* user_ids */,
                 userManagerInternal.getUserTypesForStatsd(newUsers) /* user_types */,
                 originalUsers /* original_user_ids */,
@@ -140,6 +143,13 @@
         );
     }
 
+    private static int getUid(int appId, int userId) {
+        if (userId == UserHandle.USER_ALL) {
+            userId = ActivityManager.getCurrentUser();
+        }
+        return UserHandle.getUid(userId, appId);
+    }
+
     private long getApksSize(File apkDir) {
         // TODO(b/249294752): also count apk sizes for failed installs
         final AtomicLong apksSize = new AtomicLong();
@@ -218,9 +228,9 @@
         final int[] originalUsers = info.mOrigUsers;
         final int[] originalUserTypes = userManagerInternal.getUserTypesForStatsd(originalUsers);
         FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_UNINSTALLATION_REPORTED,
-                info.mUid, removedUsers, removedUserTypes, originalUsers, originalUserTypes,
-                deleteFlags, PackageManager.DELETE_SUCCEEDED, info.mIsRemovedPackageSystemUpdate,
-                !info.mRemovedForAllUsers);
+                getUid(info.mUid, userId), removedUsers, removedUserTypes, originalUsers,
+                originalUserTypes, deleteFlags, PackageManager.DELETE_SUCCEEDED,
+                info.mIsRemovedPackageSystemUpdate, !info.mRemovedForAllUsers);
         final String packageName = info.mRemovedPackage;
         final long versionCode = info.mRemovedPackageVersionCode;
         reportUninstallationToSecurityLog(packageName, versionCode, userId);
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 6562de96..53fdfaa 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -1262,6 +1262,12 @@
         return pkgState.isApkInUpdatedApex();
     }
 
+    @Nullable
+    @Override
+    public String getApexModuleName() {
+        return pkgState.getApexModuleName();
+    }
+
     public PackageSetting setDomainSetId(@NonNull UUID domainSetId) {
         mDomainSetId = domainSetId;
         onChanged();
@@ -1317,6 +1323,11 @@
         return this;
     }
 
+    public PackageSetting setApexModuleName(@Nullable String apexModuleName) {
+        pkgState.setApexModuleName(apexModuleName);
+        return this;
+    }
+
     @NonNull
     @Override
     public PackageStateUnserialized getTransientState() {
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index 970f8ce..a13c568 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -281,6 +281,7 @@
                 ri.handleAllWebDataURI = browserCount == n;
                 ri.activityInfo = new ActivityInfo(ri.activityInfo);
                 ri.activityInfo.labelRes = ResolverActivity.getLabelRes(intent.getAction());
+                if (ri.userHandle == null) ri.userHandle = UserHandle.of(userId);
                 // If all of the options come from the same package, show the application's
                 // label and icon instead of the generic resolver's.
                 // Some calls like Intent.resolveActivityInfo query the ResolveInfo from here
diff --git a/services/core/java/com/android/server/pm/ScanPartition.java b/services/core/java/com/android/server/pm/ScanPartition.java
index e1d2b3b..9ee6035 100644
--- a/services/core/java/com/android/server/pm/ScanPartition.java
+++ b/services/core/java/com/android/server/pm/ScanPartition.java
@@ -16,13 +16,17 @@
 
 package com.android.server.pm;
 
+import static com.android.server.pm.PackageManagerService.SCAN_AS_APK_IN_APEX;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_FACTORY;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_ODM;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_OEM;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_PRODUCT;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM_EXT;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_VENDOR;
+import static com.android.server.pm.PackageManagerService.SCAN_DROP_CACHE;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.pm.PackagePartitions;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -32,14 +36,18 @@
 /**
  * List of partitions to be scanned during system boot
  */
-@VisibleForTesting
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public class ScanPartition extends PackagePartitions.SystemPartition {
     @PackageManagerService.ScanFlags
     public final int scanFlag;
 
+    @Nullable
+    public final ApexManager.ActiveApexInfo apexInfo;
+
     public ScanPartition(@NonNull PackagePartitions.SystemPartition partition) {
         super(partition);
         scanFlag = scanFlagForPartition(partition);
+        apexInfo = null;
     }
 
     /**
@@ -48,9 +56,21 @@
      * partition along with any specified additional scan flags.
      */
     public ScanPartition(@NonNull File folder, @NonNull ScanPartition original,
-            @PackageManagerService.ScanFlags int additionalScanFlag) {
+            @Nullable ApexManager.ActiveApexInfo apexInfo) {
         super(folder, original);
-        this.scanFlag = original.scanFlag | additionalScanFlag;
+        var scanFlags = original.scanFlag;
+        this.apexInfo = apexInfo;
+        if (apexInfo != null) {
+            scanFlags |= SCAN_AS_APK_IN_APEX;
+            if (apexInfo.isFactory) {
+                scanFlags |= SCAN_AS_FACTORY;
+            }
+            if (apexInfo.activeApexChanged) {
+                scanFlags |= SCAN_DROP_CACHE;
+            }
+        }
+        //noinspection WrongConstant
+        this.scanFlag = scanFlags;
     }
 
     private static int scanFlagForPartition(PackagePartitions.SystemPartition partition) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 97fb0c2..aedf782 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -5052,6 +5052,7 @@
         pw.print(prefix); pw.print("  privatePkgFlags="); printFlags(pw, ps.getPrivateFlags(),
                 PRIVATE_FLAG_DUMP_SPEC);
         pw.println();
+        pw.print(prefix); pw.print("  apexModuleName="); pw.println(ps.getApexModuleName());
 
         if (pkg != null && pkg.getOverlayTarget() != null) {
             pw.print(prefix); pw.print("  overlayTarget="); pw.println(pkg.getOverlayTarget());
@@ -5263,7 +5264,8 @@
                     && !packageName.equals(ps.getPackageName())) {
                 continue;
             }
-            if (ps.getPkg() != null && ps.getPkg().isApex()) {
+            if (ps.getPkg() != null && ps.getPkg().isApex()
+                    && !dumpState.isOptionEnabled(DumpState.OPTION_INCLUDE_APEX)) {
                 // Filter APEX packages which will be dumped in the APEX section
                 continue;
             }
@@ -5319,7 +5321,8 @@
                         && !packageName.equals(ps.getPackageName())) {
                     continue;
                 }
-                if (ps.getPkg() != null && ps.getPkg().isApex()) {
+                if (ps.getPkg() != null && ps.getPkg().isApex()
+                        && !dumpState.isOptionEnabled(DumpState.OPTION_INCLUDE_APEX)) {
                     // Filter APEX packages which will be dumped in the APEX section
                     continue;
                 }
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 4f7c2bd..23156d1 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -158,7 +158,7 @@
                 final AndroidPackage pkg;
                 try {
                     pkg = installPackageHelper.scanSystemPackageTracedLI(
-                            ps.getPath(), parseFlags, SCAN_INITIAL);
+                            ps.getPath(), parseFlags, SCAN_INITIAL, null);
                     loaded.add(pkg);
 
                 } catch (PackageManagerException e) {
diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
index 411c19f..d3fba7c 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -24,7 +24,7 @@
 
 import com.android.server.art.ReasonMapping;
 import com.android.server.art.model.ArtFlags;
-import com.android.server.art.model.OptimizeParams;
+import com.android.server.art.model.DexoptParams;
 import com.android.server.pm.DexOptHelper;
 import com.android.server.pm.PackageManagerService;
 
@@ -201,22 +201,22 @@
     }
 
     /**
-     * Returns an {@link OptimizeParams} instance corresponding to this object, for use with
+     * Returns an {@link DexoptParams} instance corresponding to this object, for use with
      * {@link com.android.server.art.ArtManagerLocal}.
      *
-     * @param extraFlags extra {@link ArtFlags#OptimizeFlags} to set in the returned
-     *     {@code OptimizeParams} beyond those converted from this object
+     * @param extraFlags extra {@link ArtFlags#DexoptFlags} to set in the returned
+     *     {@code DexoptParams} beyond those converted from this object
      * @return null if the settings cannot be accurately represented, and hence the old
      *     PackageManager/installd code paths need to be used.
      */
-    public @Nullable OptimizeParams convertToOptimizeParams(/*@OptimizeFlags*/ int extraFlags) {
+    public @Nullable DexoptParams convertToDexoptParams(/*@DexoptFlags*/ int extraFlags) {
         if (mSplitName != null) {
             DexOptHelper.reportArtManagerFallback(
                     mPackageName, "Request to optimize only split " + mSplitName);
             return null;
         }
 
-        /*@OptimizeFlags*/ int flags = extraFlags;
+        /*@DexoptFlags*/ int flags = extraFlags;
         if ((mFlags & DEXOPT_CHECK_FOR_PROFILES_UPDATES) == 0
                 && isProfileGuidedCompilerFilter(mCompilerFilter)) {
             // ART Service doesn't support bypassing the profile update check when profiles are
@@ -322,7 +322,7 @@
                         "Invalid compilation reason " + mCompilationReason);
         }
 
-        return new OptimizeParams.Builder(reason, flags)
+        return new DexoptParams.Builder(reason, flags)
                 .setCompilerFilter(mCompilerFilter)
                 .setPriorityClass(priority)
                 .build();
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 5fdead0..a12c9d0 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -417,4 +417,11 @@
      * @hide
      */
     boolean isVendor();
+
+    /**
+     * The name of the APEX module containing this package, if it is an APEX or APK-in-APEX.
+     * @hide
+     */
+    @Nullable
+    String getApexModuleName();
 }
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 8dee8ee..bc6dab4 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -154,6 +154,8 @@
     private final SigningInfo mSigningInfo;
     @NonNull
     private final SparseArray<PackageUserState> mUserStates;
+    @Nullable
+    private final String mApexModuleName;
 
     private PackageStateImpl(@NonNull PackageState pkgState, @Nullable AndroidPackage pkg) {
         mAndroidPackage = pkg;
@@ -206,6 +208,8 @@
             mUserStates.put(userStates.keyAt(index),
                     UserStateImpl.copy(userStates.valueAt(index)));
         }
+
+        mApexModuleName = pkgState.getApexModuleName();
     }
 
     @NonNull
@@ -714,6 +718,11 @@
     }
 
     @DataClass.Generated.Member
+    public @Nullable String getApexModuleName() {
+        return mApexModuleName;
+    }
+
+    @DataClass.Generated.Member
     public @NonNull PackageStateImpl setBooleans( int value) {
         mBooleans = value;
         return this;
@@ -723,7 +732,7 @@
             time = 1671671043929L,
             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 @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 boolean isApex()\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\nprivate final @android.annotation.Nullable java.lang.String mApexModuleName\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 boolean isApex()\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/pm/pkg/PackageStateUnserialized.java b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
index 57fbfe9..19c0886 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
@@ -54,7 +54,6 @@
     private List<String> usesLibraryFiles = emptyList();
 
     private boolean updatedSystemApp;
-    private boolean apkInApex;
     private boolean apkInUpdatedApex;
 
     @NonNull
@@ -70,6 +69,9 @@
     @NonNull
     private final PackageSetting mPackageSetting;
 
+    @Nullable
+    private String mApexModuleName;
+
     public PackageStateUnserialized(@NonNull PackageSetting packageSetting) {
         mPackageSetting = packageSetting;
     }
@@ -138,11 +140,11 @@
         }
 
         this.updatedSystemApp = other.updatedSystemApp;
-        this.apkInApex = other.apkInApex;
         this.apkInUpdatedApex = other.apkInUpdatedApex;
         this.lastPackageUsageTimeInMills = other.lastPackageUsageTimeInMills;
         this.overrideSeInfo = other.overrideSeInfo;
         this.seInfo = other.seInfo;
+        this.mApexModuleName = other.mApexModuleName;
         mPackageSetting.onChanged();
     }
 
@@ -187,12 +189,6 @@
         return this;
     }
 
-    public PackageStateUnserialized setApkInApex(boolean value) {
-        apkInApex = value;
-        mPackageSetting.onChanged();
-        return this;
-    }
-
     public PackageStateUnserialized setApkInUpdatedApex(boolean value) {
         apkInUpdatedApex = value;
         mPackageSetting.onChanged();
@@ -218,6 +214,13 @@
         return this;
     }
 
+    @NonNull
+    public PackageStateUnserialized setApexModuleName(@NonNull String value) {
+        mApexModuleName = value;
+        mPackageSetting.onChanged();
+        return this;
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -254,11 +257,6 @@
     }
 
     @DataClass.Generated.Member
-    public boolean isApkInApex() {
-        return apkInApex;
-    }
-
-    @DataClass.Generated.Member
     public boolean isApkInUpdatedApex() {
         return apkInUpdatedApex;
     }
@@ -292,11 +290,16 @@
         return mPackageSetting;
     }
 
+    @DataClass.Generated.Member
+    public @Nullable String getApexModuleName() {
+        return mApexModuleName;
+    }
+
     @DataClass.Generated(
-            time = 1666291743725L,
+            time = 1671483772254L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java",
-            inputSignatures = "private  boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibraryWrapper> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate  boolean updatedSystemApp\nprivate  boolean apkInApex\nprivate  boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate @android.annotation.NonNull java.lang.String seInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryInfo(com.android.server.pm.pkg.SharedLibraryWrapper)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryFile(java.lang.String)\nprivate  long[] lazyInitLastPackageUsageTimeInMills()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic  long getLatestPackageUseTimeInMills()\npublic  long getLatestForegroundPackageUseTimeInMills()\npublic  void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized setSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
+            inputSignatures = "private  boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibraryWrapper> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate  boolean updatedSystemApp\nprivate  boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate @android.annotation.NonNull java.lang.String seInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\nprivate @android.annotation.Nullable java.lang.String mApexModuleName\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryInfo(com.android.server.pm.pkg.SharedLibraryWrapper)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryFile(java.lang.String)\nprivate  long[] lazyInitLastPackageUsageTimeInMills()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic  long getLatestPackageUseTimeInMills()\npublic  long getLatestForegroundPackageUseTimeInMills()\npublic  void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic  com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic  com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized setSeInfo(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized setApexModuleName(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
index 842f685..35c42a3 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
@@ -35,6 +35,7 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DebugUtils;
@@ -1197,6 +1198,7 @@
             res.iconResourceId = info.getIcon();
             res.system = res.activityInfo.applicationInfo.isSystemApp();
             res.isInstantAppAvailable = userState.isInstantApp();
+            res.userHandle = UserHandle.of(userId);
             return res;
         }
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 320b8e1..b7a801a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -661,7 +661,7 @@
                     dispatchMediaKeyRepeatWithWakeLock((KeyEvent)msg.obj);
                     break;
                 case MSG_DISPATCH_SHOW_RECENTS:
-                    showRecentApps(false);
+                    showRecents();
                     break;
                 case MSG_DISPATCH_SHOW_GLOBAL_ACTIONS:
                     showGlobalActionsInternal();
@@ -2910,7 +2910,7 @@
                 break;
             case KeyEvent.KEYCODE_RECENT_APPS:
                 if (down && repeatCount == 0) {
-                    showRecentApps(false /* triggeredFromAltTab */);
+                    showRecents();
                 }
                 return key_consumed;
             case KeyEvent.KEYCODE_APP_SWITCH:
@@ -3094,22 +3094,23 @@
                 }
                 break;
             case KeyEvent.KEYCODE_TAB:
-                if (down && event.isMetaPressed()) {
-                    if (!keyguardOn && isUserSetupComplete()) {
-                        showRecentApps(false);
-                        return key_consumed;
-                    }
-                } else if (down && repeatCount == 0) {
-                    // Display task switcher for ALT-TAB.
-                    if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) {
-                        final int shiftlessModifiers =
-                                event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
-                        if (KeyEvent.metaStateHasModifiers(
-                                shiftlessModifiers, KeyEvent.META_ALT_ON)) {
-                            mRecentAppsHeldModifiers = shiftlessModifiers;
-                            showRecentApps(true);
+                if (down) {
+                    if (event.isMetaPressed()) {
+                        if (!keyguardOn && isUserSetupComplete()) {
+                            showRecents();
                             return key_consumed;
                         }
+                    } else {
+                        // Display task switcher for ALT-TAB.
+                        if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) {
+                            final int modifiers = event.getModifiers();
+                            if (KeyEvent.metaStateHasModifiers(modifiers, KeyEvent.META_ALT_ON)) {
+                                mRecentAppsHeldModifiers = modifiers;
+                                showRecentsFromAltTab(KeyEvent.metaStateHasModifiers(modifiers,
+                                        KeyEvent.META_SHIFT_ON));
+                                return key_consumed;
+                            }
+                        }
                     }
                 }
                 break;
@@ -3646,11 +3647,19 @@
         mHandler.obtainMessage(MSG_DISPATCH_SHOW_RECENTS).sendToTarget();
     }
 
-    private void showRecentApps(boolean triggeredFromAltTab) {
+    private void showRecents() {
         mPreloadedRecentApps = false; // preloading no longer needs to be canceled
         StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
         if (statusbar != null) {
-            statusbar.showRecentApps(triggeredFromAltTab);
+            statusbar.showRecentApps(false /* triggeredFromAltTab */, false /* forward */);
+        }
+    }
+
+    private void showRecentsFromAltTab(boolean forward) {
+        mPreloadedRecentApps = false; // preloading no longer needs to be canceled
+        StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+        if (statusbar != null) {
+            statusbar.showRecentApps(true /* triggeredFromAltTab */, forward);
         }
     }
 
@@ -4307,7 +4316,7 @@
             case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY:
             case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY:
             case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: {
-                if (!mStylusButtonsDisabled) {
+                if (down && !mStylusButtonsDisabled) {
                     sendSystemKeyToStatusBarAsync(keyCode);
                 }
                 result &= ~ACTION_PASS_TO_USER;
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 326d709..ed6a46f 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -737,6 +737,7 @@
         }
         TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
         tm.notifyUserActivity();
+        mInputManagerInternal.notifyUserActivity();
         mPolicy.userActivity(displayGroupId, event);
         mFaceDownDetector.userActivity(event);
         mScreenUndimDetector.userActivity(displayGroupId);
diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
index 60dbbdd..3fcb08a 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -710,6 +710,11 @@
                 if (gnssChargeUC != EnergyConsumerSnapshot.UNAVAILABLE) {
                     mStats.updateGnssEnergyConsumerStatsLocked(gnssChargeUC, elapsedRealtime);
                 }
+
+                final long cameraChargeUC = energyConsumerDeltas.cameraChargeUC;
+                if (cameraChargeUC != EnergyConsumerSnapshot.UNAVAILABLE) {
+                    mStats.updateCameraEnergyConsumerStatsLocked(cameraChargeUC, elapsedRealtime);
+                }
             }
             // Inform mStats about each applicable custom energy bucket.
             if (energyConsumerDeltas != null
@@ -904,6 +909,9 @@
                 case EnergyConsumerType.WIFI:
                     buckets[EnergyConsumerStats.POWER_BUCKET_WIFI] = true;
                     break;
+                case EnergyConsumerType.CAMERA:
+                    buckets[EnergyConsumerStats.POWER_BUCKET_CAMERA] = true;
+                    break;
             }
         }
         return buckets;
@@ -955,6 +963,9 @@
         if ((flags & UPDATE_WIFI) != 0) {
             addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.WIFI);
         }
+        if ((flags & UPDATE_CAMERA) != 0) {
+            addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.CAMERA);
+        }
 
         if (energyConsumerIds.size() == 0) {
             return null;
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 c559436..d622fd7 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -178,7 +178,7 @@
     // TODO: remove "tcp" from network methods, since we measure total stats.
 
     // Current on-disk Parcel version. Must be updated when the format of the parcelable changes
-    public static final int VERSION = 210;
+    public static final int VERSION = 211;
 
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
@@ -649,10 +649,12 @@
         int UPDATE_BT = 0x08;
         int UPDATE_RPM = 0x10;
         int UPDATE_DISPLAY = 0x20;
-        int RESET = 0x40;
+        int UPDATE_CAMERA = 0x40;
+        int RESET = 0x80;
 
         int UPDATE_ALL =
-                UPDATE_CPU | UPDATE_WIFI | UPDATE_RADIO | UPDATE_BT | UPDATE_RPM | UPDATE_DISPLAY;
+                UPDATE_CPU | UPDATE_WIFI | UPDATE_RADIO | UPDATE_BT | UPDATE_RPM | UPDATE_DISPLAY
+                        | UPDATE_CAMERA;
 
         int UPDATE_ON_PROC_STATE_CHANGE = UPDATE_WIFI | UPDATE_RADIO | UPDATE_BT;
 
@@ -665,6 +667,7 @@
                 UPDATE_BT,
                 UPDATE_RPM,
                 UPDATE_DISPLAY,
+                UPDATE_CAMERA,
                 UPDATE_ALL,
         })
         @Retention(RetentionPolicy.SOURCE)
@@ -6244,6 +6247,8 @@
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
                 .noteCameraTurnedOnLocked(elapsedRealtimeMs);
+
+        scheduleSyncExternalStatsLocked("camera-on", ExternalStatsSync.UPDATE_CAMERA);
     }
 
     @GuardedBy("this")
@@ -6259,6 +6264,8 @@
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
                 .noteCameraTurnedOffLocked(elapsedRealtimeMs);
+
+        scheduleSyncExternalStatsLocked("camera-off", ExternalStatsSync.UPDATE_CAMERA);
     }
 
     @GuardedBy("this")
@@ -6273,6 +6280,8 @@
                 uid.noteResetCameraLocked(elapsedRealtimeMs);
             }
         }
+
+        scheduleSyncExternalStatsLocked("camera-reset", ExternalStatsSync.UPDATE_CAMERA);
     }
 
     @GuardedBy("this")
@@ -7414,6 +7423,12 @@
         return getPowerBucketConsumptionUC(EnergyConsumerStats.POWER_BUCKET_WIFI);
     }
 
+    @GuardedBy("this")
+    @Override
+    public long getCameraEnergyConsumptionUC() {
+        return getPowerBucketConsumptionUC(EnergyConsumerStats.POWER_BUCKET_CAMERA);
+    }
+
     /**
      * Returns the consumption (in microcoulombs) that the given standard power bucket consumed.
      * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable
@@ -8427,6 +8442,12 @@
                     processState);
         }
 
+        @GuardedBy("mBsi")
+        @Override
+        public long getCameraEnergyConsumptionUC() {
+            return getEnergyConsumptionUC(EnergyConsumerStats.POWER_BUCKET_CAMERA);
+        }
+
         /**
          * Gets the minimum of the uid's foreground activity time and its PROCESS_STATE_TOP time
          * since last marked. Also sets the mark time for both these timers.
@@ -8477,6 +8498,20 @@
             return gnssTimeUs;
         }
 
+        /**
+         * Gets the uid's time spent using the camera since last marked. Also sets the mark time for
+         * the camera timer.
+         */
+        private long markCameraTimeUs(long elapsedRealtimeMs) {
+            final StopwatchTimer timer = mCameraTurnedOnTimer;
+            if (timer == null) {
+                return 0;
+            }
+            final long cameraTimeUs = timer.getTimeSinceMarkLocked(elapsedRealtimeMs * 1000);
+            timer.setMark(elapsedRealtimeMs);
+            return cameraTimeUs;
+        }
+
         public StopwatchTimer createAudioTurnedOnTimerLocked() {
             if (mAudioTurnedOnTimer == null) {
                 mAudioTurnedOnTimer = new StopwatchTimer(mBsi.mClock, Uid.this, AUDIO_TURNED_ON,
@@ -12902,6 +12937,53 @@
     }
 
     /**
+     * Accumulate camera charge consumption and distribute it to the correct state and the apps.
+     *
+     * @param chargeUC amount of charge (microcoulombs) used by the camera since this was last
+     *         called.
+     */
+    @GuardedBy("this")
+    public void updateCameraEnergyConsumerStatsLocked(long chargeUC, long elapsedRealtimeMs) {
+        if (DEBUG_ENERGY) Slog.d(TAG, "Updating camera stats: " + chargeUC);
+        if (mGlobalEnergyConsumerStats == null) {
+            return;
+        }
+
+        if (!mOnBatteryInternal || chargeUC <= 0) {
+            // There's nothing further to update.
+            return;
+        }
+
+        if (mIgnoreNextExternalStats) {
+            // Although under ordinary resets we won't get here, and typically a new sync will
+            // happen right after the reset, strictly speaking we need to set all mark times to now.
+            final int uidStatsSize = mUidStats.size();
+            for (int i = 0; i < uidStatsSize; i++) {
+                final Uid uid = mUidStats.valueAt(i);
+                uid.markCameraTimeUs(elapsedRealtimeMs);
+            }
+            return;
+        }
+
+        mGlobalEnergyConsumerStats.updateStandardBucket(
+                EnergyConsumerStats.POWER_BUCKET_CAMERA, chargeUC);
+
+        // Collect the per uid time since mark so that we can normalize power.
+        final SparseDoubleArray cameraTimeUsArray = new SparseDoubleArray();
+
+        // Note: Iterating over all UIDs may be suboptimal.
+        final int uidStatsSize = mUidStats.size();
+        for (int i = 0; i < uidStatsSize; i++) {
+            final Uid uid = mUidStats.valueAt(i);
+            final long cameraTimeUs = uid.markCameraTimeUs(elapsedRealtimeMs);
+            if (cameraTimeUs == 0) continue;
+            cameraTimeUsArray.put(uid.getUid(), (double) cameraTimeUs);
+        }
+        distributeEnergyToUidsLocked(EnergyConsumerStats.POWER_BUCKET_CAMERA, chargeUC,
+                cameraTimeUsArray, 0, elapsedRealtimeMs);
+    }
+
+    /**
      * Accumulate Custom power bucket charge, globally and for each app.
      *
      * @param totalChargeUC charge (microcoulombs) used for this bucket since this was last called.
diff --git a/services/core/java/com/android/server/power/stats/CameraPowerCalculator.java b/services/core/java/com/android/server/power/stats/CameraPowerCalculator.java
index 16892034..89991bf 100644
--- a/services/core/java/com/android/server/power/stats/CameraPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/CameraPowerCalculator.java
@@ -48,27 +48,44 @@
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query);
 
-        final long durationMs = batteryStats.getCameraOnTime(rawRealtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED) / 1000;
-        final double powerMah = mPowerEstimator.calculatePower(durationMs);
+        long consumptionUc = batteryStats.getCameraEnergyConsumptionUC();
+        int powerModel = getPowerModel(consumptionUc, query);
+        long durationMs =
+                batteryStats.getCameraOnTime(
+                        rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED) / 1000;
+        double powerMah;
+        if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
+            powerMah = uCtoMah(consumptionUc);
+        } else {
+            powerMah = mPowerEstimator.calculatePower(durationMs);
+        }
+
         builder.getAggregateBatteryConsumerBuilder(
-                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
                 .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA, durationMs)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah);
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah, powerModel);
         builder.getAggregateBatteryConsumerBuilder(
-                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
                 .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA, durationMs)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah);
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah, powerModel);
     }
 
     @Override
     protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
-        final long durationMs =
+        long consumptionUc = app.getBatteryStatsUid().getCameraEnergyConsumptionUC();
+        int powerModel = getPowerModel(consumptionUc, query);
+        long durationMs =
                 mPowerEstimator.calculateDuration(u.getCameraTurnedOnTimer(), rawRealtimeUs,
                         BatteryStats.STATS_SINCE_CHARGED);
-        final double powerMah = mPowerEstimator.calculatePower(durationMs);
+        double powerMah;
+        if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
+            powerMah = uCtoMah(consumptionUc);
+        } else {
+            powerMah = mPowerEstimator.calculatePower(durationMs);
+        }
+
         app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA, durationMs)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah);
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah, powerModel);
     }
 }
diff --git a/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java b/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java
index 18595ca..939a08b 100644
--- a/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java
+++ b/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java
@@ -123,6 +123,9 @@
         /** The chargeUC for {@link EnergyConsumerType#WIFI}. */
         public long wifiChargeUC = UNAVAILABLE;
 
+        /** The chargeUC for {@link EnergyConsumerType#CAMERA}. */
+        public long cameraChargeUC = UNAVAILABLE;
+
         /** Map of {@link EnergyConsumerType#OTHER} ordinals to their total chargeUC. */
         public @Nullable long[] otherTotalChargeUC = null;
 
@@ -256,6 +259,10 @@
                     output.wifiChargeUC = deltaChargeUC;
                     break;
 
+                case EnergyConsumerType.CAMERA:
+                    output.cameraChargeUC = deltaChargeUC;
+                    break;
+
                 case EnergyConsumerType.OTHER:
                     if (output.otherTotalChargeUC == null) {
                         output.otherTotalChargeUC = new long[mNumOtherOrdinals];
@@ -458,6 +465,9 @@
                 case EnergyConsumerType.WIFI:
                     chargeUC[i] = delta.wifiChargeUC;
                     break;
+                case EnergyConsumerType.CAMERA:
+                    chargeUC[i] = delta.cameraChargeUC;
+                    break;
                 case EnergyConsumerType.OTHER:
                     if (delta.otherTotalChargeUC != null) {
                         chargeUC[i] = delta.otherTotalChargeUC[energyConsumer.ordinal];
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerService.java b/services/core/java/com/android/server/resources/ResourcesManagerService.java
index eec3a02..94aa518 100644
--- a/services/core/java/com/android/server/resources/ResourcesManagerService.java
+++ b/services/core/java/com/android/server/resources/ResourcesManagerService.java
@@ -74,8 +74,8 @@
         @Override
         protected void dump(@NonNull FileDescriptor fd,
                 @NonNull PrintWriter pw, @Nullable String[] args) {
-            try {
-                mActivityManagerService.dumpAllResources(ParcelFileDescriptor.dup(fd), pw);
+            try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd)) {
+                mActivityManagerService.dumpAllResources(pfd, pw);
             } catch (Exception e) {
                 pw.println("Exception while trying to dump all resources: " + e.getMessage());
                 e.printStackTrace(pw);
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
index 7d8336a..a75d110 100644
--- a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
+++ b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
@@ -62,13 +62,12 @@
 
     private int dumpResources() throws RemoteException {
         String processId = getNextArgRequired();
-        try {
+        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(getOutFileDescriptor())) {
             ConditionVariable lock = new ConditionVariable();
             RemoteCallback
                     finishCallback = new RemoteCallback(result -> lock.open(), null);
 
-            if (!mInterface.dumpResources(processId,
-                    ParcelFileDescriptor.dup(getOutFileDescriptor()), finishCallback)) {
+            if (!mInterface.dumpResources(processId, pfd, finishCallback)) {
                 getErrPrintWriter().println("RESOURCES DUMP FAILED on process " + processId);
                 return -1;
             }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 392fda9..0fd6d9b 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -39,7 +39,7 @@
 
     void cancelPreloadRecentApps();
 
-    void showRecentApps(boolean triggeredFromAltTab);
+    void showRecentApps(boolean triggeredFromAltTab, boolean forward);
 
     void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
 
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 8d71d9c..97ca8df 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -454,10 +454,10 @@
         }
 
         @Override
-        public void showRecentApps(boolean triggeredFromAltTab) {
+        public void showRecentApps(boolean triggeredFromAltTab, boolean forward) {
             if (mBar != null) {
                 try {
-                    mBar.showRecentApps(triggeredFromAltTab);
+                    mBar.showRecentApps(triggeredFromAltTab, forward);
                 } catch (RemoteException ex) {}
             }
         }
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index ebee995..d4f2f2d 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -24,11 +24,13 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
 import android.graphics.Rect;
+import android.media.PlaybackParams;
 import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.AdResponse;
@@ -85,6 +87,11 @@
 public class TvInteractiveAppManagerService extends SystemService {
     private static final boolean DEBUG = false;
     private static final String TAG = "TvInteractiveAppManagerService";
+
+    private static final String METADATA_CLASS_NAME =
+            "android.media.tv.interactive.AppLinkInfo.ClassName";
+    private static final String METADATA_URI =
+            "android.media.tv.interactive.AppLinkInfo.Uri";
     // A global lock.
     private final Object mLock = new Object();
     private final Context mContext;
@@ -101,6 +108,8 @@
     // TODO: remove mGetServiceListCalled if onBootPhrase work correctly
     @GuardedBy("mLock")
     private boolean mGetServiceListCalled = false;
+    @GuardedBy("mLock")
+    private boolean mGetAppLinkInfoListCalled = false;
 
     private final UserManager mUserManager;
 
@@ -120,6 +129,41 @@
     }
 
     @GuardedBy("mLock")
+    private void buildAppLinkInfoLocked(int userId) {
+        UserState userState = getOrCreateUserStateLocked(userId);
+        if (DEBUG) {
+            Slogf.d(TAG, "buildAppLinkInfoLocked");
+        }
+        PackageManager pm = mContext.getPackageManager();
+        List<ApplicationInfo> appInfos = pm.getInstalledApplicationsAsUser(
+                PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA), userId);
+        List<AppLinkInfo> appLinkInfos = new ArrayList<>();
+        for (ApplicationInfo appInfo : appInfos) {
+            AppLinkInfo info = buildAppLinkInfoLocked(appInfo);
+            if (info != null) {
+                appLinkInfos.add(info);
+            }
+        }
+        // sort the list by package name
+        Collections.sort(appLinkInfos, Comparator.comparing(AppLinkInfo::getComponentName));
+        userState.mAppLinkInfoList.clear();
+        userState.mAppLinkInfoList.addAll(appLinkInfos);
+    }
+
+    @GuardedBy("mLock")
+    private AppLinkInfo buildAppLinkInfoLocked(ApplicationInfo appInfo) {
+        if (appInfo.metaData == null || appInfo.packageName == null) {
+            return null;
+        }
+        String className = appInfo.metaData.getString(METADATA_CLASS_NAME, null);
+        String uri = appInfo.metaData.getString(METADATA_URI, null);
+        if (className == null || uri == null) {
+            return null;
+        }
+        return new AppLinkInfo(appInfo.packageName, className, uri);
+    }
+
+    @GuardedBy("mLock")
     private void buildTvInteractiveAppServiceListLocked(int userId, String[] updatedPackages) {
         UserState userState = getOrCreateUserStateLocked(userId);
         userState.mPackageSet.clear();
@@ -310,6 +354,7 @@
         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
             synchronized (mLock) {
                 buildTvInteractiveAppServiceListLocked(mCurrentUserId, null);
+                buildAppLinkInfoLocked(mCurrentUserId);
             }
         }
     }
@@ -321,6 +366,7 @@
                 synchronized (mLock) {
                     if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) {
                         buildTvInteractiveAppServiceListLocked(userId, packages);
+                        buildAppLinkInfoLocked(userId);
                     }
                 }
             }
@@ -427,6 +473,7 @@
 
             mCurrentUserId = userId;
             buildTvInteractiveAppServiceListLocked(userId, null);
+            buildAppLinkInfoLocked(userId);
         }
     }
 
@@ -512,6 +559,7 @@
     private void startProfileLocked(int userId) {
         mRunningProfiles.add(userId);
         buildTvInteractiveAppServiceListLocked(userId, null);
+        buildAppLinkInfoLocked(userId);
     }
 
     @GuardedBy("mLock")
@@ -667,6 +715,26 @@
         }
 
         @Override
+        public List<AppLinkInfo> getAppLinkInfoList(int userId) {
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, "getAppLinkInfoList");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    if (!mGetAppLinkInfoListCalled) {
+                        buildAppLinkInfoLocked(userId);
+                        mGetAppLinkInfoListCalled = true;
+                    }
+                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    List<AppLinkInfo> appLinkInfos = new ArrayList<>(userState.mAppLinkInfoList);
+                    return appLinkInfos;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void registerAppLinkInfo(String tiasId, AppLinkInfo appLinkInfo, int userId) {
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
                     Binder.getCallingUid(), userId, "registerAppLinkInfo: " + appLinkInfo);
@@ -1268,6 +1336,31 @@
         }
 
         @Override
+        public void sendCurrentVideoBounds(IBinder sessionToken, Rect bounds, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "sendCurrentVideoBounds(bounds=%s)", bounds.toString());
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "sendCurrentVideoBounds");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).sendCurrentVideoBounds(bounds);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in sendCurrentVideoBounds", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void sendCurrentChannelUri(IBinder sessionToken, Uri channelUri, int userId) {
             if (DEBUG) {
                 Slogf.d(TAG, "sendCurrentChannelUri(channelUri=%s)", channelUri.toString());
@@ -1496,6 +1589,118 @@
         }
 
         @Override
+        public void notifyTimeShiftPlaybackParams(
+                IBinder sessionToken, PlaybackParams params, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "notifyTimeShiftPlaybackParams(params=%s)", params);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(
+                    Binder.getCallingPid(), callingUid, userId, "notifyTimeShiftPlaybackParams");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState =
+                                getSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+                        getSessionLocked(sessionState).notifyTimeShiftPlaybackParams(params);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyTimeShiftPlaybackParams", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void notifyTimeShiftStatusChanged(
+                IBinder sessionToken, String inputId, int status, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "notifyTimeShiftStatusChanged(inputId=%s, status=%d)",
+                        inputId, status);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(
+                    Binder.getCallingPid(), callingUid, userId, "notifyTimeShiftStatusChanged");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState =
+                                getSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+                        getSessionLocked(sessionState).notifyTimeShiftStatusChanged(
+                                inputId, status);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyTimeShiftStatusChanged", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void notifyTimeShiftStartPositionChanged(
+                IBinder sessionToken, String inputId, long timeMs, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "notifyTimeShiftStartPositionChanged(inputId=%s, timeMs=%d)",
+                        inputId, timeMs);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(
+                    Binder.getCallingPid(), callingUid, userId,
+                    "notifyTimeShiftStartPositionChanged");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState =
+                                getSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+                        getSessionLocked(sessionState).notifyTimeShiftStartPositionChanged(
+                                inputId, timeMs);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyTimeShiftStartPositionChanged", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void notifyTimeShiftCurrentPositionChanged(
+                IBinder sessionToken, String inputId, long timeMs, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "notifyTimeShiftCurrentPositionChanged(inputId=%s, timeMs=%d)",
+                        inputId, timeMs);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(
+                    Binder.getCallingPid(), callingUid, userId,
+                    "notifyTimeShiftCurrentPositionChanged");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState =
+                                getSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+                        getSessionLocked(sessionState).notifyTimeShiftCurrentPositionChanged(
+                                inputId, timeMs);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyTimeShiftCurrentPositionChanged", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void setSurface(IBinder sessionToken, Surface surface, int userId) {
             final int callingUid = Binder.getCallingUid();
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
@@ -1883,6 +2088,8 @@
 
         // A set of all TV Interactive App service packages.
         private final Set<String> mPackageSet = new HashSet<>();
+        // A list of all app link infos.
+        private final List<AppLinkInfo> mAppLinkInfoList = new ArrayList<>();
 
         // A list of callbacks.
         private final RemoteCallbackList<ITvInteractiveAppManagerCallback> mCallbacks =
@@ -2255,6 +2462,27 @@
         }
 
         @Override
+        public void onTimeShiftCommandRequest(
+                @TvInteractiveAppService.TimeShiftCommandType String cmdType,
+                Bundle parameters) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onTimeShiftCommandRequest (cmdType=" + cmdType
+                            + ", parameters=" + parameters.toString() + ")");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onTimeShiftCommandRequest(
+                            cmdType, parameters, mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onTimeShiftCommandRequest", e);
+                }
+            }
+        }
+
+        @Override
         public void onSetVideoBounds(Rect rect) {
             synchronized (mLock) {
                 if (DEBUG) {
@@ -2272,6 +2500,23 @@
         }
 
         @Override
+        public void onRequestCurrentVideoBounds() {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRequestCurrentVideoBounds");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onRequestCurrentVideoBounds(mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onRequestCurrentVideoBounds", e);
+                }
+            }
+        }
+
+        @Override
         public void onRequestCurrentChannelUri() {
             synchronized (mLock) {
                 if (DEBUG) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index 79de282..25ce280 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -18,10 +18,10 @@
 
 import static android.app.WallpaperManager.FLAG_LOCK;
 
-import static com.android.server.wallpaper.WallpaperManagerService.WALLPAPER;
-import static com.android.server.wallpaper.WallpaperManagerService.WALLPAPER_CROP;
-import static com.android.server.wallpaper.WallpaperManagerService.WALLPAPER_LOCK_CROP;
-import static com.android.server.wallpaper.WallpaperManagerService.WALLPAPER_LOCK_ORIG;
+import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
+import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_CROP;
+import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_LOCK_CROP;
+import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_LOCK_ORIG;
 import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
 
 import android.app.IWallpaperManagerCallback;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 962eb86..6edfebf 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -32,7 +32,14 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
+import static com.android.server.wallpaper.WallpaperUtils.RECORD_FILE;
+import static com.android.server.wallpaper.WallpaperUtils.RECORD_LOCK_FILE;
+import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
+import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_CROP;
+import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_INFO;
+import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_LOCK_ORIG;
 import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
+import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -199,20 +206,6 @@
      */
     private static final long MIN_WALLPAPER_CRASH_TIME = 10000;
     private static final int MAX_WALLPAPER_COMPONENT_LOG_LENGTH = 128;
-    static final String WALLPAPER = "wallpaper_orig";
-    static final String WALLPAPER_CROP = "wallpaper";
-    static final String WALLPAPER_LOCK_ORIG = "wallpaper_lock_orig";
-    static final String WALLPAPER_LOCK_CROP = "wallpaper_lock";
-    static final String WALLPAPER_INFO = "wallpaper_info.xml";
-    private static final String RECORD_FILE = "decode_record";
-    private static final String RECORD_LOCK_FILE = "decode_lock_record";
-
-    // All the various per-user state files we need to be aware of
-    private static final String[] sPerUserFiles = new String[] {
-        WALLPAPER, WALLPAPER_CROP,
-        WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP,
-        WALLPAPER_INFO
-    };
 
     /**
      * Observes the wallpaper for changes and notifies all IWallpaperServiceCallbacks
@@ -892,12 +885,6 @@
     private boolean mShuttingDown;
 
     /**
-     * ID of the current wallpaper, changed every time anything sets a wallpaper.
-     * This is used for external detection of wallpaper update activity.
-     */
-    private int mWallpaperId;
-
-    /**
      * Name of the component used to display bitmap wallpapers from either the gallery or
      * built-in wallpapers.
      */
@@ -979,13 +966,6 @@
         }
     }
 
-    int makeWallpaperIdLocked() {
-        do {
-            ++mWallpaperId;
-        } while (mWallpaperId == 0);
-        return mWallpaperId;
-    }
-
     private boolean supportsMultiDisplay(WallpaperConnection connection) {
         if (connection != null) {
             return connection.mInfo == null // This is image wallpaper
@@ -1855,11 +1835,9 @@
                         final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
                         t.traceBegin("Wallpaper_selinux_restorecon-" + userId);
                         try {
-                            final File wallpaperDir = getWallpaperDir(userId);
-                            for (String filename : sPerUserFiles) {
-                                File f = new File(wallpaperDir, filename);
-                                if (f.exists()) {
-                                    SELinux.restorecon(f);
+                            for (File file: WallpaperUtils.getWallpaperFiles(userId)) {
+                                if (file.exists()) {
+                                    SELinux.restorecon(file);
                                 }
                             }
                         } finally {
@@ -1875,12 +1853,9 @@
     void onRemoveUser(int userId) {
         if (userId < 1) return;
 
-        final File wallpaperDir = getWallpaperDir(userId);
         synchronized (mLock) {
             stopObserversLocked(userId);
-            for (String filename : sPerUserFiles) {
-                new File(wallpaperDir, filename).delete();
-            }
+            WallpaperUtils.getWallpaperFiles(userId).forEach(File::delete);
             mUserRestorecon.delete(userId);
         }
     }
@@ -2877,8 +2852,8 @@
         setWallpaperComponent(name, UserHandle.getCallingUserId(), FLAG_SYSTEM);
     }
 
-    private void setWallpaperComponent(ComponentName name, @SetWallpaperFlags int which,
-            int userId) {
+    @VisibleForTesting
+    void setWallpaperComponent(ComponentName name, @SetWallpaperFlags int which, int userId) {
         userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
                 false /* all */, true /* full */, "changing live wallpaper", null /* pkg */);
         checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
@@ -3671,8 +3646,8 @@
         final int id = parser.getAttributeInt(null, "id", -1);
         if (id != -1) {
             wallpaper.wallpaperId = id;
-            if (id > mWallpaperId) {
-                mWallpaperId = id;
+            if (id > WallpaperUtils.getCurrentWallpaperId()) {
+                WallpaperUtils.setCurrentWallpaperId(id);
             }
         } else {
             wallpaper.wallpaperId = makeWallpaperIdLocked();
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperUtils.java b/services/core/java/com/android/server/wallpaper/WallpaperUtils.java
index a9b8092..d0311e3 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperUtils.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperUtils.java
@@ -19,10 +19,68 @@
 import android.os.Environment;
 
 import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
 
 class WallpaperUtils {
 
+    static final String WALLPAPER = "wallpaper_orig";
+    static final String WALLPAPER_CROP = "wallpaper";
+    static final String WALLPAPER_LOCK_ORIG = "wallpaper_lock_orig";
+    static final String WALLPAPER_LOCK_CROP = "wallpaper_lock";
+    static final String WALLPAPER_INFO = "wallpaper_info.xml";
+    static final String RECORD_FILE = "decode_record";
+    static final String RECORD_LOCK_FILE = "decode_lock_record";
+
+    // All the various per-user state files we need to be aware of
+    private static final String[] sPerUserFiles = new String[] {
+            WALLPAPER, WALLPAPER_CROP,
+            WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP,
+            WALLPAPER_INFO
+    };
+
+    /**
+     * ID of the current wallpaper, incremented every time anything sets a wallpaper.
+     * This is used for external detection of wallpaper update activity.
+     */
+    private static int sWallpaperId;
+
     static File getWallpaperDir(int userId) {
         return Environment.getUserSystemDirectory(userId);
     }
+
+    /**
+     * generate a new wallpaper id
+     * should be called with the {@link WallpaperManagerService} lock held
+     */
+    static int makeWallpaperIdLocked() {
+        do {
+            ++sWallpaperId;
+        } while (sWallpaperId == 0);
+        return sWallpaperId;
+    }
+
+    /**
+     * returns the id of the current wallpaper (the last one that has been set)
+     */
+    static int getCurrentWallpaperId() {
+        return sWallpaperId;
+    }
+
+    /**
+     * sets the id of the current wallpaper
+     * used when a wallpaper with higher id than current is loaded from settings
+     */
+    static void setCurrentWallpaperId(int id) {
+        sWallpaperId = id;
+    }
+
+    static List<File> getWallpaperFiles(int userId) {
+        File wallpaperDir = getWallpaperDir(userId);
+        List<File> result = new ArrayList<File>();
+        for (int i = 0; i < sPerUserFiles.length; i++) {
+            result.add(new File(wallpaperDir, sPerUserFiles[i]));
+        }
+        return result;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index e2ab216..65127e4 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -891,8 +891,7 @@
                 // to show the border. We will do so when the pending message is handled.
                 if (!mHandler.hasMessages(
                         MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
-                    setMagnifiedRegionBorderShown(
-                            isMagnifying() || isForceShowingMagnifiableBounds(), true);
+                    setMagnifiedRegionBorderShown(isForceShowingMagnifiableBounds(), true);
                 }
             }
 
@@ -1057,7 +1056,7 @@
                 // rotation or folding/unfolding the device. In the rotation case, the screenshot
                 // used for rotation already has the border. After the rotation is complete
                 // we will show the border.
-                if (isMagnifying() || isForceShowingMagnifiableBounds()) {
+                if (isForceShowingMagnifiableBounds()) {
                     setMagnifiedRegionBorderShown(false, false);
                     final long delay = (long) (mLongAnimationDuration
                             * mService.getWindowAnimationScaleLocked());
@@ -1398,8 +1397,7 @@
 
                     case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
                         synchronized (mService.mGlobalLock) {
-                            if (mMagnifedViewport.isMagnifying()
-                                    || isForceShowingMagnifiableBounds()) {
+                            if (isForceShowingMagnifiableBounds()) {
                                 mMagnifedViewport.setMagnifiedRegionBorderShown(true, true);
                                 mService.scheduleAnimationLocked();
                             }
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 4428be7..d4895ed 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -46,6 +46,7 @@
 import static com.android.server.wm.ActivityRecord.State.PAUSING;
 import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
@@ -240,11 +241,21 @@
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityStopped");
             r = ActivityRecord.isInRootTaskLocked(token);
             if (r != null) {
+                if (!r.isState(STOPPING, RESTARTING_PROCESS)
+                        && mTaskSupervisor.hasScheduledRestartTimeouts(r)) {
+                    // Recover the restarting state which was replaced by other lifecycle changes.
+                    r.setState(RESTARTING_PROCESS, "continue-restart");
+                }
                 if (r.attachedToProcess() && r.isState(RESTARTING_PROCESS)) {
                     // The activity was requested to restart from
                     // {@link #restartActivityProcessIfVisible}.
                     restartingName = r.app.mName;
                     restartingUid = r.app.mUid;
+                    // Make EnsureActivitiesVisibleHelper#makeVisibleAndRestartIfNeeded not skip
+                    // restarting non-top activity.
+                    if (r != r.getTask().topRunningActivity()) {
+                        r.setVisibleRequested(false);
+                    }
                 }
                 r.activityStopped(icicle, persistentState, description);
             }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 45ae3d8..6abd3d7 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -9357,6 +9357,15 @@
             configChangeFlags = 0;
             return;
         }
+        if (!preserveWindow) {
+            // If the activity is the IME input target, ensure storing the last IME shown state
+            // before relaunching it for restoring the IME visibility once its new window focused.
+            final InputTarget imeInputTarget = mDisplayContent.getImeInputTarget();
+            mLastImeShown = imeInputTarget != null && imeInputTarget.getWindowState() != null
+                    && imeInputTarget.getWindowState().mActivityRecord == this
+                    && mDisplayContent.mInputMethodWindow != null
+                    && mDisplayContent.mInputMethodWindow.isVisible();
+        }
         // Do not waiting for translucent activity if it is going to relaunch.
         final Task rootTask = getRootTask();
         if (rootTask != null && rootTask.mTranslucentActivityWaiting == this) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 407ffd0..919bab8 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2297,6 +2297,10 @@
         mHandler.sendEmptyMessageDelayed(SLEEP_TIMEOUT_MSG, SLEEP_TIMEOUT);
     }
 
+    boolean hasScheduledRestartTimeouts(ActivityRecord r) {
+        return mHandler.hasMessages(RESTART_ACTIVITY_PROCESS_TIMEOUT_MSG, r);
+    }
+
     void removeRestartTimeouts(ActivityRecord r) {
         mHandler.removeMessages(RESTART_ACTIVITY_PROCESS_TIMEOUT_MSG, r);
     }
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index d395f12..db88f0f 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -36,7 +36,6 @@
 import android.animation.ArgbEvaluator;
 import android.content.Context;
 import android.graphics.Color;
-import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -251,9 +250,6 @@
                     screenshotBuffer.getColorSpace());
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
 
-            GraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer(
-                    screenshotBuffer.getHardwareBuffer());
-
             t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);
             t.reparent(mBackColorSurface, displayContent.getSurfaceControl());
             // If hdr layers are on-screen, e.g. picture-in-picture mode, the screenshot of
@@ -263,10 +259,11 @@
             t.setLayer(mBackColorSurface, -1);
             t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
             t.setAlpha(mBackColorSurface, 1);
-            t.setBuffer(mScreenshotLayer, buffer);
+            t.setBuffer(mScreenshotLayer, hardwareBuffer);
             t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
             t.show(mScreenshotLayer);
             t.show(mBackColorSurface);
+            hardwareBuffer.close();
 
             if (mRoundedCornerOverlay != null) {
                 for (SurfaceControl sc : mRoundedCornerOverlay) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 5e081d5..e538584 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2406,7 +2406,7 @@
             if (isDisplayRotation) {
                 // This isn't cheap, so only do it for display rotations.
                 changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma(
-                        screenshotBuffer.getHardwareBuffer(), screenshotBuffer.getColorSpace());
+                        buffer, screenshotBuffer.getColorSpace());
             }
             SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get();
 
@@ -2418,6 +2418,7 @@
             t.setLayer(snapshotSurface, Integer.MAX_VALUE);
             t.apply();
             t.close();
+            buffer.close();
 
             // Detach the screenshot on the sync transaction (the screenshot is just meant to
             // freeze the window until the sync transaction is applied (with all its other
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4e7613b..0243a98 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9177,6 +9177,7 @@
 
     boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) {
         final Task imeTargetWindowTask;
+        boolean hadRequestedShowIme = false;
         synchronized (mGlobalLock) {
             final WindowState imeTargetWindow = mWindowMap.get(imeTargetWindowToken);
             if (imeTargetWindow == null) {
@@ -9186,11 +9187,14 @@
             if (imeTargetWindowTask == null) {
                 return false;
             }
+            if (imeTargetWindow.mActivityRecord != null) {
+                hadRequestedShowIme = imeTargetWindow.mActivityRecord.mLastImeShown;
+            }
         }
         final TaskSnapshot snapshot = getTaskSnapshot(imeTargetWindowTask.mTaskId,
                 imeTargetWindowTask.mUserId, false /* isLowResolution */,
                 false /* restoreFromDisk */);
-        return snapshot != null && snapshot.hasImeSurface();
+        return snapshot != null && snapshot.hasImeSurface() || hadRequestedShowIme;
     }
 
     @Override
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index 595d03d..be60946 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -39,15 +39,17 @@
         implements ProviderSession.ProviderInternalCallback<Void> {
     private static final String TAG = "GetRequestSession";
 
-    public ClearRequestSession(Context context, int userId,
+    public ClearRequestSession(Context context, int userId, int callingUid,
             IClearCredentialStateCallback callback, ClearCredentialStateRequest request,
             CallingAppInfo callingAppInfo) {
-        super(context, userId, request, callback, RequestInfo.TYPE_UNDEFINED, callingAppInfo);
+        super(context, userId, callingUid, request, callback, RequestInfo.TYPE_UNDEFINED,
+                callingAppInfo);
     }
 
     /**
      * Creates a new provider session, and adds it list of providers that are contributing to
      * this session.
+     *
      * @return the provider session created within this request session, for the given provider
      * info.
      */
@@ -111,8 +113,10 @@
         Log.i(TAG, "respondToClientWithResponseAndFinish");
         try {
             mClientCallback.onSuccess();
+            logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ true);
         } catch (RemoteException e) {
             Log.i(TAG, "Issue while propagating the response to the client");
+            logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ false);
         }
         finishSession();
     }
@@ -124,10 +128,12 @@
         } catch (RemoteException e) {
             e.printStackTrace();
         }
+        logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ false);
         finishSession();
     }
+
     private void processResponses() {
-        for (ProviderSession session: mProviders.values()) {
+        for (ProviderSession session : mProviders.values()) {
             if (session.isProviderResponseSet()) {
                 // If even one provider responded successfully, send back the response
                 // TODO: Aggregate other exceptions
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 82c2358..acfa491 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -44,11 +44,12 @@
         implements ProviderSession.ProviderInternalCallback<CreateCredentialResponse> {
     private static final String TAG = "CreateRequestSession";
 
-    CreateRequestSession(@NonNull Context context, int userId,
+    CreateRequestSession(@NonNull Context context, int userId, int callingUid,
             CreateCredentialRequest request,
             ICreateCredentialCallback callback,
             CallingAppInfo callingAppInfo) {
-        super(context, userId, request, callback, RequestInfo.TYPE_CREATE, callingAppInfo);
+        super(context, userId, callingUid, request, callback, RequestInfo.TYPE_CREATE,
+                callingAppInfo);
     }
 
     /**
@@ -63,7 +64,7 @@
             RemoteCredentialService remoteCredentialService) {
         ProviderCreateSession providerCreateSession = ProviderCreateSession
                 .createNewSession(mContext, mUserId, providerInfo,
-                this, remoteCredentialService);
+                        this, remoteCredentialService);
         if (providerCreateSession != null) {
             Log.i(TAG, "In startProviderSession - provider session created and being added");
             mProviders.put(providerCreateSession.getComponentName().flattenToString(),
@@ -81,8 +82,9 @@
                             mClientAppInfo.getPackageName()),
                     providerDataList));
         } catch (RemoteException e) {
-            Log.i(TAG, "Issue with invoking pending intent: " + e.getMessage());
-            // TODO: Propagate failure
+            respondToClientWithErrorAndFinish(
+                    CreateCredentialException.TYPE_UNKNOWN,
+                    "Unable to invoke selector");
         }
     }
 
@@ -106,8 +108,7 @@
 
     @Override
     public void onUiCancellation() {
-        // TODO("Replace with properly defined error type")
-        respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREDENTIAL,
+        respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_USER_CANCELED,
                 "User cancelled the selector");
     }
 
@@ -115,8 +116,10 @@
         Log.i(TAG, "respondToClientWithResponseAndFinish");
         try {
             mClientCallback.onResponse(response);
+            logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ true);
         } catch (RemoteException e) {
             Log.i(TAG, "Issue while responding to client: " + e.getMessage());
+            logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ false);
         }
         finishSession();
     }
@@ -128,6 +131,7 @@
         } catch (RemoteException e) {
             Log.i(TAG, "Issue while responding to client: " + e.getMessage());
         }
+        logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ false);
         finishSession();
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index aefd300..f76cf49 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -269,11 +269,13 @@
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
 
             int userId = UserHandle.getCallingUserId();
+            int callingUid = Binder.getCallingUid();
             // New request session, scoped for this request only.
             final GetRequestSession session =
                     new GetRequestSession(
                             getContext(),
                             userId,
+                            callingUid,
                             callback,
                             request,
                             constructCallingAppInfo(callingPackage, userId));
@@ -319,10 +321,12 @@
 
             // New request session, scoped for this request only.
             int userId = UserHandle.getCallingUserId();
+            int callingUid = Binder.getCallingUid();
             final CreateRequestSession session =
                     new CreateRequestSession(
                             getContext(),
                             userId,
+                            callingUid,
                             request,
                             callback,
                             constructCallingAppInfo(callingPackage, userId));
@@ -434,10 +438,12 @@
 
             // New request session, scoped for this request only.
             int userId = UserHandle.getCallingUserId();
+            int callingUid = Binder.getCallingUid();
             final ClearRequestSession session =
                     new ClearRequestSession(
                             getContext(),
                             userId,
+                            callingUid,
                             callback,
                             request,
                             constructCallingAppInfo(callingPackage, userId));
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index c7fa72c..f7c5905 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -41,10 +41,10 @@
         implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
     private static final String TAG = "GetRequestSession";
 
-    public GetRequestSession(Context context, int userId,
+    public GetRequestSession(Context context, int userId, int callingUid,
             IGetCredentialCallback callback, GetCredentialRequest request,
             CallingAppInfo callingAppInfo) {
-        super(context, userId, request, callback, RequestInfo.TYPE_GET, callingAppInfo);
+        super(context, userId, callingUid, request, callback, RequestInfo.TYPE_GET, callingAppInfo);
     }
 
     /**
@@ -76,8 +76,8 @@
                     mRequestId, mClientRequest, mClientAppInfo.getPackageName()),
                     providerDataList));
         } catch (RemoteException e) {
-            Log.i(TAG, "Issue with invoking pending intent: " + e.getMessage());
-            // TODO: Propagate failure
+            respondToClientWithErrorAndFinish(
+                    GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector");
         }
     }
 
@@ -104,8 +104,10 @@
     private void respondToClientWithResponseAndFinish(GetCredentialResponse response) {
         try {
             mClientCallback.onResponse(response);
+            logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ true);
         } catch (RemoteException e) {
             Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage());
+            logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ false);
         }
         finishSession();
     }
@@ -117,13 +119,13 @@
             Log.i(TAG, "Issue while responding to client with error : " + e.getMessage());
 
         }
+        logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ false);
         finishSession();
     }
 
     @Override
     public void onUiCancellation() {
-        // TODO("Replace with user cancelled error type when ready")
-        respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
+        respondToClientWithErrorAndFinish(GetCredentialException.TYPE_USER_CANCELED,
                 "User cancelled the selector");
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
index 8796314..c2b346f 100644
--- a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
+++ b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
@@ -39,6 +39,12 @@
         return pendingIntentResponse.getResultCode() == Activity.RESULT_OK;
     }
 
+    /** Returns true if the pending intent was cancelled by the user. */
+    public static boolean isCancelledResponse(
+            ProviderPendingIntentResponse pendingIntentResponse) {
+        return pendingIntentResponse.getResultCode() == Activity.RESULT_CANCELED;
+    }
+
     /** Extracts the {@link CredentialsResponseContent} object added to the result data. */
     public static CredentialsResponseContent extractResponseContent(Intent resultData) {
         if (resultData == null) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 27eaa0b..7a24a22 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -263,9 +263,9 @@
                 Log.i(TAG, "Pending intent contains provider exception");
                 return exception;
             }
+        } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
+            return new CreateCredentialException(CreateCredentialException.TYPE_USER_CANCELED);
         } else {
-            Log.i(TAG, "Pending intent result code not Activity.RESULT_OK");
-            // TODO("Update with unknown exception when ready")
             return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREDENTIAL);
         }
         return null;
@@ -273,12 +273,11 @@
 
     /**
      * When an invalid state occurs, e.g. entry mismatch or no response from provider,
-     * we send back a TYPE_NO_CREDENTIAL error as to the developer, it is the same as not
-     * getting any credentials back.
+     * we send back a TYPE_UNKNOWN error as to the developer.
      */
     private void invokeCallbackOnInternalInvalidState() {
         mCallbacks.onFinalErrorReceived(mComponentName,
-                CreateCredentialException.TYPE_NO_CREDENTIAL,
+                CreateCredentialException.TYPE_UNKNOWN,
                 null);
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index de93af4..95f2313 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -375,6 +375,11 @@
     private void onAuthenticationEntrySelected(
             @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
         //TODO: Other provider intent statuses
+        if (providerPendingIntentResponse == null) {
+            Log.i(TAG, "providerPendingIntentResponse is null");
+            onUpdateEmptyResponse();
+        }
+
         GetCredentialException exception = maybeGetPendingIntentException(
                 providerPendingIntentResponse);
         if (exception != null) {
@@ -393,7 +398,7 @@
         }
 
         Log.i(TAG, "No error or respond found in pending intent response");
-        invokeCallbackOnInternalInvalidState();
+        onUpdateEmptyResponse();
     }
 
     private void onActionEntrySelected(ProviderPendingIntentResponse
@@ -415,12 +420,16 @@
         }
     }
 
+    private void onUpdateEmptyResponse() {
+        updateStatusAndInvokeCallback(Status.NO_CREDENTIALS);
+    }
+
     @Nullable
     private GetCredentialException maybeGetPendingIntentException(
             ProviderPendingIntentResponse pendingIntentResponse) {
         if (pendingIntentResponse == null) {
             Log.i(TAG, "pendingIntentResponse is null");
-            return new GetCredentialException(GetCredentialException.TYPE_NO_CREDENTIAL);
+            return null;
         }
         if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
             GetCredentialException exception = PendingIntentResultHandler
@@ -429,8 +438,9 @@
                 Log.i(TAG, "Pending intent contains provider exception");
                 return exception;
             }
+        } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
+            return new GetCredentialException(GetCredentialException.TYPE_USER_CANCELED);
         } else {
-            Log.i(TAG, "Pending intent result code not Activity.RESULT_OK");
             return new GetCredentialException(GetCredentialException.TYPE_NO_CREDENTIAL);
         }
         return null;
@@ -438,12 +448,10 @@
 
     /**
      * When an invalid state occurs, e.g. entry mismatch or no response from provider,
-     * we send back a TYPE_NO_CREDENTIAL error as to the developer, it is the same as not
-     * getting any credentials back.
+     * we send back a TYPE_UNKNOWN error as to the developer.
      */
     private void invokeCallbackOnInternalInvalidState() {
         mCallbacks.onFinalErrorReceived(mComponentName,
-                GetCredentialException.TYPE_NO_CREDENTIAL,
-                null);
+                GetCredentialException.TYPE_UNKNOWN, null);
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 7036dfb..678c752 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -133,7 +133,7 @@
         PENDING_INTENT_INVOKED,
         CREDENTIAL_RECEIVED_FROM_SELECTION,
         SAVE_ENTRIES_RECEIVED, CANCELED,
-        COMPLETE
+        NO_CREDENTIALS, COMPLETE
     }
 
     /** Converts exception to a provider session status. */
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 0c3c34e..8e44f0f 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -16,6 +16,13 @@
 
 package com.android.server.credentials;
 
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CLEAR_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CREATE_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_GET_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_SUCCESS;
+
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.content.Context;
@@ -29,6 +36,8 @@
 import android.service.credentials.CredentialProviderInfo;
 import android.util.Log;
 
+import com.android.internal.util.FrameworkStatsLog;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
@@ -37,28 +46,53 @@
  * Base class of a request session, that listens to UI events. This class must be extended
  * every time a new response type is expected from the providers.
  */
-abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialManagerUiCallback{
+abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialManagerUiCallback {
     private static final String TAG = "RequestSession";
 
+    // Metrics constants
+    private static final int METRICS_API_NAME_UNKNOWN =
+            CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_UNKNOWN;
+    private static final int METRICS_API_NAME_GET_CREDENTIAL =
+            CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_GET_CREDENTIAL;
+    private static final int METRICS_API_NAME_CREATE_CREDENTIAL =
+            CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CREATE_CREDENTIAL;
+    private static final int METRICS_API_NAME_CLEAR_CREDENTIAL =
+            CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CLEAR_CREDENTIAL;
+    private static final int METRICS_API_STATUS_SUCCESS =
+            CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_SUCCESS;
+    private static final int METRICS_API_STATUS_FAILURE =
+            CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_FAILURE;
+
     // TODO: Revise access levels of attributes
-    @NonNull protected final T mClientRequest;
-    @NonNull protected final U mClientCallback;
-    @NonNull protected final IBinder mRequestId;
-    @NonNull protected final Context mContext;
-    @NonNull protected final CredentialManagerUi mCredentialManagerUi;
-    @NonNull protected final String mRequestType;
-    @NonNull protected final Handler mHandler;
-    @UserIdInt protected final int mUserId;
-    @NonNull protected final CallingAppInfo mClientAppInfo;
+    @NonNull
+    protected final T mClientRequest;
+    @NonNull
+    protected final U mClientCallback;
+    @NonNull
+    protected final IBinder mRequestId;
+    @NonNull
+    protected final Context mContext;
+    @NonNull
+    protected final CredentialManagerUi mCredentialManagerUi;
+    @NonNull
+    protected final String mRequestType;
+    @NonNull
+    protected final Handler mHandler;
+    @UserIdInt
+    protected final int mUserId;
+    private final int mCallingUid;
+    @NonNull
+    protected final CallingAppInfo mClientAppInfo;
 
     protected final Map<String, ProviderSession> mProviders = new HashMap<>();
 
     protected RequestSession(@NonNull Context context,
-            @UserIdInt int userId, @NonNull T clientRequest, U clientCallback,
+            @UserIdInt int userId, int callingUid, @NonNull T clientRequest, U clientCallback,
             @NonNull String requestType,
             CallingAppInfo callingAppInfo) {
         mContext = context;
         mUserId = userId;
+        mCallingUid = callingUid;
         mClientRequest = clientRequest;
         mClientCallback = clientCallback;
         mRequestType = requestType;
@@ -117,6 +151,33 @@
         return false;
     }
 
+    // TODO: move these definitions to a separate logging focused class.
+    enum RequestType {
+        GET_CREDENTIALS,
+        CREATE_CREDENTIALS,
+        CLEAR_CREDENTIALS,
+    }
+
+    private static int getApiNameFromRequestType(RequestType requestType) {
+        switch (requestType) {
+            case GET_CREDENTIALS:
+                return METRICS_API_NAME_GET_CREDENTIAL;
+            case CREATE_CREDENTIALS:
+                return METRICS_API_NAME_CREATE_CREDENTIAL;
+            case CLEAR_CREDENTIALS:
+                return METRICS_API_NAME_CLEAR_CREDENTIAL;
+            default:
+                return METRICS_API_NAME_UNKNOWN;
+        }
+    }
+
+    protected void logApiCalled(RequestType requestType, boolean isSuccessful) {
+        FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED,
+                /* api_name */getApiNameFromRequestType(requestType), /* caller_uid */
+                mCallingUid, /* api_status */
+                isSuccessful ? METRICS_API_STATUS_SUCCESS : METRICS_API_STATUS_FAILURE);
+    }
+
     /**
      * Returns true if at least one provider is ready for UI invocation, and no
      * provider is pending a response.
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index f549797..e416718 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -211,6 +211,12 @@
         }
     }
 
+    internal fun onSystemReady() {
+        mutateState {
+            with(policy) { onSystemReady() }
+        }
+    }
+
     private val PackageManagerLocal.allPackageStates:
         Pair<Map<String, PackageState>, Map<String, PackageState>>
         get() = withUnfilteredSnapshot().use { it.packageStates to it.disabledSystemPackageStates }
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index e0f94c7..07a5e72 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -255,6 +255,13 @@
         }
     }
 
+    fun MutateStateScope.onSystemReady() {
+        newState.systemState.isSystemReady = true
+        forEachSchemePolicy {
+            with(it) { onSystemReady() }
+        }
+    }
+
     fun BinaryXmlPullParser.parseSystemState(state: AccessState) {
         forEachTag {
             when (tagName) {
@@ -362,6 +369,8 @@
 
     open fun MutateStateScope.onPackageUninstalled(packageName: String, appId: Int, userId: Int) {}
 
+    open fun MutateStateScope.onSystemReady() {}
+
     open fun BinaryXmlPullParser.parseSystemState(state: AccessState) {}
 
     open fun BinaryXmlSerializer.serializeSystemState(state: AccessState) {}
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
index 9616193..5532311 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -50,6 +50,8 @@
     var privilegedPermissionAllowlistPackages: IndexedListSet<String>,
     var permissionAllowlist: PermissionAllowlist,
     var implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>,
+    var isSystemReady: Boolean,
+    // TODO: Get and watch the state for deviceAndProfileOwners
     // Mapping from user ID to package name.
     var deviceAndProfileOwners: IntMap<String>,
     val permissionGroups: IndexedMap<String, PermissionGroupInfo>,
@@ -67,6 +69,7 @@
         IndexedListSet(),
         PermissionAllowlist(),
         IndexedMap(),
+        false,
         IntMap(),
         IndexedMap(),
         IndexedMap(),
@@ -85,6 +88,7 @@
             privilegedPermissionAllowlistPackages,
             permissionAllowlist,
             implicitToSourcePermissions,
+            isSystemReady,
             deviceAndProfileOwners,
             permissionGroups.copy { it },
             permissionTrees.copy { it },
diff --git a/services/permission/java/com/android/server/permission/access/permission/Permission.kt b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
index 7bfca12..714480c 100644
--- a/services/permission/java/com/android/server/permission/access/permission/Permission.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
@@ -91,6 +91,9 @@
     inline val isKnownSigner: Boolean
         get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER)
 
+    inline val isModule: Boolean
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_MODULE)
+
     inline val isOem: Boolean
         get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_OEM)
 
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 903fad3..c7e9371 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -1747,7 +1747,7 @@
     override fun writeLegacyPermissionStateTEMP() {}
 
     override fun onSystemReady() {
-        // TODO STOPSHIP privappPermissionsViolationsfix check
+        service.onSystemReady()
         permissionControllerManager = PermissionControllerManager(
             context, PermissionThread.getHandler()
         )
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
index d0833bd..694efbb 100644
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
@@ -54,6 +54,8 @@
         IndexedListSet<OnPermissionFlagsChangedListener>()
     private val onPermissionFlagsChangedListenersLock = Any()
 
+    private val privilegedPermissionAllowlistViolations = IndexedSet<String>()
+
     override val subjectScheme: String
         get() = UidUri.SCHEME
 
@@ -734,7 +736,7 @@
             } else {
                 newFlags = newFlags andInv PermissionFlags.LEGACY_GRANTED
                 val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED)
-                val isLeanBackNotificationsPermission = newState.systemState.isLeanback &&
+                val isLeanbackNotificationsPermission = newState.systemState.isLeanback &&
                     permissionName in NOTIFICATIONS_PERMISSIONS
                 val isImplicitPermission = anyPackageInAppId(appId) {
                     permissionName in it.androidPackage!!.implicitPermissions
@@ -748,7 +750,7 @@
                     }
                     !sourcePermission.isRuntime
                 } ?: false
-                val shouldGrantByImplicit = isLeanBackNotificationsPermission ||
+                val shouldGrantByImplicit = isLeanbackNotificationsPermission ||
                     (isImplicitPermission && isAnySourcePermissionNonRuntime)
                 if (shouldGrantByImplicit) {
                     newFlags = newFlags or PermissionFlags.IMPLICIT_GRANTED
@@ -917,7 +919,21 @@
         if (packageState.isUpdatedSystemApp) {
             return true
         }
-        // TODO: Enforce the allowlist on boot
+        // Only enforce the privileged permission allowlist on boot
+        if (!newState.systemState.isSystemReady) {
+            // Apps that are in updated apex's do not need to be allowlisted
+            if (!packageState.isApkInUpdatedApex) {
+                Log.w(
+                    LOG_TAG, "Privileged permission ${permission.name} for package" +
+                    " ${packageState.packageName} (${packageState.path}) not in" +
+                    " privileged permission allowlist"
+                )
+                if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
+                    privilegedPermissionAllowlistViolations += "${packageState.packageName}" +
+                        " (${packageState.path}): ${permission.name}"
+                }
+            }
+        }
         return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE
     }
 
@@ -1106,6 +1122,12 @@
             // Special permission for the recents app.
             return true
         }
+        // TODO(b/261913353): STOPSHIP: Add AndroidPackage.apexModuleName.
+        // This should be androidPackage.apexModuleName instead
+        if (permission.isModule && androidPackage.packageName != null) {
+            // Special permission granted for APKs inside APEX modules.
+            return true
+        }
         return false
     }
 
@@ -1155,6 +1177,13 @@
         return uid == ownerUid
     }
 
+    override fun MutateStateScope.onSystemReady() {
+        if (!privilegedPermissionAllowlistViolations.isEmpty()) {
+            throw IllegalStateException("Signature|privileged permissions not in privileged" +
+                " permission allowlist: $privilegedPermissionAllowlistViolations")
+        }
+    }
+
     override fun BinaryXmlPullParser.parseSystemState(state: AccessState) {
         with(persistence) { this@parseSystemState.parseSystemState(state) }
     }
diff --git a/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
index dbc0da7..c8797e2 100644
--- a/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.when;
+
 import android.annotation.UserIdInt;
 import android.app.job.JobScheduler;
 import android.content.Context;
@@ -31,6 +33,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.Shadows;
@@ -45,14 +49,20 @@
     private BackupManagerConstants mConstants;
     private ShadowJobScheduler mShadowJobScheduler;
 
+    @Mock
+    private UserBackupManagerService mUserBackupManagerService;
+
     @UserIdInt private int mUserOneId;
     @UserIdInt private int mUserTwoId;
 
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
         mConstants = new BackupManagerConstants(Handler.getMain(), mContext.getContentResolver());
         mConstants.start();
+        when(mUserBackupManagerService.getConstants()).thenReturn(mConstants);
+        when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(true);
 
         mShadowJobScheduler = Shadows.shadowOf(mContext.getSystemService(JobScheduler.class));
 
@@ -69,8 +79,8 @@
 
     @Test
     public void testSchedule_afterScheduling_jobExists() {
-        FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
-        FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
+        FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
+        FullBackupJob.schedule(mUserTwoId, mContext, 0, mUserBackupManagerService);
 
         assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull();
         assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNotNull();
@@ -78,18 +88,34 @@
 
     @Test
     public void testCancel_afterCancelling_jobDoesntExist() {
-        FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
-        FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
+        FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
+        FullBackupJob.schedule(mUserTwoId, mContext, 0, mUserBackupManagerService);
         FullBackupJob.cancel(mUserOneId, mContext);
         FullBackupJob.cancel(mUserTwoId, mContext);
 
         assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull();
         assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNull();
     }
+
+    @Test
+    public void testSchedule_isNoopIfDisabled() {
+        when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(false);
+        FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
+
+        assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull();
+    }
+
+    @Test
+    public void testSchedule_schedulesJobIfEnabled() {
+        when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(true);
+        FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
+
+        assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull();
+    }
 //
     @Test
     public void testSchedule_onlySchedulesForRequestedUser() {
-        FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
+        FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
 
         assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull();
         assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNull();
@@ -97,8 +123,8 @@
 //
     @Test
     public void testCancel_onlyCancelsForRequestedUser() {
-        FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
-        FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
+        FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
+        FullBackupJob.schedule(mUserTwoId, mContext, 0, mUserBackupManagerService);
         FullBackupJob.cancel(mUserOneId, mContext);
 
         assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull();
diff --git a/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
index 1c5fac2..712ac55 100644
--- a/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.when;
+
 import android.annotation.UserIdInt;
 import android.content.Context;
 import android.os.Handler;
@@ -30,6 +32,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
@@ -41,14 +45,20 @@
     private Context mContext;
     private BackupManagerConstants mConstants;
 
+    @Mock
+    private UserBackupManagerService mUserBackupManagerService;
+
     @UserIdInt private int mUserOneId;
     @UserIdInt private int mUserTwoId;
 
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
         mConstants = new BackupManagerConstants(Handler.getMain(), mContext.getContentResolver());
         mConstants.start();
+        when(mUserBackupManagerService.getConstants()).thenReturn(mConstants);
+        when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(true);
 
         mUserOneId = UserHandle.USER_SYSTEM;
         mUserTwoId = mUserOneId + 1;
@@ -62,6 +72,22 @@
     }
 
     @Test
+    public void testSchedule_isNoopIfDisabled() {
+        when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(false);
+        KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
+
+        assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
+    }
+
+    @Test
+    public void testSchedule_schedulesJobIfEnabled() {
+        when(mUserBackupManagerService.isFrameworkSchedulingEnabled()).thenReturn(true);
+        KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
+
+        assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue();
+    }
+
+    @Test
     public void testIsScheduled_beforeScheduling_returnsFalse() {
         assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
         assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse();
@@ -69,8 +95,8 @@
 
     @Test
     public void testIsScheduled_afterScheduling_returnsTrue() {
-        KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
-        KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
+        KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
+        KeyValueBackupJob.schedule(mUserTwoId, mContext, mUserBackupManagerService);
 
         assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue();
         assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isTrue();
@@ -78,8 +104,8 @@
 
     @Test
     public void testIsScheduled_afterCancelling_returnsFalse() {
-        KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
-        KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
+        KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
+        KeyValueBackupJob.schedule(mUserTwoId, mContext, mUserBackupManagerService);
         KeyValueBackupJob.cancel(mUserOneId, mContext);
         KeyValueBackupJob.cancel(mUserTwoId, mContext);
 
@@ -89,7 +115,7 @@
 
     @Test
     public void testIsScheduled_afterScheduling_returnsTrueOnlyForScheduledUser() {
-        KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
+        KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
 
         assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue();
         assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse();
@@ -97,8 +123,8 @@
 
     @Test
     public void testIsScheduled_afterCancelling_returnsFalseOnlyForCancelledUser() {
-        KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
-        KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
+        KeyValueBackupJob.schedule(mUserOneId, mContext, mUserBackupManagerService);
+        KeyValueBackupJob.schedule(mUserTwoId, mContext, mUserBackupManagerService);
         KeyValueBackupJob.cancel(mUserOneId, mContext);
 
         assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
new file mode 100644
index 0000000..73d04c6
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.inputmethod;
+
+import static android.inputmethodservice.InputMethodService.IME_ACTIVE;
+
+import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
+import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_INVALID;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_IMPLICIT;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.os.RemoteException;
+import android.view.inputmethod.InputMethodManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test the behavior of {@link DefaultImeVisibilityApplier} when performing or applying the IME
+ * visibility state.
+ *
+ * Build/Install/Run:
+ * atest FrameworksInputMethodSystemServerTests:DefaultImeVisibilityApplierTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTestBase {
+    private DefaultImeVisibilityApplier mVisibilityApplier;
+
+    @Before
+    public void setUp() throws RemoteException {
+        super.setUp();
+        mVisibilityApplier =
+                (DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier();
+        mInputMethodManagerService.mCurFocusedWindowClient = mock(
+                InputMethodManagerService.ClientState.class);
+    }
+
+    @Test
+    public void testPerformShowIme() throws Exception {
+        mVisibilityApplier.performShowIme(mWindowToken, null, null, SHOW_SOFT_INPUT);
+        verifyShowSoftInput(false, true, InputMethodManager.SHOW_IMPLICIT);
+    }
+
+    @Test
+    public void testPerformHideIme() throws Exception {
+        mVisibilityApplier.performHideIme(mWindowToken, null, null, HIDE_SOFT_INPUT);
+        verifyHideSoftInput(false, true);
+    }
+
+    @Test
+    public void testApplyImeVisibility_throwForInvalidState() {
+        mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_INVALID);
+        assertThrows(IllegalArgumentException.class,
+                () -> mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_INVALID));
+    }
+
+    @Test
+    public void testApplyImeVisibility_showIme() {
+        mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME);
+        verify(mMockWindowManagerInternal).showImePostLayout(eq(mWindowToken), any());
+    }
+
+    @Test
+    public void testApplyImeVisibility_hideIme() {
+        mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME);
+        verify(mMockWindowManagerInternal).hideIme(eq(mWindowToken), anyInt(), any());
+    }
+
+    @Test
+    public void testApplyImeVisibility_hideImeExplicit() throws Exception {
+        mInputMethodManagerService.mImeWindowVis = IME_ACTIVE;
+        mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME_EXPLICIT);
+        verifyHideSoftInput(true, true);
+    }
+
+    @Test
+    public void testApplyImeVisibility_hideNotAlways() throws Exception {
+        mInputMethodManagerService.mImeWindowVis = IME_ACTIVE;
+        mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME_NOT_ALWAYS);
+        verifyHideSoftInput(true, true);
+    }
+
+    @Test
+    public void testApplyImeVisibility_showImeImplicit() throws Exception {
+        mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME_IMPLICIT);
+        verifyShowSoftInput(true, true, InputMethodManager.SHOW_IMPLICIT);
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
new file mode 100644
index 0000000..8415fe1
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.inputmethod;
+
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
+
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
+import static com.android.server.inputmethod.InputMethodManagerService.FALLBACK_DISPLAY_ID;
+import static com.android.server.inputmethod.InputMethodManagerService.ImeDisplayValidator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.inputmethod.InputMethodManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.wm.WindowManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test the behavior of {@link ImeVisibilityStateComputer} and {@link ImeVisibilityApplier} when
+ * requesting the IME visibility.
+ *
+ * Build/Install/Run:
+ * atest FrameworksInputMethodSystemServerTests:ImeVisibilityStateComputerTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTestBase {
+    private ImeVisibilityStateComputer mComputer;
+    private int mImeDisplayPolicy = DISPLAY_IME_POLICY_LOCAL;
+
+    @Before
+    public void setUp() throws RemoteException {
+        super.setUp();
+        ImeVisibilityStateComputer.Injector injector = new ImeVisibilityStateComputer.Injector() {
+            @Override
+            public WindowManagerInternal getWmService() {
+                return mMockWindowManagerInternal;
+            }
+
+            @Override
+            public ImeDisplayValidator getImeValidator() {
+                return displayId -> mImeDisplayPolicy;
+            }
+        };
+        mComputer = new ImeVisibilityStateComputer(mInputMethodManagerService, injector);
+    }
+
+    @Test
+    public void testRequestImeVisibility_showImplicit() {
+        initImeTargetWindowState(mWindowToken);
+        boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT);
+        mComputer.requestImeVisibility(mWindowToken, res);
+
+        final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
+        assertThat(state).isNotNull();
+        assertThat(state.hasEdiorFocused()).isTrue();
+        assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
+        assertThat(state.isRequestedImeVisible()).isTrue();
+
+        assertThat(mComputer.mRequestedShowExplicitly).isFalse();
+    }
+
+    @Test
+    public void testRequestImeVisibility_showExplicit() {
+        initImeTargetWindowState(mWindowToken);
+        boolean res = mComputer.onImeShowFlags(null, 0 /* show explicit */);
+        mComputer.requestImeVisibility(mWindowToken, res);
+
+        final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
+        assertThat(state).isNotNull();
+        assertThat(state.hasEdiorFocused()).isTrue();
+        assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
+        assertThat(state.isRequestedImeVisible()).isTrue();
+
+        assertThat(mComputer.mRequestedShowExplicitly).isTrue();
+    }
+
+    @Test
+    public void testRequestImeVisibility_showImplicit_a11yNoImePolicy() {
+        // Precondition: set AccessibilityService#SHOW_MODE_HIDDEN policy
+        mComputer.getImePolicy().setA11yRequestNoSoftKeyboard(SHOW_MODE_HIDDEN);
+
+        initImeTargetWindowState(mWindowToken);
+        boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT);
+        mComputer.requestImeVisibility(mWindowToken, res);
+
+        final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
+        assertThat(state).isNotNull();
+        assertThat(state.hasEdiorFocused()).isTrue();
+        assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
+        assertThat(state.isRequestedImeVisible()).isFalse();
+
+        assertThat(mComputer.mRequestedShowExplicitly).isFalse();
+    }
+
+    @Test
+    public void testRequestImeVisibility_showImplicit_imeHiddenPolicy() {
+        // Precondition: set IME hidden display policy before calling showSoftInput
+        mComputer.getImePolicy().setImeHiddenByDisplayPolicy(true);
+
+        initImeTargetWindowState(mWindowToken);
+        boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT);
+        mComputer.requestImeVisibility(mWindowToken, res);
+
+        final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
+        assertThat(state).isNotNull();
+        assertThat(state.hasEdiorFocused()).isTrue();
+        assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
+        assertThat(state.isRequestedImeVisible()).isFalse();
+
+        assertThat(mComputer.mRequestedShowExplicitly).isFalse();
+    }
+
+    @Test
+    public void testRequestImeVisibility_hideNotAlways() {
+        // Precondition: ensure IME has shown before hiding request.
+        mComputer.setInputShown(true);
+
+        initImeTargetWindowState(mWindowToken);
+        assertThat(mComputer.canHideIme(null, InputMethodManager.HIDE_NOT_ALWAYS)).isTrue();
+        mComputer.requestImeVisibility(mWindowToken, false);
+
+        final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
+        assertThat(state).isNotNull();
+        assertThat(state.hasEdiorFocused()).isTrue();
+        assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
+        assertThat(state.isRequestedImeVisible()).isFalse();
+    }
+
+    @Test
+    public void testComputeImeDisplayId() {
+        final ImeTargetWindowState state = mComputer.getOrCreateWindowState(mWindowToken);
+
+        mImeDisplayPolicy = DISPLAY_IME_POLICY_LOCAL;
+        mComputer.computeImeDisplayId(state, DEFAULT_DISPLAY);
+        assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse();
+        assertThat(state.getImeDisplayId()).isEqualTo(DEFAULT_DISPLAY);
+
+        mComputer.computeImeDisplayId(state, 10 /* displayId */);
+        assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse();
+        assertThat(state.getImeDisplayId()).isEqualTo(10);
+
+        mImeDisplayPolicy = DISPLAY_IME_POLICY_HIDE;
+        mComputer.computeImeDisplayId(state, 10 /* displayId */);
+        assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isTrue();
+        assertThat(state.getImeDisplayId()).isEqualTo(INVALID_DISPLAY);
+
+        mImeDisplayPolicy = DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
+        mComputer.computeImeDisplayId(state, 10 /* displayId */);
+        assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse();
+        assertThat(state.getImeDisplayId()).isEqualTo(FALLBACK_DISPLAY_ID);
+    }
+
+    private void initImeTargetWindowState(IBinder windowToken) {
+        final ImeTargetWindowState state = new ImeTargetWindowState(SOFT_INPUT_STATE_UNCHANGED,
+                0, true, true, true);
+        mComputer.setWindowState(windowToken, state);
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 640bde3..804bb49 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -26,6 +26,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -200,9 +201,8 @@
                         "TestServiceThread",
                         Process.THREAD_PRIORITY_FOREGROUND, /* allowIo */
                         false);
-        mInputMethodManagerService =
-                new InputMethodManagerService(
-                        mContext, mServiceThread, mMockInputMethodBindingController);
+        mInputMethodManagerService = new InputMethodManagerService(mContext, mServiceThread,
+                mMockInputMethodBindingController);
 
         // Start a InputMethodManagerService.Lifecycle to publish and manage the lifecycle of
         // InputMethodManagerService, which is closer to the real situation.
@@ -239,12 +239,17 @@
 
     protected void verifyShowSoftInput(boolean setVisible, boolean showSoftInput)
             throws RemoteException {
+        verifyShowSoftInput(setVisible, showSoftInput, anyInt());
+    }
+
+    protected void verifyShowSoftInput(boolean setVisible, boolean showSoftInput, int showFlags)
+            throws RemoteException {
         synchronized (ImfLock.class) {
             verify(mMockInputMethodBindingController, times(setVisible ? 1 : 0))
                     .setCurrentMethodVisible();
         }
         verify(mMockInputMethod, times(showSoftInput ? 1 : 0))
-                .showSoftInput(any(), any(), anyInt(), any());
+                .showSoftInput(any(), any(), eq(showFlags), any());
     }
 
     protected void verifyHideSoftInput(boolean setNotVisible, boolean hideSoftInput)
diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp
index 83677c2..47e7a37 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -30,11 +30,16 @@
         "truth-prebuilt",
     ],
     static_libs: [
+        "ApexInstallHelper",
         "cts-host-utils",
         "frameworks-base-hostutils",
         "PackageManagerServiceHostTestsIntentVerifyUtils",
     ],
     test_suites: ["general-tests"],
+    data: [
+        ":PackageManagerTestApex",
+        ":PackageManagerTestApexApp",
+    ],
     java_resources: [
         ":PackageManagerTestOverlayActor",
         ":PackageManagerTestOverlay",
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/ApexUpdateTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/ApexUpdateTest.kt
new file mode 100644
index 0000000..44b4e30
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/ApexUpdateTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.pm.test
+
+import com.android.modules.testing.utils.ApexInstallHelper
+import com.android.tradefed.invoker.TestInformation
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.AfterClass
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class ApexUpdateTest : BaseHostJUnit4Test() {
+
+    companion object {
+        private const val APEX_NAME = "com.android.server.pm.test.apex"
+        private const val APK_IN_APEX_NAME = "$APEX_NAME.app"
+        private const val APK_FILE_NAME = "PackageManagerTestApexApp.apk"
+
+        private lateinit var apexInstallHelper: ApexInstallHelper
+
+        @JvmStatic
+        @BeforeClassWithInfo
+        fun initApexHelper(testInformation: TestInformation) {
+            apexInstallHelper = ApexInstallHelper(testInformation)
+        }
+
+        @JvmStatic
+        @AfterClass
+        fun revertChanges() {
+            apexInstallHelper.revertChanges()
+        }
+    }
+
+    @Before
+    @After
+    fun uninstallApp() {
+        device.uninstallPackage(APK_IN_APEX_NAME)
+    }
+
+    @Test
+    fun apexModuleName() {
+        // Install the test APEX and assert it's returned as the APEX module itself
+        // (null when not --include-apex)
+        apexInstallHelper.pushApexAndReboot("PackageManagerTestApex.apex")
+        assertModuleName(APEX_NAME).isNull()
+        assertModuleName(APEX_NAME, includeApex = true).isEqualTo(APEX_NAME)
+
+        // Check the APK-in-APEX, ensuring there is only 1 active package
+        assertModuleName(APK_IN_APEX_NAME).isEqualTo(APEX_NAME)
+        assertModuleName(APK_IN_APEX_NAME, hidden = true).isNull()
+
+        // Then install a /data update to the APK-in-APEX
+        device.installPackage(testInformation.getDependencyFile(APK_FILE_NAME, false), false)
+
+        // Verify same as above
+        assertModuleName(APEX_NAME, includeApex = true).isEqualTo(APEX_NAME)
+        assertModuleName(APK_IN_APEX_NAME).isEqualTo(APEX_NAME)
+
+        // But also check that the /data variant now has a hidden package
+        assertModuleName(APK_IN_APEX_NAME, hidden = true).isEqualTo(APEX_NAME)
+
+        // Reboot the device and check that values are preserved
+        device.reboot()
+        assertModuleName(APEX_NAME, includeApex = true).isEqualTo(APEX_NAME)
+        assertModuleName(APK_IN_APEX_NAME).isEqualTo(APEX_NAME)
+        assertModuleName(APK_IN_APEX_NAME, hidden = true).isEqualTo(APEX_NAME)
+
+        // Revert the install changes (delete system image APEX) and check that it's gone
+        apexInstallHelper.revertChanges()
+        assertModuleName(APEX_NAME, includeApex = true).isNull()
+
+        // Verify the module name is no longer associated with the APK-in-APEX,
+        // which is now just a regular /data APK with no hidden system variant.
+        // The assertion for the valid /data APK uses "null" because the value
+        // printed for normal packages is "apexModuleName=null". As opposed to
+        // a literal null indicating the package variant doesn't exist
+        assertModuleName(APK_IN_APEX_NAME).isEqualTo("null")
+        assertModuleName(APK_IN_APEX_NAME, hidden = true).isEqualTo(null)
+    }
+
+    private fun assertModuleName(
+        packageName: String,
+        hidden: Boolean = false,
+        includeApex: Boolean = false
+    ) = assertThat(
+        device.executeShellCommand(
+                "dumpsys package ${"--include-apex".takeIf { includeApex }} $packageName"
+        )
+            .lineSequence()
+            .map(String::trim)
+            .dropWhile { !it.startsWith(if (hidden) "Hidden system packages:" else "Packages:")}
+            .dropWhile { !it.startsWith("Package [$packageName]") }
+            .takeWhile { !it.startsWith("User 0:") }
+            .firstOrNull { it.startsWith("apexModuleName=") }
+            ?.removePrefix("apexModuleName=")
+    )
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Apex/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Apex/Android.bp
new file mode 100644
index 0000000..aef365e
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Apex/Android.bp
@@ -0,0 +1,40 @@
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex {
+    name: "PackageManagerTestApex",
+    apps: ["PackageManagerTestApexApp"],
+    androidManifest: "AndroidManifestApex.xml",
+    file_contexts: ":apex.test-file_contexts",
+    key: "apex.test.key",
+    certificate: ":apex.test.certificate",
+    min_sdk_version: "33",
+    installable: true,
+    updatable: true,
+}
+
+android_test_helper_app {
+    name: "PackageManagerTestApexApp",
+    manifest: "AndroidManifestApp.xml",
+    sdk_version: "33",
+    min_sdk_version: "33",
+    apex_available: ["PackageManagerTestApex"],
+    certificate: ":apex.test.certificate",
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Apex/AndroidManifestApex.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Apex/AndroidManifestApex.xml
new file mode 100644
index 0000000..575b2bc
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Apex/AndroidManifestApex.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<manifest package="com.android.server.pm.test.apex">
+    <application/>
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Apex/AndroidManifestApp.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Apex/AndroidManifestApp.xml
new file mode 100644
index 0000000..87fb5cc
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Apex/AndroidManifestApp.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<manifest package="com.android.server.pm.test.apex.app">
+    <application/>
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Apex/apex_manifest.json b/services/tests/PackageManagerServiceTests/host/test-apps/Apex/apex_manifest.json
new file mode 100644
index 0000000..b89581d
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Apex/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.server.pm.test.apex",
+  "version": 1
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
index 2f909aa..5b0e2f3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -518,6 +518,7 @@
         apexInfo.isActive = isActive;
         apexInfo.isFactory = isFactory;
         apexInfo.modulePath = apexFile.getPath();
+        apexInfo.preinstalledModulePath = apexFile.getPath();
         return apexInfo;
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
index 8b36da5..d5aa7fe 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
@@ -413,8 +413,8 @@
         mDexOptThread.join(TEST_WAIT_TIMEOUT_MS);
         mCancelThread.join(TEST_WAIT_TIMEOUT_MS);
 
-        // Always reschedule for periodic job
-        verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, false);
+        // The job should be rescheduled.
+        verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, true /* wantsReschedule */);
         verifyLastControlDexOptBlockingCall(false);
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index bcba4a1..c08f6bf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -28,7 +28,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.server.wallpaper.WallpaperManagerService.WALLPAPER;
+import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
 
 import static org.hamcrest.core.IsNot.not;
 import static org.junit.Assert.assertEquals;
@@ -71,7 +71,6 @@
 import android.util.Xml;
 import android.view.Display;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -89,6 +88,7 @@
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -110,7 +110,6 @@
  * atest FrameworksMockingServicesTests:WallpaperManagerServiceTests
  */
 @Presubmit
-@FlakyTest(bugId = 129797242)
 @RunWith(AndroidJUnit4.class)
 public class WallpaperManagerServiceTests {
 
@@ -273,8 +272,10 @@
 
     /**
      * Tests setWallpaperComponent and clearWallpaper should work as expected.
+     * TODO ignored since the assumption never passes. to be investigated.
      */
     @Test
+    @Ignore("b/264533465")
     public void testSetThenClearComponent() {
         // Skip if there is no pre-defined default wallpaper component.
         assumeThat(sDefaultWallpaperComponent,
@@ -285,7 +286,7 @@
         verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
         verifyCurrentSystemData(testUserId);
 
-        mService.setWallpaperComponent(sImageWallpaperComponentName);
+        mService.setWallpaperComponent(sImageWallpaperComponentName, FLAG_SYSTEM, testUserId);
         verifyLastWallpaperData(testUserId, sImageWallpaperComponentName);
         verifyCurrentSystemData(testUserId);
 
@@ -312,7 +313,7 @@
 
         WallpaperManagerService.WallpaperConnection.DisplayConnector connector =
                 mService.mLastWallpaper.connection.getDisplayConnectorOrCreate(DEFAULT_DISPLAY);
-        mService.setWallpaperComponent(sDefaultWallpaperComponent);
+        mService.setWallpaperComponent(sDefaultWallpaperComponent, FLAG_SYSTEM, testUserId);
 
         verify(connector.mEngine).dispatchWallpaperCommand(
                 eq(COMMAND_REAPPLY), anyInt(), anyInt(), anyInt(), any());
@@ -454,7 +455,7 @@
     public void testGetAdjustedWallpaperColorsOnDimming() throws RemoteException {
         final int testUserId = USER_SYSTEM;
         mService.switchUser(testUserId, null);
-        mService.setWallpaperComponent(sDefaultWallpaperComponent);
+        mService.setWallpaperComponent(sDefaultWallpaperComponent, FLAG_SYSTEM, testUserId);
         WallpaperData wallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, testUserId);
 
         // Mock a wallpaper data with color hints that support dark text and dark theme
diff --git a/services/tests/servicestests/res/raw/dummy_keyboard_layout.kcm b/services/tests/servicestests/res/raw/dummy_keyboard_layout.kcm
new file mode 100644
index 0000000..ea6bc98
--- /dev/null
+++ b/services/tests/servicestests/res/raw/dummy_keyboard_layout.kcm
@@ -0,0 +1,311 @@
+# Copyright (C) 2023 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.
+
+#
+# English (US) keyboard layout.
+# Unlike the default (generic) keyboard layout, English (US) does not contain any
+# special ALT characters.
+#
+
+type OVERLAY
+
+### ROW 1
+
+key GRAVE {
+    label:                              '`'
+    base:                               '`'
+    shift:                              '~'
+}
+
+key 1 {
+    label:                              '1'
+    base:                               '1'
+    shift:                              '!'
+}
+
+key 2 {
+    label:                              '2'
+    base:                               '2'
+    shift:                              '@'
+}
+
+key 3 {
+    label:                              '3'
+    base:                               '3'
+    shift:                              '#'
+}
+
+key 4 {
+    label:                              '4'
+    base:                               '4'
+    shift:                              '$'
+}
+
+key 5 {
+    label:                              '5'
+    base:                               '5'
+    shift:                              '%'
+}
+
+key 6 {
+    label:                              '6'
+    base:                               '6'
+    shift:                              '^'
+}
+
+key 7 {
+    label:                              '7'
+    base:                               '7'
+    shift:                              '&'
+}
+
+key 8 {
+    label:                              '8'
+    base:                               '8'
+    shift:                              '*'
+}
+
+key 9 {
+    label:                              '9'
+    base:                               '9'
+    shift:                              '('
+}
+
+key 0 {
+    label:                              '0'
+    base:                               '0'
+    shift:                              ')'
+}
+
+key MINUS {
+    label:                              '-'
+    base:                               '-'
+    shift:                              '_'
+}
+
+key EQUALS {
+    label:                              '='
+    base:                               '='
+    shift:                              '+'
+}
+
+### ROW 2
+
+key Q {
+    label:                              'Q'
+    base:                               'q'
+    shift, capslock:                    'Q'
+}
+
+key W {
+    label:                              'W'
+    base:                               'w'
+    shift, capslock:                    'W'
+}
+
+key E {
+    label:                              'E'
+    base:                               'e'
+    shift, capslock:                    'E'
+}
+
+key R {
+    label:                              'R'
+    base:                               'r'
+    shift, capslock:                    'R'
+}
+
+key T {
+    label:                              'T'
+    base:                               't'
+    shift, capslock:                    'T'
+}
+
+key Y {
+    label:                              'Y'
+    base:                               'y'
+    shift, capslock:                    'Y'
+}
+
+key U {
+    label:                              'U'
+    base:                               'u'
+    shift, capslock:                    'U'
+}
+
+key I {
+    label:                              'I'
+    base:                               'i'
+    shift, capslock:                    'I'
+}
+
+key O {
+    label:                              'O'
+    base:                               'o'
+    shift, capslock:                    'O'
+}
+
+key P {
+    label:                              'P'
+    base:                               'p'
+    shift, capslock:                    'P'
+}
+
+key LEFT_BRACKET {
+    label:                              '['
+    base:                               '['
+    shift:                              '{'
+}
+
+key RIGHT_BRACKET {
+    label:                              ']'
+    base:                               ']'
+    shift:                              '}'
+}
+
+key BACKSLASH {
+    label:                              '\\'
+    base:                               '\\'
+    shift:                              '|'
+}
+
+### ROW 3
+
+key A {
+    label:                              'A'
+    base:                               'a'
+    shift, capslock:                    'A'
+}
+
+key S {
+    label:                              'S'
+    base:                               's'
+    shift, capslock:                    'S'
+}
+
+key D {
+    label:                              'D'
+    base:                               'd'
+    shift, capslock:                    'D'
+}
+
+key F {
+    label:                              'F'
+    base:                               'f'
+    shift, capslock:                    'F'
+}
+
+key G {
+    label:                              'G'
+    base:                               'g'
+    shift, capslock:                    'G'
+}
+
+key H {
+    label:                              'H'
+    base:                               'h'
+    shift, capslock:                    'H'
+}
+
+key J {
+    label:                              'J'
+    base:                               'j'
+    shift, capslock:                    'J'
+}
+
+key K {
+    label:                              'K'
+    base:                               'k'
+    shift, capslock:                    'K'
+}
+
+key L {
+    label:                              'L'
+    base:                               'l'
+    shift, capslock:                    'L'
+}
+
+key SEMICOLON {
+    label:                              ';'
+    base:                               ';'
+    shift:                              ':'
+}
+
+key APOSTROPHE {
+    label:                              '\''
+    base:                               '\''
+    shift:                              '"'
+}
+
+### ROW 4
+
+key Z {
+    label:                              'Z'
+    base:                               'z'
+    shift, capslock:                    'Z'
+}
+
+key X {
+    label:                              'X'
+    base:                               'x'
+    shift, capslock:                    'X'
+}
+
+key C {
+    label:                              'C'
+    base:                               'c'
+    shift, capslock:                    'C'
+}
+
+key V {
+    label:                              'V'
+    base:                               'v'
+    shift, capslock:                    'V'
+}
+
+key B {
+    label:                              'B'
+    base:                               'b'
+    shift, capslock:                    'B'
+}
+
+key N {
+    label:                              'N'
+    base:                               'n'
+    shift, capslock:                    'N'
+}
+
+key M {
+    label:                              'M'
+    base:                               'm'
+    shift, capslock:                    'M'
+}
+
+key COMMA {
+    label:                              ','
+    base:                               ','
+    shift:                              '<'
+}
+
+key PERIOD {
+    label:                              '.'
+    base:                               '.'
+    shift:                              '>'
+}
+
+key SLASH {
+    label:                              '/'
+    base:                               '/'
+    shift:                              '?'
+}
diff --git a/services/tests/servicestests/res/xml/keyboard_layouts.xml b/services/tests/servicestests/res/xml/keyboard_layouts.xml
new file mode 100644
index 0000000..b5a05fc
--- /dev/null
+++ b/services/tests/servicestests/res/xml/keyboard_layouts.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 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.
+  -->
+
+<keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <keyboard-layout
+        android:name="keyboard_layout_english_uk"
+        android:label="English (UK)"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        android:keyboardLocale="en-Latn-GB"
+        android:keyboardLayoutType="qwerty" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_english_us"
+        android:label="English (US)"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        android:keyboardLocale="en-Latn,en-Latn-US"
+        android:keyboardLayoutType="qwerty" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_english_us_intl"
+        android:label="English (International)"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        android:keyboardLocale="en-Latn-US"
+        android:keyboardLayoutType="extended" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_english_us_dvorak"
+        android:label="English (Dvorak)"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        android:keyboardLocale="en-Latn-US"
+        android:keyboardLayoutType="dvorak" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_german"
+        android:label="German"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        android:keyboardLocale="de-Latn"
+        android:keyboardLayoutType="qwertz" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_french"
+        android:label="French"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        android:keyboardLocale="fr-Latn-FR"
+        android:keyboardLayoutType="azerty" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_russian_qwerty"
+        android:label="Russian"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        android:keyboardLocale="ru-Cyrl"
+        android:keyboardLayoutType="qwerty" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_russian"
+        android:label="Russian"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        android:keyboardLocale="ru-Cyrl" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_vendorId:1,productId:1"
+        android:label="vendorId:1,productId:1"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        androidprv:vendorId="1"
+        androidprv:productId="1" />
+</keyboard-layouts>
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 0780d21..d996e37 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -214,7 +214,8 @@
     }
 
     private void notRegistered_publicMethodsShouldBeBenign(int displayId) {
-        assertFalse(mFullScreenMagnificationController.isMagnifying(displayId));
+        checkActivatedAndMagnifyingState(/* activated= */false, /* magnifying= */false, displayId);
+
         assertFalse(
                 mFullScreenMagnificationController.magnificationRegionContains(displayId, 100,
                         100));
@@ -646,9 +647,9 @@
                 .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
                         SERVICE_ID_2);
         assertFalse(mFullScreenMagnificationController.resetIfNeeded(displayId, SERVICE_ID_1));
-        assertTrue(mFullScreenMagnificationController.isMagnifying(displayId));
+        checkActivatedAndMagnifyingState(/* activated= */true, /* magnifying= */true, displayId);
         assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, SERVICE_ID_2));
-        assertFalse(mFullScreenMagnificationController.isMagnifying(displayId));
+        checkActivatedAndMagnifyingState(/* activated= */false, /* magnifying= */false, displayId);
     }
 
     @Test
@@ -667,7 +668,7 @@
         assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, false));
         verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId),
                 eq(INITIAL_MAGNIFICATION_REGION), any(MagnificationConfig.class));
-        assertFalse(mFullScreenMagnificationController.isMagnifying(displayId));
+        checkActivatedAndMagnifyingState(/* activated= */false, /* magnifying= */false, displayId);
         assertFalse(mFullScreenMagnificationController.resetIfNeeded(displayId, false));
     }
 
@@ -731,7 +732,7 @@
         mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
         mStateListener.onAnimationEnd(mMockValueAnimator);
 
-        assertFalse(mFullScreenMagnificationController.isMagnifying(DISPLAY_0));
+        checkActivatedAndMagnifyingState(/* activated= */false, /* magnifying= */false, displayId);
         verify(lastAnimationCallback).onResult(true);
     }
 
@@ -749,8 +750,8 @@
         mMessageCapturingHandler.sendAllMessages();
         br.onReceive(mMockContext, null);
         mMessageCapturingHandler.sendAllMessages();
-        assertFalse(mFullScreenMagnificationController.isMagnifying(DISPLAY_0));
-        assertFalse(mFullScreenMagnificationController.isMagnifying(DISPLAY_1));
+        checkActivatedAndMagnifyingState(/* activated= */false, /* magnifying= */false, DISPLAY_0);
+        checkActivatedAndMagnifyingState(/* activated= */false, /* magnifying= */false, DISPLAY_1);
     }
 
     @Test
@@ -768,7 +769,7 @@
         mMessageCapturingHandler.sendAllMessages();
         callbacks.onUserContextChanged();
         mMessageCapturingHandler.sendAllMessages();
-        assertFalse(mFullScreenMagnificationController.isMagnifying(displayId));
+        checkActivatedAndMagnifyingState(/* activated= */false, /* magnifying= */false, displayId);
     }
 
     @Test
@@ -784,10 +785,10 @@
         MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
         zoomIn2xToMiddle(displayId);
         mMessageCapturingHandler.sendAllMessages();
-        assertTrue(mFullScreenMagnificationController.isMagnifying(displayId));
+        checkActivatedAndMagnifyingState(/* activated= */true, /* magnifying= */true, displayId);
         callbacks.onDisplaySizeChanged();
         mMessageCapturingHandler.sendAllMessages();
-        assertFalse(mFullScreenMagnificationController.isMagnifying(displayId));
+        checkActivatedAndMagnifyingState(/* activated= */false, /* magnifying= */false, DISPLAY_0);
     }
 
     @Test
@@ -1133,23 +1134,17 @@
     }
 
     @Test
-    public void testSetForceShowMagnifiableBounds() {
+    public void testZoomTo1x_shouldActivatedAndForceShowMagnifiableBounds() {
         register(DISPLAY_0);
+        final float scale = 1.0f;
+        mFullScreenMagnificationController.setScaleAndCenter(
+                DISPLAY_0, scale, Float.NaN, Float.NaN, true, SERVICE_ID_1);
 
-        mFullScreenMagnificationController.setForceShowMagnifiableBounds(DISPLAY_0, true);
-
+        checkActivatedAndMagnifyingState(/* activated= */true, /* magnifying= */false, DISPLAY_0);
         verify(mMockWindowManager).setForceShowMagnifiableBounds(DISPLAY_0, true);
     }
 
     @Test
-    public void testIsForceShowMagnifiableBounds() {
-        register(DISPLAY_0);
-        mFullScreenMagnificationController.setForceShowMagnifiableBounds(DISPLAY_0, true);
-
-        assertTrue(mFullScreenMagnificationController.isForceShowMagnifiableBounds(DISPLAY_0));
-    }
-
-    @Test
     public void testSetScale_toMagnifying_shouldNotifyActivatedState() {
         setScaleToMagnifying();
 
@@ -1220,7 +1215,15 @@
         float scale = 2.0f;
         mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
                 false, SERVICE_ID_1);
-        assertTrue(mFullScreenMagnificationController.isMagnifying(displayId));
+        checkActivatedAndMagnifyingState(/* activated= */true, /* magnifying= */true, displayId);
+    }
+
+    private void checkActivatedAndMagnifyingState(
+            boolean activated, boolean magnifying, int displayId) {
+        final boolean isActivated = mFullScreenMagnificationController.isActivated(displayId);
+        final boolean isMagnifying = mFullScreenMagnificationController.getScale(displayId) > 1.0f;
+        assertTrue(isActivated == activated);
+        assertTrue(isMagnifying == magnifying);
     }
 
     private MagnificationCallbacks getMagnificationCallbacks(int displayId) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 0fed89b..5334e4c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -19,6 +19,7 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_POINTER_INDEX_SHIFT;
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
 
@@ -78,24 +79,25 @@
  * {@code
  *      digraph {
  *          IDLE -> SHORTCUT_TRIGGERED [label="a11y\nbtn"]
- *          SHORTCUT_TRIGGERED -> IDLE [label="a11y\nbtn"]
  *          IDLE -> DOUBLE_TAP [label="2tap"]
  *          DOUBLE_TAP -> IDLE [label="timeout"]
- *          DOUBLE_TAP -> TRIPLE_TAP_AND_HOLD [label="down"]
- *          SHORTCUT_TRIGGERED -> TRIPLE_TAP_AND_HOLD [label="down"]
- *          TRIPLE_TAP_AND_HOLD -> ZOOMED [label="up"]
- *          TRIPLE_TAP_AND_HOLD -> DRAGGING_TMP [label="hold/\nswipe"]
- *          DRAGGING_TMP -> IDLE [label="release"]
+ *          DOUBLE_TAP -> ZOOMED [label="tap"]
+ *          DOUBLE_TAP -> NON_ACTIVATED_ZOOMED_TMP [label="hold"]
+ *          NON_ACTIVATED_ZOOMED_TMP -> IDLE [label="release"]
+ *          SHORTCUT_TRIGGERED -> IDLE [label="a11y\nbtn"]
+ *          SHORTCUT_TRIGGERED -> ZOOMED[label="tap"]
+ *          SHORTCUT_TRIGGERED -> ACTIVATED_ZOOMED_TMP [label="hold"]
+ *          SHORTCUT_TRIGGERED -> PANNING [label="2hold]
  *          ZOOMED -> ZOOMED_DOUBLE_TAP [label="2tap"]
- *          ZOOMED_DOUBLE_TAP -> ZOOMED [label="timeout"]
- *          ZOOMED_DOUBLE_TAP -> DRAGGING [label="hold"]
- *          ZOOMED_DOUBLE_TAP -> IDLE [label="tap"]
- *          DRAGGING -> ZOOMED [label="release"]
  *          ZOOMED -> IDLE [label="a11y\nbtn"]
  *          ZOOMED -> PANNING [label="2hold"]
+ *          ZOOMED_DOUBLE_TAP -> ZOOMED [label="timeout"]
+ *          ZOOMED_DOUBLE_TAP -> ACTIVATED_ZOOMED_TMP [label="hold"]
+ *          ZOOMED_DOUBLE_TAP -> IDLE [label="tap"]
+ *          ACTIVATED_ZOOMED_TMP -> ZOOMED [label="release"]
+ *          PANNING -> ZOOMED [label="release"]
  *          PANNING -> PANNING_SCALING [label="pinch"]
  *          PANNING_SCALING -> ZOOMED [label="release"]
- *          PANNING -> ZOOMED [label="release"]
  *      }
  * }
  */
@@ -107,12 +109,11 @@
     public static final int STATE_2TAPS = 3;
     public static final int STATE_ZOOMED_2TAPS = 4;
     public static final int STATE_SHORTCUT_TRIGGERED = 5;
-    public static final int STATE_DRAGGING_TMP = 6;
-    public static final int STATE_DRAGGING = 7;
+    public static final int STATE_NON_ACTIVATED_ZOOMED_TMP = 6;
+    public static final int STATE_ACTIVATED_ZOOMED_TMP = 7;
     public static final int STATE_PANNING = 8;
     public static final int STATE_SCALING_AND_PANNING = 9;
 
-
     public static final int FIRST_STATE = STATE_IDLE;
     public static final int LAST_STATE = STATE_SCALING_AND_PANNING;
 
@@ -164,10 +165,6 @@
             public boolean magnificationRegionContains(int displayId, float x, float y) {
                 return true;
             }
-
-            @Override
-            void setForceShowMagnifiableBounds(int displayId, boolean show) {
-            }
         };
         mFullScreenMagnificationController.register(DISPLAY_0);
         mClock = new OffsettableClock.Stopped();
@@ -266,11 +263,11 @@
     @SuppressWarnings("Convert2MethodRef")
     @Test
     public void testAlternativeTransitions_areWorking() {
-        // A11y button followed by a tap&hold turns temporary "viewport dragging" zoom on
+        // A11y button followed by a tap&hold turns temporary "viewport dragging" zoom in
         assertTransition(STATE_SHORTCUT_TRIGGERED, () -> {
             send(downEvent());
             fastForward1sec();
-        }, STATE_DRAGGING_TMP);
+        }, STATE_ACTIVATED_ZOOMED_TMP);
 
         // A11y button followed by a tap turns zoom on
         assertTransition(STATE_SHORTCUT_TRIGGERED, () -> tap(), STATE_ZOOMED);
@@ -281,7 +278,6 @@
         // A11y button turns zoom off
         assertTransition(STATE_ZOOMED, () -> triggerShortcut(), STATE_IDLE);
 
-
         // Double tap times out while zoomed
         assertTransition(STATE_ZOOMED_2TAPS, () -> {
             allowEventDelegation();
@@ -291,8 +287,11 @@
         // tap+tap+swipe doesn't get delegated
         assertTransition(STATE_2TAPS, () -> swipe(), STATE_IDLE);
 
-        // tap+tap+swipe initiates viewport dragging immediately
-        assertTransition(STATE_2TAPS, () -> swipeAndHold(), STATE_DRAGGING_TMP);
+        // tap+tap+swipe&hold initiates temporary viewport dragging zoom in immediately
+        assertTransition(STATE_2TAPS, () -> swipeAndHold(), STATE_NON_ACTIVATED_ZOOMED_TMP);
+
+        // release when activated temporary zoom in back to zoomed
+        assertTransition(STATE_ACTIVATED_ZOOMED_TMP, () -> upEvent(), STATE_ZOOMED);
     }
 
     @Test
@@ -337,8 +336,10 @@
 
     @Test
     public void testTripleTapAndHold_zoomsImmediately() {
-        assertZoomsImmediatelyOnSwipeFrom(STATE_2TAPS);
-        assertZoomsImmediatelyOnSwipeFrom(STATE_SHORTCUT_TRIGGERED);
+        assertZoomsImmediatelyOnSwipeFrom(STATE_2TAPS, STATE_NON_ACTIVATED_ZOOMED_TMP);
+        assertZoomsImmediatelyOnSwipeFrom(STATE_SHORTCUT_TRIGGERED, STATE_ACTIVATED_ZOOMED_TMP);
+        assertZoomsImmediatelyOnSwipeFrom(STATE_ZOOMED_2TAPS, STATE_ACTIVATED_ZOOMED_TMP);
+
     }
 
     @Test
@@ -391,10 +392,10 @@
         PointF pointer3 = new PointF(DEFAULT_X * 2, DEFAULT_Y);
 
         send(downEvent());
-        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}));
-        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2, pointer3}));
-        send(pointerEvent(ACTION_POINTER_UP, new PointF[] {pointer1, pointer2, pointer3}));
-        send(pointerEvent(ACTION_POINTER_UP, new PointF[] {pointer1, pointer2, pointer3}));
+        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1));
+        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2, pointer3}, 2));
+        send(pointerEvent(ACTION_POINTER_UP, new PointF[] {pointer1, pointer2, pointer3}, 2));
+        send(pointerEvent(ACTION_POINTER_UP, new PointF[] {pointer1, pointer2, pointer3}, 2));
         send(upEvent());
 
         assertIn(STATE_ZOOMED);
@@ -411,38 +412,53 @@
     }
 
     @Test
-    public void testFirstFingerSwipe_TwoPinterDownAndZoomedState_panningState() {
+    public void testFirstFingerSwipe_twoPointerDownAndZoomedState_panningState() {
         goFromStateIdleTo(STATE_ZOOMED);
         PointF pointer1 = DEFAULT_POINT;
         PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
 
         send(downEvent());
-        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}));
+        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1));
         //The minimum movement to transit to panningState.
         final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
         pointer1.offset(sWipeMinDistance + 1, 0);
-        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}));
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
         assertIn(STATE_PANNING);
 
-        assertIn(STATE_PANNING);
         returnToNormalFrom(STATE_PANNING);
     }
 
     @Test
-    public void testSecondFingerSwipe_TwoPinterDownAndZoomedState_panningState() {
+    public void testSecondFingerSwipe_twoPointerDownAndZoomedState_panningState() {
         goFromStateIdleTo(STATE_ZOOMED);
         PointF pointer1 = DEFAULT_POINT;
         PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
 
         send(downEvent());
-        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}));
+        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1));
         //The minimum movement to transit to panningState.
         final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
         pointer2.offset(sWipeMinDistance + 1, 0);
-        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}));
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1));
         assertIn(STATE_PANNING);
 
+        returnToNormalFrom(STATE_PANNING);
+    }
+
+    @Test
+    public void testSecondFingerSwipe_twoPointerDownAndShortcutTriggeredState_panningState() {
+        goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
+        PointF pointer1 = DEFAULT_POINT;
+        PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+        send(downEvent());
+        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1));
+        //The minimum movement to transit to panningState.
+        final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
+        pointer2.offset(sWipeMinDistance + 1, 0);
+        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1));
         assertIn(STATE_PANNING);
+
         returnToNormalFrom(STATE_PANNING);
     }
 
@@ -474,11 +490,11 @@
         }
     }
 
-    private void assertZoomsImmediatelyOnSwipeFrom(int state) {
-        goFromStateIdleTo(state);
+    private void assertZoomsImmediatelyOnSwipeFrom(int fromState, int toState) {
+        goFromStateIdleTo(fromState);
         swipeAndHold();
-        assertIn(STATE_DRAGGING_TMP);
-        returnToNormalFrom(STATE_DRAGGING_TMP);
+        assertIn(toState);
+        returnToNormalFrom(toState);
     }
 
     private void assertTransition(int fromState, Runnable transitionAction, int toState) {
@@ -522,44 +538,51 @@
             case STATE_IDLE: {
                 check(tapCount() < 2, state);
                 check(!mMgh.mDetectingState.mShortcutTriggered, state);
+                check(!isActivated(), state);
                 check(!isZoomed(), state);
             } break;
             case STATE_ZOOMED: {
+                check(isActivated(), state);
                 check(isZoomed(), state);
                 check(tapCount() < 2, state);
             } break;
             case STATE_2TAPS: {
+                check(!isActivated(), state);
                 check(!isZoomed(), state);
                 check(tapCount() == 2, state);
             } break;
             case STATE_ZOOMED_2TAPS: {
+                check(isActivated(), state);
                 check(isZoomed(), state);
                 check(tapCount() == 2, state);
             } break;
-            case STATE_DRAGGING: {
+            case STATE_NON_ACTIVATED_ZOOMED_TMP: {
+                check(isActivated(), state);
                 check(isZoomed(), state);
                 check(mMgh.mCurrentState == mMgh.mViewportDraggingState,
                         state);
-                check(mMgh.mViewportDraggingState.mZoomedInBeforeDrag, state);
+                check(!mMgh.mViewportDraggingState.mActivatedBeforeDrag, state);
             } break;
-            case STATE_DRAGGING_TMP: {
+            case STATE_ACTIVATED_ZOOMED_TMP: {
+                check(isActivated(), state);
                 check(isZoomed(), state);
                 check(mMgh.mCurrentState == mMgh.mViewportDraggingState,
                         state);
-                check(!mMgh.mViewportDraggingState.mZoomedInBeforeDrag, state);
+                check(mMgh.mViewportDraggingState.mActivatedBeforeDrag, state);
             } break;
             case STATE_SHORTCUT_TRIGGERED: {
                 check(mMgh.mDetectingState.mShortcutTriggered, state);
+                check(isActivated(), state);
                 check(!isZoomed(), state);
             } break;
             case STATE_PANNING: {
-                check(isZoomed(), state);
+                check(isActivated(), state);
                 check(mMgh.mCurrentState == mMgh.mPanningScalingState,
                         state);
                 check(!mMgh.mPanningScalingState.mScaling, state);
             } break;
             case STATE_SCALING_AND_PANNING: {
-                check(isZoomed(), state);
+                check(isActivated(), state);
                 check(mMgh.mCurrentState == mMgh.mPanningScalingState,
                         state);
                 check(mMgh.mPanningScalingState.mScaling, state);
@@ -596,13 +619,13 @@
                     tap();
                     tap();
                 } break;
-                case STATE_DRAGGING: {
-                    goFromStateIdleTo(STATE_ZOOMED_2TAPS);
+                case STATE_NON_ACTIVATED_ZOOMED_TMP: {
+                    goFromStateIdleTo(STATE_2TAPS);
                     send(downEvent());
                     fastForward1sec();
                 } break;
-                case STATE_DRAGGING_TMP: {
-                    goFromStateIdleTo(STATE_2TAPS);
+                case STATE_ACTIVATED_ZOOMED_TMP: {
+                    goFromStateIdleTo(STATE_ZOOMED_2TAPS);
                     send(downEvent());
                     fastForward1sec();
                 } break;
@@ -654,13 +677,13 @@
             case STATE_ZOOMED_2TAPS: {
                 tap();
             } break;
-            case STATE_DRAGGING: {
+            case STATE_NON_ACTIVATED_ZOOMED_TMP: {
+                send(upEvent());
+            } break;
+            case STATE_ACTIVATED_ZOOMED_TMP: {
                 send(upEvent());
                 returnToNormalFrom(STATE_ZOOMED);
             } break;
-            case STATE_DRAGGING_TMP: {
-                send(upEvent());
-            } break;
             case STATE_SHORTCUT_TRIGGERED: {
                 triggerShortcut();
             } break;
@@ -682,8 +705,12 @@
         }
     }
 
+    private boolean isActivated() {
+        return mMgh.mFullScreenMagnificationController.isActivated(DISPLAY_0);
+    }
+
     private boolean isZoomed() {
-        return mMgh.mFullScreenMagnificationController.isMagnifying(DISPLAY_0);
+        return mMgh.mFullScreenMagnificationController.getScale(DISPLAY_0) > 1.0f;
     }
 
     private int tapCount() {
@@ -770,10 +797,10 @@
 
 
     private MotionEvent pointerEvent(int action, float x, float y) {
-        return pointerEvent(action, new PointF[] {DEFAULT_POINT, new PointF(x, y)});
+        return pointerEvent(action, new PointF[] {DEFAULT_POINT, new PointF(x, y)}, 1);
     }
 
-    private MotionEvent pointerEvent(int action, PointF[] pointersPosition) {
+    private MotionEvent pointerEvent(int action, PointF[] pointersPosition, int changedIndex) {
         final MotionEvent.PointerProperties[] PointerPropertiesArray =
                 new MotionEvent.PointerProperties[pointersPosition.length];
         for (int i = 0; i < pointersPosition.length; i++) {
@@ -792,6 +819,8 @@
             pointerCoordsArray[i] = pointerCoords;
         }
 
+        action += (changedIndex << ACTION_POINTER_INDEX_SHIFT);
+
         return MotionEvent.obtain(
                 /* downTime */ mClock.now(),
                 /* eventTime */ mClock.now(),
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 2a80ce0..231b2f32 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -495,16 +495,6 @@
     }
 
     @Test
-    public void setScaleOneThroughExternalRequest_fullScreenEnabled_removeMagnificationButton()
-            throws RemoteException {
-        setMagnificationEnabled(MODE_FULLSCREEN);
-        mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, 1.0f,
-                MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, false, TEST_SERVICE_ID);
-
-        verify(mWindowMagnificationManager).removeMagnificationButton(eq(TEST_DISPLAY));
-    }
-
-    @Test
     public void onPerformScaleAction_magnifierEnabled_handleScaleChange() throws RemoteException {
         final float newScale = 4.0f;
         setMagnificationEnabled(MODE_WINDOW);
@@ -756,7 +746,7 @@
 
         mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
 
-        assertFalse(mScreenMagnificationController.isMagnifying(TEST_DISPLAY));
+        verify(mScreenMagnificationController).reset(eq(TEST_DISPLAY), eq(false));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeEArcNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeEArcNativeWrapper.java
new file mode 100644
index 0000000..6f6cf8e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeEArcNativeWrapper.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 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.hdmi;
+
+import android.hardware.tv.hdmi.earc.IEArcStatus;
+
+final class FakeEArcNativeWrapper implements HdmiEarcController.EArcNativeWrapper {
+    private static final String TAG = "FakeEArcNativeWrapper";
+
+    private boolean mIsEArcEnabled = true;
+
+    @Override
+    public boolean nativeInit() {
+        return true;
+    }
+
+    @Override
+    public void nativeSetEArcEnabled(boolean enabled) {
+        mIsEArcEnabled = enabled;
+    }
+
+    @Override
+    public boolean nativeIsEArcEnabled() {
+        return mIsEArcEnabled;
+    }
+
+    @Override
+    public void nativeSetCallback(HdmiEarcController.EarcAidlCallback callback) {
+    }
+
+    @Override
+    public byte nativeGetState(int portId) {
+        return IEArcStatus.STATUS_IDLE;
+    }
+
+    @Override
+    public byte[] nativeGetLastReportedAudioCapabilities(int portId) {
+        return new byte[] {};
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index 29eccd4..bb50a89 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -30,7 +30,7 @@
 import java.util.List;
 import java.util.Map;
 
-/** Fake {@link NativeWrapper} useful for testing. */
+/** Fake {@link NativeWrapper} for the HDMI CEC HAL, useful for testing. */
 final class FakeNativeWrapper implements NativeWrapper {
     private static final String TAG = "FakeNativeWrapper";
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 233fd6e..cb1e78b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -90,6 +90,8 @@
     private HdmiCecController mHdmiCecController;
     private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv;
     private FakeNativeWrapper mNativeWrapper;
+    private HdmiEarcController mHdmiEarcController;
+    private FakeEArcNativeWrapper mEArcNativeWrapper;
     private FakePowerManagerWrapper mPowerManager;
     private Looper mMyLooper;
     private TestLooper mTestLooper = new TestLooper();
@@ -185,6 +187,10 @@
         mHdmiCecController = HdmiCecController.createWithNativeWrapper(
                 mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
         mHdmiControlService.setCecController(mHdmiCecController);
+        mEArcNativeWrapper = new FakeEArcNativeWrapper();
+        mHdmiEarcController = HdmiEarcController.createWithNativeWrapper(
+                mHdmiControlService, mEArcNativeWrapper);
+        mHdmiControlService.setEarcController(mHdmiEarcController);
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
         hdmiPortInfos[0] =
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 6c77405..cd6dfbf 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -87,6 +87,8 @@
     private MockAudioSystemDevice mAudioSystemDeviceSpy;
     private MockPlaybackDevice mPlaybackDeviceSpy;
     private FakeNativeWrapper mNativeWrapper;
+    private HdmiEarcController mHdmiEarcController;
+    private FakeEArcNativeWrapper mEArcNativeWrapper;
     private FakePowerManagerWrapper mPowerManager;
     private Looper mMyLooper;
     private TestLooper mTestLooper = new TestLooper();
@@ -126,6 +128,10 @@
         mHdmiCecController = HdmiCecController.createWithNativeWrapper(
                 mHdmiControlServiceSpy, mNativeWrapper, mHdmiControlServiceSpy.getAtomWriter());
         mHdmiControlServiceSpy.setCecController(mHdmiCecController);
+        mEArcNativeWrapper = new FakeEArcNativeWrapper();
+        mHdmiEarcController = HdmiEarcController.createWithNativeWrapper(
+                mHdmiControlServiceSpy, mEArcNativeWrapper);
+        mHdmiControlServiceSpy.setEarcController(mHdmiEarcController);
         mHdmiControlServiceSpy.setHdmiMhlController(HdmiMhlControllerStub.create(
                 mHdmiControlServiceSpy));
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java
index c79e219..4ac23f8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java
@@ -68,6 +68,8 @@
     private HdmiCecController mHdmiCecController;
     private HdmiEarcLocalDevice mHdmiEarcLocalDeviceTx;
     private FakeNativeWrapper mNativeWrapper;
+    private HdmiEarcController mHdmiEarcController;
+    private FakeEArcNativeWrapper mEArcNativeWrapper;
     private FakePowerManagerWrapper mPowerManager;
     private byte[] mEarcCapabilities = new byte[]{
             0x01, 0x01, 0x1a, 0x35, 0x0f, 0x7f, 0x07, 0x15, 0x07, 0x50, 0x3d, 0x1f, (byte) 0xc0,
@@ -126,6 +128,10 @@
         mHdmiCecController = HdmiCecController.createWithNativeWrapper(
                 mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
         mHdmiControlService.setCecController(mHdmiCecController);
+        mEArcNativeWrapper = new FakeEArcNativeWrapper();
+        mHdmiEarcController = HdmiEarcController.createWithNativeWrapper(
+                mHdmiControlService, mEArcNativeWrapper);
+        mHdmiControlService.setEarcController(mHdmiEarcController);
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         mHdmiControlService.initService();
         mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
index b5dad94..1d23e12 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
@@ -28,7 +28,8 @@
 import android.platform.test.annotations.Presubmit
 import android.view.InputDevice
 import androidx.test.core.app.ApplicationProvider
-import com.android.server.input.KeyboardBacklightController.BRIGHTNESS_LEVELS
+import com.android.server.input.KeyboardBacklightController.BRIGHTNESS_VALUE_FOR_LEVEL
+import com.android.server.input.KeyboardBacklightController.USER_INACTIVITY_THRESHOLD_MILLIS
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
@@ -78,6 +79,7 @@
         const val DEVICE_ID = 1
         const val LIGHT_ID = 2
         const val SECOND_LIGHT_ID = 3
+        const val MAX_BRIGHTNESS = 255
     }
 
     @get:Rule
@@ -118,10 +120,6 @@
             val args = it.arguments
             lightColorMap.put(args[1] as Int, args[2] as Int)
         }
-        `when`(native.getLightColor(anyInt(), anyInt())).then {
-            val args = it.arguments
-            lightColorMap.getOrDefault(args[1] as Int, -1)
-        }
         lightColorMap.clear()
     }
 
@@ -137,21 +135,17 @@
         `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
         `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
         keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-        // Initially backlight is at min
-        lightColorMap[LIGHT_ID] = Color.argb(BRIGHTNESS_LEVELS.first(), 0, 0, 0)
 
-        val brightnessLevelsArray = BRIGHTNESS_LEVELS.toTypedArray()
-        for (level in 1 until brightnessLevelsArray.size) {
-            keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
-            testLooper.dispatchNext()
+        for (level in 1 until BRIGHTNESS_VALUE_FOR_LEVEL.size) {
+            incrementKeyboardBacklight(DEVICE_ID)
             assertEquals(
                 "Light value for level $level mismatched",
-                Color.argb(brightnessLevelsArray[level], 0, 0, 0),
+                Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
                 lightColorMap[LIGHT_ID]
             )
             assertEquals(
                 "Light value for level $level must be correctly stored in the datastore",
-                brightnessLevelsArray[level],
+                BRIGHTNESS_VALUE_FOR_LEVEL[level],
                 dataStore.getKeyboardBacklightBrightness(
                     keyboardWithBacklight.descriptor,
                     LIGHT_ID
@@ -159,72 +153,49 @@
             )
         }
 
-        for (level in brightnessLevelsArray.size - 2 downTo 0) {
-            keyboardBacklightController.decrementKeyboardBacklight(DEVICE_ID)
-            testLooper.dispatchNext()
-            assertEquals(
-                "Light value for level $level mismatched",
-                Color.argb(brightnessLevelsArray[level], 0, 0, 0),
-                lightColorMap[LIGHT_ID]
-            )
-            assertEquals(
-                "Light value for level $level must be correctly stored in the datastore",
-                brightnessLevelsArray[level],
-                dataStore.getKeyboardBacklightBrightness(
-                    keyboardWithBacklight.descriptor,
-                    LIGHT_ID
-                ).asInt
-            )
-        }
-    }
-
-    @Test
-    fun testKeyboardBacklightIncrementAboveMaxLevel() {
-        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-        // Initially backlight is at max
-        lightColorMap[LIGHT_ID] = Color.argb(BRIGHTNESS_LEVELS.last(), 0, 0, 0)
-
-        keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
-        testLooper.dispatchNext()
+        // Increment above max level
+        incrementKeyboardBacklight(DEVICE_ID)
         assertEquals(
             "Light value for max level mismatched",
-            Color.argb(BRIGHTNESS_LEVELS.last(), 0, 0, 0),
+            Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
             lightColorMap[LIGHT_ID]
         )
         assertEquals(
             "Light value for max level must be correctly stored in the datastore",
-            BRIGHTNESS_LEVELS.last(),
+            MAX_BRIGHTNESS,
             dataStore.getKeyboardBacklightBrightness(
                 keyboardWithBacklight.descriptor,
                 LIGHT_ID
             ).asInt
         )
-    }
 
-    @Test
-    fun testKeyboardBacklightDecrementBelowMin() {
-        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-        // Initially backlight is at min
-        lightColorMap[LIGHT_ID] = Color.argb(BRIGHTNESS_LEVELS.first(), 0, 0, 0)
+        for (level in BRIGHTNESS_VALUE_FOR_LEVEL.size - 2 downTo 0) {
+            decrementKeyboardBacklight(DEVICE_ID)
+            assertEquals(
+                "Light value for level $level mismatched",
+                Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+            assertEquals(
+                "Light value for level $level must be correctly stored in the datastore",
+                BRIGHTNESS_VALUE_FOR_LEVEL[level],
+                dataStore.getKeyboardBacklightBrightness(
+                    keyboardWithBacklight.descriptor,
+                    LIGHT_ID
+                ).asInt
+            )
+        }
 
-        keyboardBacklightController.decrementKeyboardBacklight(DEVICE_ID)
-        testLooper.dispatchNext()
+        // Decrement below min level
+        decrementKeyboardBacklight(DEVICE_ID)
         assertEquals(
             "Light value for min level mismatched",
-            Color.argb(BRIGHTNESS_LEVELS.first(), 0, 0, 0),
+            Color.argb(0, 0, 0, 0),
             lightColorMap[LIGHT_ID]
         )
         assertEquals(
             "Light value for min level must be correctly stored in the datastore",
-            BRIGHTNESS_LEVELS.first(),
+            0,
             dataStore.getKeyboardBacklightBrightness(
                 keyboardWithBacklight.descriptor,
                 LIGHT_ID
@@ -240,7 +211,7 @@
         `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight))
         keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
 
-        keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
+        incrementKeyboardBacklight(DEVICE_ID)
         assertTrue("Non Keyboard backlights should not change", lightColorMap.isEmpty())
     }
 
@@ -258,8 +229,7 @@
         )
         keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
 
-        keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
-        testLooper.dispatchNext()
+        incrementKeyboardBacklight(DEVICE_ID)
         assertEquals("Only keyboard backlights should change", 1, lightColorMap.size)
         assertNotNull("Keyboard backlight should change", lightColorMap[LIGHT_ID])
         assertNull("Input lights should not change", lightColorMap[SECOND_LIGHT_ID])
@@ -275,14 +245,15 @@
         dataStore.setKeyboardBacklightBrightness(
             keyboardWithBacklight.descriptor,
             LIGHT_ID,
-            BRIGHTNESS_LEVELS.last()
+            MAX_BRIGHTNESS
         )
-        lightColorMap.clear()
 
         keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        keyboardBacklightController.notifyUserActivity()
+        testLooper.dispatchNext()
         assertEquals(
             "Keyboard backlight level should be restored to the level saved in the data store",
-            Color.argb(BRIGHTNESS_LEVELS.last(), 0, 0, 0),
+            Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
             lightColorMap[LIGHT_ID]
         )
     }
@@ -295,11 +266,12 @@
         dataStore.setKeyboardBacklightBrightness(
             keyboardWithBacklight.descriptor,
             LIGHT_ID,
-            BRIGHTNESS_LEVELS.last()
+            MAX_BRIGHTNESS
         )
-        lightColorMap.clear()
 
         keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        keyboardBacklightController.notifyUserActivity()
+        testLooper.dispatchNext()
         assertTrue(
             "Keyboard backlight should not be changed until its added",
             lightColorMap.isEmpty()
@@ -307,22 +279,22 @@
 
         `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
         keyboardBacklightController.onInputDeviceChanged(DEVICE_ID)
+        keyboardBacklightController.notifyUserActivity()
+        testLooper.dispatchNext()
         assertEquals(
             "Keyboard backlight level should be restored to the level saved in the data store",
-            Color.argb(BRIGHTNESS_LEVELS.last(), 0, 0, 0),
+            Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
             lightColorMap[LIGHT_ID]
         )
     }
 
     @Test
-    fun testKeyboardBacklightT_registerUnregisterListener() {
+    fun testKeyboardBacklight_registerUnregisterListener() {
         val keyboardWithBacklight = createKeyboard(DEVICE_ID)
         val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
         `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
         `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
         keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-        // Initially backlight is at min
-        lightColorMap[LIGHT_ID] = Color.argb(BRIGHTNESS_LEVELS.first(), 0, 0, 0)
 
         // Register backlight listener
         val listener = KeyboardBacklightListener()
@@ -343,8 +315,8 @@
             lastBacklightState!!.brightnessLevel
         )
         assertEquals(
-            "Backlight state maxBrightnessLevel should be " + (BRIGHTNESS_LEVELS.size - 1),
-            (BRIGHTNESS_LEVELS.size - 1),
+            "Backlight state maxBrightnessLevel should be " + (BRIGHTNESS_VALUE_FOR_LEVEL.size - 1),
+            (BRIGHTNESS_VALUE_FOR_LEVEL.size - 1),
             lastBacklightState!!.maxBrightnessLevel
         )
         assertEquals(
@@ -357,12 +329,70 @@
         keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0)
 
         lastBacklightState = null
-        keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
-        testLooper.dispatchNext()
+        incrementKeyboardBacklight(DEVICE_ID)
 
         assertNull("Listener should not receive any updates", lastBacklightState)
     }
 
+    @Test
+    fun testKeyboardBacklight_userActivity() {
+        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+        dataStore.setKeyboardBacklightBrightness(
+            keyboardWithBacklight.descriptor,
+            LIGHT_ID,
+            MAX_BRIGHTNESS
+        )
+
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        keyboardBacklightController.notifyUserActivity()
+        testLooper.dispatchNext()
+        assertEquals(
+            "Keyboard backlight level should be restored to the level saved in the data store",
+            Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
+            lightColorMap[LIGHT_ID]
+        )
+
+        testLooper.moveTimeForward(USER_INACTIVITY_THRESHOLD_MILLIS + 1000)
+        testLooper.dispatchNext()
+        assertEquals(
+            "Keyboard backlight level should be turned off after inactivity",
+            0,
+            lightColorMap[LIGHT_ID]
+        )
+    }
+
+    @Test
+    fun testKeyboardBacklight_displayOnOff() {
+        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+        dataStore.setKeyboardBacklightBrightness(
+            keyboardWithBacklight.descriptor,
+            LIGHT_ID,
+            MAX_BRIGHTNESS
+        )
+
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        keyboardBacklightController.handleInteractiveStateChange(true /* isDisplayOn */)
+        assertEquals(
+            "Keyboard backlight level should be restored to the level saved in the data " +
+                    "store when display turned on",
+            Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
+            lightColorMap[LIGHT_ID]
+        )
+
+        keyboardBacklightController.handleInteractiveStateChange(false /* isDisplayOn */)
+        assertEquals(
+            "Keyboard backlight level should be turned off after display is turned off",
+            0,
+            lightColorMap[LIGHT_ID]
+        )
+    }
+
     inner class KeyboardBacklightListener : IKeyboardBacklightListener.Stub() {
         override fun onBrightnessChanged(
             deviceId: Int,
@@ -378,6 +408,18 @@
         }
     }
 
+    private fun incrementKeyboardBacklight(deviceId: Int) {
+        keyboardBacklightController.incrementKeyboardBacklight(deviceId)
+        keyboardBacklightController.notifyUserActivity()
+        testLooper.dispatchAll()
+    }
+
+    private fun decrementKeyboardBacklight(deviceId: Int) {
+        keyboardBacklightController.decrementKeyboardBacklight(deviceId)
+        keyboardBacklightController.notifyUserActivity()
+        testLooper.dispatchAll()
+    }
+
     class KeyboardBacklightState(
         val deviceId: Int,
         val brightnessLevel: Int,
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
new file mode 100644
index 0000000..34540c3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -0,0 +1,820 @@
+/*
+ * Copyright (C) 2023 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.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.hardware.input.IInputManager
+import android.hardware.input.InputManager
+import android.hardware.input.KeyboardLayout
+import android.icu.lang.UScript
+import android.icu.util.ULocale
+import android.os.Bundle
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.provider.Settings
+import android.view.InputDevice
+import android.view.inputmethod.InputMethodInfo
+import android.view.inputmethod.InputMethodSubtype
+import androidx.test.core.R
+import androidx.test.core.app.ApplicationProvider
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
+import java.util.Locale
+
+private fun createKeyboard(
+    deviceId: Int,
+    vendorId: Int,
+    productId: Int,
+    languageTag: String,
+    layoutType: String
+): InputDevice =
+    InputDevice.Builder()
+        .setId(deviceId)
+        .setName("Device $deviceId")
+        .setDescriptor("descriptor $deviceId")
+        .setSources(InputDevice.SOURCE_KEYBOARD)
+        .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
+        .setExternal(true)
+        .setVendorId(vendorId)
+        .setProductId(productId)
+        .setKeyboardLanguageTag(languageTag)
+        .setKeyboardLayoutType(layoutType)
+        .build()
+
+/**
+ * Tests for {@link Default UI} and {@link New UI}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:KeyboardLayoutManagerTests
+ */
+@Presubmit
+class KeyboardLayoutManagerTests {
+    companion object {
+        const val DEVICE_ID = 1
+        const val VENDOR_SPECIFIC_DEVICE_ID = 2
+        const val ENGLISH_DVORAK_DEVICE_ID = 3
+        const val USER_ID = 4
+        const val IME_ID = "ime_id"
+        const val PACKAGE_NAME = "KeyboardLayoutManagerTests"
+        const val RECEIVER_NAME = "DummyReceiver"
+        private const val ENGLISH_US_LAYOUT_NAME = "keyboard_layout_english_us"
+        private const val ENGLISH_UK_LAYOUT_NAME = "keyboard_layout_english_uk"
+        private const val VENDOR_SPECIFIC_LAYOUT_NAME = "keyboard_layout_vendorId:1,productId:1"
+    }
+
+    private val ENGLISH_US_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_US_LAYOUT_NAME)
+    private val ENGLISH_UK_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_UK_LAYOUT_NAME)
+    private val VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR =
+        createLayoutDescriptor(VENDOR_SPECIFIC_LAYOUT_NAME)
+
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    @Mock
+    private lateinit var iInputManager: IInputManager
+
+    @Mock
+    private lateinit var native: NativeInputManagerService
+
+    @Mock
+    private lateinit var packageManager: PackageManager
+    private lateinit var keyboardLayoutManager: KeyboardLayoutManager
+
+    private lateinit var imeInfo: InputMethodInfo
+    private var nextImeSubtypeId = 0
+    private lateinit var context: Context
+    private lateinit var dataStore: PersistentDataStore
+    private lateinit var testLooper: TestLooper
+
+    // Devices
+    private lateinit var keyboardDevice: InputDevice
+    private lateinit var vendorSpecificKeyboardDevice: InputDevice
+    private lateinit var englishDvorakKeyboardDevice: InputDevice
+
+    @Before
+    fun setup() {
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
+            override fun openRead(): InputStream? {
+                throw FileNotFoundException()
+            }
+
+            override fun startWrite(): FileOutputStream? {
+                throw IOException()
+            }
+
+            override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
+        })
+        testLooper = TestLooper()
+        keyboardLayoutManager = KeyboardLayoutManager(context, native, dataStore, testLooper.looper)
+        setupInputDevices()
+        setupBroadcastReceiver()
+        setupIme()
+    }
+
+    private fun setupInputDevices() {
+        val inputManager = InputManager.resetInstance(iInputManager)
+        Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+            .thenReturn(inputManager)
+
+        keyboardDevice = createKeyboard(DEVICE_ID, 0, 0, "", "")
+        vendorSpecificKeyboardDevice = createKeyboard(VENDOR_SPECIFIC_DEVICE_ID, 1, 1, "", "")
+        englishDvorakKeyboardDevice =
+            createKeyboard(ENGLISH_DVORAK_DEVICE_ID, 0, 0, "en", "dvorak")
+        Mockito.`when`(iInputManager.inputDeviceIds)
+            .thenReturn(intArrayOf(DEVICE_ID, VENDOR_SPECIFIC_DEVICE_ID, ENGLISH_DVORAK_DEVICE_ID))
+        Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
+        Mockito.`when`(iInputManager.getInputDevice(VENDOR_SPECIFIC_DEVICE_ID))
+            .thenReturn(vendorSpecificKeyboardDevice)
+        Mockito.`when`(iInputManager.getInputDevice(ENGLISH_DVORAK_DEVICE_ID))
+            .thenReturn(englishDvorakKeyboardDevice)
+    }
+
+    private fun setupBroadcastReceiver() {
+        Mockito.`when`(context.packageManager).thenReturn(packageManager)
+
+        val info = createMockReceiver()
+        Mockito.`when`(packageManager.queryBroadcastReceivers(Mockito.any(), Mockito.anyInt()))
+            .thenReturn(listOf(info))
+        Mockito.`when`(packageManager.getReceiverInfo(Mockito.any(), Mockito.anyInt()))
+            .thenReturn(info.activityInfo)
+
+        val resources = context.resources
+        Mockito.`when`(
+            packageManager.getResourcesForApplication(
+                Mockito.any(
+                    ApplicationInfo::class.java
+                )
+            )
+        ).thenReturn(resources)
+    }
+
+    private fun setupIme() {
+        imeInfo = InputMethodInfo(PACKAGE_NAME, RECEIVER_NAME, "", "", 0)
+    }
+
+    @Test
+    fun testDefaultUi_getKeyboardLayouts() {
+        NewSettingsApiFlag(false).use {
+            val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
+            assertNotEquals(
+                "Default UI: Keyboard layout API should not return empty array",
+                0,
+                keyboardLayouts.size
+            )
+            assertTrue(
+                "Default UI: Keyboard layout API should provide English(US) layout",
+                hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getKeyboardLayouts() {
+        NewSettingsApiFlag(true).use {
+            val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
+            assertNotEquals(
+                "New UI: Keyboard layout API should not return empty array",
+                0,
+                keyboardLayouts.size
+            )
+            assertTrue(
+                "New UI: Keyboard layout API should provide English(US) layout",
+                hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getKeyboardLayoutsForInputDevice() {
+        NewSettingsApiFlag(false).use {
+            val keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutsForInputDevice(keyboardDevice.identifier)
+            assertNotEquals(
+                "Default UI: getKeyboardLayoutsForInputDevice API should not return empty array",
+                0,
+                keyboardLayouts.size
+            )
+            assertTrue(
+                "Default UI: getKeyboardLayoutsForInputDevice API should provide English(US) " +
+                        "layout",
+                hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+            )
+
+            val vendorSpecificKeyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutsForInputDevice(
+                    vendorSpecificKeyboardDevice.identifier
+                )
+            assertEquals(
+                "Default UI: getKeyboardLayoutsForInputDevice API should return only vendor " +
+                        "specific layout",
+                1,
+                vendorSpecificKeyboardLayouts.size
+            )
+            assertEquals(
+                "Default UI: getKeyboardLayoutsForInputDevice API should return vendor specific " +
+                        "layout",
+                VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR,
+                vendorSpecificKeyboardLayouts[0].descriptor
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getKeyboardLayoutsForInputDevice() {
+        NewSettingsApiFlag(true).use {
+            val keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutsForInputDevice(keyboardDevice.identifier)
+            assertEquals(
+                "New UI: getKeyboardLayoutsForInputDevice API should always return empty array",
+                0,
+                keyboardLayouts.size
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getSetCurrentKeyboardLayoutForInputDevice() {
+        NewSettingsApiFlag(false).use {
+            assertNull(
+                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null if " +
+                        "nothing was set",
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+
+            keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier,
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            val keyboardLayout =
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            assertEquals(
+                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return the set " +
+                        "layout",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayout
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getSetCurrentKeyboardLayoutForInputDevice() {
+        NewSettingsApiFlag(true).use {
+            keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier,
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertNull(
+                "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null " +
+                        "even after setCurrentKeyboardLayoutForInputDevice",
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getEnabledKeyboardLayoutsForInputDevice() {
+        NewSettingsApiFlag(false).use {
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+
+            val keyboardLayouts =
+                keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
+                    keyboardDevice.identifier
+                )
+            assertEquals(
+                "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return added " +
+                        "layout",
+                1,
+                keyboardLayouts.size
+            )
+            assertEquals(
+                "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return " +
+                        "English(US) layout",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayouts[0]
+            )
+            assertEquals(
+                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
+                        "English(US) layout (Auto select the first enabled layout)",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+
+            keyboardLayoutManager.removeKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertEquals(
+                "Default UI: getKeyboardLayoutsForInputDevice API should return 0 layouts",
+                0,
+                keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
+                    keyboardDevice.identifier
+                ).size
+            )
+            assertNull(
+                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null after " +
+                        "the enabled layout is removed",
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getEnabledKeyboardLayoutsForInputDevice() {
+        NewSettingsApiFlag(true).use {
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+
+            assertEquals(
+                "New UI: getEnabledKeyboardLayoutsForInputDevice API should return always return " +
+                        "an empty array",
+                0,
+                keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
+                    keyboardDevice.identifier
+                ).size
+            )
+            assertNull(
+                "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null",
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_switchKeyboardLayout() {
+        NewSettingsApiFlag(false).use {
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+            assertEquals(
+                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
+                        "English(US) layout",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+
+            keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1)
+
+            // Throws null pointer because trying to show toast using TestLooper
+            assertThrows(NullPointerException::class.java) { testLooper.dispatchAll() }
+            assertEquals("Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
+                    "English(UK) layout",
+                ENGLISH_UK_LAYOUT_DESCRIPTOR,
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_switchKeyboardLayout() {
+        NewSettingsApiFlag(true).use {
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+
+            keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1)
+            testLooper.dispatchAll()
+
+            assertNull("New UI: getCurrentKeyboardLayoutForInputDevice API should always return " +
+                    "null",
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getKeyboardLayout() {
+        NewSettingsApiFlag(false).use {
+            val keyboardLayout =
+                keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
+            assertEquals("Default UI: getKeyboardLayout API should return correct Layout from " +
+                    "available layouts",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayout!!.descriptor
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getKeyboardLayout() {
+        NewSettingsApiFlag(true).use {
+            val keyboardLayout =
+                keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
+            assertEquals("New UI: getKeyboardLayout API should return correct Layout from " +
+                    "available layouts",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayout!!.descriptor
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getSetKeyboardLayoutForInputDevice_WithImeInfo() {
+        NewSettingsApiFlag(false).use {
+            val imeSubtype = createImeSubtype()
+            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+                ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+            val keyboardLayout =
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
+                )
+            assertNull(
+                "Default UI: getKeyboardLayoutForInputDevice API should always return null",
+                keyboardLayout
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getSetKeyboardLayoutForInputDevice_withImeInfo() {
+        NewSettingsApiFlag(true).use {
+            val imeSubtype = createImeSubtype()
+
+            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+                ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+            assertEquals(
+                "New UI: getKeyboardLayoutForInputDevice API should return the set layout",
+                ENGLISH_UK_LAYOUT_DESCRIPTOR,
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
+                )
+            )
+
+            // This should replace previously set layout
+            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertEquals(
+                "New UI: getKeyboardLayoutForInputDevice API should return the last set layout",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getKeyboardLayoutListForInputDevice() {
+        NewSettingsApiFlag(false).use {
+            val keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtype()
+                )
+            assertEquals("Default UI: getKeyboardLayoutListForInputDevice API should always " +
+                    "return empty array",
+                0,
+                keyboardLayouts.size
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getKeyboardLayoutListForInputDevice() {
+        NewSettingsApiFlag(true).use {
+            // Check Layouts for "hi-Latn". It should return all 'Latn' keyboard layouts
+            var keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtypeForLanguageTag("hi-Latn")
+                )
+            assertNotEquals(
+                "New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
+                        "supported layouts with matching script code",
+                0,
+                keyboardLayouts.size
+            )
+
+            val englishScripts = UScript.getCode(Locale.forLanguageTag("hi-Latn"))
+            for (kl in keyboardLayouts) {
+                var isCompatible = false
+                for (i in 0 until kl.locales.size()) {
+                    val locale: Locale = kl.locales.get(i) ?: continue
+                    val scripts = UScript.getCode(locale)
+                    if (scripts != null && areScriptsCompatible(scripts, englishScripts)) {
+                        isCompatible = true
+                        break
+                    }
+                }
+                assertTrue(
+                    "New UI: getKeyboardLayoutListForInputDevice API should only return " +
+                            "compatible layouts but found " + kl.descriptor,
+                    isCompatible
+                )
+            }
+
+            // Check Layouts for "hi" which by default uses 'Deva' script.
+            keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtypeForLanguageTag("hi")
+                )
+            assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return empty " +
+                    "list if no supported layouts available",
+                0,
+                keyboardLayouts.size
+            )
+
+            // If user manually selected some layout, always provide it in the layout list
+            val imeSubtype = createImeSubtypeForLanguageTag("hi")
+            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    imeSubtype
+                )
+            assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return user " +
+                    "selected layout even if the script is incompatible with IME",
+                    1,
+                keyboardLayouts.size
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTag() {
+        NewSettingsApiFlag(true).use {
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTag("en-US"),
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTag("en-GB"),
+                ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTag("de"),
+                createLayoutDescriptor("keyboard_layout_german")
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTag("fr-FR"),
+                createLayoutDescriptor("keyboard_layout_french")
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTag("ru"),
+                createLayoutDescriptor("keyboard_layout_russian")
+            )
+            assertNull(
+                "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " +
+                        "layout available",
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtypeForLanguageTag("it")
+                )
+            )
+            assertNull(
+                "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " +
+                        "layout for script code is available",
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtypeForLanguageTag("en-Deva")
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTagAndLayoutType() {
+        NewSettingsApiFlag(true).use {
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("en-US", "qwerty"),
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("en-US", "dvorak"),
+                createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+            )
+            // Try to match layout type even if country doesn't match
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("en-GB", "dvorak"),
+                createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+            )
+            // Choose layout based on layout type priority, if layout type is not provided by IME
+            // (Qwerty > Dvorak > Extended)
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("en-US", ""),
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("en-GB", "qwerty"),
+                ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"),
+                createLayoutDescriptor("keyboard_layout_german")
+            )
+            // Wrong layout type should match with language if provided layout type not available
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"),
+                createLayoutDescriptor("keyboard_layout_german")
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("fr-FR", "azerty"),
+                createLayoutDescriptor("keyboard_layout_french")
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("ru", "qwerty"),
+                createLayoutDescriptor("keyboard_layout_russian_qwerty")
+            )
+            // If layout type is empty then prioritize KCM with empty layout type
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
+                createLayoutDescriptor("keyboard_layout_russian")
+            )
+            assertNull("New UI: getDefaultKeyboardLayoutForInputDevice should return null when " +
+                    "no layout for script code is available",
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "")
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withHwLanguageTagAndLayoutType() {
+        NewSettingsApiFlag(true).use {
+            // Should return English dvorak even if IME current layout is qwerty, since HW says the
+            // keyboard is a Dvorak keyboard
+            assertCorrectLayout(
+                englishDvorakKeyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("en", "qwerty"),
+                createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+            )
+
+            // Fallback to IME information if the HW provided layout script is incompatible with the
+            // provided IME subtype
+            assertCorrectLayout(
+                englishDvorakKeyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
+                createLayoutDescriptor("keyboard_layout_russian")
+            )
+        }
+    }
+
+    private fun assertCorrectLayout(
+        device: InputDevice,
+        imeSubtype: InputMethodSubtype,
+        expectedLayout: String
+    ) {
+        assertEquals(
+            "New UI: getDefaultKeyboardLayoutForInputDevice should return $expectedLayout",
+            expectedLayout,
+            keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                device.identifier, USER_ID, imeInfo, imeSubtype
+            )
+        )
+    }
+
+    private fun createImeSubtype(): InputMethodSubtype =
+        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++).build()
+
+    private fun createImeSubtypeForLanguageTag(languageTag: String): InputMethodSubtype =
+        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++)
+            .setLanguageTag(languageTag).build()
+
+    private fun createImeSubtypeForLanguageTagAndLayoutType(
+        languageTag: String,
+        layoutType: String
+    ): InputMethodSubtype =
+        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++)
+            .setPhysicalKeyboardHint(ULocale.forLanguageTag(languageTag), layoutType).build()
+
+    private fun hasLayout(layoutList: Array<KeyboardLayout>, layoutDesc: String): Boolean {
+        for (kl in layoutList) {
+            if (kl.descriptor == layoutDesc) {
+                return true
+            }
+        }
+        return false
+    }
+
+    private fun createLayoutDescriptor(keyboardName: String): String =
+        "$PACKAGE_NAME/$RECEIVER_NAME/$keyboardName"
+
+    private fun areScriptsCompatible(scriptList1: IntArray, scriptList2: IntArray): Boolean {
+        for (s1 in scriptList1) {
+            for (s2 in scriptList2) {
+                if (s1 == s2) return true
+            }
+        }
+        return false
+    }
+
+    private fun createMockReceiver(): ResolveInfo {
+        val info = ResolveInfo()
+        info.activityInfo = ActivityInfo()
+        info.activityInfo.packageName = PACKAGE_NAME
+        info.activityInfo.name = RECEIVER_NAME
+        info.activityInfo.applicationInfo = ApplicationInfo()
+        info.activityInfo.metaData = Bundle()
+        info.activityInfo.metaData.putInt(
+            InputManager.META_DATA_KEYBOARD_LAYOUTS,
+            R.xml.keyboard_layouts
+        )
+        info.serviceInfo = ServiceInfo()
+        info.serviceInfo.packageName = PACKAGE_NAME
+        info.serviceInfo.name = RECEIVER_NAME
+        return info
+    }
+
+    private inner class NewSettingsApiFlag constructor(enabled: Boolean) : AutoCloseable {
+        init {
+            Settings.Global.putString(
+                context.contentResolver,
+                "settings_new_keyboard_ui", enabled.toString()
+            )
+        }
+
+        override fun close() {
+            Settings.Global.putString(
+                context.contentResolver,
+                "settings_new_keyboard_ui",
+                ""
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
index f5029ec..f2e03aa 100644
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
@@ -165,6 +165,7 @@
                 awaitJobStart(DEFAULT_WAIT_TIMEOUT));
     }
 
+    @FlakyTest
     @Test
     public void testFeatureFlag() throws Exception {
         Settings.Global.putInt(mContext.getContentResolver(),
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 41e3a08..60a033f 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -423,6 +423,21 @@
         checkPasswordHistoryLength(userId, 3);
     }
 
+    @Test(expected=NullPointerException.class)
+    public void testSetBooleanRejectsNullKey() {
+        mService.setBoolean(null, false, 0);
+    }
+
+    @Test(expected=NullPointerException.class)
+    public void testSetLongRejectsNullKey() {
+        mService.setLong(null, 0, 0);
+    }
+
+    @Test(expected=NullPointerException.class)
+    public void testSetStringRejectsNullKey() {
+        mService.setString(null, "value", 0);
+    }
+
     private void checkPasswordHistoryLength(int userId, int expectedLen) {
         String history = mService.getString(LockPatternUtils.PASSWORD_HISTORY_KEY, "", userId);
         String[] hashes = TextUtils.split(history, LockPatternUtils.PASSWORD_HISTORY_DELIMITER);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 03d5b17..05208441e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -266,6 +266,20 @@
     }
 
     @Test
+    public void testNullKey() {
+        mStorage.setString(null, "value", 0);
+
+        // Verify that this doesn't throw an exception.
+        assertEquals("value", mStorage.readKeyValue(null, null, 0));
+
+        // The read that happens as part of prefetchUser shouldn't throw an exception either.
+        mStorage.clearCache();
+        mStorage.prefetchUser(0);
+
+        assertEquals("value", mStorage.readKeyValue(null, null, 0));
+    }
+
+    @Test
     public void testRemoveUser() {
         mStorage.writeKeyValue("key", "value", 0);
         mStorage.writeKeyValue("key", "value", 1);
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
index d80aa57..ccf530f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -539,7 +539,8 @@
             NoSuchFieldException, PackageManager.NameNotFoundException {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
         InstallSourceInfo installSourceInfo = new InstallSourceInfo(
-                /* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
+                /* initiatingPackageName = */ INSTALLER_NAME_1,
+                /* initiatingPackageSigningInfo = */ null,
                 /* originatingPackageName = */ null,
                 /* installingPackageName = */ INSTALLER_NAME_1);
         assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
@@ -575,7 +576,8 @@
             NoSuchFieldException, PackageManager.NameNotFoundException {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
         InstallSourceInfo installSourceInfo = new InstallSourceInfo(
-                /* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
+                /* initiatingPackageName = */ INSTALLER_NAME_1,
+                /* initiatingPackageSigningInfo = */ null,
                 /* originatingPackageName = */ null,
                 /* installingPackageName = */ INSTALLER_NAME_1);
         assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
@@ -619,7 +621,8 @@
             NoSuchFieldException, PackageManager.NameNotFoundException {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
         InstallSourceInfo installSourceInfo = new InstallSourceInfo(
-                /* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
+                /* initiatingPackageName = */ INSTALLER_NAME_1,
+                /* initiatingPackageSigningInfo = */ null,
                 /* originatingPackageName = */ null,
                 /* installingPackageName = */ INSTALLER_NAME_1);
         assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
@@ -667,7 +670,8 @@
             NoSuchFieldException, PackageManager.NameNotFoundException {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
         InstallSourceInfo installSourceInfo = new InstallSourceInfo(
-                /* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
+                /* initiatingPackageName = */ INSTALLER_NAME_1,
+                /* initiatingPackageSigningInfo = */ null,
                 /* originatingPackageName = */ null,
                 /* installingPackageName = */ INSTALLER_NAME_1);
         assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
@@ -711,7 +715,52 @@
         assertEquals(1, packages.size());
         assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
     }
+    @Test
+    public void testHandleUsageEvent_packageAddedThroughAdb() throws
+            NoSuchFieldException, PackageManager.NameNotFoundException {
+        assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+        InstallSourceInfo installSourceInfo = new InstallSourceInfo(
+                /* initiatingPackageName = */ null, //currently ADB installer sets field to null
+                /* initiatingPackageSigningInfo = */ null,
+                /* originatingPackageName = */ null,
+                /* installingPackageName = */ INSTALLER_NAME_1);
+        // b/265203007
+        when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
+        ApplicationInfo appInfo = mock(ApplicationInfo.class);
 
+        when(mPackageManager.getApplicationInfoAsUser(
+                eq(PACKAGE_NAME_1),
+                any(),
+                anyInt())
+        ).thenReturn(appInfo);
+
+        long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
+                - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+        FieldSetter.setField(appInfo,
+                ApplicationInfo.class.getDeclaredField("createTimestamp"),
+                createTimestamp);
+
+        int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
+        assertEquals(USER_ID_1, UserHandle.getUserId(uid));
+
+        // The following  usage events generation is the same as
+        // testHandleUsageEvent_packageAddedOutsideTimeFrame2 test. The only difference is that
+        // for ADB installs the initiatingPackageName is null, despite being detected as a
+        // background install. Since we do not want to treat side-loaded apps as background install
+        // getBackgroundInstalledPackages() is expected to return null
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+                anyString(), anyString(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+        generateUsageEvent(Event.ACTIVITY_STOPPED,
+                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
+
+        mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
+        mTestLooper.dispatchAll();
+
+        var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+        assertNull(packages);
+    }
     @Test
     public void testPackageRemoved() {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index 2ebe215..cff4cc7 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.server.power.stats.BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL;
 import static com.android.server.power.stats.BatteryStatsImpl.ExternalStatsSync.UPDATE_BT;
+import static com.android.server.power.stats.BatteryStatsImpl.ExternalStatsSync.UPDATE_CAMERA;
 import static com.android.server.power.stats.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU;
 import static com.android.server.power.stats.BatteryStatsImpl.ExternalStatsSync.UPDATE_DISPLAY;
 import static com.android.server.power.stats.BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO;
@@ -104,6 +105,11 @@
         tempAllIds.add(gnssId);
         mPowerStatsInternal.incrementEnergyConsumption(gnssId, 787878);
 
+        final int cameraId =
+                mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.CAMERA, 0, "camera");
+        tempAllIds.add(cameraId);
+        mPowerStatsInternal.incrementEnergyConsumption(cameraId, 901234);
+
         final int mobileRadioId = mPowerStatsInternal.addEnergyConsumer(
                 EnergyConsumerType.MOBILE_RADIO, 0, "mobile_radio");
         tempAllIds.add(mobileRadioId);
@@ -171,6 +177,12 @@
         Arrays.sort(receivedCpuIds);
         assertArrayEquals(cpuClusterIds, receivedCpuIds);
 
+        final EnergyConsumerResult[] cameraResults =
+                mBatteryExternalStatsWorker.getEnergyConsumersLocked(UPDATE_CAMERA).getNow(null);
+        // Results should only have the camera energy consumer
+        assertEquals(1, cameraResults.length);
+        assertEquals(cameraId, cameraResults[0].id);
+
         final EnergyConsumerResult[] allResults =
                 mBatteryExternalStatsWorker.getEnergyConsumersLocked(UPDATE_ALL).getNow(null);
         // All energy consumer results should be available
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
index e4ab21b..5fce32f0 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
@@ -36,41 +36,113 @@
 public class CameraPowerCalculatorTest {
     private static final double PRECISION = 0.00001;
 
-    private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP1_UID = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP2_UID = Process.FIRST_APPLICATION_UID + 43;
 
     @Rule
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
-            .setAveragePower(PowerProfile.POWER_CAMERA, 360.0);
+            .setAveragePower(PowerProfile.POWER_CAMERA, 360.0)
+            .initMeasuredEnergyStatsLocked();
 
     @Test
     public void testTimerBasedModel() {
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
-        stats.noteCameraOnLocked(APP_UID, 1000, 1000);
-        stats.noteCameraOffLocked(APP_UID, 2000, 2000);
+        synchronized (stats) { // To keep the GuardedBy check happy
+            stats.noteCameraOnLocked(APP1_UID, 1000, 1000);
+            stats.noteCameraOffLocked(APP1_UID, 2000, 2000);
+            stats.noteCameraOnLocked(APP2_UID, 3000, 3000);
+            stats.noteCameraOffLocked(APP2_UID, 5000, 5000);
+        }
+
+        CameraPowerCalculator calculator =
+                new CameraPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
+
+        UidBatteryConsumer app1Consumer = mStatsRule.getUidBatteryConsumer(APP1_UID);
+        assertThat(app1Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(1000);
+        assertThat(app1Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isWithin(PRECISION).of(0.1);
+        assertThat(app1Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        UidBatteryConsumer app2Consumer = mStatsRule.getUidBatteryConsumer(APP2_UID);
+        assertThat(app2Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(2000);
+        assertThat(app2Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isWithin(PRECISION).of(0.2);
+        assertThat(app2Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceBatteryConsumer
+                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(3000);
+        assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isWithin(PRECISION).of(0.3);
+        assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsBatteryConsumer
+                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(3000);
+        assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isWithin(PRECISION).of(0.3);
+        assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+    }
+
+    @Test
+    public void testEnergyConsumptionBasedModel() {
+        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+        synchronized (stats) { // To keep the GuardedBy check happy
+            stats.noteCameraOnLocked(APP1_UID, 1000, 1000);
+            stats.noteCameraOffLocked(APP1_UID, 2000, 2000);
+            stats.updateCameraEnergyConsumerStatsLocked(720_000, 2100); // 0.72C == 0.2mAh
+            stats.noteCameraOnLocked(APP2_UID, 3000, 3000);
+            stats.noteCameraOffLocked(APP2_UID, 5000, 5000);
+            stats.updateCameraEnergyConsumerStatsLocked(1_080_000, 5100); // 0.3mAh
+        }
 
         CameraPowerCalculator calculator =
                 new CameraPowerCalculator(mStatsRule.getPowerProfile());
 
         mStatsRule.apply(calculator);
 
-        UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID);
-        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
+        UidBatteryConsumer app1Consumer = mStatsRule.getUidBatteryConsumer(APP1_UID);
+        assertThat(app1Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
                 .isEqualTo(1000);
-        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isWithin(PRECISION).of(0.1);
+        assertThat(app1Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isWithin(PRECISION).of(0.2);
+        assertThat(app1Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+
+        UidBatteryConsumer app2Consumer = mStatsRule.getUidBatteryConsumer(APP2_UID);
+        assertThat(app2Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(2000);
+        assertThat(app2Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isWithin(PRECISION).of(0.3);
+        assertThat(app2Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(deviceBatteryConsumer
                 .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isEqualTo(1000);
+                .isEqualTo(3000);
         assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isWithin(PRECISION).of(0.1);
+                .isWithin(PRECISION).of(0.5);
+        assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsBatteryConsumer
                 .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isEqualTo(1000);
+                .isEqualTo(3000);
         assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isWithin(PRECISION).of(0.1);
+                .isWithin(PRECISION).of(0.5);
+        assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java b/services/tests/servicestests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
index 558f396..28f4799 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
@@ -248,6 +248,32 @@
         assertThat(details.toString()).isEqualTo("DISPLAY=2667 HPU=3200000 GPU=0 IPU &_=0");
     }
 
+    @Test
+    public void testUpdateAndGetDelta_updatesCameraCharge() {
+        EnergyConsumer cameraConsumer =
+                createEnergyConsumer(7, 0, EnergyConsumerType.CAMERA, "CAMERA");
+        final EnergyConsumerSnapshot snapshot =
+                new EnergyConsumerSnapshot(createIdToConsumerMap(cameraConsumer));
+
+        // An initial result with only one energy consumer
+        EnergyConsumerResult[] result0 = new EnergyConsumerResult[]{
+                createEnergyConsumerResult(cameraConsumer.id, 60_000, null, null),
+        };
+        snapshot.updateAndGetDelta(result0, VOLTAGE_1);
+
+        // A subsequent result
+        EnergyConsumerResult[] result1 = new EnergyConsumerResult[]{
+                createEnergyConsumerResult(cameraConsumer.id, 90_000, null, null),
+        };
+        EnergyConsumerDeltaData delta = snapshot.updateAndGetDelta(result1, VOLTAGE_1);
+
+        // Verify that the delta between the two results is reported.
+        BatteryStats.EnergyConsumerDetails details = snapshot.getEnergyConsumerDetails(delta);
+        assertThat(details.consumers).hasLength(1);
+        long expectedDeltaUC = calculateChargeConsumedUC(60_000, VOLTAGE_1, 90_000, VOLTAGE_1);
+        assertThat(details.chargeUC[0]).isEqualTo(expectedDeltaUC);
+    }
+
     private static EnergyConsumer createEnergyConsumer(int id, int ord, byte type, String name) {
         final EnergyConsumer ec = new EnergyConsumer();
         ec.id = id;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index d54d1fe..5f8a2b5 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -839,7 +839,8 @@
                 .setName("bubblebot")
                 .build();
         RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
-        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(),
+        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0,
+                new Intent().setPackage(mContext.getPackageName()),
                 PendingIntent.FLAG_MUTABLE);
         Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
         Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
@@ -9227,7 +9228,8 @@
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         ArrayList<Notification.Action> extraAction = new ArrayList<>();
         RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
-        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(),
+        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0,
+                new Intent().setPackage(mContext.getPackageName()),
                 PendingIntent.FLAG_MUTABLE);
         Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
         Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index a76b82b..fd1ca68 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -407,7 +407,7 @@
 
     void assertShowRecentApps() {
         waitForIdle();
-        verify(mStatusBarManagerInternal).showRecentApps(anyBoolean());
+        verify(mStatusBarManagerInternal).showRecentApps(anyBoolean(), anyBoolean());
     }
 
     void assertSwitchKeyboardLayout() {
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 113f5ec..3eb7fe3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -295,6 +295,15 @@
 
         assertEquals(RESTARTING_PROCESS, mActivity.getState());
         assertNotEquals(originalOverrideBounds, mActivity.getBounds());
+
+        // Even if the state is changed (e.g. a floating activity on top is finished and make it
+        // resume), the restart procedure should recover the state and continue to kill the process.
+        mActivity.setState(RESUMED, "anyStateChange");
+        doReturn(true).when(mSupervisor).hasScheduledRestartTimeouts(mActivity);
+        mAtm.mActivityClientController.activityStopped(mActivity.token, null /* icicle */,
+                null /* persistentState */, null /* description */);
+        assertEquals(RESTARTING_PROCESS, mActivity.getState());
+        verify(mSupervisor).removeRestartTimeouts(mActivity);
     }
 
     @Test
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index b6aed2db..a480ebd 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -536,7 +536,6 @@
         private boolean mInHostModeWithNoAccessoryConnected;
         private boolean mSourcePower;
         private boolean mSinkPower;
-        private boolean mConfigured;
         private boolean mAudioAccessoryConnected;
         private boolean mAudioAccessorySupported;
         private boolean mConnectedToDataDisabledPort;
@@ -571,7 +570,12 @@
         private final UsbPermissionManager mPermissionManager;
         private NotificationManager mNotificationManager;
 
+        /**
+         * Do not debounce for the first disconnect after resetUsbGadget.
+         */
+        protected boolean mResetUsbGadgetDisableDebounce;
         protected boolean mConnected;
+        protected boolean mConfigured;
         protected long mScreenUnlockedFunctions;
         protected boolean mBootCompleted;
         protected boolean mCurrentFunctionsApplied;
@@ -716,15 +720,29 @@
                 Slog.e(TAG, "unknown state " + state);
                 return;
             }
-            if (configured == 0) removeMessages(MSG_UPDATE_STATE);
             if (connected == 1) removeMessages(MSG_FUNCTION_SWITCH_TIMEOUT);
             Message msg = Message.obtain(this, MSG_UPDATE_STATE);
             msg.arg1 = connected;
             msg.arg2 = configured;
-            // debounce disconnects to avoid problems bringing up USB tethering
-            sendMessageDelayed(msg,
+            if (DEBUG) {
+                Slog.i(TAG, "mResetUsbGadgetDisableDebounce:" + mResetUsbGadgetDisableDebounce
+                       + " connected:" + connected + "configured:" + configured);
+            }
+            if (mResetUsbGadgetDisableDebounce) {
+                // Do not debounce disconnect after resetUsbGadget.
+                sendMessage(msg);
+                if (connected == 1) mResetUsbGadgetDisableDebounce = false;
+            } else {
+                if (configured == 0) {
+                    removeMessages(MSG_UPDATE_STATE);
+                    if (DEBUG) Slog.i(TAG, "removeMessages MSG_UPDATE_STATE");
+                }
+                if (connected == 1) removeMessages(MSG_FUNCTION_SWITCH_TIMEOUT);
+                // debounce disconnects to avoid problems bringing up USB tethering.
+                sendMessageDelayed(msg,
                     (connected == 0) ? (mScreenLocked ? DEVICE_STATE_UPDATE_DELAY
                                                       : DEVICE_STATE_UPDATE_DELAY_EXT) : 0);
+            }
         }
 
         public void updateHostState(UsbPort port, UsbPortStatus status) {
@@ -974,7 +992,10 @@
                     int operationId = sUsbOperationCount.incrementAndGet();
                     mConnected = (msg.arg1 == 1);
                     mConfigured = (msg.arg2 == 1);
-
+                    if (DEBUG) {
+                        Slog.i(TAG, "handleMessage MSG_UPDATE_STATE " + "mConnected:" + mConnected
+                               + " mConfigured:" + mConfigured);
+                    }
                     updateUsbNotification(false);
                     updateAdbNotification(false);
                     if (mBootCompleted) {
@@ -2132,9 +2153,16 @@
                         }
 
                         try {
+                            // MSG_ACCESSORY_MODE_ENTER_TIMEOUT has to be removed to allow exiting
+                            // AOAP mode during resetUsbGadget.
+                            removeMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT);
+                            if (mConfigured) {
+                                mResetUsbGadgetDisableDebounce = true;
+                            }
                             mUsbGadgetHal.reset();
                         } catch (Exception e) {
                             Slog.e(TAG, "reset Usb Gadget failed", e);
+                            mResetUsbGadgetDisableDebounce = false;
                         }
                     }
                     break;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 9643282..13c0f17 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -251,11 +251,29 @@
             @Nullable String attributionTag,
             @Nullable IVoiceInteractionSessionShowCallback showCallback,
             @Nullable IBinder activityToken) {
+        try {
+            if (mService != null) {
+                mService.prepareToShowSession(args, flags);
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException while calling prepareToShowSession", e);
+        }
+
         if (mActiveSession == null) {
             mActiveSession = new VoiceInteractionSessionConnection(mServiceStub,
                     mSessionComponentName, mUser, mContext, this,
                     mInfo.getServiceInfo().applicationInfo.uid, mHandler);
         }
+        if (!mActiveSession.mBound) {
+            try {
+                if (mService != null) {
+                    mService.showSessionFailed();
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "RemoteException while calling showSessionFailed", e);
+            }
+        }
+
         List<ActivityAssistInfo> allVisibleActivities =
                 LocalServices.getService(ActivityTaskManagerInternal.class)
                         .getTopVisibleActivities();
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index f90eabc..d4a8600 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -108,6 +108,21 @@
         }
     }
 
+    /**
+     * Check whether the caller (or self, if not processing an IPC) has internet permission.
+     * @param context app context
+     * @param message detail message
+     * @return true if permission is granted, else false
+     */
+    public static boolean checkInternetPermissionNoThrow(Context context, String message) {
+        try {
+            context.enforcePermission(Manifest.permission.INTERNET,
+                    Binder.getCallingPid(), Binder.getCallingUid(), message);
+            return true;
+        } catch (SecurityException se) {
+            return false;
+        }
+    }
 
     /**
      * Check whether the caller (or self, if not processing an IPC) has non dangerous
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6f462b1..92d1e5b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1796,8 +1796,6 @@
      * Instead, each sim carrier should have a single country code, apply per carrier based iso
      * code as an override. The overridden value can be read from
      * {@link TelephonyManager#getSimCountryIso()} and {@link SubscriptionInfo#getCountryIso()}
-     *
-     * @hide
      */
     public static final String KEY_SIM_COUNTRY_ISO_OVERRIDE_STRING =
             "sim_country_iso_override_string";
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index a2a110d..26b4bbc 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -46,6 +46,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.telephony.IIntegerConsumer;
+import com.android.internal.telephony.IPhoneSubInfo;
 import com.android.internal.telephony.ISms;
 import com.android.internal.telephony.ITelephony;
 import com.android.internal.telephony.SmsRawData;
@@ -3508,4 +3509,41 @@
     private static String formatCrossStackMessageId(long id) {
         return "{x-message-id:" + id + "}";
     }
+
+    /**
+     * Fetches the EF_PSISMSC value from the UICC that contains the Public Service Identity of
+     * the SM-SC (either a SIP URI or tel URI). The EF_PSISMSC of ISIM and USIM can be found in
+     * DF_TELECOM.
+     * The EF_PSISMSC value is used by the ME to submit SMS over IP as defined in 24.341 [55].
+     *
+     * @return Uri : Public Service Identity of SM-SC from the ISIM or USIM if the ISIM is not
+     * available.
+     * @throws SecurityException if the caller does not have the required permission/privileges.
+     * @throws IllegalStateException in case of telephony service is not available.
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+    public Uri getSmscIdentity() {
+        Uri smscUri = Uri.EMPTY;
+        try {
+            IPhoneSubInfo info = TelephonyManager.getSubscriberInfoService();
+            if (info == null) {
+                Rlog.e(TAG, "getSmscIdentity(): IPhoneSubInfo instance is NULL");
+                throw new IllegalStateException("Telephony service is not available");
+            }
+            /** Fetches the SIM EF_PSISMSC value based on subId and appType */
+            smscUri = info.getSmscIdentity(getSubscriptionId(), TelephonyManager.APPTYPE_ISIM);
+            if (Uri.EMPTY.equals(smscUri)) {
+                /** Fallback in case where ISIM is not available */
+                smscUri = info.getSmscIdentity(getSubscriptionId(), TelephonyManager.APPTYPE_USIM);
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "getSmscIdentity(): Exception : " + ex);
+            ex.rethrowAsRuntimeException();
+        }
+        return smscUri;
+    }
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 0ad5ba0..913ea9a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17294,8 +17294,6 @@
      * If displaying the performance boost notification is throttled, it will be for the amount of
      * time specified by {@link CarrierConfigManager
      * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}.
-     * If a foreground application requests premium capabilities, the performance boost notification
-     * will be displayed to the user regardless of the throttled status.
      * We will show the performance boost notification to the user up to the daily and monthly
      * maximum number of times specified by
      * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT} and
@@ -17319,14 +17317,11 @@
     public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS = 4;
 
     /**
-     * Purchase premium capability failed because a foreground application requested the same
-     * capability. The notification for the current application will be dismissed and a new
-     * notification will be displayed to the user for the foreground application.
-     * Subsequent attempts will return
-     * {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS} until the foreground
-     * application's request is completed.
+     * Purchase premium capability failed because the requesting application is not in the
+     * foreground. Subsequent attempts will return the same error until the requesting application
+     * moves to the foreground.
      */
-    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN = 5;
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_FOREGROUND = 5;
 
     /**
      * Purchase premium capability failed because the user canceled the operation.
@@ -17423,7 +17418,7 @@
             PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS,
-            PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_FOREGROUND,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR,
@@ -17453,8 +17448,8 @@
                 return "ALREADY_PURCHASED";
             case PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_IN_PROGRESS:
                 return "ALREADY_IN_PROGRESS";
-            case PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN:
-                return "OVERRIDDEN";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_FOREGROUND:
+                return "NOT_FOREGROUND";
             case PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED:
                 return "USER_CANCELED";
             case PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED:
@@ -17491,10 +17486,12 @@
      * @param executor The callback executor for the response.
      * @param callback The result of the purchase request.
      *                 One of {@link PurchasePremiumCapabilityResult}.
-     * @throws SecurityException if the caller does not hold permission READ_BASIC_PHONE_STATE.
+     * @throws SecurityException if the caller does not hold permissions
+     *         READ_BASIC_PHONE_STATE or INTERNET.
      * @see #isPremiumCapabilityAvailableForPurchase(int) to check whether the capability is valid.
      */
-    @RequiresPermission(android.Manifest.permission.READ_BASIC_PHONE_STATE)
+    @RequiresPermission(allOf = {android.Manifest.permission.READ_BASIC_PHONE_STATE,
+            android.Manifest.permission.INTERNET})
     public void purchasePremiumCapability(@PremiumCapability int capability,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull @PurchasePremiumCapabilityResult Consumer<Integer> callback) {
@@ -17715,37 +17712,6 @@
     }
 
     /**
-     * Fetches the EFPSISMSC value from the SIM that contains the Public Service Identity
-     * of the SM-SC (either a SIP URI or tel URI), the value is common for both appType
-     * {@link #APPTYPE_ISIM} and {@link #APPTYPE_SIM}.
-     * The EFPSISMSC value is used by the ME to submit SMS over IP as defined in 24.341 [55].
-     *
-     * @param appType ICC Application type {@link #APPTYPE_ISIM} or {@link #APPTYPE_USIM}
-     * @return SIP URI or tel URI of the Public Service Identity of the SM-SC
-     * @throws SecurityException if the caller does not have the required permission/privileges
-     * @hide
-     */
-    @NonNull
-    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
-    public String getSmscIdentity(int appType) {
-        try {
-            IPhoneSubInfo info = getSubscriberInfoService();
-            if (info == null) {
-                Rlog.e(TAG, "getSmscIdentity(): IPhoneSubInfo instance is NULL");
-                return null;
-            }
-            /** Fetches the SIM PSISMSC params based on subId and appType */
-            return info.getSmscIdentity(getSubId(), appType);
-        } catch (RemoteException ex) {
-            Rlog.e(TAG, "getSmscIdentity(): RemoteException: " + ex.getMessage());
-        } catch (NullPointerException ex) {
-            Rlog.e(TAG, "getSmscIdentity(): NullPointerException: " + ex.getMessage());
-        }
-        return null;
-    }
-
-    /**
      * Returns a constant indicating the state of sim for the slot index.
      *
      * @param slotIndex Logical SIM slot index.
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index b49181e..3bb9be0 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -447,12 +447,12 @@
     /**
      * A callback for the response to a UCE request. The method
      * {@link CapabilitiesCallback#onCapabilitiesReceived} will be called zero or more times as the
-     * capabilities are received for each requested contact.
+     * capabilities are fetched from multiple sources, both cached on the device and on the network.
      * <p>
      * This request will take a varying amount of time depending on if the contacts requested are
      * cached or if it requires a network query. The timeout time of these requests can vary
      * depending on the network, however in poor cases it could take up to a minute for a request
-     * to timeout. In that time only a subset of capabilities may have been retrieved.
+     * to timeout. In that time, only a subset of capabilities may have been retrieved.
      * <p>
      * After {@link CapabilitiesCallback#onComplete} or {@link CapabilitiesCallback#onError} has
      * been called, the reference to this callback will be discarded on the service side.
@@ -463,22 +463,29 @@
     public interface CapabilitiesCallback {
 
         /**
-         * Notify this application that the pending capability request has returned successfully
-         * for one or more of the requested contacts.
+         * The pending capability request has completed successfully for one or more of the
+         * requested contacts.
+         * This may be called one or more times before the request is fully completed, as
+         * capabilities may need to be fetched from multiple sources both on device and on the
+         * network. Once the capabilities of all the requested contacts have been received,
+         * {@link #onComplete()} will be called. If there was an error during the capability
+         * exchange process, {@link #onError(int, long)} will be called instead.
          * @param contactCapabilities List of capabilities associated with each contact requested.
          */
         void onCapabilitiesReceived(@NonNull List<RcsContactUceCapability> contactCapabilities);
 
         /**
-         * The pending request has completed successfully due to all requested contacts information
-         * being delivered. The callback {@link #onCapabilitiesReceived(List)}
-         * for each contacts is required to be called before {@link #onComplete} is called.
+         * Called when the pending request has completed successfully due to all requested contacts
+         * information being delivered. The callback {@link #onCapabilitiesReceived(List)} will be
+         * called one or more times and will contain the contacts in the request that the device has
+         * received capabilities for.
          *
-         * @deprecated Replaced by {@link #onComplete(SipDetails)}, deprecated for
-         * SIP information.
+         * @see #onComplete(SipDetails) onComplete(SipDetails) provides more information related to
+         * the underlying SIP transaction used to perform the capabilities exchange. Either this
+         * method or the alternate method should be implemented to determine when the request has
+         * completed successfully.
          */
-        @Deprecated
-        void onComplete();
+        default void onComplete() {}
 
         /**
          * The pending request has resulted in an error and may need to be retried, depending on the
@@ -487,18 +494,26 @@
          * @param retryIntervalMillis The time in milliseconds the requesting application should
          * wait before retrying, if non-zero.
          *
-         * @deprecated Replaced by {@link #onError(int, long, SipDetails)}, deprecated for
-         * SIP information.
+         * @see #onError(int, long, SipDetails) onError(int, long, SipDetails) provides more
+         * information related to the underlying SIP transaction that resulted in an error. Either
+         * this method or the alternative method should be implemented to determine when the
+         * request has completed with an error.
          */
-        @Deprecated
-        void onError(@ErrorCode int errorCode, long retryIntervalMillis);
+        default void onError(@ErrorCode int errorCode, long retryIntervalMillis) {}
 
         /**
-         * The pending request has completed successfully due to all requested contacts information
-         * being delivered. The callback {@link #onCapabilitiesReceived(List)}
-         * for each contacts is required to be called before {@link #onComplete} is called.
+         * Called when the pending request has completed successfully due to all requested contacts
+         * information being delivered. The callback {@link #onCapabilitiesReceived(List)} will be
+         * called one or more times and will contain the contacts in the request that the device has
+         * received capabilities for.
          *
-         * @param details The SIP information related to this request.
+         * This method contains more information about the underlying SIP transaction if it exists.
+         * If this information is not needed, {@link #onComplete()} can be implemented
+         * instead.
+         *
+         * @param details The SIP information related to this request if the device supports
+         *                supplying this information. This parameter will be {@code null} if this
+         *                information is not available.
          */
         default void onComplete(@Nullable SipDetails details) {
             onComplete();
@@ -507,10 +522,16 @@
         /**
          * The pending request has resulted in an error and may need to be retried, depending on the
          * error code.
+         *
+         * This method contains more information about the underlying SIP transaction if it exists.
+         * If this information is not needed, {@link #onError(int, long)} can be implemented
+         * instead.
          * @param errorCode The reason for the framework being unable to process the request.
          * @param retryIntervalMillis The time in milliseconds the requesting application should
          * wait before retrying, if non-zero.
-         * @param details The SIP information related to this request.
+         * @param details The SIP information related to this request if the device supports
+         *                supplying this information. This parameter will be {@code null} if this
+         *                information is not available.
          */
         default void onError(@ErrorCode int errorCode, long retryIntervalMillis,
                 @Nullable SipDetails details) {
diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
index 4fa7f43..3dfc81e 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import android.telephony.ImsiEncryptionInfo;
+import android.net.Uri;
 
 /**
  * Interface used to retrieve various phone-related subscriber information.
@@ -220,18 +221,17 @@
             String callingPackage, String callingFeatureId);
 
     /**
-     * Fetches the EFPSISMSC value from the SIM that contains the Public Service Identity
-     * of the SM-SC (either a SIP URI or tel URI), the value is common for both appType
-     * {@link #APPTYPE_ISIM} and {@link #APPTYPE_SIM}.
-     * The EFPSISMSC value is used by the ME to submit SMS over IP as defined in 24.341 [55].
+     * Fetches the EF_PSISMSC value from the UICC that contains the Public Service Identity of
+     * the SM-SC (either a SIP URI or tel URI). The EF_PSISMSC of ISIM and USIM can be found in
+     * DF_TELECOM.
+     * The EF_PSISMSC value is used by the ME to submit SMS over IP as defined in 24.341 [55].
      *
-     * @param appType ICC Application type {@link #APPTYPE_ISIM} or {@link #APPTYPE_USIM}
-     * @return SIP URI or tel URI of the Public Service Identity of the SM-SC
+     * @return Uri : Public Service Identity of SM-SC
      * @throws SecurityException if the caller does not have the required permission/privileges
      * @hide
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)")
-    String getSmscIdentity(int subId, int appType);
+    Uri getSmscIdentity(int subId, int appType);
 
     /**
      * Fetches the sim service table from the EFUST/EFIST based on the application type
@@ -249,9 +249,9 @@
      * @param appType of type int of either {@link #APPTYPE_USIM} or {@link #APPTYPE_ISIM}.
      * @return HexString represents sim service table else null.
      * @throws SecurityException if the caller does not have the required permission/privileges
+     * @throws IllegalStateException in case if phone or UiccApplication is not available.
      * @hide
      */
-
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)")
     String getSimServiceTable(int subId, int appType);
 }