Merge "Abort queued-sleep transition if display shouldn't sleep anymore" into udc-dev
diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
index fd8ddbc..6c8af39 100644
--- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
@@ -59,6 +59,10 @@
      */
     void reportAppUsage(String packageName, int userId);
 
+    /** @return {@code true} if the app is considered buggy from JobScheduler's perspective. */
+    boolean isAppConsideredBuggy(int callingUserId, @NonNull String callingPackageName,
+            int timeoutBlameUserId, @NonNull String timeoutBlamePackageName);
+
     /**
      * @return {@code true} if the given notification is associated with any user-initiated jobs.
      */
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
index 8a5d094..ae86afb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
@@ -123,21 +123,21 @@
         if (oldDetails == null) {
             if (jobStatus.startedAsUserInitiatedJob) {
                 Counter.logIncrementWithUid(
-                        "job_scheduler.value_cntr_w_uid_initial_setNotification_call_required",
+                        "job_scheduler.value_cntr_w_uid_initial_set_notification_call_required",
                         jobStatus.getUid());
             } else {
                 Counter.logIncrementWithUid(
-                        "job_scheduler.value_cntr_w_uid_initial_setNotification_call_optional",
+                        "job_scheduler.value_cntr_w_uid_initial_set_notification_call_optional",
                         jobStatus.getUid());
             }
         } else {
             if (jobStatus.startedAsUserInitiatedJob) {
                 Counter.logIncrementWithUid(
-                        "job_scheduler.value_cntr_w_uid_subsequent_setNotification_call_required",
+                        "job_scheduler.value_cntr_w_uid_subsequent_set_notification_call_required",
                         jobStatus.getUid());
             } else {
                 Counter.logIncrementWithUid(
-                        "job_scheduler.value_cntr_w_uid_subsequent_setNotification_call_optional",
+                        "job_scheduler.value_cntr_w_uid_subsequent_set_notification_call_optional",
                         jobStatus.getUid());
             }
             if (oldDetails.notificationId != notificationId) {
@@ -145,7 +145,7 @@
                 removeNotificationAssociation(hostingContext, JobParameters.STOP_REASON_UNDEFINED,
                         jobStatus);
                 Counter.logIncrementWithUid(
-                        "job_scheduler.value_cntr_w_uid_setNotification_changed_notification_ids",
+                        "job_scheduler.value_cntr_w_uid_set_notification_changed_notification_ids",
                         jobStatus.getUid());
             }
         }
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 cbc9263..f99bcf1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -322,16 +322,25 @@
     private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
     private static final String QUOTA_TRACKER_SCHEDULE_LOGGED =
             ".schedulePersisted out-of-quota logged";
+    private static final String QUOTA_TRACKER_TIMEOUT_UIJ_TAG = "timeout-uij";
+    private static final String QUOTA_TRACKER_TIMEOUT_EJ_TAG = "timeout-ej";
+    private static final String QUOTA_TRACKER_TIMEOUT_REG_TAG = "timeout-reg";
+    private static final String QUOTA_TRACKER_TIMEOUT_TOTAL_TAG = "timeout-total";
+    private static final String QUOTA_TRACKER_ANR_TAG = "anr";
     private static final Category QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED = new Category(
             ".schedulePersisted()");
     private static final Category QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED = new Category(
             ".schedulePersisted out-of-quota logged");
-    private static final Categorizer QUOTA_CATEGORIZER = (userId, packageName, tag) -> {
-        if (QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG.equals(tag)) {
-            return QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED;
-        }
-        return QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED;
-    };
+    private static final Category QUOTA_TRACKER_CATEGORY_TIMEOUT_UIJ =
+            new Category(QUOTA_TRACKER_TIMEOUT_UIJ_TAG);
+    private static final Category QUOTA_TRACKER_CATEGORY_TIMEOUT_EJ =
+            new Category(QUOTA_TRACKER_TIMEOUT_EJ_TAG);
+    private static final Category QUOTA_TRACKER_CATEGORY_TIMEOUT_REG =
+            new Category(QUOTA_TRACKER_TIMEOUT_REG_TAG);
+    private static final Category QUOTA_TRACKER_CATEGORY_TIMEOUT_TOTAL =
+            new Category(QUOTA_TRACKER_TIMEOUT_TOTAL_TAG);
+    private static final Category QUOTA_TRACKER_CATEGORY_ANR = new Category(QUOTA_TRACKER_ANR_TAG);
+    private static final Category QUOTA_TRACKER_CATEGORY_DISABLED = new Category("disabled");
 
     /**
      * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
@@ -493,10 +502,18 @@
                     }
                     switch (name) {
                         case Constants.KEY_ENABLE_API_QUOTAS:
+                        case Constants.KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC:
                         case Constants.KEY_API_QUOTA_SCHEDULE_COUNT:
                         case Constants.KEY_API_QUOTA_SCHEDULE_WINDOW_MS:
                         case Constants.KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT:
                         case Constants.KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION:
+                        case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT:
+                        case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT:
+                        case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT:
+                        case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT:
+                        case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS:
+                        case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT:
+                        case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS:
                             if (!apiQuotaScheduleUpdated) {
                                 mConstants.updateApiQuotaConstantsLocked();
                                 updateQuotaTracker();
@@ -583,10 +600,26 @@
 
     @VisibleForTesting
     void updateQuotaTracker() {
-        mQuotaTracker.setEnabled(mConstants.ENABLE_API_QUOTAS);
+        mQuotaTracker.setEnabled(
+                mConstants.ENABLE_API_QUOTAS || mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC);
         mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED,
                 mConstants.API_QUOTA_SCHEDULE_COUNT,
                 mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
+        mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_TIMEOUT_UIJ,
+                mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT,
+                mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
+        mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_TIMEOUT_EJ,
+                mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT,
+                mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
+        mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_TIMEOUT_REG,
+                mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT,
+                mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
+        mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_TIMEOUT_TOTAL,
+                mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT,
+                mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
+        mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_ANR,
+                mConstants.EXECUTION_SAFEGUARDS_UDC_ANR_COUNT,
+                mConstants.EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS);
     }
 
     /**
@@ -616,6 +649,8 @@
                 "conn_low_signal_strength_relax_frac";
         private static final String KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
                 "prefetch_force_batch_relax_threshold_ms";
+        // This has been enabled for 3+ full releases. We're unlikely to disable it.
+        // TODO(141645789): remove this flag
         private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas";
         private static final String KEY_API_QUOTA_SCHEDULE_COUNT = "aq_schedule_count";
         private static final String KEY_API_QUOTA_SCHEDULE_WINDOW_MS = "aq_schedule_window_ms";
@@ -623,6 +658,22 @@
                 "aq_schedule_throw_exception";
         private static final String KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT =
                 "aq_schedule_return_failure";
+        private static final String KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC =
+                "enable_execution_safeguards_udc";
+        private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT =
+                "es_u_timeout_uij_count";
+        private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT =
+                "es_u_timeout_ej_count";
+        private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT =
+                "es_u_timeout_reg_count";
+        private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT =
+                "es_u_timeout_total_count";
+        private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS =
+                "es_u_timeout_window_ms";
+        private static final String KEY_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT =
+                "es_u_anr_count";
+        private static final String KEY_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS =
+                "es_u_anr_window_ms";
 
         private static final String KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS =
                 "runtime_free_quota_max_limit_ms";
@@ -662,6 +713,17 @@
         private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS;
         private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true;
         private static final boolean DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
+        private static final boolean DEFAULT_ENABLE_EXECUTION_SAFEGUARDS_UDC = true;
+        private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
+        // EJs have a shorter timeout, so set a higher limit for them to start with.
+        private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 5;
+        private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 3;
+        private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT = 10;
+        private static final long DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS =
+                24 * HOUR_IN_MILLIS;
+        private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT = 3;
+        private static final long DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS =
+                6 * HOUR_IN_MILLIS;
         @VisibleForTesting
         public static final long DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = 30 * MINUTE_IN_MILLIS;
         @VisibleForTesting
@@ -774,6 +836,55 @@
         public boolean API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT =
                 DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT;
 
+        /**
+         * Whether to enable the execution safeguards added in UDC.
+         */
+        public boolean ENABLE_EXECUTION_SAFEGUARDS_UDC = DEFAULT_ENABLE_EXECUTION_SAFEGUARDS_UDC;
+        /**
+         * The maximum number of times an app can have a user-iniated job time out before the system
+         * begins removing some of the app's privileges.
+         */
+        public int EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT =
+                DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT;
+        /**
+         * The maximum number of times an app can have an expedited job time out before the system
+         * begins removing some of the app's privileges.
+         */
+        public int EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT =
+                DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT;
+        /**
+         * The maximum number of times an app can have a regular job time out before the system
+         * begins removing some of the app's privileges.
+         */
+        public int EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT =
+                DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT;
+        /**
+         * The maximum number of times an app can have jobs time out before the system
+         * attempts to restrict most of the app's privileges.
+         */
+        public int EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT =
+                DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT;
+        /**
+         * The time window that {@link #EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT},
+         * {@link #EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT},
+         * {@link #EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT}, and
+         * {@link #EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT} should be evaluated over.
+         */
+        public long EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS =
+                DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS;
+
+        /**
+         * The maximum number of times an app can ANR from JobScheduler's perspective before
+         * JobScheduler will attempt to restrict the app.
+         */
+        public int EXECUTION_SAFEGUARDS_UDC_ANR_COUNT = DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT;
+        /**
+         * The time window that {@link #EXECUTION_SAFEGUARDS_UDC_ANR_COUNT}
+         * should be evaluated over.
+         */
+        public long EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS =
+                DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS;
+
         /** The maximum amount of time we will let a job run for when quota is "free". */
         public long RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
 
@@ -915,6 +1026,9 @@
         private void updateApiQuotaConstantsLocked() {
             ENABLE_API_QUOTAS = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_ENABLE_API_QUOTAS, DEFAULT_ENABLE_API_QUOTAS);
+            ENABLE_EXECUTION_SAFEGUARDS_UDC = DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC, DEFAULT_ENABLE_EXECUTION_SAFEGUARDS_UDC);
             // Set a minimum value on the quota limit so it's not so low that it interferes with
             // legitimate use cases.
             API_QUOTA_SCHEDULE_COUNT = Math.max(250,
@@ -931,6 +1045,40 @@
                     DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT,
                     DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT);
+
+            // Set a minimum value on the timeout limit so it's not so low that it interferes with
+            // legitimate use cases.
+            EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = Math.max(2,
+                    DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                            KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT,
+                            DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT));
+            EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = Math.max(2,
+                    DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                            KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT,
+                            DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT));
+            EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = Math.max(2,
+                    DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                            KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT,
+                            DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT));
+            final int highestTimeoutCount = Math.max(EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT,
+                    Math.max(EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT,
+                            EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT));
+            EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT = Math.max(highestTimeoutCount,
+                    DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                            KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT,
+                            DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT));
+            EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS = DeviceConfig.getLong(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS,
+                    DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
+            EXECUTION_SAFEGUARDS_UDC_ANR_COUNT = Math.max(1,
+                    DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                            KEY_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT,
+                            DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT));
+            EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS = DeviceConfig.getLong(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS,
+                    DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS);
         }
 
         private void updateRuntimeConstantsLocked() {
@@ -1029,6 +1177,23 @@
             pw.print(KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT,
                     API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT).println();
 
+            pw.print(KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC, ENABLE_EXECUTION_SAFEGUARDS_UDC)
+                    .println();
+            pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT,
+                    EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT).println();
+            pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT,
+                    EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT).println();
+            pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT,
+                    EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT).println();
+            pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT,
+                    EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT).println();
+            pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS,
+                    EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS).println();
+            pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT,
+                    EXECUTION_SAFEGUARDS_UDC_ANR_COUNT).println();
+            pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS,
+                    EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS).println();
+
             pw.print(KEY_RUNTIME_MIN_GUARANTEE_MS, RUNTIME_MIN_GUARANTEE_MS).println();
             pw.print(KEY_RUNTIME_MIN_EJ_GUARANTEE_MS, RUNTIME_MIN_EJ_GUARANTEE_MS).println();
             pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
@@ -2252,12 +2417,52 @@
         // Set up the app standby bucketing tracker
         mStandbyTracker = new StandbyTracker();
         mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
-        mQuotaTracker = new CountQuotaTracker(context, QUOTA_CATEGORIZER);
-        mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED,
-                mConstants.API_QUOTA_SCHEDULE_COUNT,
-                mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
+
+        final Categorizer quotaCategorizer = (userId, packageName, tag) -> {
+            if (QUOTA_TRACKER_TIMEOUT_UIJ_TAG.equals(tag)) {
+                return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
+                        ? QUOTA_TRACKER_CATEGORY_TIMEOUT_UIJ
+                        : QUOTA_TRACKER_CATEGORY_DISABLED;
+            }
+            if (QUOTA_TRACKER_TIMEOUT_EJ_TAG.equals(tag)) {
+                return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
+                        ? QUOTA_TRACKER_CATEGORY_TIMEOUT_EJ
+                        : QUOTA_TRACKER_CATEGORY_DISABLED;
+            }
+            if (QUOTA_TRACKER_TIMEOUT_REG_TAG.equals(tag)) {
+                return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
+                        ? QUOTA_TRACKER_CATEGORY_TIMEOUT_REG
+                        : QUOTA_TRACKER_CATEGORY_DISABLED;
+            }
+            if (QUOTA_TRACKER_TIMEOUT_TOTAL_TAG.equals(tag)) {
+                return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
+                        ? QUOTA_TRACKER_CATEGORY_TIMEOUT_TOTAL
+                        : QUOTA_TRACKER_CATEGORY_DISABLED;
+            }
+            if (QUOTA_TRACKER_ANR_TAG.equals(tag)) {
+                return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
+                        ? QUOTA_TRACKER_CATEGORY_ANR
+                        : QUOTA_TRACKER_CATEGORY_DISABLED;
+            }
+            if (QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG.equals(tag)) {
+                return mConstants.ENABLE_API_QUOTAS
+                        ? QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED
+                        : QUOTA_TRACKER_CATEGORY_DISABLED;
+            }
+            if (QUOTA_TRACKER_SCHEDULE_LOGGED.equals(tag)) {
+                return mConstants.ENABLE_API_QUOTAS
+                        ? QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED
+                        : QUOTA_TRACKER_CATEGORY_DISABLED;
+            }
+            Slog.wtf(TAG, "Unexpected category tag: " + tag);
+            return QUOTA_TRACKER_CATEGORY_DISABLED;
+        };
+        mQuotaTracker = new CountQuotaTracker(context, quotaCategorizer);
+        updateQuotaTracker();
         // Log at most once per minute.
+        // Set outside updateQuotaTracker() since this is intentionally not configurable.
         mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED, 1, 60_000);
+        mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_DISABLED, Integer.MAX_VALUE, 60_000);
 
         mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class);
         mAppStandbyInternal.addListener(mStandbyTracker);
@@ -2762,6 +2967,48 @@
                 0 /* Reset cumulativeExecutionTime because of successful execution */);
     }
 
+    @VisibleForTesting
+    void maybeProcessBuggyJob(@NonNull JobStatus jobStatus, int debugStopReason) {
+        boolean jobTimedOut = debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT;
+        // If madeActive = 0, the job never actually started.
+        if (!jobTimedOut && jobStatus.madeActive > 0) {
+            final long executionDurationMs = sUptimeMillisClock.millis() - jobStatus.madeActive;
+            // The debug reason may be different if we stopped the job for some other reason
+            // (eg. constraints), so look at total execution time to be safe.
+            if (jobStatus.startedAsUserInitiatedJob) {
+                // TODO: factor in different min guarantees for different UI job types
+                jobTimedOut = executionDurationMs >= mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+            } else if (jobStatus.startedAsExpeditedJob) {
+                jobTimedOut = executionDurationMs >= mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
+            } else {
+                jobTimedOut = executionDurationMs >= mConstants.RUNTIME_MIN_GUARANTEE_MS;
+            }
+        }
+        if (jobTimedOut) {
+            final int userId = jobStatus.getTimeoutBlameUserId();
+            final String pkg = jobStatus.getTimeoutBlamePackageName();
+            mQuotaTracker.noteEvent(userId, pkg,
+                    jobStatus.startedAsUserInitiatedJob
+                            ? QUOTA_TRACKER_TIMEOUT_UIJ_TAG
+                            : (jobStatus.startedAsExpeditedJob
+                                    ? QUOTA_TRACKER_TIMEOUT_EJ_TAG
+                                    : QUOTA_TRACKER_TIMEOUT_REG_TAG));
+            if (!mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_TIMEOUT_TOTAL_TAG)) {
+                mAppStandbyInternal.restrictApp(
+                        pkg, userId, UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
+            }
+        }
+
+        if (debugStopReason == JobParameters.INTERNAL_STOP_REASON_ANR) {
+            final int callingUserId = jobStatus.getUserId();
+            final String callingPkg = jobStatus.getServiceComponent().getPackageName();
+            if (!mQuotaTracker.noteEvent(callingUserId, callingPkg, QUOTA_TRACKER_ANR_TAG)) {
+                mAppStandbyInternal.restrictApp(callingPkg, callingUserId,
+                        UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
+            }
+        }
+    }
+
     // JobCompletedListener implementations.
 
     /**
@@ -2784,6 +3031,8 @@
         mLastCompletedJobTimeElapsed[mLastCompletedJobIndex] = sElapsedRealtimeClock.millis();
         mLastCompletedJobIndex = (mLastCompletedJobIndex + 1) % NUM_COMPLETED_JOB_HISTORY;
 
+        maybeProcessBuggyJob(jobStatus, debugStopReason);
+
         if (debugStopReason == JobParameters.INTERNAL_STOP_REASON_UNINSTALL
                 || debugStopReason == JobParameters.INTERNAL_STOP_REASON_DATA_CLEARED) {
             // The job should have already been cleared from the rest of the JS tracking. No need
@@ -3511,26 +3760,36 @@
             if (job.shouldTreatAsUserInitiatedJob()
                     && checkRunUserInitiatedJobsPermission(
                             job.getSourceUid(), job.getSourcePackageName())) {
+                // The calling package is the one doing the work, so use it in the
+                // timeout quota checks.
+                final boolean isWithinTimeoutQuota = mQuotaTracker.isWithinQuota(
+                        job.getTimeoutBlameUserId(), job.getTimeoutBlamePackageName(),
+                        QUOTA_TRACKER_TIMEOUT_UIJ_TAG);
+                final long upperLimitMs = isWithinTimeoutQuota
+                        ? mConstants.RUNTIME_UI_LIMIT_MS
+                        : mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
                 if (job.getJob().getRequiredNetwork() != null) {
                     // User-initiated data transfers.
                     if (mConstants.RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS) {
                         final long estimatedTransferTimeMs =
                                 mConnectivityController.getEstimatedTransferTimeMs(job);
                         if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) {
-                            return mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS;
+                            return Math.min(upperLimitMs,
+                                    mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS);
                         }
                         // Try to give the job at least as much time as we think the transfer
                         // will take, but cap it at the maximum limit.
                         final long factoredTransferTimeMs = (long) (estimatedTransferTimeMs
                                 * mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR);
-                        return Math.min(mConstants.RUNTIME_UI_LIMIT_MS,
+                        return Math.min(upperLimitMs,
                                 Math.max(factoredTransferTimeMs,
                                         mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS));
                     }
-                    return Math.max(mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
-                            mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS);
+                    return Math.min(upperLimitMs,
+                            Math.max(mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+                                    mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS));
                 }
-                return mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+                return Math.min(upperLimitMs, mConstants.RUNTIME_MIN_UI_GUARANTEE_MS);
             } else if (job.shouldTreatAsExpeditedJob()) {
                 // Don't guarantee RESTRICTED jobs more than 5 minutes.
                 return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX
@@ -3547,13 +3806,24 @@
         synchronized (mLock) {
             if (job.shouldTreatAsUserInitiatedJob()
                     && checkRunUserInitiatedJobsPermission(
-                            job.getSourceUid(), job.getSourcePackageName())) {
+                            job.getSourceUid(), job.getSourcePackageName())
+                    && mQuotaTracker.isWithinQuota(job.getTimeoutBlameUserId(),
+                            job.getTimeoutBlamePackageName(),
+                            QUOTA_TRACKER_TIMEOUT_UIJ_TAG)) {
                 return mConstants.RUNTIME_UI_LIMIT_MS;
             }
             if (job.shouldTreatAsUserInitiatedJob()) {
                 return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
             }
-            return Math.min(mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+            // Only let the app use the higher runtime if it hasn't repeatedly timed out.
+            final String timeoutTag = job.shouldTreatAsExpeditedJob()
+                    ? QUOTA_TRACKER_TIMEOUT_EJ_TAG : QUOTA_TRACKER_TIMEOUT_REG_TAG;
+            final long upperLimitMs =
+                    mQuotaTracker.isWithinQuota(job.getTimeoutBlameUserId(),
+                            job.getTimeoutBlamePackageName(), timeoutTag)
+                            ? mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS
+                            : mConstants.RUNTIME_MIN_GUARANTEE_MS;
+            return Math.min(upperLimitMs,
                     mConstants.USE_TARE_POLICY
                             ? mTareController.getMaxJobExecutionTimeMsLocked(job)
                             : mQuotaController.getMaxJobExecutionTimeMsLocked(job));
@@ -3797,6 +4067,17 @@
         }
 
         @Override
+        public boolean isAppConsideredBuggy(int callingUserId, @NonNull String callingPackageName,
+                int timeoutBlameUserId, @NonNull String timeoutBlamePackageName) {
+            return !mQuotaTracker.isWithinQuota(callingUserId, callingPackageName,
+                            QUOTA_TRACKER_ANR_TAG)
+                    || !mQuotaTracker.isWithinQuota(callingUserId, callingPackageName,
+                            QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)
+                    || !mQuotaTracker.isWithinQuota(timeoutBlameUserId, timeoutBlamePackageName,
+                            QUOTA_TRACKER_TIMEOUT_TOTAL_TAG);
+        }
+
+        @Override
         public boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId,
                 int userId, @NonNull String packageName) {
             if (packageName == null) {
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 0b08b6f..5f795b6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -1331,7 +1331,7 @@
                 // FINISHED/NO-RETRY.
                 onSlowAppResponseLocked(/* reschedule */ false, /* updateStopReasons */ true,
                         /* texCounterMetricId */
-                        "job_scheduler.value_cntr_w_uid_slow_app_response_onStartJob",
+                        "job_scheduler.value_cntr_w_uid_slow_app_response_on_start_job",
                         /* debugReason */ "timed out while starting",
                         /* anrMessage */ "No response to onStartJob",
                         CompatChanges.isChangeEnabled(ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES,
@@ -1343,7 +1343,7 @@
                 // other reason.
                 onSlowAppResponseLocked(/* reschedule */ true, /* updateStopReasons */ false,
                         /* texCounterMetricId */
-                        "job_scheduler.value_cntr_w_uid_slow_app_response_onStopJob",
+                        "job_scheduler.value_cntr_w_uid_slow_app_response_on_stop_job",
                         /* debugReason */ "timed out while stopping",
                         /* anrMessage */ "No response to onStopJob",
                         CompatChanges.isChangeEnabled(ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES,
@@ -1400,7 +1400,7 @@
                 } else if (mAwaitingNotification) {
                     onSlowAppResponseLocked(/* reschedule */ true, /* updateStopReasons */ true,
                             /* texCounterMetricId */
-                            "job_scheduler.value_cntr_w_uid_slow_app_response_setNotification",
+                            "job_scheduler.value_cntr_w_uid_slow_app_response_set_notification",
                             /* debugReason */ "timed out while stopping",
                             /* anrMessage */ "required notification not provided",
                             /* triggerAnr */ true);
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 edd531d..3baa9e6 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
@@ -1088,13 +1088,77 @@
         return UserHandle.getUserId(callingUid);
     }
 
+    private boolean shouldBlameSourceForTimeout() {
+        // If the system scheduled the job on behalf of an app, assume the app is the one
+        // doing the work and blame the app directly. This is the case with things like
+        // syncs via SyncManager.
+        // If the system didn't schedule the job on behalf of an app, then
+        // blame the app doing the actual work. Proxied jobs are a little tricky.
+        // Proxied jobs scheduled by built-in system apps like DownloadManager may be fine
+        // and we could consider exempting those jobs. For example, in DownloadManager's
+        // case, all it does is download files and the code is vetted. A timeout likely
+        // means it's downloading a large file, which isn't an error. For now, DownloadManager
+        // is an exempted app, so this shouldn't be an issue.
+        // However, proxied jobs coming from other system apps (such as those that can
+        // be updated separately from an OTA) may not be fine and we would want to apply
+        // this policy to those jobs/apps.
+        // TODO(284512488): consider exempting DownloadManager or other system apps
+        return UserHandle.isCore(callingUid);
+    }
+
+    /**
+     * Returns the package name that should most likely be blamed for the job timing out.
+     */
+    public String getTimeoutBlamePackageName() {
+        if (shouldBlameSourceForTimeout()) {
+            return sourcePackageName;
+        }
+        return getServiceComponent().getPackageName();
+    }
+
+    /**
+     * Returns the UID that should most likely be blamed for the job timing out.
+     */
+    public int getTimeoutBlameUid() {
+        if (shouldBlameSourceForTimeout()) {
+            return sourceUid;
+        }
+        return callingUid;
+    }
+
+    /**
+     * Returns the userId that should most likely be blamed for the job timing out.
+     */
+    public int getTimeoutBlameUserId() {
+        if (shouldBlameSourceForTimeout()) {
+            return sourceUserId;
+        }
+        return UserHandle.getUserId(callingUid);
+    }
+
     /**
      * Returns an appropriate standby bucket for the job, taking into account any standby
      * exemptions.
      */
     public int getEffectiveStandbyBucket() {
+        final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class);
+        final boolean isBuggy = jsi.isAppConsideredBuggy(
+                getUserId(), getServiceComponent().getPackageName(),
+                getTimeoutBlameUserId(), getTimeoutBlamePackageName());
+
         final int actualBucket = getStandbyBucket();
         if (actualBucket == EXEMPTED_INDEX) {
+            // EXEMPTED apps always have their jobs exempted, even if they're buggy, because the
+            // user has explicitly told the system to avoid restricting the app for power reasons.
+            if (isBuggy) {
+                final String pkg;
+                if (getServiceComponent().getPackageName().equals(sourcePackageName)) {
+                    pkg = sourcePackageName;
+                } else {
+                    pkg = getServiceComponent().getPackageName() + "/" + sourcePackageName;
+                }
+                Slog.w(TAG, "Exempted app " + pkg + " considered buggy");
+            }
             return actualBucket;
         }
         if (uidActive || getJob().isExemptedFromAppStandby()) {
@@ -1102,13 +1166,18 @@
             // like other ACTIVE apps.
             return ACTIVE_INDEX;
         }
+        // If the app is considered buggy, but hasn't yet been put in the RESTRICTED bucket
+        // (potentially because it's used frequently by the user), limit its effective bucket
+        // so that it doesn't get to run as much as a normal ACTIVE app.
+        final int highestBucket = isBuggy ? WORKING_INDEX : ACTIVE_INDEX;
         if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
                 && mHasMediaBackupExemption) {
-            // Cap it at WORKING_INDEX as media back up jobs are important to the user, and the
+            // Treat it as if it's at least WORKING_INDEX since media backup jobs are important
+            // to the user, and the
             // source package may not have been used directly in a while.
-            return Math.min(WORKING_INDEX, actualBucket);
+            return Math.max(highestBucket, Math.min(WORKING_INDEX, actualBucket));
         }
-        return actualBucket;
+        return Math.max(highestBucket, actualBucket);
     }
 
     /** Returns the real standby bucket of the job. */
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 07958dd..1c29982 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -773,6 +773,14 @@
             // If quota is currently "free", then the job can run for the full amount of time,
             // regardless of bucket (hence using charging instead of isQuotaFreeLocked()).
             if (mService.isBatteryCharging()
+                    // The top and foreground cases here were added because apps in those states
+                    // aren't really restricted and the work could be something the user is
+                    // waiting for. Now that user-initiated jobs are a defined concept, we may
+                    // not need these exemptions as much. However, UIJs are currently limited
+                    // (as of UDC) to data transfer work. There may be other work that could
+                    // rely on this exception. Once we add more UIJ types, we can re-evaluate
+                    // the need for these exceptions.
+                    // TODO: re-evaluate the need for these exceptions
                     || mTopAppCache.get(jobStatus.getSourceUid())
                     || isTopStartedJobLocked(jobStatus)
                     || isUidInForeground(jobStatus.getSourceUid())) {
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 55e6815..7d38377 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -3025,7 +3025,7 @@
         public static final long DEFAULT_INITIAL_FOREGROUND_SERVICE_START_TIMEOUT =
                 COMPRESS_TIME ? ONE_MINUTE : 30 * ONE_MINUTE;
         public static final long DEFAULT_AUTO_RESTRICTED_BUCKET_DELAY_MS =
-                COMPRESS_TIME ? ONE_MINUTE : ONE_DAY;
+                COMPRESS_TIME ? ONE_MINUTE : ONE_HOUR;
         public static final boolean DEFAULT_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS = true;
         public static final long DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS =
                 2 * ONE_MINUTE;
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 57935e3..70e924a 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -2933,62 +2933,21 @@
             }
         }
 
-        if (!isComponentExist(context, cn)) {
-            cn = null;
-        }
-
-        return cn;
-    }
-
-    /**
-     * Return {@link ComponentName} of the CMF default wallpaper, or
-     * {@link #getDefaultWallpaperComponent(Context)} if none is defined.
-     *
-     * @hide
-     */
-    public static ComponentName getCmfDefaultWallpaperComponent(Context context) {
-        ComponentName cn = null;
-        String[] cmfWallpaperMap = context.getResources().getStringArray(
-                com.android.internal.R.array.cmf_default_wallpaper_component);
-        if (cmfWallpaperMap == null || cmfWallpaperMap.length == 0) {
-            Log.d(TAG, "No CMF wallpaper config");
-            return getDefaultWallpaperComponent(context);
-        }
-
-        for (String entry : cmfWallpaperMap) {
-            String[] cmfWallpaper;
-            if (!TextUtils.isEmpty(entry)) {
-                cmfWallpaper = entry.split(",");
-                if (cmfWallpaper != null && cmfWallpaper.length == 2 && VALUE_CMF_COLOR.equals(
-                        cmfWallpaper[0]) && !TextUtils.isEmpty(cmfWallpaper[1])) {
-                    cn = ComponentName.unflattenFromString(cmfWallpaper[1]);
-                    break;
-                }
+        // Check if the package exists
+        if (cn != null) {
+            try {
+                final PackageManager packageManager = context.getPackageManager();
+                packageManager.getPackageInfo(cn.getPackageName(),
+                        PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+            } catch (PackageManager.NameNotFoundException e) {
+                cn = null;
             }
         }
 
-        if (!isComponentExist(context, cn)) {
-            cn = null;
-        }
-
         return cn;
     }
 
-    private static boolean isComponentExist(Context context, ComponentName cn) {
-        if (cn == null) {
-            return false;
-        }
-        try {
-            final PackageManager packageManager = context.getPackageManager();
-            packageManager.getPackageInfo(cn.getPackageName(),
-                    PackageManager.MATCH_DIRECT_BOOT_AWARE
-                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
-        } catch (PackageManager.NameNotFoundException e) {
-            return false;
-        }
-        return true;
-    }
-
     /**
      * Register a callback for lock wallpaper observation. Only the OS may use this.
      *
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index b2a9230..da5e40a 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -11369,7 +11369,8 @@
      * @throws SecurityException     if the caller is not a profile owner on an organization-owned
      *                               managed profile.
      * @throws IllegalStateException if called after the device setup has been completed.
-     * @throws UnsupportedOperationException if the api is not enabled.
+     * @throws UnsupportedOperationException if managed subscriptions policy is not explicitly
+     *         enabled by the device policy management role holder during device setup.
      * @see ManagedSubscriptionsPolicy
      */
     public void setManagedSubscriptionsPolicy(@Nullable ManagedSubscriptionsPolicy policy) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1bbb7b4..a28be4b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5459,7 +5459,7 @@
     }
 
     private void updateRenderHdrSdrRatio() {
-        mRenderHdrSdrRatio = mDisplay.getHdrSdrRatio();
+        mRenderHdrSdrRatio = Math.min(mDesiredHdrSdrRatio, mDisplay.getHdrSdrRatio());
         mUpdateHdrSdrRatioInfo = true;
     }
 
@@ -5487,22 +5487,14 @@
                 mHdrSdrRatioChangedListener = null;
             } else {
                 mHdrSdrRatioChangedListener = display -> {
-                    setTargetHdrSdrRatio(display.getHdrSdrRatio());
+                    updateRenderHdrSdrRatio();
+                    invalidate();
                 };
                 mDisplay.registerHdrSdrRatioChangedListener(mExecutor, mHdrSdrRatioChangedListener);
             }
         }
     }
 
-    /** happylint */
-    public void setTargetHdrSdrRatio(float ratio) {
-        if (mRenderHdrSdrRatio != ratio) {
-            mRenderHdrSdrRatio = ratio;
-            mUpdateHdrSdrRatioInfo = true;
-            invalidate();
-        }
-    }
-
     @Override
     public void requestChildFocus(View child, View focused) {
         if (DEBUG_INPUT_RESIZE) {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 00f8db0..ee8c0f8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1817,16 +1817,6 @@
          specified -->
     <string name="default_wallpaper_component" translatable="false">@null</string>
 
-    <!-- CMF colors to default wallpaper component map, the component with color matching the device
-         color will be the cmf default wallpapers. The default wallpaper will be default wallpaper
-         component if not specified.
-
-         E.g. for SLV color, and com.android.example/com.android.example.SlVDefaultWallpaper
-         <item>SLV,com.android.example/com.android.example.SlVDefaultWallpaper</item> -->
-    <string-array name="cmf_default_wallpaper_component" translatable="false">
-        <!-- Add packages here -->
-    </string-array>
-
     <!-- By default a product has no distinct default lock wallpaper -->
     <item name="default_lock_wallpaper" type="drawable">@null</item>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a8518af..dc4eafd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2116,7 +2116,6 @@
   <java-symbol type="string" name="data_usage_rapid_body" />
   <java-symbol type="string" name="data_usage_rapid_app_body" />
   <java-symbol type="string" name="default_wallpaper_component" />
-  <java-symbol type="array" name="cmf_default_wallpaper_component" />
   <java-symbol type="string" name="device_storage_monitor_notification_channel" />
   <java-symbol type="string" name="dlg_ok" />
   <java-symbol type="string" name="dump_heap_notification" />
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index d6e1a82..467e9c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -375,16 +375,15 @@
                     insetsState.getDisplayFrame(),
                     WindowInsets.Type.navigationBars(),
                     false /* ignoreVisibility */);
-            outInsets.set(insets.left, insets.top, insets.right, insets.bottom);
             int position = navigationBarPosition(res, displayWidth, displayHeight, displayRotation);
             int navBarSize =
                     getNavigationBarSize(res, position, displayWidth > displayHeight, uiMode);
             if (position == NAV_BAR_BOTTOM) {
-                outInsets.bottom = Math.max(outInsets.bottom , navBarSize);
+                outInsets.bottom = Math.max(insets.bottom , navBarSize);
             } else if (position == NAV_BAR_RIGHT) {
-                outInsets.right = Math.max(outInsets.right , navBarSize);
+                outInsets.right = Math.max(insets.right , navBarSize);
             } else if (position == NAV_BAR_LEFT) {
-                outInsets.left = Math.max(outInsets.left , navBarSize);
+                outInsets.left = Math.max(insets.left , navBarSize);
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 5a9c25f..c491fed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -70,6 +70,8 @@
 import com.android.wm.shell.freeform.FreeformComponents;
 import com.android.wm.shell.fullscreen.FullscreenTaskListener;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.keyguard.KeyguardTransitions;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
@@ -77,8 +79,6 @@
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
-import com.android.wm.shell.keyguard.KeyguardTransitions;
 import com.android.wm.shell.recents.RecentTasks;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
@@ -102,13 +102,13 @@
 import com.android.wm.shell.unfold.UnfoldTransitionHandler;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
+import java.util.Optional;
+
 import dagger.BindsOptionalOf;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
-import java.util.Optional;
-
 /**
  * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
  * accessible from components within the WM subcomponent (can be explicitly exposed to the
@@ -548,13 +548,15 @@
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler,
             @ShellAnimationThread ShellExecutor animExecutor,
-            ShellCommandHandler shellCommandHandler) {
+            ShellCommandHandler shellCommandHandler,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
         if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) {
             // TODO(b/238217847): Force override shell init if registration is disabled
             shellInit = new ShellInit(mainExecutor);
         }
         return new Transitions(context, shellInit, shellController, organizer, pool,
-                displayController, mainExecutor, mainHandler, animExecutor, shellCommandHandler);
+                displayController, mainExecutor, mainHandler, animExecutor, shellCommandHandler,
+                rootTaskDisplayAreaOrganizer);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 5086e2c..bf75132 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -816,6 +816,12 @@
         final ActivityManager.RunningTaskInfo taskInfo = pipChange.getTaskInfo();
         final SurfaceControl leash = pipChange.getLeash();
         final int startRotation = pipChange.getStartRotation();
+        // Check again in case some callers use startEnterAnimation directly so the flag was not
+        // set in startAnimation, e.g. from DefaultMixedHandler.
+        if (!mInFixedRotation) {
+            mEndFixedRotation = pipChange.getEndFixedRotation();
+            mInFixedRotation = mEndFixedRotation != ROTATION_UNDEFINED;
+        }
         final int endRotation = mInFixedRotation ? mEndFixedRotation : pipChange.getEndRotation();
 
         setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
@@ -844,7 +850,7 @@
                 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()
                 && mPipTransitionState.getInSwipePipToHomeTransition()) {
             handleSwipePipToHomeTransition(startTransaction, finishTransaction, leash,
-                    sourceHintRect, destinationBounds, rotationDelta, taskInfo);
+                    sourceHintRect, destinationBounds, taskInfo);
             return;
         }
 
@@ -935,8 +941,15 @@
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull SurfaceControl leash, @Nullable Rect sourceHintRect,
-            @NonNull Rect destinationBounds, int rotationDelta,
+            @NonNull Rect destinationBounds,
             @NonNull ActivityManager.RunningTaskInfo pipTaskInfo) {
+        if (mInFixedRotation) {
+            // If rotation changes when returning to home, the transition should contain both the
+            // entering PiP and the display change (PipController#startSwipePipToHome has updated
+            // the display layout to new rotation). So it is not expected to see fixed rotation.
+            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                    "%s: SwipePipToHome should not use fixed rotation %d", TAG, mEndFixedRotation);
+        }
         final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
         if (swipePipToHomeOverlay != null) {
             // Launcher fade in the overlay on top of the fullscreen Task. It is possible we
@@ -947,12 +960,7 @@
             mPipOrganizer.mSwipePipToHomeOverlay = null;
         }
 
-        Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
-        if (!Transitions.SHELL_TRANSITIONS_ROTATION && rotationDelta % 2 == 1) {
-            // PipController#startSwipePipToHome has updated the display layout to new rotation,
-            // so flip the source bounds to match the same orientation.
-            sourceBounds = new Rect(0, 0, sourceBounds.height(), sourceBounds.width());
-        }
+        final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
         final PipAnimationController.PipTransitionAnimator animator =
                 mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
                         destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 8723f9b..66da125 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -595,8 +595,8 @@
                         cancel(mWillFinishToHome, true /* withScreenshots */, "display change");
                         return;
                     }
-                    // Don't consider order-only changes as changing apps.
-                    if (!TransitionUtil.isOrderOnly(change)) {
+                    // Don't consider order-only & non-leaf changes as changing apps.
+                    if (!TransitionUtil.isOrderOnly(change) && isLeafTask) {
                         hasChangingApp = true;
                     }
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 38911db..c8e88fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -2499,6 +2499,7 @@
             // handling to the mixed-handler to deal with splitting it up.
             if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info,
                     startTransaction, finishTransaction, finishCallback)) {
+                mSplitLayout.update(startTransaction);
                 return true;
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 715f835..3bf278c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -58,7 +58,6 @@
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
-import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
@@ -76,6 +75,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -103,6 +103,7 @@
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.internal.policy.TransitionAnimation;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
@@ -137,6 +138,7 @@
     private final Rect mInsets = new Rect(0, 0, 0, 0);
     private float mTransitionAnimationScaleSetting = 1.0f;
 
+    private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
     private final int mCurrentUserId;
 
     private Drawable mEnterpriseThumbnailDrawable;
@@ -157,7 +159,8 @@
             @NonNull DisplayController displayController,
             @NonNull TransactionPool transactionPool,
             @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
-            @NonNull ShellExecutor animExecutor) {
+            @NonNull ShellExecutor animExecutor,
+            @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) {
         mDisplayController = displayController;
         mTransactionPool = transactionPool;
         mContext = context;
@@ -168,6 +171,7 @@
         mCurrentUserId = UserHandle.myUserId();
         mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
         shellInit.addInitCallback(this::onInit, this);
+        mRootTDAOrganizer = rootTDAOrganizer;
     }
 
     private void onInit() {
@@ -510,10 +514,8 @@
         }
 
         if (backgroundColorForTransition != 0) {
-            for (int i = 0; i < info.getRootCount(); ++i) {
-                addBackgroundToTransition(info.getRoot(i).getLeash(), backgroundColorForTransition,
-                        startTransaction, finishTransaction);
-            }
+            addBackgroundColorOnTDA(info, backgroundColorForTransition, startTransaction,
+                    finishTransaction);
         }
 
         if (postStartTransactionCallbacks.size() > 0) {
@@ -543,6 +545,28 @@
         return true;
     }
 
+    private void addBackgroundColorOnTDA(@NonNull TransitionInfo info,
+            @ColorInt int color, @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction) {
+        final Color bgColor = Color.valueOf(color);
+        final float[] colorArray = new float[] { bgColor.red(), bgColor.green(), bgColor.blue() };
+
+        for (int i = 0; i < info.getRootCount(); ++i) {
+            final int displayId = info.getRoot(i).getDisplayId();
+            final SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder()
+                    .setName("animation-background")
+                    .setCallsite("DefaultTransitionHandler")
+                    .setColorLayer();
+
+            mRootTDAOrganizer.attachToDisplayArea(displayId, colorLayerBuilder);
+            final SurfaceControl backgroundSurface = colorLayerBuilder.build();
+            startTransaction.setColor(backgroundSurface, colorArray)
+                    .setLayer(backgroundSurface, -1)
+                    .show(backgroundSurface);
+            finishTransaction.remove(backgroundSurface);
+        }
+    }
+
     private static boolean isDreamTransition(@NonNull TransitionInfo info) {
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
@@ -696,9 +720,10 @@
 
         if (a != null) {
             if (!a.isInitialized()) {
-                final int width = endBounds.width();
-                final int height = endBounds.height();
-                a.initialize(width, height, width, height);
+                final Rect animationRange = TransitionUtil.isClosingType(changeMode)
+                        ? change.getStartAbsBounds() : change.getEndAbsBounds();
+                a.initialize(animationRange.width(), animationRange.height(),
+                        endBounds.width(), endBounds.height());
             }
             a.restrictDuration(MAX_ANIMATION_DURATION);
             a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
@@ -886,17 +911,18 @@
     }
 
     private static void applyTransformation(long time, SurfaceControl.Transaction t,
-            SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix,
+            SurfaceControl leash, Animation anim, Transformation tmpTransformation, float[] matrix,
             Point position, float cornerRadius, @Nullable Rect immutableClipRect) {
-        anim.getTransformation(time, transformation);
+        tmpTransformation.clear();
+        anim.getTransformation(time, tmpTransformation);
         if (position != null) {
-            transformation.getMatrix().postTranslate(position.x, position.y);
+            tmpTransformation.getMatrix().postTranslate(position.x, position.y);
         }
-        t.setMatrix(leash, transformation.getMatrix(), matrix);
-        t.setAlpha(leash, transformation.getAlpha());
+        t.setMatrix(leash, tmpTransformation.getMatrix(), matrix);
+        t.setAlpha(leash, tmpTransformation.getAlpha());
 
         final Rect clipRect = immutableClipRect == null ? null : new Rect(immutableClipRect);
-        Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE);
+        Insets extensionInsets = Insets.min(tmpTransformation.getInsets(), Insets.NONE);
         if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) {
             // Clip out any overflowing edge extension
             clipRect.inset(extensionInsets);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 2314376e..cdc82ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -69,6 +69,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
@@ -265,7 +266,8 @@
             @NonNull Handler mainHandler,
             @NonNull ShellExecutor animExecutor) {
         this(context, shellInit, shellController, organizer, pool, displayController, mainExecutor,
-                mainHandler, animExecutor, null);
+                mainHandler, animExecutor, null,
+                new RootTaskDisplayAreaOrganizer(mainExecutor, context));
     }
 
     public Transitions(@NonNull Context context,
@@ -277,7 +279,8 @@
             @NonNull ShellExecutor mainExecutor,
             @NonNull Handler mainHandler,
             @NonNull ShellExecutor animExecutor,
-            @Nullable ShellCommandHandler shellCommandHandler) {
+            @Nullable ShellCommandHandler shellCommandHandler,
+            @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) {
         mOrganizer = organizer;
         mContext = context;
         mMainExecutor = mainExecutor;
@@ -285,7 +288,7 @@
         mDisplayController = displayController;
         mPlayerImpl = new TransitionPlayerImpl();
         mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
-                displayController, pool, mainExecutor, mainHandler, animExecutor);
+                displayController, pool, mainExecutor, mainHandler, animExecutor, rootTDAOrganizer);
         mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
         mShellController = shellController;
         // The very last handler (0 in the list) should be the default one.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
new file mode 100644
index 0000000..f7ce870
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
@@ -0,0 +1,200 @@
+/*
+ * 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.wm.shell.flicker.pip
+
+import android.app.Instrumentation
+import android.os.SystemClock
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.helpers.WindowUtils
+import android.tools.device.traces.parsers.toFlickerComponent
+import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from an app via auto-enter property when navigating to home from split screen.
+ *
+ * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
+ *
+ * Actions:
+ * ```
+ *     Launch an app in full screen
+ *     Select "Auto-enter PiP" radio button
+ *     Open all apps and drag another app icon to enter split screen
+ *     Press Home button or swipe up to go Home and put [pipApp] in pip mode
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. All assertions are inherited from [EnterPipTest]
+ *     2. Part of the test setup occurs automatically via
+ *        [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) :
+        AutoEnterPipOnGoToHomeTest(flicker) {
+    private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
+    /** Second app used to enter split screen mode */
+    protected val secondAppForSplitScreen = getSplitScreenApp(instrumentation)
+    fun getSplitScreenApp(instrumentation: Instrumentation): StandardAppHelper =
+            SimpleAppHelper(
+                    instrumentation,
+                    ActivityOptions.SplitScreen.Primary.LABEL,
+                    ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+            )
+
+    /** Defines the transition used to run the test */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                secondAppForSplitScreen.launchViaIntent(wmHelper)
+                pipApp.launchViaIntent(wmHelper)
+                tapl.goHome()
+                enterSplitScreen()
+                // wait until split screen is established
+                wmHelper
+                        .StateSyncBuilder()
+                        .withWindowSurfaceAppeared(pipApp)
+                        .withWindowSurfaceAppeared(secondAppForSplitScreen)
+                        .withSplitDividerVisible()
+                        .waitForAndVerify()
+                pipApp.enableAutoEnterForPipActivity()
+            }
+            teardown {
+                // close gracefully so that onActivityUnpinned() can be called before force exit
+                pipApp.closePipWindow(wmHelper)
+                pipApp.exit(wmHelper)
+                secondAppForSplitScreen.exit(wmHelper)
+            }
+            transitions { tapl.goHome() }
+        }
+
+    // TODO(b/285400227) merge the code in a common utility - this is copied from SplitScreenUtils
+    private val TIMEOUT_MS = 3_000L
+    private val overviewSnapshotSelector: BySelector
+        get() = By.res(LAUNCHER_UI_PACKAGE_NAME, "snapshot")
+    private fun enterSplitScreen() {
+        // Note: The initial split position in landscape is different between tablet and phone.
+        // In landscape, tablet will let the first app split to right side, and phone will
+        // split to left side.
+        if (tapl.isTablet) {
+            // TAPL's currentTask on tablet is sometimes not what we expected if the overview
+            // contains more than 3 task views. We need to use uiautomator directly to find the
+            // second task to split.
+            tapl.workspace.switchToOverview().overviewActions.clickSplit()
+            val snapshots = tapl.device.wait(Until.findObjects(overviewSnapshotSelector),
+                    TIMEOUT_MS)
+            if (snapshots == null || snapshots.size < 1) {
+                error("Fail to find a overview snapshot to split.")
+            }
+
+            // Find the second task in the upper right corner in split select mode by sorting
+            // 'left' in descending order and 'top' in ascending order.
+            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
+                t2.getVisibleBounds().left - t1.getVisibleBounds().left
+            }
+            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
+                t1.getVisibleBounds().top - t2.getVisibleBounds().top
+            }
+            snapshots[0].click()
+        } else {
+            tapl.workspace
+                    .switchToOverview()
+                    .currentTask
+                    .tapMenu()
+                    .tapSplitMenuItem()
+                    .currentTask
+                    .open()
+        }
+        SystemClock.sleep(TIMEOUT_MS)
+    }
+
+    @Presubmit
+    @Test
+    override fun pipOverlayLayerAppearThenDisappear() {
+        // when entering from split screen we use alpha animation, without overlay
+    }
+
+    @Presubmit
+    @Test
+    override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
+        // when entering from split screen we use alpha animation, without overlay
+    }
+
+    @Presubmit
+    @Test
+    override fun pipLayerReduces() {
+        // when entering from split screen we use alpha animation, so there is no size change
+        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+        super.pipLayerReduces()
+    }
+
+    @Presubmit
+    @Test
+    override fun pipAppLayerAlwaysVisible() {
+        // pip layer in gesture nav will disappear during transition with alpha animation
+        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+        super.pipAppLayerAlwaysVisible()
+    }
+
+    @Presubmit
+    @Test
+    override fun pipWindowRemainInsideVisibleBounds() {
+        if (tapl.isTablet) {
+            flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+        } else {
+            // on phones home does not rotate in landscape, PiP enters back to portrait
+            // orientation so use display bounds from that orientation for assertion
+            flicker.assertWmVisibleRegion(pipApp) { coversAtMost(portraitDisplayBounds) }
+        }
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                    // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                    supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index ef7bedf..b95732e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -53,7 +53,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
+open class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { tapl.goHome() }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
index 95121de..cdbdb85 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
@@ -62,7 +62,7 @@
      */
     @Presubmit
     @Test
-    fun pipWindowRemainInsideVisibleBounds() {
+    open fun pipWindowRemainInsideVisibleBounds() {
         flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 3fcb7f3..ffc0479 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -116,10 +116,7 @@
                 && !TextUtils.isEmpty(mPackageName)) {
             RouteListingPreference routeListingPreference =
                     mRouterManager.getRouteListingPreference(mPackageName);
-            if (routeListingPreference != null) {
-                Api34Impl.onRouteListingPreferenceUpdated(null, routeListingPreference,
-                        mPreferenceItemMap);
-            }
+            Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference, mPreferenceItemMap);
         }
         refreshDevices();
     }
@@ -631,7 +628,7 @@
         }
 
         /**
-         * Ignore callback here since we'll also receive {@link onRequestFailed} with reason code.
+         * Ignore callback here since we'll also receive {@link #onRequestFailed} with reason code.
          */
         @Override
         public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) {
@@ -656,9 +653,9 @@
         public void onRouteListingPreferenceUpdated(
                 String packageName,
                 RouteListingPreference routeListingPreference) {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
-                Api34Impl.onRouteListingPreferenceUpdated(packageName, routeListingPreference,
-                        mPreferenceItemMap);
+            if (TextUtils.equals(mPackageName, packageName)) {
+                Api34Impl.onRouteListingPreferenceUpdated(
+                        routeListingPreference, mPreferenceItemMap);
                 refreshDevices();
             }
         }
@@ -746,7 +743,6 @@
 
         @DoNotInline
         static void onRouteListingPreferenceUpdated(
-                String packageName,
                 RouteListingPreference routeListingPreference,
                 Map<String, RouteListingPreference.Item> preferenceItemMap) {
             preferenceItemMap.clear();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index aa5f3df..39780f3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -73,6 +73,7 @@
 public class InfoMediaManagerTest {
 
     private static final String TEST_PACKAGE_NAME = "com.test.packagename";
+    private static final String TEST_PACKAGE_NAME_2 = "com.test.packagename2";
     private static final String TEST_ID = "test_id";
     private static final String TEST_ID_1 = "test_id_1";
     private static final String TEST_ID_2 = "test_id_2";
@@ -308,7 +309,54 @@
     }
 
     @Test
-    public void onRouteChanged_getAvailableRoutesWithPrefernceListExit_ordersRoutes() {
+    public void onRouteChanged_getAvailableRoutesWithPreferenceListExit_ordersRoutes() {
+        RouteListingPreference routeListingPreference = setUpPreferenceList(TEST_PACKAGE_NAME);
+        setUpSelectedRoutes(TEST_PACKAGE_NAME);
+
+        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+        final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
+        routingSessionInfos.add(sessionInfo);
+
+        when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos);
+        when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID));
+
+        setAvailableRoutesList(TEST_PACKAGE_NAME);
+
+        mInfoMediaManager.mRouterManager = mRouterManager;
+        mInfoMediaManager.mMediaRouterCallback.onRouteListingPreferenceUpdated(TEST_PACKAGE_NAME,
+                routeListingPreference);
+        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
+
+        assertThat(mInfoMediaManager.mMediaDevices).hasSize(3);
+        assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID);
+        assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_4);
+        assertThat(mInfoMediaManager.mMediaDevices.get(1).isSuggestedDevice()).isTrue();
+        assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_3);
+    }
+
+    @Test
+    public void onRouteChanged_preferenceListUpdateWithDifferentPkg_notOrdersRoutes() {
+        RouteListingPreference routeListingPreference = setUpPreferenceList(TEST_PACKAGE_NAME_2);
+        setUpSelectedRoutes(TEST_PACKAGE_NAME);
+
+        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+        final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
+        routingSessionInfos.add(sessionInfo);
+
+        when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos);
+        when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID));
+
+        setAvailableRoutesList(TEST_PACKAGE_NAME);
+        mInfoMediaManager.mRouterManager = mRouterManager;
+        mInfoMediaManager.mMediaRouterCallback.onRouteListingPreferenceUpdated(TEST_PACKAGE_NAME_2,
+                routeListingPreference);
+        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
+
+        assertThat(mInfoMediaManager.mMediaDevices).hasSize(1);
+        assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID);
+    }
+
+    private RouteListingPreference setUpPreferenceList(String packageName) {
         ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
                 Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
         final List<RouteListingPreference.Item> preferenceItemList = new ArrayList<>();
@@ -324,57 +372,40 @@
         RouteListingPreference routeListingPreference =
                 new RouteListingPreference.Builder().setItems(
                         preferenceItemList).setUseSystemOrdering(false).build();
-        when(mRouterManager.getRouteListingPreference(TEST_PACKAGE_NAME))
+        when(mRouterManager.getRouteListingPreference(packageName))
                 .thenReturn(routeListingPreference);
+        return routeListingPreference;
+    }
 
+    private void setUpSelectedRoutes(String packageName) {
         final List<MediaRoute2Info> selectedRoutes = new ArrayList<>();
         final MediaRoute2Info info = mock(MediaRoute2Info.class);
         when(info.getId()).thenReturn(TEST_ID);
-        when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(info.getClientPackageName()).thenReturn(packageName);
         when(info.isSystemRoute()).thenReturn(true);
         selectedRoutes.add(info);
         when(mRouterManager.getSelectedRoutes(any())).thenReturn(selectedRoutes);
-
-        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
-        final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
-        routingSessionInfos.add(sessionInfo);
-
-        when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos);
-        when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID));
-
-        setAvailableRoutesList();
-
-        mInfoMediaManager.mRouterManager = mRouterManager;
-        mInfoMediaManager.mMediaRouterCallback.onRouteListingPreferenceUpdated(TEST_PACKAGE_NAME,
-                routeListingPreference);
-        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
-
-        assertThat(mInfoMediaManager.mMediaDevices).hasSize(3);
-        assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID);
-        assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_4);
-        assertThat(mInfoMediaManager.mMediaDevices.get(1).isSuggestedDevice()).isTrue();
-        assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_3);
     }
 
-    private List<MediaRoute2Info> setAvailableRoutesList() {
+    private List<MediaRoute2Info> setAvailableRoutesList(String packageName) {
         final List<MediaRoute2Info> availableRoutes = new ArrayList<>();
         final MediaRoute2Info availableInfo1 = mock(MediaRoute2Info.class);
         when(availableInfo1.getId()).thenReturn(TEST_ID_2);
-        when(availableInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(availableInfo1.getClientPackageName()).thenReturn(packageName);
         when(availableInfo1.getType()).thenReturn(TYPE_REMOTE_TV);
         availableRoutes.add(availableInfo1);
 
         final MediaRoute2Info availableInfo2 = mock(MediaRoute2Info.class);
         when(availableInfo2.getId()).thenReturn(TEST_ID_3);
-        when(availableInfo2.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(availableInfo2.getClientPackageName()).thenReturn(packageName);
         availableRoutes.add(availableInfo2);
 
         final MediaRoute2Info availableInfo3 = mock(MediaRoute2Info.class);
         when(availableInfo3.getId()).thenReturn(TEST_ID_4);
-        when(availableInfo3.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(availableInfo3.getClientPackageName()).thenReturn(packageName);
         availableRoutes.add(availableInfo3);
 
-        when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(
+        when(mRouterManager.getAvailableRoutes(packageName)).thenReturn(
                 availableRoutes);
 
         return availableRoutes;
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 0a9a184..0aa121d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -996,7 +996,6 @@
             android:name=".notetask.shortcut.LaunchNoteTaskActivity"
             android:exported="true"
             android:excludeFromRecents="true"
-            android:resizeableActivity="false"
             android:theme="@android:style/Theme.NoDisplay" >
 
             <intent-filter>
@@ -1012,7 +1011,6 @@
             android:exported="false"
             android:enabled="true"
             android:excludeFromRecents="true"
-            android:resizeableActivity="false"
             android:theme="@android:style/Theme.NoDisplay" />
 
         <activity
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 25272ae..ccfbaf1 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -215,7 +215,7 @@
                     debugLog { "onShowNoteTask - opened as app bubble: $info" }
                 }
                 is NoteTaskLaunchMode.Activity -> {
-                    if (activityManager.isInForeground(info.packageName)) {
+                    if (info.isKeyguardLocked && activityManager.isInForeground(info.packageName)) {
                         // Force note task into background by calling home.
                         val intent = createHomeIntent()
                         context.startActivityAsUser(intent, user)
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
index fae325c..4420002 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
@@ -25,12 +25,14 @@
  * An entry point represents where the note task has ben called from. In rare cases, it may
  * represent a "re-entry" (i.e., [APP_CLIPS]).
  */
-enum class
-NoteTaskEntryPoint {
+enum class NoteTaskEntryPoint {
 
     /** @see [LaunchNoteTaskActivity] */
     WIDGET_PICKER_SHORTCUT,
 
+    /** @see [LaunchNoteTaskActivity] */
+    WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE,
+
     /** @see [NoteTaskQuickAffordanceConfig] */
     QUICK_AFFORDANCE,
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
index 48a5933..a79057e 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
 import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
 import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
+import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent
 import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
 import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT
 import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
@@ -41,40 +43,45 @@
     /** Logs a [NoteTaskInfo] as an **open** [NoteTaskUiEvent], including package name and uid. */
     fun logNoteTaskOpened(info: NoteTaskInfo) {
         val event =
-            when (info.entryPoint) {
-                TAIL_BUTTON -> {
-                    if (info.isKeyguardLocked) {
-                        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
-                    } else {
-                        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+                when (info.entryPoint) {
+                    TAIL_BUTTON -> {
+                        if (info.isKeyguardLocked) {
+                            NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+                        } else {
+                            NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+                        }
                     }
+
+                    WIDGET_PICKER_SHORTCUT,
+                    WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE -> NOTE_OPENED_VIA_SHORTCUT
+
+                    QUICK_AFFORDANCE -> NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
+                    APP_CLIPS,
+                    KEYBOARD_SHORTCUT,
+                    null -> return
                 }
-                WIDGET_PICKER_SHORTCUT -> NOTE_OPENED_VIA_SHORTCUT
-                QUICK_AFFORDANCE -> NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
-                APP_CLIPS -> return
-                KEYBOARD_SHORTCUT -> return
-                null -> return
-            }
         uiEventLogger.log(event, info.uid, info.packageName)
     }
 
     /** Logs a [NoteTaskInfo] as a **closed** [NoteTaskUiEvent], including package name and uid. */
     fun logNoteTaskClosed(info: NoteTaskInfo) {
         val event =
-            when (info.entryPoint) {
-                TAIL_BUTTON -> {
-                    if (info.isKeyguardLocked) {
-                        NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED
-                    } else {
-                        NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON
+                when (info.entryPoint) {
+                    TAIL_BUTTON -> {
+                        if (info.isKeyguardLocked) {
+                            NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+                        } else {
+                            NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON
+                        }
                     }
+
+                    WIDGET_PICKER_SHORTCUT,
+                    WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE,
+                    QUICK_AFFORDANCE,
+                    APP_CLIPS,
+                    KEYBOARD_SHORTCUT,
+                    null -> return
                 }
-                WIDGET_PICKER_SHORTCUT -> return
-                QUICK_AFFORDANCE -> return
-                APP_CLIPS -> return
-                KEYBOARD_SHORTCUT -> return
-                null -> return
-            }
         uiEventLogger.log(event, info.uid, info.packageName)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt
index a758347..269eb87 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.notetask
 
 import android.os.UserHandle
+import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE
 
 /** Contextual information required to launch a Note Task by [NoteTaskController]. */
 data class NoteTaskInfo(
@@ -27,7 +28,7 @@
 ) {
 
     val launchMode: NoteTaskLaunchMode =
-        if (isKeyguardLocked) {
+        if (isKeyguardLocked || entryPoint == WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE) {
             NoteTaskLaunchMode.Activity
         } else {
             NoteTaskLaunchMode.AppBubble
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
index 441b9f5..754c365 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
@@ -51,7 +51,8 @@
         val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
 
         return ShortcutInfo.Builder(context, NoteTaskController.SHORTCUT_ID)
-            .setIntent(LaunchNoteTaskActivity.newIntent(context = context))
+            .setIntent(LaunchNoteTaskActivity.createIntent(context))
+            .setActivity(LaunchNoteTaskActivity.createComponent(context))
             .setShortLabel(context.getString(R.string.note_task_button_label))
             .setLongLived(true)
             .setIcon(icon)
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
index 8ca13b9..7ef149d 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.notetask.shortcut
 
+import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
 import android.os.Bundle
@@ -72,7 +73,13 @@
                 controller.startNoteTaskProxyActivityForUser(mainUser)
             }
         } else {
-            controller.showNoteTask(entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT)
+            val entryPoint =
+                if (isInMultiWindowMode) {
+                    NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE
+                } else {
+                    NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
+                }
+            controller.showNoteTask(entryPoint)
         }
         finish()
     }
@@ -80,11 +87,14 @@
     companion object {
 
         /** Creates a new [Intent] set to start [LaunchNoteTaskActivity]. */
-        fun newIntent(context: Context): Intent {
-            return Intent(context, LaunchNoteTaskActivity::class.java).apply {
+        fun createIntent(context: Context): Intent =
+            Intent(context, LaunchNoteTaskActivity::class.java).apply {
                 // Intent's action must be set in shortcuts, or an exception will be thrown.
                 action = Intent.ACTION_CREATE_NOTE
             }
-        }
+
+        /** Creates a new [ComponentName] for [LaunchNoteTaskActivity]. */
+        fun createComponent(context: Context): ComponentName =
+            ComponentName(context, LaunchNoteTaskActivity::class.java)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 23b5241..314566e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -633,12 +633,17 @@
         mFSIUpdateCandidates.removeAll(toRemoveForFSI)
     }
 
-    /** When an action is pressed on a notification, end HeadsUp lifetime extension. */
+    /**
+     * When an action is pressed on a notification, make sure we don't lifetime-extend it in the
+     * future by informing the HeadsUpManager, and make sure we don't keep lifetime-extending it if
+     * we already are.
+     *
+     * @see HeadsUpManager.setUserActionMayIndirectlyRemove
+     * @see HeadsUpManager.canRemoveImmediately
+     */
     private val mActionPressListener = Consumer<NotificationEntry> { entry ->
-        if (mNotifsExtendingLifetime.contains(entry)) {
-            val removeInMillis = mHeadsUpManager.getEarliestRemovalTime(entry.key)
-            mExecutor.executeDelayed({ endNotifLifetimeExtensionIfExtended(entry) }, removeInMillis)
-        }
+        mHeadsUpManager.setUserActionMayIndirectlyRemove(entry)
+        mExecutor.execute { endNotifLifetimeExtensionIfExtended(entry) }
     }
 
     private val mLifetimeExtender = object : NotifLifetimeExtender {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index ed8050a..92a7854 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -393,6 +393,31 @@
         }
     }
 
+    /**
+     * Notes that the user took an action on an entry that might indirectly cause the system or the
+     * app to remove the notification.
+     *
+     * @param entry the entry that might be indirectly removed by the user's action
+     *
+     * @see com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator#mActionPressListener
+     * @see #canRemoveImmediately(String)
+     */
+    public void setUserActionMayIndirectlyRemove(@NonNull NotificationEntry entry) {
+        HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
+        if (headsUpEntry != null) {
+            headsUpEntry.userActionMayIndirectlyRemove = true;
+        }
+    }
+
+    @Override
+    public boolean canRemoveImmediately(@NonNull String key) {
+        HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
+        if (headsUpEntry != null && headsUpEntry.userActionMayIndirectlyRemove) {
+            return true;
+        }
+        return super.canRemoveImmediately(key);
+    }
+
     @NonNull
     @Override
     protected HeadsUpEntry createAlertEntry() {
@@ -421,6 +446,8 @@
      */
     protected class HeadsUpEntry extends AlertEntry {
         public boolean remoteInputActive;
+        public boolean userActionMayIndirectlyRemove;
+
         protected boolean expanded;
         protected boolean wasUnpinned;
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 109c1cf..b848d2e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -930,7 +930,6 @@
                 showRingerDrawer();
             }
         });
-        updateSelectedRingerContainerDescription(mIsRingerDrawerOpen);
 
         mRingerDrawerVibrate.setOnClickListener(
                 new RingerDrawerItemClickListener(RINGER_MODE_VIBRATE));
@@ -993,18 +992,6 @@
                         : 0;
     }
 
-    @VisibleForTesting String getSelectedRingerContainerDescription() {
-        return mSelectedRingerContainer.getContentDescription().toString();
-    }
-
-    @VisibleForTesting void toggleRingerDrawer(boolean show) {
-        if (show) {
-            showRingerDrawer();
-        } else {
-            hideRingerDrawer();
-        }
-    }
-
     /** Animates in the ringer drawer. */
     private void showRingerDrawer() {
         if (mIsRingerDrawerOpen) {
@@ -1082,7 +1069,12 @@
                     .start();
         }
 
-        updateSelectedRingerContainerDescription(true);
+        // When the ringer drawer is open, tapping the currently selected ringer will set the ringer
+        // to the current ringer mode. Change the content description to that, instead of the 'tap
+        // to change ringer mode' default.
+        mSelectedRingerContainer.setContentDescription(
+                mContext.getString(getStringDescriptionResourceForRingerMode(
+                        mState.ringerModeInternal)));
 
         mIsRingerDrawerOpen = true;
     }
@@ -1128,38 +1120,14 @@
                 .translationY(0f)
                 .start();
 
-        updateSelectedRingerContainerDescription(false);
+        // When the drawer is closed, tapping the selected ringer drawer will open it, allowing the
+        // user to change the ringer.
+        mSelectedRingerContainer.setContentDescription(
+                mContext.getString(R.string.volume_ringer_change));
 
         mIsRingerDrawerOpen = false;
     }
 
-
-    /**
-     * @param open false to set the description when drawer is closed
-     */
-    private void updateSelectedRingerContainerDescription(boolean open) {
-        if (mState == null) return;
-
-        String currentMode = mContext.getString(getStringDescriptionResourceForRingerMode(
-                mState.ringerModeInternal));
-        String tapToSelect;
-
-        if (open) {
-            // When the ringer drawer is open, tapping the currently selected ringer will set the
-            // ringer to the current ringer mode. Change the content description to that, instead of
-            // the 'tap to change ringer mode' default.
-            tapToSelect = "";
-
-        } else {
-            // When the drawer is closed, tapping the selected ringer drawer will open it, allowing
-            // the user to change the ringer. The user needs to know that, and also the current mode
-            currentMode += ", ";
-            tapToSelect = mContext.getString(R.string.volume_ringer_change);
-        }
-
-        mSelectedRingerContainer.setContentDescription(currentMode + tapToSelect);
-    }
-
     private void initSettingsH(int lockTaskModeState) {
         if (mSettingsView != null) {
             mSettingsView.setVisibility(
@@ -1735,7 +1703,7 @@
         });
     }
 
-    @VisibleForTesting int getStringDescriptionResourceForRingerMode(int mode) {
+    private int getStringDescriptionResourceForRingerMode(int mode) {
         switch (mode) {
             case RINGER_MODE_SILENT:
                 return R.string.volume_ringer_status_silent;
@@ -1817,7 +1785,6 @@
             updateVolumeRowH(row);
         }
         updateRingerH();
-        updateSelectedRingerContainerDescription(mIsRingerDrawerOpen);
         mWindow.setTitle(composeWindowTitle());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index e99f8b6..0954f6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -72,6 +72,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.doNothing
 import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
@@ -227,31 +228,7 @@
 
     // region showNoteTask
     @Test
-    fun showNoteTask_keyguardIsLocked_shouldStartActivityAndLogUiEvent() {
-        val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true)
-        whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
-        whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
-
-        createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!)
-
-        val intentCaptor = argumentCaptor<Intent>()
-        val userCaptor = argumentCaptor<UserHandle>()
-        verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
-        assertThat(intentCaptor.value).run {
-            hasAction(ACTION_CREATE_NOTE)
-            hasPackage(NOTE_TASK_PACKAGE_NAME)
-            hasFlags(FLAG_ACTIVITY_NEW_TASK)
-            hasFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
-            hasFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
-            extras().bool(EXTRA_USE_STYLUS_MODE).isTrue()
-        }
-        assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
-        verify(eventLogger).logNoteTaskOpened(expectedInfo)
-        verifyZeroInteractions(bubbles)
-    }
-
-    @Test
-    fun showNoteTaskWithUser_keyguardIsLocked_shouldStartActivityWithExpectedUserAndLogUiEvent() {
+    fun showNoteTaskAsUser_keyguardIsLocked_shouldStartActivityWithExpectedUserAndLogUiEvent() {
         val user10 = UserHandle.of(/* userId= */ 10)
         val expectedInfo =
             NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true, user = user10)
@@ -278,6 +255,30 @@
     }
 
     @Test
+    fun showNoteTask_keyguardIsLocked_notesIsClosed_shouldStartActivityAndLogUiEvent() {
+        val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true)
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
+        whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
+
+        createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!)
+
+        val intentCaptor = argumentCaptor<Intent>()
+        val userCaptor = argumentCaptor<UserHandle>()
+        verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+        assertThat(intentCaptor.value).run {
+            hasAction(ACTION_CREATE_NOTE)
+            hasPackage(NOTE_TASK_PACKAGE_NAME)
+            hasFlags(FLAG_ACTIVITY_NEW_TASK)
+            hasFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
+            hasFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+            extras().bool(EXTRA_USE_STYLUS_MODE).isTrue()
+        }
+        assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
+        verify(eventLogger).logNoteTaskOpened(expectedInfo)
+        verifyZeroInteractions(bubbles)
+    }
+
+    @Test
     fun showNoteTask_keyguardIsLocked_noteIsOpen_shouldCloseActivityAndLogUiEvent() {
         val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true)
         whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
@@ -301,7 +302,7 @@
     }
 
     @Test
-    fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesWithoutLoggingUiEvent() {
+    fun showNoteTask_keyguardIsUnlocked_noteIsClosed_shouldStartBubblesWithoutLoggingUiEvent() {
         val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = false)
         whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
         whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
@@ -309,7 +310,23 @@
         createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!)
 
         // Context package name used to create bubble icon from drawable resource id
-        verify(context).packageName
+        verify(context, atLeastOnce()).packageName
+        verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle)
+        verifyZeroInteractions(eventLogger)
+    }
+
+    @Test
+    fun showNoteTask_keyguardIsUnlocked_noteIsOpen_shouldStartBubblesWithoutLoggingUiEvent() {
+        val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = false)
+        whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
+        whenever(activityManager.getRunningTasks(anyInt()))
+            .thenReturn(listOf(NOTE_RUNNING_TASK_INFO))
+
+        createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!)
+
+        // Context package name used to create bubble icon from drawable resource id
+        verify(context, atLeastOnce()).packageName
         verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle)
         verifyZeroInteractions(eventLogger)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
index 3435450..24f39d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
@@ -16,37 +16,47 @@
 package com.android.systemui.notetask
 
 import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 
 /** atest SystemUITests:NoteTaskInfoTest */
 @SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(AndroidTestingRunner::class)
 internal class NoteTaskInfoTest : SysuiTestCase() {
 
-    private fun createNoteTaskInfo(): NoteTaskInfo =
-        NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID, UserHandle.of(0))
-
     @Test
     fun launchMode_keyguardLocked_launchModeActivity() {
-        val underTest = createNoteTaskInfo().copy(isKeyguardLocked = true)
+        val underTest = DEFAULT_INFO.copy(isKeyguardLocked = true)
 
         assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.Activity)
     }
 
     @Test
-    fun launchMode_keyguardUnlocked_launchModeActivity() {
-        val underTest = createNoteTaskInfo().copy(isKeyguardLocked = false)
+    fun launchMode_multiWindowMode_launchModeActivity() {
+        val underTest = DEFAULT_INFO.copy(entryPoint = WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE)
+
+        assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.Activity)
+    }
+
+    @Test
+    fun launchMode_keyguardUnlocked_launchModeAppBubble() {
+        val underTest = DEFAULT_INFO.copy(isKeyguardLocked = false)
 
         assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.AppBubble)
     }
 
     private companion object {
-        const val NOTES_PACKAGE_NAME = "com.android.note.app"
-        const val NOTES_UID = 123456
+
+        val DEFAULT_INFO =
+            NoteTaskInfo(
+                packageName = "com.android.note.app",
+                uid = 123456,
+                user = UserHandle.of(0),
+            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 283efe2..257cc5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -221,16 +221,35 @@
     }
 
     @Test
-    fun hunExtensionCancelledWhenHunActionPressed() {
+    fun actionPressCancelsExistingLifetimeExtension() {
         whenever(headsUpManager.isSticky(anyString())).thenReturn(true)
         addHUN(entry)
+
         whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(false)
         whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L)
-        assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
+        assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, /* reason = */ 0))
+
         actionPressListener.accept(entry)
-        executor.advanceClockToLast()
         executor.runAllReady()
-        verify(headsUpManager, times(1)).removeNotification(eq(entry.key), eq(true))
+        verify(endLifetimeExtension, times(1)).onEndLifetimeExtension(notifLifetimeExtender, entry)
+
+        collectionListener.onEntryRemoved(entry, /* reason = */ 0)
+        verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any())
+    }
+
+    @Test
+    fun actionPressPreventsFutureLifetimeExtension() {
+        whenever(headsUpManager.isSticky(anyString())).thenReturn(true)
+        addHUN(entry)
+
+        actionPressListener.accept(entry)
+        verify(headsUpManager, times(1)).setUserActionMayIndirectlyRemove(entry)
+
+        whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(true)
+        assertFalse(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
+
+        collectionListener.onEntryRemoved(entry, /* reason = */ 0)
+        verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any())
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index b0c1486..85b1ec1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -89,6 +89,7 @@
 
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -102,6 +103,7 @@
 /**
  * Tests for {@link NotificationStackScrollLayout}.
  */
+@Ignore("b/255552856")
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -373,6 +375,20 @@
     }
 
     @Test
+    public void testAppearFractionCalculation() {
+        // appear start position
+        when(mNotificationShelf.getIntrinsicHeight()).thenReturn(100);
+        // because it's the same as shelf height, appear start position equals shelf height
+        mStackScroller.mStatusBarHeight = 100;
+        // appear end position
+        when(mEmptyShadeView.getHeight()).thenReturn(200);
+
+        assertEquals(0f, mStackScroller.calculateAppearFraction(100));
+        assertEquals(1f, mStackScroller.calculateAppearFraction(200));
+        assertEquals(0.5f, mStackScroller.calculateAppearFraction(150));
+    }
+
+    @Test
     public void testAppearFractionCalculationIsNotNegativeWhenShelfBecomesSmaller() {
         // this situation might occur if status bar height is defined in pixels while shelf height
         // in dp and screen density changes - appear start position
@@ -574,7 +590,6 @@
 
     @Test
     public void testReInflatesFooterViews() {
-        when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
         clearInvocations(mStackScroller);
         mStackScroller.reinflateViews();
         verify(mStackScroller).setFooterView(any());
@@ -901,7 +916,7 @@
         mStackScroller.setHasFilteredOutSeenNotifications(true);
         mStackScroller.updateEmptyShadeView(true, false);
 
-        verify(mEmptyShadeView).setFooterText(not(eq(0)));
+        verify(mEmptyShadeView).setFooterText(not(0));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index 487d26d..a797e03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -20,7 +20,6 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotSame;
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -346,4 +345,17 @@
         assertEquals(HeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
                 mUiEventLoggerFake.eventId(0));
     }
+
+    @Test
+    public void testSetUserActionMayIndirectlyRemove() {
+        NotificationEntry notifEntry = new NotificationEntryBuilder()
+                .setSbn(createNewNotification(/* id= */ 0))
+                .build();
+
+        mHeadsUpManager.showNotification(notifEntry);
+        assertFalse(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+
+        mHeadsUpManager.setUserActionMayIndirectlyRemove(notifEntry);
+        assertTrue(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 2ea6368..8f725be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -16,16 +16,11 @@
 
 package com.android.systemui.volume;
 
-import static android.media.AudioManager.RINGER_MODE_NORMAL;
-import static android.media.AudioManager.RINGER_MODE_SILENT;
-import static android.media.AudioManager.RINGER_MODE_VIBRATE;
-
 import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
 import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
 import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
 
 import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotSame;
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -298,7 +293,7 @@
     @Test
     public void testSelectVibrateFromDrawer() {
         final State initialUnsetState = new State();
-        initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL;
+        initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
         mDialog.onStateChangedH(initialUnsetState);
 
         mActiveRinger.performClick();
@@ -312,7 +307,7 @@
     @Test
     public void testSelectMuteFromDrawer() {
         final State initialUnsetState = new State();
-        initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL;
+        initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
         mDialog.onStateChangedH(initialUnsetState);
 
         mActiveRinger.performClick();
@@ -334,7 +329,7 @@
 
         // Make sure we've actually changed the ringer mode.
         verify(mVolumeDialogController, times(1)).setRingerMode(
-                RINGER_MODE_NORMAL, false);
+                AudioManager.RINGER_MODE_NORMAL, false);
     }
 
     /**
@@ -516,85 +511,6 @@
         }
     }
 
-    private enum RingerDrawerState {INIT, OPEN, CLOSE}
-
-    @Test
-    public void ringerModeNormal_ringerContainerDescribesItsState() {
-        assertRingerContainerDescribesItsState(RINGER_MODE_NORMAL, RingerDrawerState.INIT);
-    }
-
-    @Test
-    public void ringerModeSilent_ringerContainerDescribesItsState() {
-        assertRingerContainerDescribesItsState(RINGER_MODE_SILENT, RingerDrawerState.INIT);
-    }
-
-    @Test
-    public void ringerModeVibrate_ringerContainerDescribesItsState() {
-        assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.INIT);
-    }
-
-    @Test
-    public void ringerModeNormal_openDrawer_ringerContainerDescribesItsState() {
-        assertRingerContainerDescribesItsState(RINGER_MODE_NORMAL, RingerDrawerState.OPEN);
-    }
-
-    @Test
-    public void ringerModeSilent_openDrawer_ringerContainerDescribesItsState() {
-        assertRingerContainerDescribesItsState(RINGER_MODE_SILENT, RingerDrawerState.OPEN);
-    }
-
-    @Test
-    public void ringerModeVibrate_openDrawer_ringerContainerDescribesItsState() {
-        assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.OPEN);
-    }
-
-    @Test
-    public void ringerModeNormal_closeDrawer_ringerContainerDescribesItsState() {
-        assertRingerContainerDescribesItsState(RINGER_MODE_NORMAL, RingerDrawerState.CLOSE);
-    }
-
-    @Test
-    public void ringerModeSilent_closeDrawer_ringerContainerDescribesItsState() {
-        assertRingerContainerDescribesItsState(RINGER_MODE_SILENT, RingerDrawerState.CLOSE);
-    }
-
-    @Test
-    public void ringerModeVibrate_closeDrawer_ringerContainerDescribesItsState() {
-        assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.CLOSE);
-    }
-
-    /**
-     * The content description should include ringer state, and the correct one.
-     */
-    private void assertRingerContainerDescribesItsState(int ringerMode,
-            RingerDrawerState drawerState) {
-        State state = createShellState();
-        state.ringerModeInternal = ringerMode;
-        mDialog.onStateChangedH(state);
-
-        mDialog.show(SHOW_REASON_UNKNOWN);
-
-        if (drawerState != RingerDrawerState.INIT) {
-            // in both cases we first open the drawer
-            mDialog.toggleRingerDrawer(true);
-
-            if (drawerState == RingerDrawerState.CLOSE) {
-                mDialog.toggleRingerDrawer(false);
-            }
-        }
-
-        String ringerContainerDescription = mDialog.getSelectedRingerContainerDescription();
-        String ringerDescription = mContext.getString(
-                mDialog.getStringDescriptionResourceForRingerMode(ringerMode));
-
-        if (drawerState == RingerDrawerState.OPEN) {
-            assertEquals(ringerDescription, ringerContainerDescription);
-        } else {
-            assertNotSame(ringerDescription, ringerContainerDescription);
-            assertTrue(ringerContainerDescription.startsWith(ringerDescription));
-        }
-    }
-
     @After
     public void teardown() {
         if (mDialog != null) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 6b55d7e..cd2f844 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -987,8 +987,12 @@
                     "Virtual device doesn't have a virtual display with ID " + displayId);
         }
 
-        releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
-
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/WallpaperUpdateReceiver.java b/services/core/java/com/android/server/WallpaperUpdateReceiver.java
index 2812233..9917892 100644
--- a/services/core/java/com/android/server/WallpaperUpdateReceiver.java
+++ b/services/core/java/com/android/server/WallpaperUpdateReceiver.java
@@ -88,7 +88,7 @@
         } else {
             //live wallpaper
             ComponentName currCN = info.getComponent();
-            ComponentName defaultCN = WallpaperManager.getCmfDefaultWallpaperComponent(context);
+            ComponentName defaultCN = WallpaperManager.getDefaultWallpaperComponent(context);
             if (!currCN.equals(defaultCN)) {
                 return true;
             }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 1f8ff73..fc72a77 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -2214,6 +2214,7 @@
                 mDeviceInventory.removePreferredDevicesForStrategyInt(mAccessibilityStrategyId);
             }
             mDeviceInventory.applyConnectedDevicesRoles();
+            mDeviceInventory.reapplyExternalDevicesRoles();
         } else {
             mDeviceInventory.setPreferredDevicesForStrategyInt(
                     mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice));
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index d1cae49..219dda3 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -361,23 +361,34 @@
                         AudioSystem.DEVICE_STATE_AVAILABLE,
                         di.mDeviceCodecFormat);
             }
-            mAppliedStrategyRoles.clear();
             mAppliedStrategyRolesInt.clear();
-            mAppliedPresetRoles.clear();
             mAppliedPresetRolesInt.clear();
             applyConnectedDevicesRoles_l();
         }
+        reapplyExternalDevicesRoles();
+    }
+
+    /*package*/ void reapplyExternalDevicesRoles() {
+        synchronized (mDevicesLock) {
+            mAppliedStrategyRoles.clear();
+            mAppliedPresetRoles.clear();
+        }
         synchronized (mPreferredDevices) {
             mPreferredDevices.forEach((strategy, devices) -> {
-                setPreferredDevicesForStrategy(strategy, devices); });
+                setPreferredDevicesForStrategy(strategy, devices);
+            });
         }
         synchronized (mNonDefaultDevices) {
             mNonDefaultDevices.forEach((strategy, devices) -> {
                 addDevicesRoleForStrategy(strategy, AudioSystem.DEVICE_ROLE_DISABLED,
-                        devices, false /* internal */); });
+                        devices, false /* internal */);
+            });
         }
         synchronized (mPreferredDevicesForCapturePreset) {
-            // TODO: call audiosystem to restore
+            mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> {
+                setDevicesRoleForCapturePreset(
+                        capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
+            });
         }
     }
 
@@ -1163,6 +1174,7 @@
             return mAudioSystem.removeDevicesRoleForStrategy(s, r, d); });
         purgeRoles(mAppliedPresetRolesInt, (p, r, d) -> {
             return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d); });
+        reapplyExternalDevicesRoles();
     }
 
     @GuardedBy("mDevicesLock")
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f2d1357..aaf13eb 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3714,11 +3714,16 @@
             if (parser.getName().equals(TAG_PERMISSIONS)) {
                 final LegacyPermissionState legacyState;
                 if (ps.hasSharedUser()) {
-                    legacyState = getSettingLPr(ps.getSharedUserAppId()).getLegacyPermissionState();
+                    final SettingBase sharedUserSettings = getSettingLPr(
+                            ps.getSharedUserAppId());
+                    legacyState = sharedUserSettings != null
+                            ? sharedUserSettings.getLegacyPermissionState() : null;
                 } else {
                     legacyState = ps.getLegacyPermissionState();
                 }
-                readInstallPermissionsLPr(parser, legacyState, users);
+                if (legacyState != null) {
+                    readInstallPermissionsLPr(parser, legacyState, users);
+                }
             } else if (parser.getName().equals(TAG_USES_STATIC_LIB)) {
                 readUsesStaticLibLPw(parser, ps);
             } else if (parser.getName().equals(TAG_USES_SDK_LIB)) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index a020728..5b3514c 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2547,10 +2547,7 @@
         enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS,
                 "getShareTargets");
         final ComponentName chooser = injectChooserActivity();
-        final String pkg = (chooser != null
-                && mPackageManagerInternal.getComponentEnabledSetting(chooser,
-                injectBinderCallingUid(), userId) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
-                ? chooser.getPackageName() : mContext.getPackageName();
+        final String pkg = chooser != null ? chooser.getPackageName() : mContext.getPackageName();
         synchronized (mLock) {
             throwIfUserLockedL(userId);
             final List<ShortcutManager.ShareShortcutInfo> shortcutInfoList = new ArrayList<>();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 3e7ae33..2499529 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -141,6 +141,7 @@
 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.SharedUserApi;
 import com.android.server.pm.pkg.component.ComponentMutateUtils;
 import com.android.server.pm.pkg.component.ParsedPermission;
 import com.android.server.pm.pkg.component.ParsedPermissionGroup;
@@ -4538,8 +4539,13 @@
             final int appId = ps.getAppId();
             final LegacyPermissionState legacyState;
             if (ps.hasSharedUser()) {
-                legacyState = mPackageManagerInt.getSharedUserApi(
-                        ps.getSharedUserAppId()).getSharedUserLegacyPermissionState();
+                final int sharedUserId = ps.getSharedUserAppId();
+                SharedUserApi sharedUserApi = mPackageManagerInt.getSharedUserApi(sharedUserId);
+                if (sharedUserApi == null) {
+                    Slog.wtf(TAG, "Missing shared user Api for " + sharedUserId);
+                    return;
+                }
+                legacyState = sharedUserApi.getSharedUserLegacyPermissionState();
             } else {
                 legacyState = ps.getLegacyPermissionState();
             }
@@ -4584,8 +4590,13 @@
             ps.setInstallPermissionsFixed(false);
             final LegacyPermissionState legacyState;
             if (ps.hasSharedUser()) {
-                legacyState = mPackageManagerInt.getSharedUserApi(
-                        ps.getSharedUserAppId()).getSharedUserLegacyPermissionState();
+                final int sharedUserId = ps.getSharedUserAppId();
+                SharedUserApi sharedUserApi = mPackageManagerInt.getSharedUserApi(sharedUserId);
+                if (sharedUserApi == null) {
+                    Slog.wtf(TAG, "Missing shared user Api for " + sharedUserId);
+                    return;
+                }
+                legacyState = sharedUserApi.getSharedUserLegacyPermissionState();
             } else {
                 legacyState = ps.getLegacyPermissionState();
             }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index 9ff6a0d..d87fca4 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -133,16 +133,14 @@
      */
     final Rect cropHint = new Rect(0, 0, 0, 0);
 
-    WallpaperData(int userId, File wallpaperDir, String inputFileName, String cropFileName) {
-        this.userId = userId;
-        wallpaperFile = new File(wallpaperDir, inputFileName);
-        cropFile = new File(wallpaperDir, cropFileName);
-    }
-
     WallpaperData(int userId, @SetWallpaperFlags int wallpaperType) {
-        this(userId, getWallpaperDir(userId),
-                (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_ORIG : WALLPAPER,
-                (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_CROP : WALLPAPER_CROP);
+        this.userId = userId;
+        this.mWhich = wallpaperType;
+        File wallpaperDir = getWallpaperDir(userId);
+        String wallpaperFileName = (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_ORIG : WALLPAPER;
+        String cropFileName = (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_CROP : WALLPAPER_CROP;
+        this.wallpaperFile = new File(wallpaperDir, wallpaperFileName);
+        this.cropFile = new File(wallpaperDir, cropFileName);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index a079875..9e9b344 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1588,7 +1588,7 @@
         mShuttingDown = false;
         mImageWallpaper = ComponentName.unflattenFromString(
                 context.getResources().getString(R.string.image_wallpaper_component));
-        mDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(context);
+        mDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(context);
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mIPackageManager = AppGlobals.getPackageManager();
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 1a322ff..5e2618b 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -454,7 +454,7 @@
         if (mSource.getType() == WindowInsets.Type.ime()) {
             setClientVisible(target.isRequestedVisible(WindowInsets.Type.ime()));
         }
-        final Transaction t = mDisplayContent.getSyncTransaction();
+        final Transaction t = mWindowContainer.getSyncTransaction();
         mWindowContainer.startAnimation(t, mAdapter, !mClientVisible /* hidden */,
                 ANIMATION_TYPE_INSETS_CONTROL);
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index bb6f805..7104739 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3290,6 +3290,12 @@
             scheduleAnimation();
         }
 
+        // Let organizer manage task visibility for shell transition. So don't change it's
+        // visibility during collecting.
+        if (mTransitionController.isCollecting() && mCreatedByOrganizer) {
+            return;
+        }
+
         // We intend to let organizer manage task visibility but it doesn't
         // have enough information until we finish shell transitions.
         // In the mean time we do an easy fix here.
@@ -5687,17 +5693,28 @@
     }
 
     private boolean moveTaskToBackInner(@NonNull Task task) {
-        moveToBack("moveTaskToBackInner", task);
-
-        if (inPinnedWindowingMode()) {
-            mTaskSupervisor.removeRootTask(this);
-            return true;
+        if (mTransitionController.isShellTransitionsEnabled()) {
+            // Preventing from update surface position for WindowState if configuration changed,
+            // because the position is depends on WindowFrame, so update the position before
+            // relayout will only update it to "old" position.
+            mAtmService.deferWindowLayout();
         }
+        try {
+            moveToBack("moveTaskToBackInner", task);
 
-        mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */,
-                mDisplayContent.mDisplayId, false /* markFrozenIfConfigChanged */,
-                false /* deferResume */);
+            if (inPinnedWindowingMode()) {
+                mTaskSupervisor.removeRootTask(this);
+                return true;
+            }
 
+            mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */,
+                    mDisplayContent.mDisplayId, false /* markFrozenIfConfigChanged */,
+                    false /* deferResume */);
+        } finally {
+            if (mTransitionController.isShellTransitionsEnabled()) {
+                mAtmService.continueWindowLayout();
+            }
+        }
         ActivityRecord topActivity = getDisplayArea().topRunningActivity();
         Task topRootTask = topActivity.getRootTask();
         if (topRootTask != null && topRootTask != this && topActivity.isState(RESUMED)) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 9a5f766..795d022 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -690,6 +690,7 @@
         if (!wc.mDisplayContent.getDisplayPolicy().isScreenOnFully()
                 || wc.mDisplayContent.getDisplayInfo().state == Display.STATE_OFF) {
             mFlags |= WindowManager.TRANSIT_FLAG_INVISIBLE;
+            return;
         }
 
         if (mContainerFreezer == null) {
@@ -1190,7 +1191,11 @@
         // processed all the participants first (in particular, we want to trigger pip-enter first)
         for (int i = 0; i < mParticipants.size(); ++i) {
             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
-            if (ar != null) {
+            // If the activity was just inserted to an invisible task, it will keep INITIALIZING
+            // state. Then no need to notify the callback to avoid clearing some states
+            // unexpectedly, e.g. launch-task-behind.
+            if (ar != null && (ar.isVisibleRequested()
+                    || !ar.isState(ActivityRecord.State.INITIALIZING))) {
                 mController.dispatchLegacyAppTransitionFinished(ar);
             }
         }
@@ -2244,11 +2249,17 @@
             WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, wc);
 
             // Make leash based on highest (z-order) direct child of ancestor with a participant.
+            // Check whether the ancestor is belonged to last parent, shouldn't happen.
+            final boolean hasReparent = !wc.isDescendantOf(ancestor);
             WindowContainer leashReference = wc;
-            while (leashReference.getParent() != ancestor) {
-                leashReference = leashReference.getParent();
+            if (hasReparent) {
+                Slog.e(TAG, "Did not find common ancestor! Ancestor= " + ancestor
+                        + " target= " + wc);
+            } else {
+                while (leashReference.getParent() != ancestor) {
+                    leashReference = leashReference.getParent();
+                }
             }
-
             final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
                     "Transition Root: " + leashReference.getName()).build();
             rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots");
@@ -2307,23 +2318,13 @@
                 task.fillTaskInfo(tinfo);
                 change.setTaskInfo(tinfo);
                 change.setRotationAnimation(getTaskRotationAnimation(task));
-                final ActivityRecord topMostActivity = task.getTopMostActivity();
-                change.setAllowEnterPip(topMostActivity != null
-                        && topMostActivity.checkEnterPictureInPictureAppOpsState());
                 final ActivityRecord topRunningActivity = task.topRunningActivity();
-                if (topRunningActivity != null && task.mDisplayContent != null
-                        // Display won't be rotated for multi window Task, so the fixed rotation
-                        // won't be applied. This can happen when the windowing mode is changed
-                        // before the previous fixed rotation is applied.
-                        && (!task.inMultiWindowMode() || !topRunningActivity.inMultiWindowMode())) {
-                    // If Activity is in fixed rotation, its will be applied with the next rotation,
-                    // when the Task is still in the previous rotation.
-                    final int taskRotation = task.getWindowConfiguration().getDisplayRotation();
-                    final int activityRotation = topRunningActivity.getWindowConfiguration()
-                            .getDisplayRotation();
-                    if (taskRotation != activityRotation) {
-                        change.setEndFixedRotation(activityRotation);
+                if (topRunningActivity != null) {
+                    if (topRunningActivity.info.supportsPictureInPicture()) {
+                        change.setAllowEnterPip(
+                                topRunningActivity.checkEnterPictureInPictureAppOpsState());
                     }
+                    setEndFixedRotationIfNeeded(change, task, topRunningActivity);
                 }
             } else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) {
                 change.setRotationAnimation(ROTATION_ANIMATION_SEAMLESS);
@@ -2431,6 +2432,48 @@
         }
         return animOptions;
     }
+
+    private static void setEndFixedRotationIfNeeded(@NonNull TransitionInfo.Change change,
+            @NonNull Task task, @NonNull ActivityRecord taskTopRunning) {
+        if (!taskTopRunning.isVisibleRequested()) {
+            // Fixed rotation only applies to opening or changing activity.
+            return;
+        }
+        if (task.inMultiWindowMode() && taskTopRunning.inMultiWindowMode()) {
+            // Display won't be rotated for multi window Task, so the fixed rotation won't be
+            // applied. This can happen when the windowing mode is changed before the previous
+            // fixed rotation is applied. Check both task and activity because the activity keeps
+            // fullscreen mode when the task is entering PiP.
+            return;
+        }
+        final int taskRotation = task.getWindowConfiguration().getDisplayRotation();
+        final int activityRotation = taskTopRunning.getWindowConfiguration()
+                .getDisplayRotation();
+        // If the Activity uses fixed rotation, its rotation will be applied to display after
+        // the current transition is done, while the Task is still in the previous rotation.
+        if (taskRotation != activityRotation) {
+            change.setEndFixedRotation(activityRotation);
+            return;
+        }
+
+        // For example, the task is entering PiP so it no longer decides orientation. If the next
+        // orientation source (it could be an activity which was behind the PiP or launching to top)
+        // will change display rotation, then set the fixed rotation hint as well so the animation
+        // can consider the rotated position.
+        if (!task.inPinnedWindowingMode() || taskTopRunning.mDisplayContent.inTransition()) {
+            return;
+        }
+        final WindowContainer<?> orientationSource =
+                taskTopRunning.mDisplayContent.getLastOrientationSource();
+        if (orientationSource == null) {
+            return;
+        }
+        final int nextRotation = orientationSource.getWindowConfiguration().getDisplayRotation();
+        if (taskRotation != nextRotation) {
+            change.setEndFixedRotation(nextRotation);
+        }
+    }
+
     /**
      * Finds the top-most common ancestor of app targets.
      *
@@ -2453,6 +2496,20 @@
                 // Skip the non-app window or windows on a different display
                 continue;
             }
+            // Re-initiate the last parent as the initial ancestor instead of the top target.
+            // When move a leaf task from organized task to display area, try to keep the transition
+            // root be the original organized task for close transition animation.
+            // Otherwise, shell will use wrong root layer to play animation.
+            // Note: Since the target is sorted, so only need to do this at the lowest target.
+            if (change.mStartParent != null && wc.getParent() != null
+                    && change.mStartParent.isAttached() && wc.getParent() != change.mStartParent
+                    && i == targets.size() - 1) {
+                final int transitionMode = change.getTransitMode(wc);
+                if (transitionMode == TRANSIT_CLOSE || transitionMode == TRANSIT_TO_BACK) {
+                    ancestor = change.mStartParent;
+                    continue;
+                }
+            }
             while (!wc.isDescendantOf(ancestor)) {
                 ancestor = ancestor.getParent();
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d84c85c..67572bf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -88,6 +88,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
 import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.fixScale;
 import static android.view.WindowManagerGlobal.ADD_OKAY;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
@@ -3591,6 +3592,7 @@
 
     public void setCurrentUser(@UserIdInt int newUserId) {
         synchronized (mGlobalLock) {
+            mAtmService.getTransitionController().requestTransitionIfNeeded(TRANSIT_OPEN, null);
             mCurrentUserId = newUserId;
             mPolicy.setCurrentUserLw(newUserId);
             mKeyguardDisableHandler.setCurrentUser(newUserId);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index c918fb8..9c1d765 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -101,7 +101,7 @@
     private final UserManager mUserManager;
 
     // TODO(b/256849338): add more granular locks
-    private final Object mLock = new Object();
+    private final Object mLock;
 
     /**
      * Map of <userId, Map<policyKey, policyState>>
@@ -122,9 +122,11 @@
 
     DevicePolicyEngine(
             @NonNull Context context,
-            @NonNull DeviceAdminServiceController deviceAdminServiceController) {
+            @NonNull DeviceAdminServiceController deviceAdminServiceController,
+            @NonNull Object lock) {
         mContext = Objects.requireNonNull(context);
         mDeviceAdminServiceController = Objects.requireNonNull(deviceAdminServiceController);
+        mLock = Objects.requireNonNull(lock);
         mUserManager = mContext.getSystemService(UserManager.class);
         mLocalPolicies = new SparseArray<>();
         mGlobalPolicies = new HashMap<>();
@@ -152,8 +154,8 @@
             PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
 
             if (policyDefinition.isNonCoexistablePolicy()) {
-                setNonCoexistableLocalPolicy(policyDefinition, localPolicyState, enforcingAdmin,
-                        value, userId, skipEnforcePolicy);
+                setNonCoexistableLocalPolicyLocked(policyDefinition, localPolicyState,
+                        enforcingAdmin, value, userId, skipEnforcePolicy);
                 return;
             }
 
@@ -173,7 +175,7 @@
             // the data structures.
             if (!skipEnforcePolicy) {
                 if (policyChanged) {
-                    onLocalPolicyChanged(policyDefinition, enforcingAdmin, userId);
+                    onLocalPolicyChangedLocked(policyDefinition, enforcingAdmin, userId);
                 }
                 boolean policyEnforced = Objects.equals(
                         localPolicyState.getCurrentResolvedPolicy(), value);
@@ -211,7 +213,7 @@
      *
      * <p>Passing a {@code null} value means the policy set by this admin should be removed.
      */
-    private <V> void setNonCoexistableLocalPolicy(
+    private <V> void setNonCoexistableLocalPolicyLocked(
             PolicyDefinition<V> policyDefinition,
             PolicyState<V> localPolicyState,
             EnforcingAdmin enforcingAdmin,
@@ -266,8 +268,8 @@
             PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
 
             if (policyDefinition.isNonCoexistablePolicy()) {
-                setNonCoexistableLocalPolicy(policyDefinition, localPolicyState, enforcingAdmin,
-                        /* value= */ null, userId, /* skipEnforcePolicy= */ false);
+                setNonCoexistableLocalPolicyLocked(policyDefinition, localPolicyState,
+                        enforcingAdmin, /* value= */ null, userId, /* skipEnforcePolicy= */ false);
                 return;
             }
 
@@ -282,7 +284,7 @@
             }
 
             if (policyChanged) {
-                onLocalPolicyChanged(policyDefinition, enforcingAdmin, userId);
+                onLocalPolicyChangedLocked(policyDefinition, enforcingAdmin, userId);
             }
 
             // For a removePolicy to be enforced, it means no current policy exists
@@ -348,7 +350,7 @@
     /**
      * Enforces the new policy and notifies relevant admins.
      */
-    private <V> void onLocalPolicyChanged(
+    private <V> void onLocalPolicyChangedLocked(
             @NonNull PolicyDefinition<V> policyDefinition,
             @NonNull EnforcingAdmin enforcingAdmin,
             int userId) {
@@ -358,7 +360,7 @@
                 policyDefinition, localPolicyState.getCurrentResolvedPolicy(), userId);
 
         // Send policy updates to admins who've set it locally
-        sendPolicyChangedToAdmins(
+        sendPolicyChangedToAdminsLocked(
                 localPolicyState,
                 enforcingAdmin,
                 policyDefinition,
@@ -369,7 +371,7 @@
         // Send policy updates to admins who've set it globally
         if (hasGlobalPolicyLocked(policyDefinition)) {
             PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
-            sendPolicyChangedToAdmins(
+            sendPolicyChangedToAdminsLocked(
                     globalPolicyState,
                     enforcingAdmin,
                     policyDefinition,
@@ -424,7 +426,7 @@
             // the data structures.
             if (!skipEnforcePolicy) {
                 if (policyChanged) {
-                    onGlobalPolicyChanged(policyDefinition, enforcingAdmin);
+                    onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
                 }
 
                 boolean policyAppliedGlobally = Objects.equals(
@@ -473,7 +475,7 @@
             boolean policyChanged = policyState.removePolicy(enforcingAdmin);
 
             if (policyChanged) {
-                onGlobalPolicyChanged(policyDefinition, enforcingAdmin);
+                onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
             }
 
             applyGlobalPolicyOnUsersWithLocalPoliciesLocked(policyDefinition, enforcingAdmin,
@@ -499,7 +501,7 @@
     /**
      * Enforces the new policy globally and notifies relevant admins.
      */
-    private <V> void onGlobalPolicyChanged(
+    private <V> void onGlobalPolicyChangedLocked(
             @NonNull PolicyDefinition<V> policyDefinition,
             @NonNull EnforcingAdmin enforcingAdmin) {
         PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
@@ -507,7 +509,7 @@
         enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
                 UserHandle.USER_ALL);
 
-        sendPolicyChangedToAdmins(
+        sendPolicyChangedToAdminsLocked(
                 policyState,
                 enforcingAdmin,
                 policyDefinition,
@@ -552,7 +554,7 @@
                         policyDefinition,
                         localPolicyState.getCurrentResolvedPolicy(),
                         userId);
-                sendPolicyChangedToAdmins(
+                sendPolicyChangedToAdminsLocked(
                         localPolicyState,
                         enforcingAdmin,
                         policyDefinition,
@@ -745,34 +747,35 @@
     }
 
     <V> void transferPolicies(EnforcingAdmin oldAdmin, EnforcingAdmin newAdmin) {
-        Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet());
-        for (PolicyKey policy : globalPolicies) {
-            PolicyState<?> policyState = mGlobalPolicies.get(policy);
-            if (policyState.getPoliciesSetByAdmins().containsKey(oldAdmin)) {
-                PolicyDefinition<V> policyDefinition =
-                        (PolicyDefinition<V>) policyState.getPolicyDefinition();
-                PolicyValue<V> policyValue =
-                        (PolicyValue<V>) policyState.getPoliciesSetByAdmins().get(oldAdmin);
-                setGlobalPolicy(policyDefinition, newAdmin, policyValue);
-            }
-        }
-
-        for (int i = 0; i < mLocalPolicies.size(); i++) {
-            int userId = mLocalPolicies.keyAt(i);
-            Set<PolicyKey> localPolicies = new HashSet<>(
-                    mLocalPolicies.get(userId).keySet());
-            for (PolicyKey policy : localPolicies) {
-                PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
+        synchronized (mLock) {
+            Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet());
+            for (PolicyKey policy : globalPolicies) {
+                PolicyState<?> policyState = mGlobalPolicies.get(policy);
                 if (policyState.getPoliciesSetByAdmins().containsKey(oldAdmin)) {
                     PolicyDefinition<V> policyDefinition =
                             (PolicyDefinition<V>) policyState.getPolicyDefinition();
                     PolicyValue<V> policyValue =
                             (PolicyValue<V>) policyState.getPoliciesSetByAdmins().get(oldAdmin);
-                    setLocalPolicy(policyDefinition, newAdmin, policyValue, userId);
+                    setGlobalPolicy(policyDefinition, newAdmin, policyValue);
+                }
+            }
+
+            for (int i = 0; i < mLocalPolicies.size(); i++) {
+                int userId = mLocalPolicies.keyAt(i);
+                Set<PolicyKey> localPolicies = new HashSet<>(
+                        mLocalPolicies.get(userId).keySet());
+                for (PolicyKey policy : localPolicies) {
+                    PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
+                    if (policyState.getPoliciesSetByAdmins().containsKey(oldAdmin)) {
+                        PolicyDefinition<V> policyDefinition =
+                                (PolicyDefinition<V>) policyState.getPolicyDefinition();
+                        PolicyValue<V> policyValue =
+                                (PolicyValue<V>) policyState.getPoliciesSetByAdmins().get(oldAdmin);
+                        setLocalPolicy(policyDefinition, newAdmin, policyValue, userId);
+                    }
                 }
             }
         }
-
         removePoliciesForAdmin(oldAdmin);
     }
 
@@ -836,7 +839,7 @@
             mLocalPolicies.get(userId).put(
                     policyDefinition.getPolicyKey(), new PolicyState<>(policyDefinition));
         }
-        return getPolicyState(mLocalPolicies.get(userId), policyDefinition);
+        return getPolicyStateLocked(mLocalPolicies.get(userId), policyDefinition);
     }
 
     private <V> void removeLocalPolicyStateLocked(
@@ -858,14 +861,14 @@
             mGlobalPolicies.put(
                     policyDefinition.getPolicyKey(), new PolicyState<>(policyDefinition));
         }
-        return getPolicyState(mGlobalPolicies, policyDefinition);
+        return getPolicyStateLocked(mGlobalPolicies, policyDefinition);
     }
 
     private <V> void removeGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) {
         mGlobalPolicies.remove(policyDefinition.getPolicyKey());
     }
 
-    private static <V> PolicyState<V> getPolicyState(
+    private static <V> PolicyState<V> getPolicyStateLocked(
             Map<PolicyKey, PolicyState<?>> policies, PolicyDefinition<V> policyDefinition) {
         try {
             // This will not throw an exception because policyDefinition is of type V, so unless
@@ -935,7 +938,7 @@
     }
 
     // TODO(b/261430877): Finalise the decision on which admins to send the updates to.
-    private <V> void sendPolicyChangedToAdmins(
+    private <V> void sendPolicyChangedToAdminsLocked(
             PolicyState<V> policyState,
             EnforcingAdmin callingAdmin,
             PolicyDefinition<V> policyDefinition,
@@ -1210,17 +1213,19 @@
             if (parentInfo == null || parentInfo.getUserHandle().getIdentifier() == userId) {
                 return;
             }
-            if (!mLocalPolicies.contains(parentInfo.getUserHandle().getIdentifier())) {
-                return;
-            }
-            for (Map.Entry<PolicyKey, PolicyState<?>> entry : mLocalPolicies.get(
-                    parentInfo.getUserHandle().getIdentifier()).entrySet()) {
-                enforcePolicyOnUser(userId, entry.getValue());
+            synchronized (mLock) {
+                if (!mLocalPolicies.contains(parentInfo.getUserHandle().getIdentifier())) {
+                    return;
+                }
+                for (Map.Entry<PolicyKey, PolicyState<?>> entry : mLocalPolicies.get(
+                        parentInfo.getUserHandle().getIdentifier()).entrySet()) {
+                    enforcePolicyOnUserLocked(userId, entry.getValue());
+                }
             }
         });
     }
 
-    private <V> void enforcePolicyOnUser(int userId, PolicyState<V> policyState) {
+    private <V> void enforcePolicyOnUserLocked(int userId, PolicyState<V> policyState) {
         if (!policyState.getPolicyDefinition().isInheritable()) {
             return;
         }
@@ -1239,26 +1244,28 @@
      */
     @NonNull
     DevicePolicyState getDevicePolicyState() {
-        Map<UserHandle, Map<PolicyKey, android.app.admin.PolicyState<?>>> policies =
-                new HashMap<>();
-        for (int i = 0; i < mLocalPolicies.size(); i++) {
-            UserHandle user = UserHandle.of(mLocalPolicies.keyAt(i));
-            policies.put(user, new HashMap<>());
-            for (PolicyKey policyKey : mLocalPolicies.valueAt(i).keySet()) {
-                policies.get(user).put(
-                        policyKey,
-                        mLocalPolicies.valueAt(i).get(policyKey).getParcelablePolicyState());
+        synchronized (mLock) {
+            Map<UserHandle, Map<PolicyKey, android.app.admin.PolicyState<?>>> policies =
+                    new HashMap<>();
+            for (int i = 0; i < mLocalPolicies.size(); i++) {
+                UserHandle user = UserHandle.of(mLocalPolicies.keyAt(i));
+                policies.put(user, new HashMap<>());
+                for (PolicyKey policyKey : mLocalPolicies.valueAt(i).keySet()) {
+                    policies.get(user).put(
+                            policyKey,
+                            mLocalPolicies.valueAt(i).get(policyKey).getParcelablePolicyState());
+                }
             }
-        }
-        if (!mGlobalPolicies.isEmpty()) {
-            policies.put(UserHandle.ALL, new HashMap<>());
-            for (PolicyKey policyKey : mGlobalPolicies.keySet()) {
-                policies.get(UserHandle.ALL).put(
-                        policyKey,
-                        mGlobalPolicies.get(policyKey).getParcelablePolicyState());
+            if (!mGlobalPolicies.isEmpty()) {
+                policies.put(UserHandle.ALL, new HashMap<>());
+                for (PolicyKey policyKey : mGlobalPolicies.keySet()) {
+                    policies.get(UserHandle.ALL).put(
+                            policyKey,
+                            mGlobalPolicies.get(policyKey).getParcelablePolicyState());
+                }
             }
+            return new DevicePolicyState(policies);
         }
-        return new DevicePolicyState(policies);
     }
 
 
@@ -1266,23 +1273,25 @@
      * Removes all local and global policies set by that admin.
      */
     void removePoliciesForAdmin(EnforcingAdmin admin) {
-        Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet());
-        for (PolicyKey policy : globalPolicies) {
-            PolicyState<?> policyState = mGlobalPolicies.get(policy);
-            if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
-                removeGlobalPolicy(policyState.getPolicyDefinition(), admin);
-            }
-        }
-
-        for (int i = 0; i < mLocalPolicies.size(); i++) {
-            Set<PolicyKey> localPolicies = new HashSet<>(
-                    mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet());
-            for (PolicyKey policy : localPolicies) {
-                PolicyState<?> policyState = mLocalPolicies.get(
-                        mLocalPolicies.keyAt(i)).get(policy);
+        synchronized (mLock) {
+            Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet());
+            for (PolicyKey policy : globalPolicies) {
+                PolicyState<?> policyState = mGlobalPolicies.get(policy);
                 if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
-                    removeLocalPolicy(
-                            policyState.getPolicyDefinition(), admin, mLocalPolicies.keyAt(i));
+                    removeGlobalPolicy(policyState.getPolicyDefinition(), admin);
+                }
+            }
+
+            for (int i = 0; i < mLocalPolicies.size(); i++) {
+                Set<PolicyKey> localPolicies = new HashSet<>(
+                        mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet());
+                for (PolicyKey policy : localPolicies) {
+                    PolicyState<?> policyState = mLocalPolicies.get(
+                            mLocalPolicies.keyAt(i)).get(policy);
+                    if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
+                        removeLocalPolicy(
+                                policyState.getPolicyDefinition(), admin, mLocalPolicies.keyAt(i));
+                    }
                 }
             }
         }
@@ -1292,23 +1301,25 @@
      * Removes all local policies for the provided {@code userId}.
      */
     private void removeLocalPoliciesForUser(int userId) {
-        if (!mLocalPolicies.contains(userId)) {
-            // No policies on user
-            return;
-        }
-
-        Set<PolicyKey> localPolicies = new HashSet<>(mLocalPolicies.get(userId).keySet());
-        for (PolicyKey policy : localPolicies) {
-            PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
-            Set<EnforcingAdmin> admins = new HashSet<>(
-                    policyState.getPoliciesSetByAdmins().keySet());
-            for (EnforcingAdmin admin : admins) {
-                removeLocalPolicy(
-                        policyState.getPolicyDefinition(), admin, userId);
+        synchronized (mLock) {
+            if (!mLocalPolicies.contains(userId)) {
+                // No policies on user
+                return;
             }
-        }
 
-        mLocalPolicies.remove(userId);
+            Set<PolicyKey> localPolicies = new HashSet<>(mLocalPolicies.get(userId).keySet());
+            for (PolicyKey policy : localPolicies) {
+                PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
+                Set<EnforcingAdmin> admins = new HashSet<>(
+                        policyState.getPoliciesSetByAdmins().keySet());
+                for (EnforcingAdmin admin : admins) {
+                    removeLocalPolicy(
+                            policyState.getPolicyDefinition(), admin, userId);
+                }
+            }
+
+            mLocalPolicies.remove(userId);
+        }
     }
 
     /**
@@ -1376,7 +1387,7 @@
      */
     private void updateDeviceAdminServiceOnPolicyRemoveLocked(
             @NonNull EnforcingAdmin enforcingAdmin) {
-        if (doesAdminHavePolicies(enforcingAdmin)) {
+        if (doesAdminHavePoliciesLocked(enforcingAdmin)) {
             return;
         }
         int userId = enforcingAdmin.getUserId();
@@ -1399,7 +1410,7 @@
                 /* actionForLog= */ "policy-removed");
     }
 
-    private boolean doesAdminHavePolicies(@NonNull EnforcingAdmin enforcingAdmin) {
+    private boolean doesAdminHavePoliciesLocked(@NonNull EnforcingAdmin enforcingAdmin) {
         for (PolicyKey policy : mGlobalPolicies.keySet()) {
             PolicyState<?> policyState = mGlobalPolicies.get(policy);
             if (policyState.getPoliciesSetByAdmins().containsKey(enforcingAdmin)) {
@@ -1420,13 +1431,17 @@
 
     @NonNull
     private Set<EnforcingAdmin> getEnforcingAdminsOnUser(int userId) {
-        return mEnforcingAdmins.contains(userId)
-                ? mEnforcingAdmins.get(userId) : Collections.emptySet();
+        synchronized (mLock) {
+            return mEnforcingAdmins.contains(userId)
+                    ? mEnforcingAdmins.get(userId) : Collections.emptySet();
+        }
     }
 
     private void write() {
-        Log.d(TAG, "Writing device policies to file.");
-        new DevicePoliciesReaderWriter().writeToFileLocked();
+        synchronized (mLock) {
+            Log.d(TAG, "Writing device policies to file.");
+            new DevicePoliciesReaderWriter().writeToFileLocked();
+        }
     }
 
     // TODO(b/256852787): trigger resolving logic after loading policies as roles are recalculated
@@ -1436,11 +1451,11 @@
         synchronized (mLock) {
             clear();
             new DevicePoliciesReaderWriter().readFromFileLocked();
-            reapplyAllPolicies();
+            reapplyAllPoliciesLocked();
         }
     }
 
-    private <V> void reapplyAllPolicies() {
+    private <V> void reapplyAllPoliciesLocked() {
         for (PolicyKey policy : mGlobalPolicies.keySet()) {
             PolicyState<?> policyState = mGlobalPolicies.get(policy);
             // Policy definition and value will always be of the same type
@@ -1470,10 +1485,8 @@
      * <p>Note that this doesn't clear any enforcements, it only clears the data structures.
      */
     void clearAllPolicies() {
-        synchronized (mLock) {
-            clear();
-            write();
-        }
+        clear();
+        write();
     }
     private void clear() {
         synchronized (mLock) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index a5b1548..e44b8cd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2093,7 +2093,8 @@
         mUserData = new SparseArray<>();
         mOwners = makeOwners(injector, pathProvider);
 
-        mDevicePolicyEngine = new DevicePolicyEngine(mContext, mDeviceAdminServiceController);
+        mDevicePolicyEngine = new DevicePolicyEngine(
+                mContext, mDeviceAdminServiceController, getLockObject());
 
         if (!mHasFeature) {
             // Skip the rest of the initialization
@@ -7840,27 +7841,29 @@
                 throw new SecurityException("Cannot wipe data. " + restriction
                         + " restriction is set for user " + userId);
             }
+        });
 
-            boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
-            boolean wipeDevice;
-            if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
-                    adminPackage,
-                    userId)) {
-                // Legacy mode
-                wipeDevice = isSystemUser;
+        boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
+        boolean wipeDevice;
+        if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
+                adminPackage,
+                userId)) {
+            // Legacy mode
+            wipeDevice = isSystemUser;
+        } else {
+            // Explicit behaviour
+            if (factoryReset) {
+                EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
+                        /*admin=*/ null,
+                        /*permission=*/ new String[]{MANAGE_DEVICE_POLICY_WIPE_DATA,
+                                MASTER_CLEAR},
+                        USES_POLICY_WIPE_DATA,
+                        adminPackage,
+                        factoryReset ? UserHandle.USER_ALL :
+                                getAffectedUser(calledOnParentInstance));
+                wipeDevice = true;
             } else {
-                // Explicit behaviour
-                if (factoryReset) {
-                    EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
-                            /*admin=*/ null,
-                            /*permission=*/ new String[]{MANAGE_DEVICE_POLICY_WIPE_DATA,
-                                    MASTER_CLEAR},
-                            USES_POLICY_WIPE_DATA,
-                            adminPackage,
-                            factoryReset ? UserHandle.USER_ALL :
-                                    getAffectedUser(calledOnParentInstance));
-                    wipeDevice = true;
-                } else {
+                mInjector.binderWithCleanCallingIdentity(() -> {
                     Preconditions.checkCallAuthorization(!isSystemUser,
                             "User %s is a system user and cannot be removed", userId);
                     boolean isLastNonHeadlessUser = getUserInfo(userId).isFull()
@@ -7871,9 +7874,11 @@
                             "Removing user %s would leave the device without any active users. "
                                     + "Consider factory resetting the device instead.",
                             userId);
-                    wipeDevice = false;
-                }
+                });
+                wipeDevice = false;
             }
+        }
+        mInjector.binderWithCleanCallingIdentity(() -> {
             if (wipeDevice) {
                 forceWipeDeviceNoLock(
                         (flags & WIPE_EXTERNAL_STORAGE) != 0,
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index c5ff8cc..dd23d9f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -28,6 +28,7 @@
 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
 import static com.android.server.job.JobSchedulerService.RARE_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -148,6 +149,9 @@
         // Used in JobConcurrencyManager.
         doReturn(mock(UserManagerInternal.class))
                 .when(() -> LocalServices.getService(UserManagerInternal.class));
+        // Used in JobStatus.
+        doReturn(mock(JobSchedulerInternal.class))
+                .when(() -> LocalServices.getService(JobSchedulerInternal.class));
         // Called via IdleController constructor.
         when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
         when(mContext.getResources()).thenReturn(mock(Resources.class));
@@ -168,6 +172,8 @@
         JobSchedulerService.sSystemClock = Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+        // Make sure the uptime is at least 24 hours so that tests that rely on high uptime work.
+        sUptimeMillisClock = getAdvancedClock(sUptimeMillisClock, 24 * HOUR_IN_MILLIS);
         // Called by DeviceIdlenessTracker
         when(mContext.getSystemService(UiModeManager.class)).thenReturn(mock(UiModeManager.class));
 
@@ -313,6 +319,260 @@
     }
 
     @Test
+    public void testGetMinJobExecutionGuaranteeMs_timeoutSafeguards_disabled() {
+        JobStatus jobUij = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+                createJobInfo(1)
+                        .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+        JobStatus jobEj = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+                createJobInfo(2).setExpedited(true));
+        JobStatus jobReg = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+                createJobInfo(3));
+        spyOn(jobUij);
+        when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+        jobUij.startedAsUserInitiatedJob = true;
+        spyOn(jobEj);
+        when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
+        jobEj.startedAsExpeditedJob = true;
+
+        mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = false;
+        mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
+        mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
+        mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
+        mService.updateQuotaTracker();
+        mService.resetScheduleQuota();
+
+        // Safeguards disabled -> no penalties.
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+        // 1 UIJ timeout. No max execution penalty yet.
+        mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+        // 2 UIJ timeouts. Safeguards disabled -> no penalties.
+        jobUij.madeActive =
+                sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+        mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+        // 1 EJ timeout. No max execution penalty yet.
+        mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+        // 2 EJ timeouts. Safeguards disabled -> no penalties.
+        jobEj.madeActive =
+                sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
+        mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+        // 1 reg timeout. No max execution penalty yet.
+        mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+        // 2 Reg timeouts. Safeguards disabled -> no penalties.
+        jobReg.madeActive =
+                sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
+        mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobReg));
+    }
+
+    @Test
+    public void testGetMinJobExecutionGuaranteeMs_timeoutSafeguards_enabled() {
+        JobStatus jobUij = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+                createJobInfo(1)
+                        .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+        JobStatus jobEj = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+                createJobInfo(2).setExpedited(true));
+        JobStatus jobReg = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+                createJobInfo(3));
+        spyOn(jobUij);
+        when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+        jobUij.startedAsUserInitiatedJob = true;
+        spyOn(jobEj);
+        when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
+        jobEj.startedAsExpeditedJob = true;
+
+        mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = true;
+        mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
+        mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
+        mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
+        mService.updateQuotaTracker();
+        mService.resetScheduleQuota();
+
+        // No timeouts -> no penalties.
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+        // 1 UIJ timeout. No execution penalty yet.
+        mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+        // Not a timeout -> 1 UIJ timeout. No execution penalty yet.
+        jobUij.madeActive = sUptimeMillisClock.millis() - 1;
+        mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+        // 2 UIJ timeouts. Min execution penalty only for UIJs.
+        jobUij.madeActive =
+                sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+        mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+        // 1 EJ timeout. No max execution penalty yet.
+        mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+        // 2 EJ timeouts. Max execution penalty for EJs.
+        jobEj.madeActive =
+                sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
+        mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+        // 1 reg timeout. No max execution penalty yet.
+        mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+        // 2 Reg timeouts. Max execution penalty for regular jobs.
+        jobReg.madeActive =
+                sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
+        mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobReg));
+    }
+
+    @Test
     public void testGetMaxJobExecutionTimeMs() {
         JobStatus jobUIDT = createJobStatus("testGetMaxJobExecutionTimeMs",
                 createJobInfo(10)
@@ -327,7 +587,7 @@
         doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
                 .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
         doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
-                .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
+                .when(tareController).getMaxJobExecutionTimeMsLocked(any());
 
         grantRunUserInitiatedJobsPermission(true);
         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
@@ -337,6 +597,306 @@
                 mService.getMaxJobExecutionTimeMs(jobUIDT));
     }
 
+    @Test
+    public void testGetMaxJobExecutionTimeMs_timeoutSafeguards_disabled() {
+        JobStatus jobUij = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+                createJobInfo(1)
+                        .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+        JobStatus jobEj = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+                createJobInfo(2).setExpedited(true));
+        JobStatus jobReg = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+                createJobInfo(3));
+        spyOn(jobUij);
+        when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+        jobUij.startedAsUserInitiatedJob = true;
+        spyOn(jobEj);
+        when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
+        jobEj.startedAsExpeditedJob = true;
+
+        QuotaController quotaController = mService.getQuotaController();
+        spyOn(quotaController);
+        TareController tareController = mService.getTareController();
+        spyOn(tareController);
+        doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
+                .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
+        doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
+                .when(tareController).getMaxJobExecutionTimeMsLocked(any());
+
+        mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = false;
+        mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
+        mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
+        mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
+        mService.updateQuotaTracker();
+        mService.resetScheduleQuota();
+
+        // Safeguards disabled -> no penalties.
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobReg));
+
+        // 1 UIJ timeout. No max execution penalty yet.
+        mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobReg));
+
+        // 2 UIJ timeouts. Safeguards disabled -> no penalties.
+        jobUij.madeActive =
+                sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+        mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobReg));
+
+        // 1 EJ timeout. No max execution penalty yet.
+        mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobReg));
+
+        // 2 EJ timeouts. Safeguards disabled -> no penalties.
+        jobEj.madeActive =
+                sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
+        mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobReg));
+
+        // 1 reg timeout. No max execution penalty yet.
+        mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobReg));
+
+        // 2 Reg timeouts. Safeguards disabled -> no penalties.
+        jobReg.madeActive =
+                sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
+        mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobReg));
+    }
+
+    @Test
+    public void testGetMaxJobExecutionTimeMs_timeoutSafeguards_enabled() {
+        JobStatus jobUij = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+                createJobInfo(1)
+                        .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+        JobStatus jobEj = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+                createJobInfo(2).setExpedited(true));
+        JobStatus jobReg = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+                createJobInfo(3));
+        spyOn(jobUij);
+        when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+        jobUij.startedAsUserInitiatedJob = true;
+        spyOn(jobEj);
+        when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
+        jobEj.startedAsExpeditedJob = true;
+
+        QuotaController quotaController = mService.getQuotaController();
+        spyOn(quotaController);
+        TareController tareController = mService.getTareController();
+        spyOn(tareController);
+        doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
+                .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
+        doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
+                .when(tareController).getMaxJobExecutionTimeMsLocked(any());
+
+        mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = true;
+        mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
+        mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
+        mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
+        mService.updateQuotaTracker();
+        mService.resetScheduleQuota();
+
+        // No timeouts -> no penalties.
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobReg));
+
+        // 1 UIJ timeout. No max execution penalty yet.
+        mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobReg));
+
+        // Not a timeout -> 1 UIJ timeout. No max execution penalty yet.
+        jobUij.madeActive = sUptimeMillisClock.millis() - 1;
+        mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobReg));
+
+        // 2 UIJ timeouts. Max execution penalty only for UIJs.
+        jobUij.madeActive =
+                sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+        mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobReg));
+
+        // 1 EJ timeout. No max execution penalty yet.
+        mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobReg));
+
+        // Not a timeout -> 1 EJ timeout. No max execution penalty yet.
+        jobEj.madeActive = sUptimeMillisClock.millis() - 1;
+        mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobReg));
+
+        // 2 EJ timeouts. Max execution penalty for EJs.
+        jobEj.madeActive =
+                sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
+        mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMaxJobExecutionTimeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobReg));
+
+        // 1 reg timeout. No max execution penalty yet.
+        mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMaxJobExecutionTimeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobReg));
+
+        // Not a timeout -> 1 reg timeout. No max execution penalty yet.
+        jobReg.madeActive = sUptimeMillisClock.millis() - 1;
+        mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMaxJobExecutionTimeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobReg));
+
+        // 2 Reg timeouts. Max execution penalty for regular jobs.
+        jobReg.madeActive =
+                sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
+        mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+        grantRunUserInitiatedJobsPermission(true);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        grantRunUserInitiatedJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                mService.getMaxJobExecutionTimeMs(jobUij));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMaxJobExecutionTimeMs(jobEj));
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMaxJobExecutionTimeMs(jobReg));
+    }
+
     /**
      * Confirm that
      * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
@@ -1226,6 +1786,7 @@
         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
         mService.updateQuotaTracker();
+        mService.resetScheduleQuota();
 
         final JobInfo job = createJobInfo().setPersisted(true).build();
         for (int i = 0; i < 500; ++i) {
@@ -1249,6 +1810,7 @@
         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
         mService.updateQuotaTracker();
+        mService.resetScheduleQuota();
 
         final JobInfo job = createJobInfo().setPersisted(true).build();
         for (int i = 0; i < 500; ++i) {
@@ -1270,6 +1832,7 @@
         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
         mService.updateQuotaTracker();
+        mService.resetScheduleQuota();
 
         final JobInfo job = createJobInfo().setPersisted(true).build();
         for (int i = 0; i < 500; ++i) {
@@ -1292,6 +1855,7 @@
         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
         mService.updateQuotaTracker();
+        mService.resetScheduleQuota();
 
         final JobInfo job = createJobInfo().setPersisted(true).build();
         for (int i = 0; i < 500; ++i) {
@@ -1315,6 +1879,7 @@
         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
         mService.updateQuotaTracker();
+        mService.resetScheduleQuota();
 
         final JobInfo job = createJobInfo().setPersisted(false).build();
         final JobWorkItem item = new JobWorkItem.Builder().build();
@@ -1337,6 +1902,7 @@
         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
         mService.updateQuotaTracker();
+        mService.resetScheduleQuota();
 
         final JobInfo job = createJobInfo().setPersisted(true).build();
         final JobWorkItem item = new JobWorkItem.Builder().build();
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 2180a78..2b56ea8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -73,6 +73,7 @@
 import android.util.DataUnit;
 
 import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerInternal;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobSchedulerService.Constants;
 import com.android.server.net.NetworkPolicyManagerInternal;
@@ -124,6 +125,10 @@
         LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
         LocalServices.addService(NetworkPolicyManagerInternal.class, mNetPolicyManagerInternal);
 
+        // Used in JobStatus.
+        LocalServices.removeServiceForTest(JobSchedulerInternal.class);
+        LocalServices.addService(JobSchedulerInternal.class, mock(JobSchedulerInternal.class));
+
         // Freeze the clocks at this moment in time
         JobSchedulerService.sSystemClock =
                 Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 05780eb..1de7e37 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -21,6 +21,7 @@
 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.job.JobSchedulerService.ACTIVE_INDEX;
+import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
 import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
 import static com.android.server.job.JobSchedulerService.RARE_INDEX;
@@ -45,6 +46,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.when;
 
@@ -582,6 +585,40 @@
     }
 
     @Test
+    public void testGetEffectiveStandbyBucket_buggyApp() {
+        when(mJobSchedulerInternal.isAppConsideredBuggy(
+                anyInt(), anyString(), anyInt(), anyString()))
+                .thenReturn(true);
+
+        final JobInfo jobInfo = new JobInfo.Builder(1234, TEST_JOB_COMPONENT).build();
+        JobStatus job = createJobStatus(jobInfo);
+
+        // Exempt apps be exempting.
+        job.setStandbyBucket(EXEMPTED_INDEX);
+        assertEquals(EXEMPTED_INDEX, job.getEffectiveStandbyBucket());
+
+        // Actual bucket is higher than the buggy cap, so the cap comes into effect.
+        job.setStandbyBucket(ACTIVE_INDEX);
+        assertEquals(WORKING_INDEX, job.getEffectiveStandbyBucket());
+
+        // Buckets at the cap or below shouldn't be affected.
+        job.setStandbyBucket(WORKING_INDEX);
+        assertEquals(WORKING_INDEX, job.getEffectiveStandbyBucket());
+
+        job.setStandbyBucket(FREQUENT_INDEX);
+        assertEquals(FREQUENT_INDEX, job.getEffectiveStandbyBucket());
+
+        job.setStandbyBucket(RARE_INDEX);
+        assertEquals(RARE_INDEX, job.getEffectiveStandbyBucket());
+
+        job.setStandbyBucket(RESTRICTED_INDEX);
+        assertEquals(RESTRICTED_INDEX, job.getEffectiveStandbyBucket());
+
+        job.setStandbyBucket(NEVER_INDEX);
+        assertEquals(NEVER_INDEX, job.getEffectiveStandbyBucket());
+    }
+
+    @Test
     public void testModifyingInternalFlags() {
         final JobInfo jobInfo =
                 new JobInfo.Builder(101, new ComponentName("foo", "bar"))
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index fb59ea2..7cc01e1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -58,6 +58,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerInternal;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.controllers.PrefetchController.PcConstants;
 
@@ -135,6 +136,9 @@
         when(mJobSchedulerService.getPackagesForUidLocked(anyInt()))
                 .thenAnswer(invocationOnMock
                         -> mPackagesForUid.get(invocationOnMock.getArgument(0)));
+        // Used in JobStatus.
+        doReturn(mock(JobSchedulerInternal.class))
+                .when(() -> LocalServices.getService(JobSchedulerInternal.class));
 
         // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
         // in the past, and PrefetchController sometimes floors values at 0, so if the test time
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 6f713e0..dce162c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -85,6 +85,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
 import com.android.server.PowerAllowlistInternal;
+import com.android.server.job.JobSchedulerInternal;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobStore;
 import com.android.server.job.controllers.QuotaController.ExecutionStats;
@@ -190,6 +191,8 @@
         doReturn(mPowerAllowlistInternal)
                 .when(() -> LocalServices.getService(PowerAllowlistInternal.class));
         // Used in JobStatus.
+        doReturn(mock(JobSchedulerInternal.class))
+                .when(() -> LocalServices.getService(JobSchedulerInternal.class));
         doReturn(mPackageManagerInternal)
                 .when(() -> LocalServices.getService(PackageManagerInternal.class));
         // Used in QuotaController.Handler.
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 51e521d..206af5b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -17,8 +17,10 @@
 package com.android.server.wallpaper;
 
 import static android.app.WallpaperManager.COMMAND_REAPPLY;
+import static android.app.WallpaperManager.FLAG_LOCK;
 import static android.app.WallpaperManager.FLAG_SYSTEM;
 import static android.os.FileObserver.CLOSE_WRITE;
+import static android.os.UserHandle.MIN_SECONDARY_USER_ID;
 import static android.os.UserHandle.USER_SYSTEM;
 import static android.view.Display.DEFAULT_DISPLAY;
 
@@ -106,6 +108,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.util.List;
 
 /**
  * Tests for the {@link WallpaperManagerService} class.
@@ -172,12 +175,12 @@
         sImageWallpaperComponentName = ComponentName.unflattenFromString(
                 sContext.getResources().getString(R.string.image_wallpaper_component));
         // Mock default wallpaper as image wallpaper if there is no pre-defined default wallpaper.
-        sDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(sContext);
+        sDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(sContext);
 
         if (sDefaultWallpaperComponent == null) {
             sDefaultWallpaperComponent = sImageWallpaperComponentName;
             doReturn(sImageWallpaperComponentName).when(() ->
-                    WallpaperManager.getCmfDefaultWallpaperComponent(any()));
+                    WallpaperManager.getDefaultWallpaperComponent(any()));
         } else {
             sContext.addMockService(sDefaultWallpaperComponent, sWallpaperService);
         }
@@ -262,6 +265,25 @@
     }
 
     /**
+     * Tests that the fundamental fields are set by the main WallpaperData constructor
+     */
+    @Test
+    public void testWallpaperDataConstructor() {
+        final int testUserId = MIN_SECONDARY_USER_ID;
+        for (int which: List.of(FLAG_LOCK, FLAG_SYSTEM)) {
+            WallpaperData newWallpaperData = new WallpaperData(testUserId, which);
+            assertEquals(which, newWallpaperData.mWhich);
+            assertEquals(testUserId, newWallpaperData.userId);
+
+            WallpaperData wallpaperData = mService.getWallpaperSafeLocked(testUserId, which);
+            assertEquals(wallpaperData.cropFile.getAbsolutePath(),
+                    newWallpaperData.cropFile.getAbsolutePath());
+            assertEquals(wallpaperData.wallpaperFile.getAbsolutePath(),
+                    newWallpaperData.wallpaperFile.getAbsolutePath());
+        }
+    }
+
+    /**
      * Tests that internal basic data should be correct after boot up.
      */
     @Test
@@ -405,10 +427,7 @@
             fail("exception occurred while writing system wallpaper attributes");
         }
 
-        WallpaperData shouldMatchSystem = new WallpaperData(systemWallpaperData.userId,
-                systemWallpaperData.wallpaperFile.getParentFile(),
-                systemWallpaperData.wallpaperFile.getAbsolutePath(),
-                systemWallpaperData.cropFile.getAbsolutePath());
+        WallpaperData shouldMatchSystem = new WallpaperData(0, FLAG_SYSTEM);
         try {
             TypedXmlPullParser parser = Xml.newBinaryPullParser();
             mService.mWallpaperDataParser.parseWallpaperAttributes(parser, shouldMatchSystem, true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index b59f027..1126726 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1346,6 +1346,16 @@
         activity1.setVisible(false);
         abortTransition.abort();
         assertTrue(activity1.isVisible());
+
+        // The mLaunchTaskBehind flag of an invisible initializing activity should not be cleared.
+        final Transition noChangeTransition = controller.createTransition(TRANSIT_OPEN);
+        noChangeTransition.collect(activity1);
+        activity1.setVisibleRequested(false);
+        activity1.setState(ActivityRecord.State.INITIALIZING, "test");
+        activity1.mLaunchTaskBehind = true;
+        mWm.mSyncEngine.abort(noChangeTransition.getSyncId());
+        noChangeTransition.finishTransition();
+        assertTrue(activity1.mLaunchTaskBehind);
     }
 
     @Test