Merge "Remove use of androidprv to work around a bug in sysyui studio build" into sc-dev
diff --git a/Android.bp b/Android.bp
index 0ffafc6..71ea3f2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -483,7 +483,7 @@
     "--api-lint-ignore-prefix org. "
 
 filegroup {
-    name: "framework-non-updatable-stub-sources",
+    name: "android-non-updatable-stub-sources",
     srcs: [
         ":framework-mime-sources", // mimemap builds separately but has no separate droidstubs.
         ":framework-non-updatable-sources",
@@ -495,6 +495,63 @@
     visibility: ["//visibility:private"],
 }
 
+// These defaults are used for both the jar stubs and the doc stubs.
+stubs_defaults {
+    name: "android-non-updatable-stubs-defaults",
+    srcs: [":android-non-updatable-stub-sources"],
+    sdk_version: "none",
+    system_modules: "none",
+    java_version: "1.8",
+    arg_files: ["core/res/AndroidManifest.xml"],
+    // TODO(b/147699819): remove below aidl includes.
+    aidl: {
+        local_include_dirs: [
+            "apex/media/aidl/stable",
+            "media/aidl",
+            // TODO: move to include-dirs for packages/modules/Connectivity when this moves out of
+            // frameworks/base
+            "packages/Connectivity/framework/aidl-export",
+            "telephony/java",
+        ],
+        include_dirs: ["frameworks/av/aidl"],
+    },
+    // These are libs from framework-internal-utils that are required (i.e. being referenced)
+    // from framework-non-updatable-sources. Add more here when there's a need.
+    // DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
+    // dependencies gets bigger.
+    libs: [
+        "android.hardware.cas-V1.2-java",
+        "android.hardware.health-V1.0-java-constants",
+        "android.hardware.radio-V1.5-java",
+        "android.hardware.radio-V1.6-java",
+        "android.hardware.thermal-V1.0-java-constants",
+        "android.hardware.thermal-V2.0-java",
+        "android.hardware.tv.input-V1.0-java-constants",
+        "android.hardware.tv.tuner-V1.0-java-constants",
+        "android.hardware.tv.tuner-V1.1-java-constants",
+        "android.hardware.usb-V1.0-java-constants",
+        "android.hardware.usb-V1.1-java-constants",
+        "android.hardware.usb.gadget-V1.0-java",
+        "android.hardware.vibrator-V1.3-java",
+        "framework-protos",
+        "stable.core.platform.api.stubs",
+        // There are a few classes from modules used by the core that
+        // need to be resolved by metalava. We use a prebuilt stub of the
+        // full sdk to ensure we can resolve them. If a new class gets added,
+        // the prebuilts/sdk/current needs to be updated.
+        "sdk_system_current_android",
+        // NOTE: The below can be removed once the prebuilt stub contains IKE.
+        "sdk_system_current_android.net.ipsec.ike",
+    ],
+    high_mem: true, // Lots of sources => high memory use, see b/170701554
+    installable: false,
+    annotations_enabled: true,
+    previous_api: ":android.api.public.latest",
+    merge_annotations_dirs: ["metalava-manual"],
+    defaults_visibility: ["//visibility:private"],
+    visibility: ["//frameworks/base/api"],
+}
+
 build = [
     "StubLibraries.bp",
     "ApiDocs.bp",
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 3862795..0f218b5 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -56,9 +56,23 @@
 ]
 
 stubs_defaults {
+    name: "android-non-updatable-doc-stubs-defaults",
+    defaults: ["android-non-updatable-stubs-defaults"],
+    srcs: [
+        // No longer part of the stubs, but are included in the docs.
+        "test-base/src/**/*.java",
+        "test-mock/src/**/*.java",
+        "test-runner/src/**/*.java",
+    ],
+    libs: framework_docs_only_libs,
+    create_doc_stubs: true,
+    write_sdk_values: true,
+}
+
+stubs_defaults {
     name: "framework-doc-stubs-default",
     srcs: [
-        ":framework-non-updatable-stub-sources",
+        ":android-non-updatable-stub-sources",
 
         // Module sources
         ":art.module.public.api{.public.stubs.source}",
@@ -103,6 +117,19 @@
 }
 
 droidstubs {
+    name: "android-non-updatable-doc-stubs",
+    defaults: ["android-non-updatable-doc-stubs-defaults"],
+    args: metalava_framework_docs_args,
+}
+
+droidstubs {
+    name: "android-non-updatable-doc-stubs-system",
+    defaults: ["android-non-updatable-doc-stubs-defaults"],
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ",
+}
+
+droidstubs {
     name: "framework-doc-stubs",
     defaults: ["framework-doc-stubs-default"],
     arg_files: [
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 720bfc0..7b8a687 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -42,62 +42,10 @@
 
 stubs_defaults {
     name: "metalava-non-updatable-api-stubs-default",
-    srcs: [":framework-non-updatable-stub-sources"],
-    sdk_version: "none",
-    system_modules: "none",
-    java_version: "1.8",
-    arg_files: ["core/res/AndroidManifest.xml"],
-    // TODO(b/147699819, b/169090544): remove below aidl includes.
-    aidl: {
-        local_include_dirs: [
-            "apex/media/aidl/stable",
-            "media/aidl",
-            // TODO: move to include-dirs for packages/modules/Connectivity when this moves out of
-            // frameworks/base
-            "packages/Connectivity/framework/aidl-export",
-            "telephony/java",
-        ],
-        include_dirs: ["frameworks/av/aidl"],
-    },
-    // These are libs from framework-internal-utils that are required (i.e. being referenced)
-    // from framework-non-updatable-sources. Add more here when there's a need.
-    // DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
-    // dependencies gets bigger.
-    libs: [
-        "android.hardware.cas-V1.2-java",
-        "android.hardware.health-V1.0-java-constants",
-        "android.hardware.radio-V1.5-java",
-        "android.hardware.radio-V1.6-java",
-        "android.hardware.thermal-V1.0-java-constants",
-        "android.hardware.thermal-V2.0-java",
-        "android.hardware.tv.input-V1.0-java-constants",
-        "android.hardware.tv.tuner-V1.0-java-constants",
-        "android.hardware.tv.tuner-V1.1-java-constants",
-        "android.hardware.usb-V1.0-java-constants",
-        "android.hardware.usb-V1.1-java-constants",
-        "android.hardware.usb.gadget-V1.0-java",
-        "android.hardware.vibrator-V1.3-java",
-        "framework-protos",
-        "stable.core.platform.api.stubs",
-        // There are a few classes from modules used by the core that
-        // need to be resolved by metalava. We use a prebuilt stub of the
-        // full sdk to ensure we can resolve them. If a new class gets added,
-        // the prebuilts/sdk/current needs to be updated.
-        "sdk_system_current_android",
-        // NOTE: The below can be removed once the prebuilt stub contains IKE.
-        "sdk_system_current_android.net.ipsec.ike",
-    ],
-    high_mem: true, // Lots of sources => high memory use, see b/170701554
-    installable: false,
-    annotations_enabled: true,
-    previous_api: ":android.api.public.latest",
-    merge_annotations_dirs: [
-        "metalava-manual",
-    ],
+    defaults: ["android-non-updatable-stubs-defaults"],
     api_levels_annotations_enabled: false,
     filter_packages: packages_to_document,
     defaults_visibility: ["//visibility:private"],
-    visibility: ["//frameworks/base/api"],
 }
 
 /////////////////////////////////////////////////////////////////////
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 70d0d5d..b3396c5 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -1135,7 +1135,7 @@
         this(context, new Injector(context));
     }
 
-    private static boolean isRtc(int type) {
+    static boolean isRtc(int type) {
         return (type == RTC || type == RTC_WAKEUP);
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
index 2dc131c..4e7311f 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
@@ -72,7 +72,8 @@
                                 alarmStore.getCount(
                                         a -> (a.getRequestedElapsed() > now + INDEFINITE_DELAY)),
                                 alarmStore.getCount(a -> (a.repeatInterval != 0)),
-                                alarmStore.getCount(a -> (a.alarmClock != null))
+                                alarmStore.getCount(a -> (a.alarmClock != null)),
+                                alarmStore.getCount(a -> AlarmManagerService.isRtc(a.type))
                         ));
                         return StatsManager.PULL_SUCCESS;
                     }
@@ -101,7 +102,8 @@
                 (a.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0,
                 a.alarmClock != null,
                 a.repeatInterval != 0,
-                reasonToStatsReason(a.mExactAllowReason));
+                reasonToStatsReason(a.mExactAllowReason),
+                AlarmManagerService.isRtc(a.type));
     }
 
     static void pushAlarmBatchDelivered(int numAlarms, int wakeups) {
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 3322841..80e9e2d 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
@@ -621,6 +621,7 @@
     }
 
     @Override
+    @GuardedBy("mLock")
     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
         final long nowElapsed = sElapsedRealtimeClock.millis();
         final int userId = jobStatus.getSourceUserId();
@@ -648,6 +649,7 @@
     }
 
     @Override
+    @GuardedBy("mLock")
     public void prepareForExecutionLocked(JobStatus jobStatus) {
         if (DEBUG) {
             Slog.d(TAG, "Prepping for " + jobStatus.toShortString());
@@ -676,6 +678,7 @@
     }
 
     @Override
+    @GuardedBy("mLock")
     public void unprepareFromExecutionLocked(JobStatus jobStatus) {
         Timer timer = mPkgTimers.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
         if (timer != null) {
@@ -691,6 +694,7 @@
     }
 
     @Override
+    @GuardedBy("mLock")
     public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
             boolean forUpdate) {
         if (jobStatus.clearTrackingController(JobStatus.TRACKING_QUOTA)) {
@@ -796,10 +800,12 @@
     }
 
     /** Returns the maximum amount of time this job could run for. */
+    @GuardedBy("mLock")
     public long getMaxJobExecutionTimeMsLocked(@NonNull final JobStatus jobStatus) {
         if (!jobStatus.shouldTreatAsExpeditedJob()) {
-            // If quota is currently "free", then the job can run for the full amount of time.
-            if (mChargeTracker.isCharging()
+            // 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 (mChargeTracker.isChargingLocked()
                     || mTopAppCache.get(jobStatus.getSourceUid())
                     || isTopStartedJobLocked(jobStatus)
                     || isUidInForeground(jobStatus.getSourceUid())) {
@@ -810,7 +816,7 @@
         }
 
         // Expedited job.
-        if (mChargeTracker.isCharging()) {
+        if (mChargeTracker.isChargingLocked()) {
             return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
         }
         if (mTopAppCache.get(jobStatus.getSourceUid()) || isTopStartedJobLocked(jobStatus)) {
@@ -828,8 +834,9 @@
     }
 
     /** @return true if the job is within expedited job quota. */
+    @GuardedBy("mLock")
     public boolean isWithinEJQuotaLocked(@NonNull final JobStatus jobStatus) {
-        if (isQuotaFree(jobStatus.getEffectiveStandbyBucket())) {
+        if (isQuotaFreeLocked(jobStatus.getEffectiveStandbyBucket())) {
             return true;
         }
         // A job is within quota if one of the following is true:
@@ -887,9 +894,10 @@
                 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
     }
 
-    private boolean isQuotaFree(final int standbyBucket) {
+    @GuardedBy("mLock")
+    private boolean isQuotaFreeLocked(final int standbyBucket) {
         // Quota constraint is not enforced while charging.
-        if (mChargeTracker.isCharging()) {
+        if (mChargeTracker.isChargingLocked()) {
             // Restricted jobs require additional constraints when charging, so don't immediately
             // mark quota as free when charging.
             return standbyBucket != RESTRICTED_INDEX;
@@ -898,11 +906,12 @@
     }
 
     @VisibleForTesting
+    @GuardedBy("mLock")
     boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
             final int standbyBucket) {
         if (standbyBucket == NEVER_INDEX) return false;
 
-        if (isQuotaFree(standbyBucket)) return true;
+        if (isQuotaFreeLocked(standbyBucket)) return true;
 
         ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
         return getRemainingExecutionTimeLocked(stats) > 0
@@ -1493,13 +1502,14 @@
     /** Schedule a cleanup alarm if necessary and there isn't already one scheduled. */
     @VisibleForTesting
     void maybeScheduleCleanupAlarmLocked() {
-        if (mNextCleanupTimeElapsed > sElapsedRealtimeClock.millis()) {
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+        if (mNextCleanupTimeElapsed > nowElapsed) {
             // There's already an alarm scheduled. Just stick with that one. There's no way we'll
             // end up scheduling an earlier alarm.
             if (DEBUG) {
                 Slog.v(TAG, "Not scheduling cleanup since there's already one at "
-                        + mNextCleanupTimeElapsed + " (in " + (mNextCleanupTimeElapsed
-                        - sElapsedRealtimeClock.millis()) + "ms)");
+                        + mNextCleanupTimeElapsed
+                        + " (in " + (mNextCleanupTimeElapsed - nowElapsed) + "ms)");
             }
             return;
         }
@@ -1521,7 +1531,7 @@
         if (nextCleanupElapsed - mNextCleanupTimeElapsed <= 10 * MINUTE_IN_MILLIS) {
             // No need to clean up too often. Delay the alarm if the next cleanup would be too soon
             // after it.
-            nextCleanupElapsed += 10 * MINUTE_IN_MILLIS;
+            nextCleanupElapsed = mNextCleanupTimeElapsed + 10 * MINUTE_IN_MILLIS;
         }
         mNextCleanupTimeElapsed = nextCleanupElapsed;
         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextCleanupElapsed, ALARM_TAG_CLEANUP,
@@ -1556,9 +1566,9 @@
 
     private void handleNewChargingStateLocked() {
         mTimerChargingUpdateFunctor.setStatus(sElapsedRealtimeClock.millis(),
-                mChargeTracker.isCharging());
+                mChargeTracker.isChargingLocked());
         if (DEBUG) {
-            Slog.d(TAG, "handleNewChargingStateLocked: " + mChargeTracker.isCharging());
+            Slog.d(TAG, "handleNewChargingStateLocked: " + mChargeTracker.isChargingLocked());
         }
         // Deal with Timers first.
         mEJPkgTimers.forEach(mTimerChargingUpdateFunctor);
@@ -1827,6 +1837,7 @@
          * Track whether we're charging. This has a slightly different definition than that of
          * BatteryController.
          */
+        @GuardedBy("mLock")
         private boolean mCharging;
 
         ChargingTracker() {
@@ -1846,7 +1857,8 @@
             mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
         }
 
-        public boolean isCharging() {
+        @GuardedBy("mLock")
+        public boolean isChargingLocked() {
             return mCharging;
         }
 
@@ -2055,9 +2067,12 @@
                     }
                     return;
                 }
-                if (mRunningBgJobs.remove(jobStatus)
-                        && !mChargeTracker.isCharging() && mRunningBgJobs.size() == 0) {
-                    emitSessionLocked(sElapsedRealtimeClock.millis());
+                final long nowElapsed = sElapsedRealtimeClock.millis();
+                final int standbyBucket = JobSchedulerService.standbyBucketForPackage(
+                        mPkg.packageName, mPkg.userId, nowElapsed);
+                if (mRunningBgJobs.remove(jobStatus) && mRunningBgJobs.size() == 0
+                        && !isQuotaFreeLocked(standbyBucket)) {
+                    emitSessionLocked(nowElapsed);
                     cancelCutoff();
                 }
             }
@@ -2077,6 +2092,7 @@
             cancelCutoff();
         }
 
+        @GuardedBy("mLock")
         private void emitSessionLocked(long nowElapsed) {
             if (mBgJobCount <= 0) {
                 // Nothing to emit.
@@ -2121,6 +2137,7 @@
             }
         }
 
+        @GuardedBy("mLock")
         private boolean shouldTrackLocked() {
             final long nowElapsed = sElapsedRealtimeClock.millis();
             final int standbyBucket = JobSchedulerService.standbyBucketForPackage(mPkg.packageName,
@@ -2132,7 +2149,7 @@
             final long topAppGracePeriodEndElapsed = mTopAppGraceCache.get(mUid);
             final boolean hasTopAppExemption = !mRegularJobTimer
                     && (mTopAppCache.get(mUid) || nowElapsed < topAppGracePeriodEndElapsed);
-            return (standbyBucket == RESTRICTED_INDEX || !mChargeTracker.isCharging())
+            return !isQuotaFreeLocked(standbyBucket)
                     && !mForegroundUids.get(mUid) && !hasTempAllowlistExemption
                     && !hasTopAppExemption;
         }
@@ -2462,30 +2479,60 @@
         }
     }
 
-    private final class DeleteTimingSessionsFunctor implements Consumer<List<TimingSession>> {
-        private final Predicate<TimingSession> mTooOld = new Predicate<TimingSession>() {
-            public boolean test(TimingSession ts) {
-                return ts.endTimeElapsed <= sElapsedRealtimeClock.millis() - MAX_PERIOD_MS;
-            }
-        };
+    private static final class TimingSessionTooOldPredicate implements Predicate<TimingSession> {
+        private long mNowElapsed;
+
+        private void updateNow() {
+            mNowElapsed = sElapsedRealtimeClock.millis();
+        }
 
         @Override
-        public void accept(List<TimingSession> sessions) {
-            if (sessions != null) {
-                // Remove everything older than MAX_PERIOD_MS time ago.
-                sessions.removeIf(mTooOld);
-            }
+        public boolean test(TimingSession ts) {
+            return ts.endTimeElapsed <= mNowElapsed - MAX_PERIOD_MS;
         }
     }
 
-    private final DeleteTimingSessionsFunctor mDeleteOldSessionsFunctor =
-            new DeleteTimingSessionsFunctor();
+    private final TimingSessionTooOldPredicate mTimingSessionTooOld =
+            new TimingSessionTooOldPredicate();
+
+    private final Consumer<List<TimingSession>> mDeleteOldSessionsFunctor = sessions -> {
+        if (sessions != null) {
+            // Remove everything older than MAX_PERIOD_MS time ago.
+            sessions.removeIf(mTimingSessionTooOld);
+        }
+    };
 
     @VisibleForTesting
     void deleteObsoleteSessionsLocked() {
+        mTimingSessionTooOld.updateNow();
+
+        // Regular sessions
         mTimingSessions.forEach(mDeleteOldSessionsFunctor);
-        // Don't delete EJ timing sessions here. They'll be removed in
-        // getRemainingEJExecutionTimeLocked().
+
+        // EJ sessions
+        for (int uIdx = 0; uIdx < mEJTimingSessions.numMaps(); ++uIdx) {
+            final int userId = mEJTimingSessions.keyAt(uIdx);
+            for (int pIdx = 0; pIdx < mEJTimingSessions.numElementsForKey(userId); ++pIdx) {
+                final String packageName = mEJTimingSessions.keyAt(uIdx, pIdx);
+                final ShrinkableDebits debits = getEJDebitsLocked(userId, packageName);
+                final List<TimingSession> sessions = mEJTimingSessions.get(userId, packageName);
+                if (sessions == null) {
+                    continue;
+                }
+
+                while (sessions.size() > 0) {
+                    final TimingSession ts = sessions.get(0);
+                    if (mTimingSessionTooOld.test(ts)) {
+                        // Stale sessions may still be factored into tally. Remove them.
+                        final long duration = ts.endTimeElapsed - ts.startTimeElapsed;
+                        debits.transactLocked(-duration);
+                        sessions.remove(0);
+                    } else {
+                        break;
+                    }
+                }
+            }
+        }
     }
 
     private class QcHandler extends Handler {
@@ -4054,7 +4101,7 @@
     @Override
     public void dumpControllerStateLocked(final IndentingPrintWriter pw,
             final Predicate<JobStatus> predicate) {
-        pw.println("Is charging: " + mChargeTracker.isCharging());
+        pw.println("Is charging: " + mChargeTracker.isChargingLocked());
         pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis());
         pw.println();
 
@@ -4231,7 +4278,8 @@
         final long token = proto.start(fieldId);
         final long mToken = proto.start(StateControllerProto.QUOTA);
 
-        proto.write(StateControllerProto.QuotaController.IS_CHARGING, mChargeTracker.isCharging());
+        proto.write(StateControllerProto.QuotaController.IS_CHARGING,
+                mChargeTracker.isChargingLocked());
         proto.write(StateControllerProto.QuotaController.ELAPSED_REALTIME,
                 sElapsedRealtimeClock.millis());
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
index 3c4961a..140cca6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
@@ -56,7 +56,7 @@
     private boolean mDockIdle;
     private boolean mProjectionActive;
     private IdlenessListener mIdleListener;
-    private final UiModeManager.OnProjectionStateChangeListener mOnProjectionStateChangeListener =
+    private final UiModeManager.OnProjectionStateChangedListener mOnProjectionStateChangedListener =
             this::onProjectionStateChanged;
 
     private AlarmManager.OnAlarmListener mIdleAlarmListener = () -> {
@@ -105,9 +105,9 @@
 
         // TODO(b/172579710): Move the callbacks off the main executor and on to
         //  JobSchedulerBackgroundThread.getExecutor() once synchronization is fixed in this class.
-        context.getSystemService(UiModeManager.class).addOnProjectionStateChangeListener(
+        context.getSystemService(UiModeManager.class).addOnProjectionStateChangedListener(
                 UiModeManager.PROJECTION_TYPE_ALL, context.getMainExecutor(),
-                mOnProjectionStateChangeListener);
+                mOnProjectionStateChangedListener);
     }
 
     private void onProjectionStateChanged(@UiModeManager.ProjectionType int activeProjectionTypes,
diff --git a/apex/media/framework/java/android/media/MediaTranscodeManager.java b/apex/media/framework/java/android/media/MediaTranscodeManager.java
index d1106a2..5742d43 100644
--- a/apex/media/framework/java/android/media/MediaTranscodeManager.java
+++ b/apex/media/framework/java/android/media/MediaTranscodeManager.java
@@ -430,10 +430,6 @@
         mUid = Os.getuid();
         mPid = Os.getpid();
         mIsLowRamDevice = mContext.getSystemService(ActivityManager.class).isLowRamDevice();
-        IMediaTranscodingService service = getService(false /*retry*/);
-        if (service != null) {
-            mTranscodingClient = registerClient(service);
-        }
     }
 
     /**
diff --git a/boot/OWNERS b/boot/OWNERS
index 0648888..0e258d0 100644
--- a/boot/OWNERS
+++ b/boot/OWNERS
@@ -1,2 +1,6 @@
 # soong-team@ as the platform_bootclasspath module is tightly coupled with Soong
 file:platform/build/soong:/OWNERS
+
+# art-team@ manages the boot image profiles for frameworks
+per-file boot-* = calin@google.com, yawanng@google.com, ngeoffray@google.com
+per-file preloaded-classes* = calin@google.com, yawanng@google.com, ngeoffray@google.com
diff --git a/build/boot/boot-image-profile.txt b/build/boot/boot-image-profile.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/build/boot/boot-image-profile.txt
diff --git a/build/boot/preloaded-classes b/build/boot/preloaded-classes
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/build/boot/preloaded-classes
diff --git a/core/api/current.txt b/core/api/current.txt
index 82264c1..de92fd1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1821,7 +1821,6 @@
     field public static final int notification_large_icon_width = 17104901; // 0x1050005
     field public static final int system_app_widget_background_radius;
     field public static final int system_app_widget_inner_radius;
-    field public static final int system_app_widget_internal_padding;
     field public static final int thumbnail_height = 17104897; // 0x1050001
     field public static final int thumbnail_width = 17104898; // 0x1050002
   }
@@ -52965,7 +52964,7 @@
   public interface UiTranslationStateCallback {
     method public void onFinished();
     method public void onPaused();
-    method public void onResumed(@NonNull android.icu.util.ULocale, @NonNull android.icu.util.ULocale);
+    method public default void onResumed(@NonNull android.icu.util.ULocale, @NonNull android.icu.util.ULocale);
     method @Deprecated public void onStarted(@NonNull String, @NonNull String);
     method public default void onStarted(@NonNull android.icu.util.ULocale, @NonNull android.icu.util.ULocale);
   }
diff --git a/core/api/removed.txt b/core/api/removed.txt
index cdaa5f53..8229019a 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -1,4 +1,12 @@
 // Signature format: 2.0
+package android {
+
+  public static final class R.dimen {
+    field public static final int __removed_system_app_widget_internal_padding;
+  }
+
+}
+
 package android.app {
 
   public class Notification implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index bffd0e7..e80615a 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -838,12 +838,12 @@
   }
 
   public class UiModeManager {
-    method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public void addOnProjectionStateChangeListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.app.UiModeManager.OnProjectionStateChangeListener);
+    method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public void addOnProjectionStateChangedListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.app.UiModeManager.OnProjectionStateChangedListener);
     method @RequiresPermission(android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED) public void enableCarMode(@IntRange(from=0) int, int);
     method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public int getActiveProjectionTypes();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public java.util.Set<java.lang.String> getProjectingPackages(int);
     method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean releaseProjection(int);
-    method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public void removeOnProjectionStateChangeListener(@NonNull android.app.UiModeManager.OnProjectionStateChangeListener);
+    method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public void removeOnProjectionStateChangedListener(@NonNull android.app.UiModeManager.OnProjectionStateChangedListener);
     method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean requestProjection(int);
     field public static final String ACTION_ENTER_CAR_MODE_PRIORITIZED = "android.app.action.ENTER_CAR_MODE_PRIORITIZED";
     field public static final String ACTION_EXIT_CAR_MODE_PRIORITIZED = "android.app.action.EXIT_CAR_MODE_PRIORITIZED";
@@ -855,7 +855,7 @@
     field public static final int PROJECTION_TYPE_NONE = 0; // 0x0
   }
 
-  public static interface UiModeManager.OnProjectionStateChangeListener {
+  public static interface UiModeManager.OnProjectionStateChangedListener {
     method public void onProjectionStateChanged(int, @NonNull java.util.Set<java.lang.String>);
   }
 
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 4975fc2..010f4e4 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -9038,8 +9038,11 @@
      *
      * @hide
      */
-    // TODO (b/186872903) Refactor how sync noted ops are propagaged.
+    // TODO (b/186872903) Refactor how sync noted ops are propagated.
     public static void prefixParcelWithAppOpsIfNeeded(@NonNull Parcel p) {
+        if (!isListeningForOpNotedInBinderTransaction()) {
+            return;
+        }
         final ArrayMap<String, ArrayMap<String, long[]>> notedAppOps =
                 sAppOpsNotedInThisBinderTransaction.get();
         if (notedAppOps == null) {
@@ -9083,9 +9086,6 @@
         }
 
         final String myPackageName = ActivityThread.currentPackageName();
-        if (myPackageName == null) {
-            return;
-        }
 
         synchronized (sLock) {
             for (int i = 0; i < packageCount; i++) {
@@ -9105,7 +9105,7 @@
                     final BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
                     for (int code = notedAppOps.nextSetBit(0); code != -1;
                             code = notedAppOps.nextSetBit(code + 1)) {
-                        if (myPackageName.equals(packageName)) {
+                        if (Objects.equals(myPackageName, packageName)) {
                             if (sOnOpNotedCallback != null) {
                                 sOnOpNotedCallback.onNoted(new SyncNotedAppOp(code,
                                         attributionTag, packageName));
@@ -9123,7 +9123,7 @@
                     }
                     for (int code = notedAppOps.nextSetBit(0); code != -1;
                             code = notedAppOps.nextSetBit(code + 1)) {
-                        if (myPackageName.equals(packageName)) {
+                        if (Objects.equals(myPackageName, packageName)) {
                             sMessageCollector.onNoted(new SyncNotedAppOp(code,
                                     attributionTag, packageName));
                         }
diff --git a/core/java/android/app/IOnProjectionStateChangeListener.aidl b/core/java/android/app/IOnProjectionStateChangedListener.aidl
similarity index 93%
rename from core/java/android/app/IOnProjectionStateChangeListener.aidl
rename to core/java/android/app/IOnProjectionStateChangedListener.aidl
index f154985..2d2bf7f 100644
--- a/core/java/android/app/IOnProjectionStateChangeListener.aidl
+++ b/core/java/android/app/IOnProjectionStateChangedListener.aidl
@@ -17,6 +17,6 @@
 package android.app;
 
 /** {@hide} */
-oneway interface IOnProjectionStateChangeListener {
+oneway interface IOnProjectionStateChangedListener {
   void onProjectionStateChanged(int activeProjectionTypes, in List<String> projectingPackages);
 }
\ No newline at end of file
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index f71eebdc..9f21bcc 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -16,7 +16,7 @@
 
 package android.app;
 
-import android.app.IOnProjectionStateChangeListener;
+import android.app.IOnProjectionStateChangedListener;
 
 /**
  * Interface used to control special UI modes.
@@ -119,12 +119,12 @@
     /**
     * Registers a listener for changes to projection state.
     */
-    void addOnProjectionStateChangeListener(in IOnProjectionStateChangeListener listener, int projectionType);
+    void addOnProjectionStateChangedListener(in IOnProjectionStateChangedListener listener, int projectionType);
 
     /**
     * Unregisters a listener for changes to projection state.
     */
-    void removeOnProjectionStateChangeListener(in IOnProjectionStateChangeListener listener);
+    void removeOnProjectionStateChangedListener(in IOnProjectionStateChangedListener listener);
 
     /**
     * Returns packages that have currently set the given projection type.
diff --git a/core/java/android/app/PictureInPictureUiState.java b/core/java/android/app/PictureInPictureUiState.java
index 3d2cb3f..32ce89a 100644
--- a/core/java/android/app/PictureInPictureUiState.java
+++ b/core/java/android/app/PictureInPictureUiState.java
@@ -42,7 +42,20 @@
     }
 
     /**
-     * Returns whether Picture-in-Picture is stashed or not.
+     * Returns whether Picture-in-Picture is stashed or not. A stashed PiP means it is only
+     * partially visible to the user, with some parts of it being off-screen. This is usually
+     * an UI state that is triggered by the user, such as flinging the PiP to the edge or letting go
+     * of PiP while dragging partially off-screen.
+     *
+     * Developers can use this in conjunction with
+     * {@link Activity#onPictureInPictureUiStateChanged(PictureInPictureUiState)} to get a signal
+     * when the PiP stash state has changed. For example, if the state changed from {@code false} to
+     * {@code true}, developers can choose to temporarily pause video playback if PiP is of video
+     * content. Vice versa, if changing from {@code true} to {@code false} and video content is
+     * paused, developers can resumevideo playback.
+     *
+     * @see <a href="http://developer.android.com/about/versions/12/features/pip-improvements">
+     *     Picture in Picture (PiP) improvements</a>
      */
     public boolean isStashed() {
         return mIsStashed;
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 24fd04b..973a8fb 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -79,7 +79,7 @@
      * @hide
      */
     @SystemApi
-    public interface OnProjectionStateChangeListener {
+    public interface OnProjectionStateChangedListener {
         /**
          * Callback invoked when projection state changes for a {@link ProjectionType} for which
          * this listener was added.
@@ -254,10 +254,10 @@
     private final Object mLock = new Object();
     /**
      * Map that stores internally created {@link InnerListener} objects keyed by their corresponding
-     * externally provided {@link OnProjectionStateChangeListener} objects.
+     * externally provided callback objects.
      */
     @GuardedBy("mLock")
-    private final Map<OnProjectionStateChangeListener, InnerListener>
+    private final Map<OnProjectionStateChangedListener, InnerListener>
             mProjectionStateListenerMap = new ArrayMap<>();
 
     /**
@@ -265,9 +265,9 @@
      * fail to remove listeners.
      */
     @GuardedBy("mLock")
-    private final OnProjectionStateChangeListenerResourceManager
-            mOnProjectionStateChangeListenerResourceManager =
-            new OnProjectionStateChangeListenerResourceManager();
+    private final OnProjectionStateChangedListenerResourceManager
+            mOnProjectionStateChangedListenerResourceManager =
+            new OnProjectionStateChangedListenerResourceManager();
 
     @UnsupportedAppUsage
     /*package*/ UiModeManager() throws ServiceNotFoundException {
@@ -687,7 +687,7 @@
 
     /**
      * Indicates no projection type. Can be used to compare with the {@link ProjectionType} in
-     * {@link OnProjectionStateChangeListener#onProjectionStateChanged(int, Set)}.
+     * {@link OnProjectionStateChangedListener#onProjectionStateChanged(int, Set)}.
      *
      * @hide
      */
@@ -706,7 +706,7 @@
     public static final int PROJECTION_TYPE_AUTOMOTIVE = 0x0001;
     /**
      * Indicates all projection types. For use with
-     * {@link #addOnProjectionStateChangeListener(int, Executor, OnProjectionStateChangeListener)}
+     * {@link #addOnProjectionStateChangedListener(int, Executor, OnProjectionStateChangedListener)}
      * and {@link #getProjectingPackages(int)}.
      *
      * @hide
@@ -829,15 +829,15 @@
      *
      * @param projectionType one or more {@link ProjectionType}s to listen for changes regarding
      * @param executor an {@link Executor} on which to invoke the callbacks
-     * @param listener the {@link OnProjectionStateChangeListener} to add
+     * @param listener the {@link OnProjectionStateChangedListener} to add
      *
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE)
-    public void addOnProjectionStateChangeListener(@ProjectionType int projectionType,
+    public void addOnProjectionStateChangedListener(@ProjectionType int projectionType,
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull OnProjectionStateChangeListener listener) {
+            @NonNull OnProjectionStateChangedListener listener) {
         synchronized (mLock) {
             if (mProjectionStateListenerMap.containsKey(listener)) {
                 Slog.i(TAG, "Attempted to add listener that was already added.");
@@ -845,12 +845,12 @@
             }
             if (mService != null) {
                 InnerListener innerListener = new InnerListener(executor, listener,
-                        mOnProjectionStateChangeListenerResourceManager);
+                        mOnProjectionStateChangedListenerResourceManager);
                 try {
-                    mService.addOnProjectionStateChangeListener(innerListener, projectionType);
+                    mService.addOnProjectionStateChangedListener(innerListener, projectionType);
                     mProjectionStateListenerMap.put(listener, innerListener);
                 } catch (RemoteException e) {
-                    mOnProjectionStateChangeListenerResourceManager.remove(innerListener);
+                    mOnProjectionStateChangedListenerResourceManager.remove(innerListener);
                     throw e.rethrowFromSystemServer();
                 }
             }
@@ -860,14 +860,14 @@
     /**
      * Removes the listener so it stops receiving updates for all {@link ProjectionType}s.
      *
-     * @param listener the {@link OnProjectionStateChangeListener} to remove
+     * @param listener the {@link OnProjectionStateChangedListener} to remove
      *
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE)
-    public void removeOnProjectionStateChangeListener(
-            @NonNull OnProjectionStateChangeListener listener) {
+    public void removeOnProjectionStateChangedListener(
+            @NonNull OnProjectionStateChangedListener listener) {
         synchronized (mLock) {
             InnerListener innerListener = mProjectionStateListenerMap.get(listener);
             if (innerListener == null) {
@@ -876,23 +876,23 @@
             }
             if (mService != null) {
                 try {
-                    mService.removeOnProjectionStateChangeListener(innerListener);
+                    mService.removeOnProjectionStateChangedListener(innerListener);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
             }
             mProjectionStateListenerMap.remove(listener);
-            mOnProjectionStateChangeListenerResourceManager.remove(innerListener);
+            mOnProjectionStateChangedListenerResourceManager.remove(innerListener);
         }
     }
 
-    private static class InnerListener extends IOnProjectionStateChangeListener.Stub {
-        private final WeakReference<OnProjectionStateChangeListenerResourceManager>
+    private static class InnerListener extends IOnProjectionStateChangedListener.Stub {
+        private final WeakReference<OnProjectionStateChangedListenerResourceManager>
                 mResourceManager;
 
         private InnerListener(@NonNull Executor executor,
-                @NonNull OnProjectionStateChangeListener outerListener,
-                @NonNull OnProjectionStateChangeListenerResourceManager resourceManager) {
+                @NonNull OnProjectionStateChangedListener outerListener,
+                @NonNull OnProjectionStateChangedListenerResourceManager resourceManager) {
             resourceManager.put(this, executor, outerListener);
             mResourceManager = new WeakReference<>(resourceManager);
         }
@@ -900,13 +900,14 @@
         @Override
         public void onProjectionStateChanged(int activeProjectionTypes,
                 List<String> projectingPackages) {
-            OnProjectionStateChangeListenerResourceManager resourceManager = mResourceManager.get();
+            OnProjectionStateChangedListenerResourceManager resourceManager =
+                    mResourceManager.get();
             if (resourceManager == null) {
                 Slog.w(TAG, "Can't execute onProjectionStateChanged, resource manager is gone.");
                 return;
             }
 
-            OnProjectionStateChangeListener outerListener = resourceManager.getOuterListener(this);
+            OnProjectionStateChangedListener outerListener = resourceManager.getOuterListener(this);
             Executor executor = resourceManager.getExecutor(this);
             if (outerListener == null || executor == null) {
                 Slog.w(TAG, "Can't execute onProjectionStatechanged, references are null.");
@@ -914,7 +915,7 @@
             }
 
             executor.execute(PooledLambda.obtainRunnable(
-                    OnProjectionStateChangeListener::onProjectionStateChanged,
+                    OnProjectionStateChangedListener::onProjectionStateChanged,
                     outerListener,
                     activeProjectionTypes,
                     new ArraySet<>(projectingPackages)).recycleOnUse());
@@ -924,15 +925,15 @@
     /**
      * Wrapper class that ensures we don't leak {@link Activity} or other large {@link Context} in
      * which this {@link UiModeManager} resides if/when it ends without unregistering associated
-     * {@link OnProjectionStateChangeListener}s.
+     * callback objects.
      */
-    private static class OnProjectionStateChangeListenerResourceManager {
-        private final Map<InnerListener, OnProjectionStateChangeListener> mOuterListenerMap =
+    private static class OnProjectionStateChangedListenerResourceManager {
+        private final Map<InnerListener, OnProjectionStateChangedListener> mOuterListenerMap =
                 new ArrayMap<>(1);
         private final Map<InnerListener, Executor> mExecutorMap = new ArrayMap<>(1);
 
         void put(@NonNull InnerListener innerListener, @NonNull Executor executor,
-                OnProjectionStateChangeListener outerListener) {
+                OnProjectionStateChangedListener outerListener) {
             mOuterListenerMap.put(innerListener, outerListener);
             mExecutorMap.put(innerListener, executor);
         }
@@ -942,7 +943,7 @@
             mExecutorMap.remove(innerListener);
         }
 
-        OnProjectionStateChangeListener getOuterListener(@NonNull InnerListener innerListener) {
+        OnProjectionStateChangedListener getOuterListener(@NonNull InnerListener innerListener) {
             return mOuterListenerMap.get(innerListener);
         }
 
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index d640a6f..be62deb7 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -95,12 +95,12 @@
     private static final float MIN_COLOR_OCCURRENCE = 0.05f;
 
     // Decides when dark theme is optimal for this wallpaper
-    private static final float DARK_THEME_MEAN_LUMINANCE = 0.25f;
+    private static final float DARK_THEME_MEAN_LUMINANCE = 0.3f;
     // Minimum mean luminosity that an image needs to have to support dark text
-    private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = 0.75f;
+    private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = 0.7f;
     // We also check if the image has dark pixels in it,
     // to avoid bright images with some dark spots.
-    private static final float DARK_PIXEL_CONTRAST = 6f;
+    private static final float DARK_PIXEL_CONTRAST = 5.5f;
     private static final float MAX_DARK_AREA = 0.025f;
 
     private final List<Color> mMainColors;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 609c014..02e64b8 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4573,27 +4573,6 @@
     }
 
     /**
-     * Determine whether the current profile password the user has set is sufficient
-     * to meet the policy requirements (e.g. quality, minimum length) that have been
-     * requested by the admins of the parent user and its profiles.
-     *
-     * @param userHandle the userId of the profile to check the password for.
-     * @return Returns true if the password would meet the current requirements, else false.
-     * @throws SecurityException if {@code userHandle} is not a managed profile.
-     * @hide
-     */
-    public boolean isProfileActivePasswordSufficientForParent(int userHandle) {
-        if (mService != null) {
-            try {
-                return mService.isProfileActivePasswordSufficientForParent(userHandle);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-        return false;
-    }
-
-    /**
      * Returns whether the given user's credential will be sufficient for all password policy
      * requirement, once the user's profile has switched to unified challenge.
      *
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 370db60..9f76bd1 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -88,7 +88,6 @@
 
     boolean isActivePasswordSufficient(int userHandle, boolean parent);
     boolean isActivePasswordSufficientForDeviceRequirement();
-    boolean isProfileActivePasswordSufficientForParent(int userHandle);
     boolean isPasswordSufficientAfterProfileUnification(int userHandle, int profileUser);
     int getPasswordComplexity(boolean parent);
     void setRequiredPasswordComplexity(int passwordComplexity, boolean parent);
diff --git a/core/java/android/app/admin/OWNERS b/core/java/android/app/admin/OWNERS
index 8462cbe..6acbef2 100644
--- a/core/java/android/app/admin/OWNERS
+++ b/core/java/android/app/admin/OWNERS
@@ -3,9 +3,9 @@
 # Android Enterprise team
 rubinxu@google.com
 sandness@google.com
-eranm@google.com
 alexkershaw@google.com
 pgrafov@google.com
 
 # Emeritus
 yamasani@google.com
+eranm@google.com
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 82da4fb..152de44 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -17,6 +17,7 @@
 package android.appwidget;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityOptions;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -68,6 +69,7 @@
 
     static final String TAG = "AppWidgetHostView";
     private static final String KEY_JAILED_ARRAY = "jail";
+    private static final String KEY_INFLATION_ID = "inflation_id";
 
     static final boolean LOGD = false;
 
@@ -97,9 +99,12 @@
     private RemoteViews.ColorResources mColorResources = null;
     // Stores the last remote views last inflated.
     private RemoteViews mLastInflatedRemoteViews = null;
+    private long mLastInflatedRemoteViewsId = -1;
 
     private Executor mAsyncExecutor;
     private CancellationSignal mLastExecutionSignal;
+    private SparseArray<Parcelable> mDelayedRestoredState;
+    private long mDelayedRestoredInflationId;
 
     /**
      * Create a host view.  Uses default fade animations.
@@ -226,6 +231,8 @@
 
         Bundle bundle = new Bundle();
         bundle.putSparseParcelableArray(KEY_JAILED_ARRAY, jail);
+        bundle.putLong(KEY_INFLATION_ID, mLastInflatedRemoteViewsId);
+        container.put(generateId(), bundle);
         container.put(generateId(), bundle);
     }
 
@@ -239,14 +246,30 @@
         final Parcelable parcelable = container.get(generateId());
 
         SparseArray<Parcelable> jail = null;
+        long inflationId = -1;
         if (parcelable instanceof Bundle) {
-            jail = ((Bundle) parcelable).getSparseParcelableArray(KEY_JAILED_ARRAY);
+            Bundle bundle = (Bundle) parcelable;
+            jail = bundle.getSparseParcelableArray(KEY_JAILED_ARRAY);
+            inflationId = bundle.getLong(KEY_INFLATION_ID, -1);
         }
 
         if (jail == null) jail = new SparseArray<>();
 
+        mDelayedRestoredState = jail;
+        mDelayedRestoredInflationId = inflationId;
+        restoreInstanceState();
+    }
+
+    void restoreInstanceState() {
+        long inflationId = mDelayedRestoredInflationId;
+        SparseArray<Parcelable> state = mDelayedRestoredState;
+        if (inflationId == -1 || inflationId != mLastInflatedRemoteViewsId) {
+            return; // We don't restore.
+        }
+        mDelayedRestoredInflationId = -1;
+        mDelayedRestoredState = null;
         try  {
-            super.dispatchRestoreInstanceState(jail);
+            super.dispatchRestoreInstanceState(state);
         } catch (Exception e) {
             Log.e(TAG, "failed to restoreInstanceState for widget id: " + mAppWidgetId + ", "
                     + (mInfo == null ? "null" : mInfo.provider), e);
@@ -476,7 +499,7 @@
      * AppWidget provider. Will animate into these new views as needed
      */
     public void updateAppWidget(RemoteViews remoteViews) {
-        this.mLastInflatedRemoteViews = remoteViews;
+        mLastInflatedRemoteViews = remoteViews;
         applyRemoteViews(remoteViews, true);
     }
 
@@ -484,17 +507,23 @@
      * Reapply the last inflated remote views, or the default view is none was inflated.
      */
     private void reapplyLastRemoteViews() {
+        SparseArray<Parcelable> savedState = new SparseArray<>();
+        saveHierarchyState(savedState);
         applyRemoteViews(mLastInflatedRemoteViews, true);
+        restoreHierarchyState(savedState);
     }
 
     /**
      * @hide
      */
-    protected void applyRemoteViews(RemoteViews remoteViews, boolean useAsyncIfPossible) {
+    protected void applyRemoteViews(@Nullable RemoteViews remoteViews, boolean useAsyncIfPossible) {
         boolean recycled = false;
         View content = null;
         Exception exception = null;
 
+        // Block state restore until the end of the apply.
+        mLastInflatedRemoteViewsId = -1;
+
         if (mLastExecutionSignal != null) {
             mLastExecutionSignal.cancel();
             mLastExecutionSignal = null;
@@ -525,6 +554,7 @@
                     rvToApply.reapply(mContext, mView, mInteractionHandler, mCurrentSize,
                             mColorResources);
                     content = mView;
+                    mLastInflatedRemoteViewsId = rvToApply.computeUniqueId(remoteViews);
                     recycled = true;
                     if (LOGD) Log.d(TAG, "was able to recycle existing layout");
                 } catch (RuntimeException e) {
@@ -537,6 +567,7 @@
                 try {
                     content = rvToApply.apply(mContext, this, mInteractionHandler,
                             mCurrentSize, mColorResources);
+                    mLastInflatedRemoteViewsId = rvToApply.computeUniqueId(remoteViews);
                     if (LOGD) Log.d(TAG, "had to inflate new layout");
                 } catch (RuntimeException e) {
                     exception = e;
@@ -557,7 +588,7 @@
                 return ;
             }
             if (exception != null) {
-                Log.w(TAG, "Error inflating RemoteViews : " + exception.toString());
+                Log.w(TAG, "Error inflating RemoteViews", exception);
             }
             content = getErrorView();
             mViewMode = VIEW_MODE_ERROR;
@@ -574,12 +605,16 @@
         }
     }
 
-    private void inflateAsync(RemoteViews remoteViews) {
+    private void inflateAsync(@NonNull RemoteViews remoteViews) {
         // Prepare a local reference to the remote Context so we're ready to
         // inflate any requested LayoutParams.
         mRemoteContext = getRemoteContext();
         int layoutId = remoteViews.getLayoutId();
 
+        if (mLastExecutionSignal != null) {
+            mLastExecutionSignal.cancel();
+        }
+
         // If our stale view has been prepared to match active, and the new
         // layout matches, try recycling it
         if (layoutId == mLayoutId && mView != null) {
@@ -611,7 +646,10 @@
         private final boolean mIsReapply;
         private final int mLayoutId;
 
-        public ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply) {
+        ViewApplyListener(
+                RemoteViews views,
+                int layoutId,
+                boolean isReapply) {
             mViews = views;
             mLayoutId = layoutId;
             mIsReapply = isReapply;
@@ -623,6 +661,10 @@
             mViewMode = VIEW_MODE_CONTENT;
 
             applyContent(v, mIsReapply, null);
+
+            mLastInflatedRemoteViewsId = mViews.computeUniqueId(mLastInflatedRemoteViews);
+            restoreInstanceState();
+            mLastExecutionSignal = null;
         }
 
         @Override
@@ -638,6 +680,7 @@
             } else {
                 applyContent(null, false, e);
             }
+            mLastExecutionSignal = null;
         }
     }
 
@@ -743,7 +786,7 @@
         }
 
         if (exception != null) {
-            Log.w(TAG, "Error inflating AppWidget " + mInfo + ": " + exception.toString());
+            Log.w(TAG, "Error inflating AppWidget " + mInfo, exception);
         }
 
         if (defaultView == null) {
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 3db1885..063ba11 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -455,13 +455,14 @@
     @Nullable
     public final CharSequence loadDescription(@NonNull Context context) {
         if (ResourceId.isValid(descriptionRes)) {
-            return context.getPackageManager()
-                    .getText(
+            CharSequence description =
+                    context.getPackageManager().getText(
                             providerInfo.packageName,
                             descriptionRes,
-                            providerInfo.applicationInfo)
-                    .toString()
-                    .trim();
+                            providerInfo.applicationInfo);
+            if (description != null) {
+                return description.toString().trim();
+            }
         }
         return null;
     }
diff --git a/core/java/android/content/pm/BaseParceledListSlice.java b/core/java/android/content/pm/BaseParceledListSlice.java
index 5c7a548..7bade74 100644
--- a/core/java/android/content/pm/BaseParceledListSlice.java
+++ b/core/java/android/content/pm/BaseParceledListSlice.java
@@ -75,16 +75,7 @@
             if (p.readInt() == 0) {
                 break;
             }
-
-            final T parcelable = readCreator(creator, p, loader);
-            if (listElementClass == null) {
-                listElementClass = parcelable.getClass();
-            } else {
-                verifySameType(listElementClass, parcelable.getClass());
-            }
-
-            mList.add(parcelable);
-
+            listElementClass = readVerifyAndAddElement(creator, p, loader, listElementClass);
             if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1));
             i++;
         }
@@ -104,11 +95,8 @@
                 return;
             }
             while (i < N && reply.readInt() != 0) {
-                final T parcelable = readCreator(creator, reply, loader);
-                verifySameType(listElementClass, parcelable.getClass());
-
-                mList.add(parcelable);
-
+                listElementClass = readVerifyAndAddElement(creator, reply, loader,
+                        listElementClass);
                 if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1));
                 i++;
             }
@@ -117,6 +105,18 @@
         }
     }
 
+    private Class<?> readVerifyAndAddElement(Parcelable.Creator<?> creator, Parcel p,
+            ClassLoader loader, Class<?> listElementClass) {
+        final T parcelable = readCreator(creator, p, loader);
+        if (listElementClass == null) {
+            listElementClass = parcelable.getClass();
+        } else {
+            verifySameType(listElementClass, parcelable.getClass());
+        }
+        mList.add(parcelable);
+        return listElementClass;
+    }
+
     private T readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader) {
         if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
             Parcelable.ClassLoaderCreator<?> classLoaderCreator =
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 51d196d..17116e2 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -223,14 +223,6 @@
     public static final long BLOCK_FLAG_SLIPPERY = android.os.IInputConstants.BLOCK_FLAG_SLIPPERY;
 
     /**
-     * Check whether apps are using MotionEvent.getRawX/Y. This is implementation-specific, and
-     * thus undefined for most 3p app usages.
-     * @hide
-     */
-    @ChangeId
-    public static final long APP_USES_RAW_INPUT_COORDS = 179274888L;
-
-    /**
      * Input Event Injection Synchronization Mode: None.
      * Never blocks.  Injection is asynchronous and is assumed always to be successful.
      * @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c8e5e42..c51c506 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2660,7 +2660,6 @@
     private static final class ContentProviderHolder {
         private final Object mLock = new Object();
 
-        @GuardedBy("mLock")
         private final Uri mUri;
         @GuardedBy("mLock")
         @UnsupportedAppUsage
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 31ca8e1..b054468 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -16,7 +16,6 @@
 
 package android.view;
 
-import static android.hardware.input.InputManager.APP_USES_RAW_INPUT_COORDS;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -24,7 +23,6 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
-import android.compat.Compatibility;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Matrix;
 import android.os.Build;
@@ -1569,6 +1567,8 @@
             int axis, int pointerIndex, int historyPos);
     @FastNative
     private static native void nativeTransform(long nativePtr, Matrix matrix);
+    @FastNative
+    private static native void nativeApplyTransform(long nativePtr, Matrix matrix);
 
     // -------------- @CriticalNative ----------------------
 
@@ -2674,7 +2674,6 @@
      * @see #AXIS_X
      */
     public final float getRawX() {
-        Compatibility.reportUnconditionalChange(APP_USES_RAW_INPUT_COORDS);
         return nativeGetRawAxisValue(mNativePtr, AXIS_X, 0, HISTORY_CURRENT);
     }
 
@@ -2688,7 +2687,6 @@
      * @see #AXIS_Y
      */
     public final float getRawY() {
-        Compatibility.reportUnconditionalChange(APP_USES_RAW_INPUT_COORDS);
         return nativeGetRawAxisValue(mNativePtr, AXIS_Y, 0, HISTORY_CURRENT);
     }
 
@@ -2705,7 +2703,6 @@
      * @see #AXIS_X
      */
     public float getRawX(int pointerIndex) {
-        Compatibility.reportUnconditionalChange(APP_USES_RAW_INPUT_COORDS);
         return nativeGetRawAxisValue(mNativePtr, AXIS_X, pointerIndex, HISTORY_CURRENT);
     }
 
@@ -2722,7 +2719,6 @@
      * @see #AXIS_Y
      */
     public float getRawY(int pointerIndex) {
-        Compatibility.reportUnconditionalChange(APP_USES_RAW_INPUT_COORDS);
         return nativeGetRawAxisValue(mNativePtr, AXIS_Y, pointerIndex, HISTORY_CURRENT);
     }
 
@@ -3266,6 +3262,21 @@
     }
 
     /**
+     * Transforms all of the points in the event directly instead of modifying the event's
+     * internal transform.
+     *
+     * @param matrix The transformation matrix to apply.
+     * @hide
+     */
+    public void applyTransform(Matrix matrix) {
+        if (matrix == null) {
+            throw new IllegalArgumentException("matrix must not be null");
+        }
+
+        nativeApplyTransform(mNativePtr, matrix);
+    }
+
+    /**
      * Add a new movement to the batch of movements in this event.  The event's
      * current location, position and size is updated to the new values.
      * The current values in the event are added to a list of historical values.
diff --git a/core/java/android/view/translation/UiTranslationStateCallback.java b/core/java/android/view/translation/UiTranslationStateCallback.java
index 968cbdc..1bae0ef 100644
--- a/core/java/android/view/translation/UiTranslationStateCallback.java
+++ b/core/java/android/view/translation/UiTranslationStateCallback.java
@@ -54,7 +54,9 @@
      * The system is requesting that the application restore from the temporarily paused state and
      * show the content in translated language.
      */
-    void onResumed(@NonNull ULocale sourceLocale, @NonNull ULocale targetLocale);
+    // TODO: Remove the default implementation when clients have implemented this.
+    default void onResumed(@NonNull ULocale sourceLocale, @NonNull ULocale targetLocale) {
+    }
 
     /**
      * The UI Translation session has ended.
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index 6b49569..ad123c1 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -16,10 +16,10 @@
 
 package android.webkit;
 
-import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.UptimeMillisLong;
 import android.app.ActivityThread;
 import android.app.Application;
 import android.app.ResourcesManager;
@@ -227,7 +227,7 @@
      * WebViewChromiumFactoryProvider#create method was invoked.
      */
     @NonNull
-    @ElapsedRealtimeLong
+    @UptimeMillisLong
     public long[] getTimestamps() {
         return WebViewFactory.getTimestamps();
     }
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 5fc5b29..9a38512 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -273,7 +273,7 @@
             // us honest and minimize usage of WebView internals when binding the proxy.
             if (sProviderInstance != null) return sProviderInstance;
 
-            sTimestamps[WEBVIEW_LOAD_START] = SystemClock.elapsedRealtime();
+            sTimestamps[WEBVIEW_LOAD_START] = SystemClock.uptimeMillis();
             final int uid = android.os.Process.myUid();
             if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
                     || uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
@@ -413,7 +413,7 @@
 
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
                     "initialApplication.createApplicationContext");
-            sTimestamps[CREATE_CONTEXT_START] = SystemClock.elapsedRealtime();
+            sTimestamps[CREATE_CONTEXT_START] = SystemClock.uptimeMillis();
             try {
                 // Construct an app context to load the Java code into the current app.
                 Context webViewContext = initialApplication.createApplicationContext(
@@ -422,7 +422,7 @@
                 sPackageInfo = newPackageInfo;
                 return webViewContext;
             } finally {
-                sTimestamps[CREATE_CONTEXT_END] = SystemClock.elapsedRealtime();
+                sTimestamps[CREATE_CONTEXT_END] = SystemClock.uptimeMillis();
                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
             }
         } catch (RemoteException | PackageManager.NameNotFoundException e) {
@@ -448,26 +448,26 @@
 
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
             try {
-                sTimestamps[ADD_ASSETS_START] = SystemClock.elapsedRealtime();
+                sTimestamps[ADD_ASSETS_START] = SystemClock.uptimeMillis();
                 for (String newAssetPath : webViewContext.getApplicationInfo().getAllApkPaths()) {
                     initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
                 }
                 sTimestamps[ADD_ASSETS_END] = sTimestamps[GET_CLASS_LOADER_START] =
-                        SystemClock.elapsedRealtime();
+                        SystemClock.uptimeMillis();
                 ClassLoader clazzLoader = webViewContext.getClassLoader();
                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
                 sTimestamps[GET_CLASS_LOADER_END] = sTimestamps[NATIVE_LOAD_START] =
-                        SystemClock.elapsedRealtime();
+                        SystemClock.uptimeMillis();
                 WebViewLibraryLoader.loadNativeLibrary(clazzLoader,
                         getWebViewLibrary(sPackageInfo.applicationInfo));
                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
                 sTimestamps[NATIVE_LOAD_END] = sTimestamps[PROVIDER_CLASS_FOR_NAME_START] =
-                        SystemClock.elapsedRealtime();
+                        SystemClock.uptimeMillis();
                 try {
                     return getWebViewProviderClass(clazzLoader);
                 } finally {
-                    sTimestamps[PROVIDER_CLASS_FOR_NAME_END] = SystemClock.elapsedRealtime();
+                    sTimestamps[PROVIDER_CLASS_FOR_NAME_END] = SystemClock.uptimeMillis();
                     Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
                 }
             } catch (ClassNotFoundException e) {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0dbdb8f..ee7818c 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -385,6 +385,11 @@
      */
     private int mViewId = View.NO_ID;
 
+    /**
+     * Id used to uniquely identify a {@link RemoteViews} instance coming from a given provider.
+     */
+    private long mProviderInstanceId = -1;
+
     /** Class cookies of the Parcel this instance was read from. */
     private Map<Class, Object> mClassCookies;
 
@@ -3646,6 +3651,7 @@
         mApplyFlags = src.mApplyFlags;
         mClassCookies = src.mClassCookies;
         mIdealSize = src.mIdealSize;
+        mProviderInstanceId = src.mProviderInstanceId;
 
         if (src.hasLandscapeAndPortraitLayouts()) {
             mLandscape = new RemoteViews(src.mLandscape);
@@ -3744,6 +3750,7 @@
             mLightBackgroundLayoutId = mPortrait.mLightBackgroundLayoutId;
         }
         mApplyFlags = parcel.readInt();
+        mProviderInstanceId = parcel.readLong();
     }
 
     private void readActionsFromParcel(Parcel parcel, int depth) {
@@ -6021,6 +6028,7 @@
             mPortrait.writeToParcel(dest, flags | PARCELABLE_ELIDE_DUPLICATES);
         }
         dest.writeInt(mApplyFlags);
+        dest.writeLong(mProviderInstanceId);
     }
 
     private void writeActionsToParcel(Parcel parcel) {
@@ -6711,4 +6719,85 @@
     public @IdRes int getViewId() {
         return mViewId;
     }
+
+    /**
+     * Set the provider instance ID.
+     *
+     * This should only be used by {@link com.android.server.appwidget.AppWidgetService}.
+     * @hide
+     */
+    public void setProviderInstanceId(long id) {
+        mProviderInstanceId = id;
+    }
+
+    /**
+     * Get the provider instance id.
+     *
+     * This should uniquely identifies {@link RemoteViews} coming from a given App Widget
+     * Provider. This changes each time the App Widget provider update the {@link RemoteViews} of
+     * its widget. Returns -1 if the {@link RemoteViews} doesn't come from an App Widget provider.
+     * @hide
+     */
+    public long getProviderInstanceId() {
+        return mProviderInstanceId;
+    }
+
+    /**
+     * Identify the child of this {@link RemoteViews}, or 0 if this is not a child.
+     *
+     * The returned value is always a small integer, currently between 0 and 17.
+     */
+    private int getChildId(@NonNull RemoteViews child) {
+        if (child == this) {
+            return 0;
+        }
+        if (hasSizedRemoteViews()) {
+            for (int i = 0; i < mSizedRemoteViews.size(); i++) {
+                if (mSizedRemoteViews.get(i) == child) {
+                    return i + 1;
+                }
+            }
+        }
+        if (hasLandscapeAndPortraitLayouts()) {
+            if (mLandscape == child) {
+                return 1;
+            } else if (mPortrait == child) {
+                return 2;
+            }
+        }
+        // This is not a child of this RemoteViews.
+        return 0;
+    }
+
+    /**
+     * Identify uniquely this RemoteViews, or returns -1 if not possible.
+     *
+     * @param parent If the {@link RemoteViews} is not a root {@link RemoteViews}, this should be
+     *              the parent that contains it.
+     *
+     * @hide
+     */
+    public long computeUniqueId(@Nullable RemoteViews parent) {
+        if (mIsRoot) {
+            long viewId = getProviderInstanceId();
+            if (viewId != -1) {
+                viewId <<= 8;
+            }
+            return viewId;
+        }
+        if (parent == null) {
+            return -1;
+        }
+        long viewId = parent.getProviderInstanceId();
+        if (viewId == -1) {
+            return -1;
+        }
+        int childId = parent.getChildId(this);
+        if (childId == -1) {
+            return -1;
+        }
+        viewId <<= 8;
+        viewId |= childId;
+        return viewId;
+    }
 }
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 9749a68..6b33428 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -86,8 +86,8 @@
     // Default height for the default loading view, in case we cannot get inflate the first view
     private static final int DEFAULT_LOADING_VIEW_HEIGHT = 50;
 
-    // We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
-    // structures;
+    // We cache the FixedSizeRemoteViewsCaches across orientation and re-inflation due to color
+    // palette changes. These are the related data structures:
     private static final HashMap<RemoteViewsCacheKey, FixedSizeRemoteViewsCache>
             sCachedRemoteViewsCaches = new HashMap<>();
     private static final HashMap<RemoteViewsCacheKey, Runnable>
diff --git a/core/java/android/widget/TextViewOnReceiveContentListener.java b/core/java/android/widget/TextViewOnReceiveContentListener.java
index 0d5bf71..6a966e0 100644
--- a/core/java/android/widget/TextViewOnReceiveContentListener.java
+++ b/core/java/android/widget/TextViewOnReceiveContentListener.java
@@ -19,7 +19,6 @@
 import static android.content.ContentResolver.SCHEME_CONTENT;
 import static android.view.ContentInfo.FLAG_CONVERT_TO_PLAIN_TEXT;
 import static android.view.ContentInfo.SOURCE_AUTOFILL;
-import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
 import static android.view.ContentInfo.SOURCE_INPUT_METHOD;
 
 import android.annotation.NonNull;
@@ -82,10 +81,6 @@
             onReceiveForAutofill((TextView) view, payload);
             return null;
         }
-        if (source == SOURCE_DRAG_AND_DROP) {
-            onReceiveForDragAndDrop((TextView) view, payload);
-            return null;
-        }
 
         // The code here follows the original paste logic from TextView:
         // https://cs.android.com/android/_/android/platform/frameworks/base/+/9fefb65aa9e7beae9ca8306b925b9fbfaeffecc9:core/java/android/widget/TextView.java;l=12644
@@ -147,13 +142,6 @@
         Selection.setSelection(editable, editable.length());
     }
 
-    private static void onReceiveForDragAndDrop(@NonNull TextView view,
-            @NonNull ContentInfo payload) {
-        final CharSequence text = coerceToText(payload.getClip(), view.getContext(),
-                payload.getFlags());
-        replaceSelection((Editable) view.getText(), text);
-    }
-
     private static @NonNull CharSequence coerceToText(@NonNull ClipData clip,
             @NonNull Context context, @Flags int flags) {
         SpannableStringBuilder ssb = new SpannableStringBuilder();
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 4e96ae7..2c92b91 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -116,13 +116,13 @@
     private ViewGroup mExpandButtonAndContentContainer;
     private NotificationExpandButton mExpandButton;
     private MessagingLinearLayout mImageMessageContainer;
-    private int mBadgedSideMargins;
+    private int mBadgeProtrusion;
     private int mConversationAvatarSize;
     private int mConversationAvatarSizeExpanded;
     private CachingIconView mIcon;
     private CachingIconView mImportanceRingView;
-    private int mExpandedGroupSideMargin;
-    private int mExpandedGroupSideMarginFacePile;
+    private int mExpandedGroupBadgeProtrusion;
+    private int mExpandedGroupBadgeProtrusionFacePile;
     private View mConversationFacePile;
     private int mNotificationBackgroundColor;
     private CharSequence mFallbackChatName;
@@ -251,8 +251,8 @@
                 R.dimen.conversation_header_expanded_padding_end);
         mContentMarginEnd = getResources().getDimensionPixelSize(
                 R.dimen.notification_content_margin_end);
-        mBadgedSideMargins = getResources().getDimensionPixelSize(
-                R.dimen.conversation_badge_side_margin);
+        mBadgeProtrusion = getResources().getDimensionPixelSize(
+                R.dimen.conversation_badge_protrusion);
         mConversationAvatarSize = getResources().getDimensionPixelSize(
                 R.dimen.conversation_avatar_size);
         mConversationAvatarSizeExpanded = getResources().getDimensionPixelSize(
@@ -263,10 +263,10 @@
                 R.dimen.conversation_icon_container_top_padding);
         mExpandedGroupMessagePadding = getResources().getDimensionPixelSize(
                 R.dimen.expanded_group_conversation_message_padding);
-        mExpandedGroupSideMargin = getResources().getDimensionPixelSize(
-                R.dimen.conversation_badge_side_margin_group_expanded);
-        mExpandedGroupSideMarginFacePile = getResources().getDimensionPixelSize(
-                R.dimen.conversation_badge_side_margin_group_expanded_face_pile);
+        mExpandedGroupBadgeProtrusion = getResources().getDimensionPixelSize(
+                R.dimen.conversation_badge_protrusion_group_expanded);
+        mExpandedGroupBadgeProtrusionFacePile = getResources().getDimensionPixelSize(
+                R.dimen.conversation_badge_protrusion_group_expanded_face_pile);
         mConversationFacePile = findViewById(R.id.conversation_face_pile);
         mFacePileAvatarSize = getResources().getDimensionPixelSize(
                 R.dimen.conversation_face_pile_avatar_size);
@@ -646,7 +646,7 @@
             facepileAvatarSize = mFacePileAvatarSizeExpandedGroup;
             facePileBackgroundSize = facepileAvatarSize + 2 * mFacePileProtectionWidthExpanded;
         }
-        LayoutParams layoutParams = (LayoutParams) mConversationIconView.getLayoutParams();
+        LayoutParams layoutParams = (LayoutParams) mConversationFacePile.getLayoutParams();
         layoutParams.width = conversationAvatarSize;
         layoutParams.height = conversationAvatarSize;
         mConversationFacePile.setLayoutParams(layoutParams);
@@ -679,29 +679,35 @@
      * update the icon position and sizing
      */
     private void updateIconPositionAndSize() {
-        int sidemargin;
+        int badgeProtrusion;
         int conversationAvatarSize;
         if (mIsOneToOne || mIsCollapsed) {
-            sidemargin = mBadgedSideMargins;
+            badgeProtrusion = mBadgeProtrusion;
             conversationAvatarSize = mConversationAvatarSize;
         } else {
-            sidemargin = mConversationFacePile.getVisibility() == VISIBLE
-                    ? mExpandedGroupSideMarginFacePile
-                    : mExpandedGroupSideMargin;
+            badgeProtrusion = mConversationFacePile.getVisibility() == VISIBLE
+                    ? mExpandedGroupBadgeProtrusionFacePile
+                    : mExpandedGroupBadgeProtrusion;
             conversationAvatarSize = mConversationAvatarSizeExpanded;
         }
-        LayoutParams layoutParams =
-                (LayoutParams) mConversationIconBadge.getLayoutParams();
-        layoutParams.topMargin = sidemargin;
-        layoutParams.setMarginStart(sidemargin);
-        mConversationIconBadge.setLayoutParams(layoutParams);
 
         if (mConversationIconView.getVisibility() == VISIBLE) {
-            layoutParams = (LayoutParams) mConversationIconView.getLayoutParams();
+            LayoutParams layoutParams = (LayoutParams) mConversationIconView.getLayoutParams();
             layoutParams.width = conversationAvatarSize;
             layoutParams.height = conversationAvatarSize;
+            layoutParams.leftMargin = badgeProtrusion;
+            layoutParams.rightMargin = badgeProtrusion;
+            layoutParams.bottomMargin = badgeProtrusion;
             mConversationIconView.setLayoutParams(layoutParams);
         }
+
+        if (mConversationFacePile.getVisibility() == VISIBLE) {
+            LayoutParams layoutParams = (LayoutParams) mConversationFacePile.getLayoutParams();
+            layoutParams.leftMargin = badgeProtrusion;
+            layoutParams.rightMargin = badgeProtrusion;
+            layoutParams.bottomMargin = badgeProtrusion;
+            mConversationFacePile.setLayoutParams(layoutParams);
+        }
     }
 
     private void updatePaddingsBasedOnContentAvailability() {
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index db4e673..498505c 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -854,16 +854,6 @@
                 && getDevicePolicyManager().isSeparateProfileChallengeAllowed(userHandle);
     }
 
-    /**
-     * Retrieves whether the current profile and device locks can be unified.
-     * @param userHandle profile user handle.
-     */
-    public boolean isSeparateProfileChallengeAllowedToUnify(int userHandle) {
-        return getDevicePolicyManager().isProfileActivePasswordSufficientForParent(userHandle)
-                && !getUserManager().hasUserRestriction(
-                        UserManager.DISALLOW_UNIFIED_PASSWORD, UserHandle.of(userHandle));
-    }
-
     private boolean hasSeparateChallenge(int userHandle) {
         try {
             return getLockSettings().getSeparateProfileChallengeEnabled(userHandle);
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index de4cede..f76cccb 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -638,6 +638,7 @@
     char hotstartupsamplesOptsBuf[sizeof("-Xps-hot-startup-method-samples:")-1 + PROPERTY_VALUE_MAX];
     char saveResolvedClassesDelayMsOptsBuf[
             sizeof("-Xps-save-resolved-classes-delay-ms:")-1 + PROPERTY_VALUE_MAX];
+    char profileMinSavePeriodOptsBuf[sizeof("-Xps-min-save-period-ms:")-1 + PROPERTY_VALUE_MAX];
     char madviseRandomOptsBuf[sizeof("-XX:MadviseRandomAccess:")-1 + PROPERTY_VALUE_MAX];
     char madviseWillNeedFileSizeVdex[
             sizeof("-XMadviseWillNeedVdexFileSize:")-1 + PROPERTY_VALUE_MAX];
@@ -670,6 +671,8 @@
     char extraOptsBuf[PROPERTY_VALUE_MAX];
     char voldDecryptBuf[PROPERTY_VALUE_MAX];
     char perfettoHprofOptBuf[sizeof("-XX:PerfettoHprof=") + PROPERTY_VALUE_MAX];
+    char perfettoJavaHeapStackOptBuf[
+            sizeof("-XX:PerfettoJavaHeapStackProf=") + PROPERTY_VALUE_MAX];
     enum {
       kEMDefault,
       kEMIntPortable,
@@ -784,6 +787,10 @@
     parseRuntimeOption("dalvik.vm.perfetto_hprof", perfettoHprofOptBuf, "-XX:PerfettoHprof=",
                        "true");
 
+    // Enable PerfettoJavaHeapStackProf in the zygote
+    parseRuntimeOption("dalvik.vm.perfetto_javaheap", perfettoJavaHeapStackOptBuf,
+                       "-XX:PerfettoJavaHeapStackProf=", "true");
+
     if (primary_zygote) {
         addOption("-Xprimaryzygote");
     }
@@ -867,6 +874,9 @@
     parseRuntimeOption("dalvik.vm.ps-resolved-classes-delay-ms", saveResolvedClassesDelayMsOptsBuf,
             "-Xps-save-resolved-classes-delay-ms:");
 
+    parseRuntimeOption("dalvik.vm.ps-min-save-period-ms", profileMinSavePeriodOptsBuf,
+            "-Xps-min-save-period-ms:");
+
     property_get("ro.config.low_ram", propBuf, "");
     if (strcmp(propBuf, "true") == 0) {
       addOption("-XX:LowMemoryMode");
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 5acbd98..6971301 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -578,6 +578,15 @@
     event->transform(matrix);
 }
 
+static void android_view_MotionEvent_nativeApplyTransform(JNIEnv* env, jclass clazz,
+                                                          jlong nativePtr, jobject matrixObj) {
+    MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+
+    std::array<float, 9> matrix;
+    AMatrix_getContents(env, matrixObj, matrix.data());
+    event->applyTransform(matrix);
+}
+
 // ----------------- @CriticalNative ------------------------------
 
 static jlong android_view_MotionEvent_nativeCopy(jlong destNativePtr, jlong sourceNativePtr,
@@ -790,6 +799,8 @@
         {"nativeGetAxisValue", "(JIII)F", (void*)android_view_MotionEvent_nativeGetAxisValue},
         {"nativeTransform", "(JLandroid/graphics/Matrix;)V",
          (void*)android_view_MotionEvent_nativeTransform},
+        {"nativeApplyTransform", "(JLandroid/graphics/Matrix;)V",
+         (void*)android_view_MotionEvent_nativeApplyTransform},
 
         // --------------- @CriticalNative ------------------
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4b828ba..4e7dd91 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -410,6 +410,8 @@
     <protected-broadcast android:name="android.net.wifi.p2p.action.WIFI_P2P_PERSISTENT_GROUPS_CHANGED" />
     <protected-broadcast android:name="android.net.conn.TETHER_STATE_CHANGED" />
     <protected-broadcast android:name="android.net.conn.INET_CONDITION_ACTION" />
+    <!-- This broadcast is no longer sent in S but it should stay protected to avoid third party
+         apps broadcasting this and confusing old system apps that may not have been updated. -->
     <protected-broadcast android:name="android.net.conn.NETWORK_CONDITIONS_MEASURED" />
     <protected-broadcast
             android:name="android.net.ConnectivityService.action.PKT_CNT_SAMPLE_INTERVAL_ELAPSED" />
diff --git a/core/res/res/layout/notification_template_conversation_icon_container.xml b/core/res/res/layout/notification_template_conversation_icon_container.xml
index a88ff0d..0438dc5 100644
--- a/core/res/res/layout/notification_template_conversation_icon_container.xml
+++ b/core/res/res/layout/notification_template_conversation_icon_container.xml
@@ -36,11 +36,14 @@
         android:layout_gravity="top|center_horizontal"
         >
 
-        <!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right -->
+        <!-- Big icon: 48x48, 12dp padding top, 16dp padding sides -->
         <com.android.internal.widget.CachingIconView
             android:id="@+id/conversation_icon"
             android:layout_width="@dimen/conversation_avatar_size"
             android:layout_height="@dimen/conversation_avatar_size"
+            android:layout_marginLeft="@dimen/conversation_badge_protrusion"
+            android:layout_marginRight="@dimen/conversation_badge_protrusion"
+            android:layout_marginBottom="@dimen/conversation_badge_protrusion"
             android:scaleType="centerCrop"
             android:importantForAccessibility="no"
             />
@@ -49,6 +52,9 @@
             android:layout="@layout/conversation_face_pile_layout"
             android:layout_width="@dimen/conversation_avatar_size"
             android:layout_height="@dimen/conversation_avatar_size"
+            android:layout_marginLeft="@dimen/conversation_badge_protrusion"
+            android:layout_marginRight="@dimen/conversation_badge_protrusion"
+            android:layout_marginBottom="@dimen/conversation_badge_protrusion"
             android:id="@+id/conversation_face_pile"
             />
 
@@ -56,8 +62,7 @@
             android:id="@+id/conversation_icon_badge"
             android:layout_width="@dimen/conversation_icon_size_badged"
             android:layout_height="@dimen/conversation_icon_size_badged"
-            android:layout_marginLeft="@dimen/conversation_badge_side_margin"
-            android:layout_marginTop="@dimen/conversation_badge_side_margin"
+            android:layout_gravity="end|bottom"
             android:clipChildren="false"
             android:clipToPadding="false"
             >
diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml
index 3564f97..eb61ea4 100644
--- a/core/res/res/layout/notification_template_material_messaging.xml
+++ b/core/res/res/layout/notification_template_material_messaging.xml
@@ -199,13 +199,11 @@
             android:id="@+id/notification_action_list_margin_target"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginTop="-20dp"
             android:clipChildren="false"
             android:orientation="vertical">
         <include layout="@layout/notification_template_smart_reply_container"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_marginTop="@dimen/notification_content_margin"
                 android:layout_marginStart="@dimen/notification_content_margin_start"
                 android:layout_marginEnd="@dimen/notification_content_margin_end" />
         <include layout="@layout/notification_material_action_list" />
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 6be6167..4f90a17 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -776,8 +776,8 @@
     <dimen name="conversation_expand_button_height">88dp</dimen>
     <!-- this is the margin between the Conversation image and the content -->
     <dimen name="conversation_image_start_margin">12dp</dimen>
-    <!-- Side margins of the conversation badge in relation to the conversation icon -->
-    <dimen name="conversation_badge_side_margin">32dp</dimen>
+    <!-- amount the badge sticks out from the conversation avatar -->
+    <dimen name="conversation_badge_protrusion">4dp</dimen>
     <!-- size of the notification badge when applied to the conversation icon -->
     <dimen name="conversation_icon_size_badged">20dp</dimen>
     <!-- size of the conversation avatar in an expanded group -->
@@ -786,10 +786,10 @@
     <dimen name="conversation_face_pile_avatar_size">32dp</dimen>
     <!-- size of the face pile icons when the group is expanded -->
     <dimen name="conversation_face_pile_avatar_size_group_expanded">@dimen/conversation_face_pile_avatar_size</dimen>
-    <!-- Side margins of the conversation badge in relation to the conversation icon when the group is expanded-->
-    <dimen name="conversation_badge_side_margin_group_expanded">@dimen/conversation_badge_side_margin</dimen>
-    <!-- Side margins of the conversation badge in relation to the conversation icon when the group is expanded-->
-    <dimen name="conversation_badge_side_margin_group_expanded_face_pile">@dimen/conversation_badge_side_margin</dimen>
+    <!-- amount the badge sticks out from the conversation avatar when the group is expanded -->
+    <dimen name="conversation_badge_protrusion_group_expanded">@dimen/conversation_badge_protrusion</dimen>
+    <!-- amount the badge sticks out from the conversation face pile when the group is expanded -->
+    <dimen name="conversation_badge_protrusion_group_expanded_face_pile">@dimen/conversation_badge_protrusion</dimen>
     <!-- The width of the protection of the face pile layout-->
     <dimen name="conversation_face_pile_protection_width">2dp</dimen>
     <!-- The width of the protection of the face pile layout when expanded-->
@@ -809,8 +809,8 @@
     <!-- The top padding of the conversation icon container when the avatar is small-->
     <dimen name="conversation_icon_container_top_padding_small_avatar">8dp</dimen>
 
-    <!-- The padding of the conversation header when expanded. This is calculated from the expand button size + notification_content_margin_end -->
-    <dimen name="conversation_header_expanded_padding_end">38dp</dimen>
+    <!-- The padding of the conversation header when expanded. This is calculated from the expand button size (56dp) - notification_content_margin_end (16dp) -->
+    <dimen name="conversation_header_expanded_padding_end">40dp</dimen>
 
     <!-- extra padding at the start of the icons when not conversations to keep them horizontally aligned with the notification icon -->
     <dimen name="messaging_layout_icon_padding_start">4dp</dimen>
@@ -936,7 +936,7 @@
     <dimen name="system_app_widget_background_radius">16dp</dimen>
     <!-- System-provided radius for inner views on app widgets. The resolved value of this resource may change at runtime. -->
     <dimen name="system_app_widget_inner_radius">8dp</dimen>
-    <!-- System-provided padding for inner views on app widgets. The resolved value of this resource may change at runtime. -->
-    <dimen name="system_app_widget_internal_padding">16dp</dimen>
+    <!-- System-provided padding for inner views on app widgets. The resolved value of this resource may change at runtime. @removed -->
+    <dimen name="__removed_system_app_widget_internal_padding">16dp</dimen>
 
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 03701c3..500a9da 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3184,7 +3184,7 @@
     <!-- System-provided dimensions for app widgets. -->
     <public name="system_app_widget_background_radius" />
     <public name="system_app_widget_inner_radius" />
-    <public name="system_app_widget_internal_padding" />
+    <public name="__removed_system_app_widget_internal_padding" />
   </staging-public-group>
 
   <staging-public-group type="bool" first-id="0x01110007">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e86d2ce..03bb168 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3390,6 +3390,23 @@
     <!-- [CHAR LIMIT=NONE] Message to show in upgrading dialog when the bulk of the upgrade work is done. -->
     <string name="android_upgrading_complete">Finishing boot.</string>
 
+    <!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button
+    is pressed during fingerprint enrollment. -->
+    <string name="fp_enrollment_powerbutton_intent_title">Turn off screen?</string>
+
+    <!-- [CHAR LIMIT=NONE] Message of dialog shown to confirm device going to sleep if the power
+    button is pressed during fingerprint enrollment. -->
+    <string name="fp_enrollment_powerbutton_intent_message">While setting up your fingerprint, you
+        pressed the Power button.\n\nThis usually turns off your screen.</string>
+
+    <!-- [CHAR LIMIT=20] Positive button of dialog shown to confirm device going to sleep if the
+    power button is pressed during fingerprint enrollment. -->
+    <string name="fp_enrollment_powerbutton_intent_positive_button">Turn off</string>
+
+    <!-- [CHAR LIMIT=20] Negative button of dialog shown to confirm device going to sleep if the
+    power button is pressed during fingerprint enrollment. -->
+    <string name="fp_enrollment_powerbutton_intent_negative_button">Cancel</string>
+
     <!-- Notification text to tell the user that a heavy-weight application is running. -->
     <string name="heavy_weight_notification"><xliff:g id="app">%1$s</xliff:g> running</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3a22efc..d8620a7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1814,6 +1814,10 @@
   <java-symbol type="string" name="bugreport_status" />
   <java-symbol type="string" name="bugreport_title" />
   <java-symbol type="string" name="faceunlock_multiple_failures" />
+  <java-symbol type="string" name="fp_enrollment_powerbutton_intent_title" />
+  <java-symbol type="string" name="fp_enrollment_powerbutton_intent_message" />
+  <java-symbol type="string" name="fp_enrollment_powerbutton_intent_positive_button" />
+  <java-symbol type="string" name="fp_enrollment_powerbutton_intent_negative_button" />
   <java-symbol type="string" name="global_actions" />
   <java-symbol type="string" name="global_action_power_off" />
   <java-symbol type="string" name="global_action_power_options" />
@@ -4080,15 +4084,15 @@
   <java-symbol type="id" name="conversation_image_message_container" />
   <java-symbol type="id" name="conversation_icon_container" />
   <java-symbol type="dimen" name="messaging_group_singleline_sender_padding_end" />
-  <java-symbol type="dimen" name="conversation_badge_side_margin" />
+  <java-symbol type="dimen" name="conversation_badge_protrusion" />
   <java-symbol type="dimen" name="conversation_avatar_size" />
   <java-symbol type="dimen" name="conversation_avatar_size_group_expanded" />
   <java-symbol type="dimen" name="conversation_face_pile_avatar_size" />
   <java-symbol type="dimen" name="conversation_face_pile_avatar_size_group_expanded" />
   <java-symbol type="dimen" name="conversation_face_pile_protection_width" />
   <java-symbol type="dimen" name="conversation_face_pile_protection_width_expanded" />
-  <java-symbol type="dimen" name="conversation_badge_side_margin_group_expanded" />
-  <java-symbol type="dimen" name="conversation_badge_side_margin_group_expanded_face_pile" />
+  <java-symbol type="dimen" name="conversation_badge_protrusion_group_expanded" />
+  <java-symbol type="dimen" name="conversation_badge_protrusion_group_expanded_face_pile" />
   <java-symbol type="dimen" name="conversation_content_start" />
   <java-symbol type="dimen" name="expanded_group_conversation_message_padding" />
   <java-symbol type="dimen" name="messaging_layout_icon_padding_start" />
diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java
index e9e58c6..a5184f2 100644
--- a/graphics/java/android/graphics/RecordingCanvas.java
+++ b/graphics/java/android/graphics/RecordingCanvas.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.os.SystemProperties;
 import android.util.Pools.SynchronizedPool;
@@ -227,11 +228,11 @@
     public void drawRipple(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
             CanvasProperty<Float> radius, CanvasProperty<Paint> paint,
             CanvasProperty<Float> progress, CanvasProperty<Float> turbulencePhase,
-            RuntimeShader shader) {
+            @ColorInt int color, RuntimeShader shader) {
         nDrawRipple(mNativeCanvasWrapper, cx.getNativeContainer(), cy.getNativeContainer(),
                 radius.getNativeContainer(), paint.getNativeContainer(),
                 progress.getNativeContainer(), turbulencePhase.getNativeContainer(),
-                shader.getNativeShaderBuilder());
+                color, shader.getNativeShaderBuilder());
     }
 
     /**
@@ -292,7 +293,7 @@
             long propCy, long propRadius, long propPaint);
     @CriticalNative
     private static native void nDrawRipple(long renderer, long propCx, long propCy, long propRadius,
-            long propPaint, long propProgress, long turbulencePhase, long runtimeEffect);
+            long propPaint, long propProgress, long turbulencePhase, int color, long runtimeEffect);
     @CriticalNative
     private static native void nDrawRoundRect(long renderer, long propLeft, long propTop,
             long propRight, long propBottom, long propRx, long propRy, long propPaint);
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index cff7dcc..a03931b 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -369,7 +369,9 @@
                         final PackageManager pm = context.getPackageManager();
                         try {
                             ApplicationInfo ai = pm.getApplicationInfo(
-                                    resPackage, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+                                    resPackage,
+                                    PackageManager.MATCH_UNINSTALLED_PACKAGES
+                                    | PackageManager.GET_SHARED_LIBRARY_FILES);
                             if (ai != null) {
                                 mObj1 = pm.getResourcesForApplication(ai);
                             } else {
diff --git a/graphics/java/android/graphics/drawable/RippleAnimationSession.java b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
index 60f73b5..ee867dd 100644
--- a/graphics/java/android/graphics/drawable/RippleAnimationSession.java
+++ b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
+import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Canvas;
@@ -211,6 +212,7 @@
                     CanvasProperty.createFloat(mProperties.getNoisePhase()),
                     CanvasProperty.createPaint(mProperties.getPaint()),
                     CanvasProperty.createFloat(mProperties.getProgress()),
+                    mProperties.getColor(),
                     mProperties.getShader());
         }
         return mCanvasProperties;
@@ -250,11 +252,12 @@
         private final FloatType mNoisePhase;
         private final PaintType mPaint;
         private final RippleShader mShader;
+        private final @ColorInt int mColor;
         private FloatType mX;
         private FloatType mY;
 
         AnimationProperties(FloatType x, FloatType y, FloatType maxRadius, FloatType noisePhase,
-                PaintType paint, FloatType progress, RippleShader shader) {
+                PaintType paint, FloatType progress, @ColorInt int color, RippleShader shader) {
             mY = y;
             mX = x;
             mMaxRadius = maxRadius;
@@ -262,6 +265,7 @@
             mPaint = paint;
             mShader = shader;
             mProgress = progress;
+            mColor = color;
         }
 
         FloatType getProgress() {
@@ -296,5 +300,9 @@
         FloatType getNoisePhase() {
             return mNoisePhase;
         }
+
+        @ColorInt int getColor() {
+            return mColor;
+        }
     }
 }
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 24d7780..518fceb 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -864,6 +864,10 @@
         boolean shouldExit = mExitingAnimation;
         mRippleActive = false;
         mExitingAnimation = false;
+        if (mRunningAnimations.size() > 0 && !shouldAnimate) {
+            // update paint when view is invalidated
+            getRipplePaint();
+        }
         drawContent(canvas);
         drawPatternedBackground(canvas, cx, cy);
         if (shouldAnimate && mRunningAnimations.size() <= MAX_RIPPLES) {
@@ -901,7 +905,7 @@
                     yProp = p.getY();
                 }
                 can.drawRipple(xProp, yProp, p.getMaxRadius(), p.getPaint(),
-                        p.getProgress(), p.getNoisePhase(), p.getShader());
+                        p.getProgress(), p.getNoisePhase(), p.getColor(), p.getShader());
             } else {
                 RippleAnimationSession.AnimationProperties<Float, Paint> p =
                         s.getProperties();
@@ -974,7 +978,7 @@
         shader.setRadius(radius);
         shader.setProgress(.0f);
         properties = new RippleAnimationSession.AnimationProperties<>(
-                cx, cy, radius, 0f, p, 0f, shader);
+                cx, cy, radius, 0f, p, 0f, color, shader);
         if (mMaskShader == null) {
             shader.setShader(null);
         } else {
diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java
index 2b4d5b4..d00492c 100644
--- a/graphics/java/android/graphics/drawable/RippleShader.java
+++ b/graphics/java/android/graphics/drawable/RippleShader.java
@@ -113,10 +113,11 @@
             + "    float sparkle = sparkles(densityUv, in_noisePhase) * ring * alpha "
             + "* turbulence;\n"
             + "    float fade = min(fadeIn, 1. - fadeOutRipple);\n"
-            + "    vec4 circle = in_color * (softCircle(p, center, in_maxRadius "
-            + "      * scaleIn, 0.2) * fade);\n"
+            + "    float circleAlpha = softCircle(p, center, in_maxRadius * scaleIn, 0.2) * fade;\n"
+            + "    vec3 color = mix(in_color.rgb, in_sparkleColor.rgb, sparkle);\n"
             + "    float mask = in_hasMask == 1. ? sample(in_shader, p).a > 0. ? 1. : 0. : 1.;\n"
-            + "    return mix(circle, in_sparkleColor, sparkle) * mask;\n"
+            + "    float a = (in_color.a * circleAlpha + in_sparkleColor.a * sparkle) * mask;\n"
+            + "    return vec4(color * a, a);\n"
             + "}";
     private static final String SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN;
     private static final double PI_ROTATE_RIGHT = Math.PI * 0.0078125;
diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java
index 66d842e..4da2a28 100644
--- a/keystore/java/android/security/keystore/AttestationUtils.java
+++ b/keystore/java/android/security/keystore/AttestationUtils.java
@@ -260,6 +260,11 @@
         } catch (SecurityException e) {
             throw e;
         } catch (Exception e) {
+            // If a DeviceIdAttestationException was previously wrapped with some other type,
+            // let's throw the original exception instead of wrapping it yet again.
+            if (e.getCause() instanceof DeviceIdAttestationException) {
+                throw (DeviceIdAttestationException) e.getCause();
+            }
             throw new DeviceIdAttestationException("Unable to perform attestation", e);
         }
     }
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 90c4d51..fd13fec 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -58,7 +58,7 @@
     <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Ubicar abajo a la derecha"</string>
     <string name="bubbles_app_settings" msgid="3617224938701566416">"Configuración de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
     <string name="bubble_dismiss_text" msgid="8816558050659478158">"Descartar burbuja"</string>
-    <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"No mostrar la conversación en burbujas"</string>
+    <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"No mostrar la conversación en burbuja"</string>
     <string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat con burbujas"</string>
     <string name="bubbles_user_education_description" msgid="4215862563054175407">"Las conversaciones nuevas aparecen como elementos flotantes o burbujas. Presiona para abrir la burbuja. Arrástrala para moverla."</string>
     <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Controla las burbujas"</string>
diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp
index 9d03ce5..d0d24a8 100644
--- a/libs/hwui/CanvasTransform.cpp
+++ b/libs/hwui/CanvasTransform.cpp
@@ -31,7 +31,7 @@
 
 namespace android::uirenderer {
 
-static SkColor makeLight(SkColor color) {
+SkColor makeLight(SkColor color) {
     Lab lab = sRGBToLab(color);
     float invertedL = std::min(110 - lab.L, 100.0f);
     if (invertedL > lab.L) {
@@ -42,7 +42,7 @@
     }
 }
 
-static SkColor makeDark(SkColor color) {
+SkColor makeDark(SkColor color) {
     Lab lab = sRGBToLab(color);
     float invertedL = std::min(110 - lab.L, 100.0f);
     if (invertedL < lab.L) {
@@ -53,7 +53,7 @@
     }
 }
 
-static SkColor transformColor(ColorTransform transform, SkColor color) {
+SkColor transformColor(ColorTransform transform, SkColor color) {
     switch (transform) {
         case ColorTransform::Light:
             return makeLight(color);
@@ -64,6 +64,17 @@
     }
 }
 
+SkColor transformColorInverse(ColorTransform transform, SkColor color) {
+    switch (transform) {
+        case ColorTransform::Dark:
+            return makeLight(color);
+        case ColorTransform::Light:
+            return makeDark(color);
+        default:
+            return color;
+    }
+}
+
 static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
     if (transform == ColorTransform::None) return;
 
diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h
index e723d64..c46a2d3 100644
--- a/libs/hwui/CanvasTransform.h
+++ b/libs/hwui/CanvasTransform.h
@@ -42,4 +42,7 @@
 
 bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette palette);
 
+SkColor transformColor(ColorTransform transform, SkColor color);
+SkColor transformColorInverse(ColorTransform transform, SkColor color);
+
 }  // namespace android::uirenderer;
\ No newline at end of file
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index 1b1be43..fb3e21f 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -49,4 +49,5 @@
 X(DrawAtlas)
 X(DrawShadowRec)
 X(DrawVectorDrawable)
+X(DrawRippleDrawable)
 X(DrawWebView)
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 170f731..442ae0f 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -36,6 +36,7 @@
 #include "SkTextBlob.h"
 #include "SkVertices.h"
 #include "VectorDrawable.h"
+#include "pipeline/skia/AnimatedDrawables.h"
 #include "pipeline/skia/FunctorDrawable.h"
 
 namespace android {
@@ -497,6 +498,18 @@
     SkPaint paint;
     BitmapPalette palette;
 };
+
+struct DrawRippleDrawable final : Op {
+    static const auto kType = Type::DrawRippleDrawable;
+    DrawRippleDrawable(const skiapipeline::RippleDrawableParams& params) : mParams(params) {}
+
+    void draw(SkCanvas* canvas, const SkMatrix&) const {
+        skiapipeline::AnimatedRippleDrawable::draw(canvas, mParams);
+    }
+
+    skiapipeline::RippleDrawableParams mParams;
+};
+
 struct DrawWebView final : Op {
     static const auto kType = Type::DrawWebView;
     DrawWebView(skiapipeline::FunctorDrawable* drawable) : drawable(sk_ref_sp(drawable)) {}
@@ -721,6 +734,10 @@
     mHasText = true;
 }
 
+void DisplayListData::drawRippleDrawable(const skiapipeline::RippleDrawableParams& params) {
+    this->push<DrawRippleDrawable>(0, params);
+}
+
 void DisplayListData::drawPatch(const SkPoint points[12], const SkColor colors[4],
                                 const SkPoint texs[4], SkBlendMode bmode, const SkPaint& paint) {
     this->push<DrawPatch>(0, points, colors, texs, bmode, paint);
@@ -851,6 +868,16 @@
     };
 }
 
+template <>
+constexpr color_transform_fn colorTransformForOp<DrawRippleDrawable>() {
+    return [](const void* opRaw, ColorTransform transform) {
+        const DrawRippleDrawable* op = reinterpret_cast<const DrawRippleDrawable*>(opRaw);
+        // Ripple drawable needs to contrast against the background, so we need the inverse color.
+        SkColor color = transformColorInverse(transform, op->mParams.color);
+        const_cast<DrawRippleDrawable*>(op)->mParams.color = color;
+    };
+}
+
 #define X(T) colorTransformForOp<T>(),
 static const color_transform_fn color_transform_fns[] = {
 #include "DisplayListOps.in"
@@ -985,6 +1012,10 @@
     fDL->drawTextBlob(blob, x, y, paint);
 }
 
+void RecordingCanvas::drawRippleDrawable(const skiapipeline::RippleDrawableParams& params) {
+    fDL->drawRippleDrawable(params);
+}
+
 void RecordingCanvas::drawImage(const sk_sp<SkImage>& image, SkScalar x, SkScalar y,
                                 const SkSamplingOptions& sampling, const SkPaint* paint,
                                 BitmapPalette palette) {
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index a6a7b12..4fae6a1 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -29,6 +29,9 @@
 #include "SkPath.h"
 #include "SkRect.h"
 
+#include "pipeline/skia/AnimatedDrawables.h"
+
+#include <SkRuntimeEffect.h>
 #include <vector>
 
 namespace android {
@@ -125,6 +128,7 @@
     void drawVertices(const SkVertices*, SkBlendMode, const SkPaint&);
     void drawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int,
                    SkBlendMode, const SkSamplingOptions&, const SkRect*, const SkPaint*);
+    void drawRippleDrawable(const skiapipeline::RippleDrawableParams& params);
     void drawShadowRec(const SkPath&, const SkDrawShadowRec&);
     void drawVectorDrawable(VectorDrawableRoot* tree);
     void drawWebView(skiapipeline::FunctorDrawable*);
@@ -184,6 +188,7 @@
 
     void drawImage(const sk_sp<SkImage>&, SkScalar left, SkScalar top, const SkSamplingOptions&,
                    const SkPaint* paint, BitmapPalette pallete);
+    void drawRippleDrawable(const skiapipeline::RippleDrawableParams& params);
 
     void drawImageRect(const sk_sp<SkImage>& image, const SkRect& src, const SkRect& dst,
                        const SkSamplingOptions&, const SkPaint*, SrcRectConstraint, BitmapPalette);
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 3056e97..d032e2b 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -815,17 +815,8 @@
     mCanvas->drawDrawable(drawable.get());
 }
 
-void SkiaCanvas::drawRipple(uirenderer::CanvasPropertyPrimitive* x,
-                            uirenderer::CanvasPropertyPrimitive* y,
-                            uirenderer::CanvasPropertyPrimitive* radius,
-                            uirenderer::CanvasPropertyPaint* paint,
-                            uirenderer::CanvasPropertyPrimitive* progress,
-                            uirenderer::CanvasPropertyPrimitive* turbulencePhase,
-                            const SkRuntimeShaderBuilder& effectBuilder) {
-    sk_sp<uirenderer::skiapipeline::AnimatedRipple> drawable(
-            new uirenderer::skiapipeline::AnimatedRipple(x, y, radius, paint, progress,
-                                                         turbulencePhase, effectBuilder));
-    mCanvas->drawDrawable(drawable.get());
+void SkiaCanvas::drawRipple(const uirenderer::skiapipeline::RippleDrawableParams& params) {
+    uirenderer::skiapipeline::AnimatedRippleDrawable::draw(mCanvas, params);
 }
 
 void SkiaCanvas::drawPicture(const SkPicture& picture) {
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 995f00c..438a40c 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -25,6 +25,7 @@
 #include "hwui/Paint.h"
 
 #include <SkCanvas.h>
+#include "pipeline/skia/AnimatedDrawables.h"
 #include "src/core/SkArenaAlloc.h"
 
 #include <cassert>
@@ -148,13 +149,7 @@
                             uirenderer::CanvasPropertyPrimitive* y,
                             uirenderer::CanvasPropertyPrimitive* radius,
                             uirenderer::CanvasPropertyPaint* paint) override;
-    virtual void drawRipple(uirenderer::CanvasPropertyPrimitive* x,
-                            uirenderer::CanvasPropertyPrimitive* y,
-                            uirenderer::CanvasPropertyPrimitive* radius,
-                            uirenderer::CanvasPropertyPaint* paint,
-                            uirenderer::CanvasPropertyPrimitive* progress,
-                            uirenderer::CanvasPropertyPrimitive* turbulencePhase,
-                            const SkRuntimeShaderBuilder& effectBuilder) override;
+    virtual void drawRipple(const uirenderer::skiapipeline::RippleDrawableParams& params) override;
 
     virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override;
     virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override;
diff --git a/libs/hwui/canvas/CanvasOpTypes.h b/libs/hwui/canvas/CanvasOpTypes.h
index b55ef9d..6e18e49 100644
--- a/libs/hwui/canvas/CanvasOpTypes.h
+++ b/libs/hwui/canvas/CanvasOpTypes.h
@@ -43,7 +43,7 @@
     DrawRoundRectProperty,
     DrawDoubleRoundRect,
     DrawCircleProperty,
-    DrawRippleProperty,
+    DrawRippleDrawable,
     DrawCircle,
     DrawOval,
     DrawArc,
diff --git a/libs/hwui/canvas/CanvasOps.h b/libs/hwui/canvas/CanvasOps.h
index 173f394..fdc97a4 100644
--- a/libs/hwui/canvas/CanvasOps.h
+++ b/libs/hwui/canvas/CanvasOps.h
@@ -145,73 +145,13 @@
     ASSERT_DRAWABLE()
 };
 
-template<>
-struct CanvasOp<CanvasOpType::DrawRippleProperty> {
-    sp<uirenderer::CanvasPropertyPrimitive> x;
-    sp<uirenderer::CanvasPropertyPrimitive> y;
-    sp<uirenderer::CanvasPropertyPrimitive> radius;
-    sp<uirenderer::CanvasPropertyPaint> paint;
-    sp<uirenderer::CanvasPropertyPrimitive> progress;
-    sp<uirenderer::CanvasPropertyPrimitive> turbulencePhase;
-    sk_sp<SkRuntimeEffect> effect;
-
-    const float PI = 3.1415926535897932384626;
-    const float PI_ROTATE_RIGHT = PI * 0.0078125;
-    const float PI_ROTATE_LEFT = PI * -0.0078125;
-    const float SCALE = 1.5;
-    const float CIRCLE_X_1 = 0.01 * cos(SCALE * 0.55);
-    const float CIRCLE_Y_1 = 0.01 * sin(SCALE * 0.55);
-    const float CIRCLE_X_2 = -0.0066 * cos(SCALE * 0.45);
-    const float CIRCLE_Y_2 = -0.0066 * sin(SCALE * 0.45);
-    const float CIRCLE_X_3 = -0.0066 * cos(SCALE * 0.35);
-    const float CIRCLE_Y_3 = -0.0066 * sin(SCALE * 0.35);
+template <>
+struct CanvasOp<CanvasOpType::DrawRippleDrawable> {
+    skiapipeline::RippleDrawableParams params;
 
     void draw(SkCanvas* canvas) const {
-        SkRuntimeShaderBuilder runtimeEffectBuilder(effect);
-
-        setUniform2f(runtimeEffectBuilder, "in_origin", x->value, y->value);
-        setUniform(runtimeEffectBuilder, "in_radius", radius);
-        setUniform(runtimeEffectBuilder, "in_progress", progress);
-        setUniform(runtimeEffectBuilder, "in_turbulencePhase", turbulencePhase);
-
-        //
-        // Keep in sync with:
-        // frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java
-        //
-        const float turbulence = turbulencePhase->value;
-        setUniform2f(runtimeEffectBuilder, "in_tCircle1", SCALE * 0.5 + (turbulence * CIRCLE_X_1),
-                     SCALE * 0.5 + (turbulence * CIRCLE_Y_1));
-        setUniform2f(runtimeEffectBuilder, "in_tCircle2", SCALE * 0.2 + (turbulence * CIRCLE_X_2),
-                     SCALE * 0.2 + (turbulence * CIRCLE_Y_2));
-        setUniform2f(runtimeEffectBuilder, "in_tCircle3", SCALE + (turbulence * CIRCLE_X_3),
-                     SCALE + (turbulence * CIRCLE_Y_3));
-        const float rotation1 = turbulence * PI_ROTATE_RIGHT + 1.7 * PI;
-        setUniform2f(runtimeEffectBuilder, "in_tRotation1", cos(rotation1), sin(rotation1));
-        const float rotation2 = turbulence * PI_ROTATE_LEFT + 2 * PI;
-        setUniform2f(runtimeEffectBuilder, "in_tRotation2", cos(rotation2), sin(rotation2));
-        const float rotation3 = turbulence * PI_ROTATE_RIGHT + 2.75 * PI;
-        setUniform2f(runtimeEffectBuilder, "in_tRotation3", cos(rotation3), sin(rotation3));
-
-        SkPaint paintMod = paint->value;
-        paintMod.setShader(runtimeEffectBuilder.makeShader(nullptr, false));
-        canvas->drawCircle(x->value, y->value, radius->value, paintMod);
+        skiapipeline::AnimatedRippleDrawable::draw(canvas, params);
     }
-
-    void setUniform(SkRuntimeShaderBuilder& effect, std::string name,
-                    sp<uirenderer::CanvasPropertyPrimitive> property) const {
-        SkRuntimeShaderBuilder::BuilderUniform uniform = effect.uniform(name.c_str());
-        if (uniform.fVar != nullptr) {
-            uniform = property->value;
-        }
-    }
-
-    void setUniform2f(SkRuntimeShaderBuilder effect, std::string name, float a, float b) const {
-        SkRuntimeShaderBuilder::BuilderUniform uniform = effect.uniform(name.c_str());
-        if (uniform.fVar != nullptr) {
-            uniform = SkV2{a, b};
-        }
-    }
-
     ASSERT_DRAWABLE()
 };
 
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 837b055..9023613 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -22,6 +22,7 @@
 
 #include <androidfw/ResourceTypes.h>
 #include "Properties.h"
+#include "pipeline/skia/AnimatedDrawables.h"
 #include "utils/Macros.h"
 
 #include <SkBitmap.h>
@@ -141,13 +142,7 @@
                             uirenderer::CanvasPropertyPrimitive* y,
                             uirenderer::CanvasPropertyPrimitive* radius,
                             uirenderer::CanvasPropertyPaint* paint) = 0;
-    virtual void drawRipple(uirenderer::CanvasPropertyPrimitive* x,
-                            uirenderer::CanvasPropertyPrimitive* y,
-                            uirenderer::CanvasPropertyPrimitive* radius,
-                            uirenderer::CanvasPropertyPaint* paint,
-                            uirenderer::CanvasPropertyPrimitive* progress,
-                            uirenderer::CanvasPropertyPrimitive* turbulencePhase,
-                            const SkRuntimeShaderBuilder& effectBuilder) = 0;
+    virtual void drawRipple(const uirenderer::skiapipeline::RippleDrawableParams& params) = 0;
 
     virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) = 0;
     virtual void drawRenderNode(uirenderer::RenderNode* renderNode) = 0;
diff --git a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
index eb5a88a..f060bb3 100644
--- a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
+++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
@@ -144,7 +144,7 @@
 static void android_view_DisplayListCanvas_drawRippleProps(
         CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jlong xPropPtr, jlong yPropPtr,
         jlong radiusPropPtr, jlong paintPropPtr, jlong progressPropPtr, jlong turbulencePhasePtr,
-        jlong builderPtr) {
+        jint color, jlong builderPtr) {
     Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
     CanvasPropertyPrimitive* xProp = reinterpret_cast<CanvasPropertyPrimitive*>(xPropPtr);
     CanvasPropertyPrimitive* yProp = reinterpret_cast<CanvasPropertyPrimitive*>(yPropPtr);
@@ -155,8 +155,12 @@
     CanvasPropertyPrimitive* progressProp =
             reinterpret_cast<CanvasPropertyPrimitive*>(progressPropPtr);
     SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(builderPtr);
-    canvas->drawRipple(xProp, yProp, radiusProp, paintProp, progressProp, turbulencePhaseProp,
-                       *builder);
+
+    const uirenderer::skiapipeline::RippleDrawableParams params =
+            uirenderer::skiapipeline::RippleDrawableParams{
+                    xProp,          yProp,     radiusProp, progressProp, turbulencePhaseProp,
+                    (SkColor)color, paintProp, *builder};
+    canvas->drawRipple(params);
 }
 
 static void android_view_DisplayListCanvas_drawWebViewFunctor(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jint functor) {
@@ -186,7 +190,7 @@
         {"nDrawCircle", "(JJJJJ)V", (void*)android_view_DisplayListCanvas_drawCircleProps},
         {"nDrawRoundRect", "(JJJJJJJJ)V", (void*)android_view_DisplayListCanvas_drawRoundRectProps},
         {"nDrawWebViewFunctor", "(JI)V", (void*)android_view_DisplayListCanvas_drawWebViewFunctor},
-        {"nDrawRipple", "(JJJJJJJJ)V", (void*)android_view_DisplayListCanvas_drawRippleProps},
+        {"nDrawRipple", "(JJJJJJJIJ)V", (void*)android_view_DisplayListCanvas_drawRippleProps},
 };
 
 int register_android_view_DisplayListCanvas(JNIEnv* env) {
diff --git a/libs/hwui/pipeline/skia/AnimatedDrawables.h b/libs/hwui/pipeline/skia/AnimatedDrawables.h
index 7d65be1..10889e7 100644
--- a/libs/hwui/pipeline/skia/AnimatedDrawables.h
+++ b/libs/hwui/pipeline/skia/AnimatedDrawables.h
@@ -22,6 +22,7 @@
 #include <math.h>
 #include <utils/RefBase.h>
 #include "CanvasProperty.h"
+#include "CanvasTransform.h"
 
 namespace android {
 namespace uirenderer {
@@ -56,89 +57,80 @@
     sp<uirenderer::CanvasPropertyPaint> mPaint;
 };
 
-class AnimatedRipple : public SkDrawable {
-public:
-    AnimatedRipple(uirenderer::CanvasPropertyPrimitive* x, uirenderer::CanvasPropertyPrimitive* y,
-                   uirenderer::CanvasPropertyPrimitive* radius,
-                   uirenderer::CanvasPropertyPaint* paint,
-                   uirenderer::CanvasPropertyPrimitive* progress,
-                   uirenderer::CanvasPropertyPrimitive* turbulencePhase,
-                   const SkRuntimeShaderBuilder& effectBuilder)
-            : mX(x)
-            , mY(y)
-            , mRadius(radius)
-            , mPaint(paint)
-            , mProgress(progress)
-            , mTurbulencePhase(turbulencePhase)
-            , mRuntimeEffectBuilder(effectBuilder) {}
+struct RippleDrawableParams {
+    sp<uirenderer::CanvasPropertyPrimitive> x;
+    sp<uirenderer::CanvasPropertyPrimitive> y;
+    sp<uirenderer::CanvasPropertyPrimitive> radius;
+    sp<uirenderer::CanvasPropertyPrimitive> progress;
+    sp<uirenderer::CanvasPropertyPrimitive> turbulencePhase;
+    SkColor color;
+    sp<uirenderer::CanvasPropertyPaint> paint;
+    SkRuntimeShaderBuilder effectBuilder;
+};
 
-protected:
-    virtual SkRect onGetBounds() override {
-        const float x = mX->value;
-        const float y = mY->value;
-        const float radius = mRadius->value;
-        return SkRect::MakeLTRB(x - radius, y - radius, x + radius, y + radius);
-    }
-    virtual void onDraw(SkCanvas* canvas) override {
-        setUniform2f("in_origin", mX->value, mY->value);
-        setUniform("in_radius", mRadius);
-        setUniform("in_progress", mProgress);
-        setUniform("in_turbulencePhase", mTurbulencePhase);
+class AnimatedRippleDrawable {
+public:
+    static void draw(SkCanvas* canvas, const RippleDrawableParams& params) {
+        auto& effectBuilder = const_cast<SkRuntimeShaderBuilder&>(params.effectBuilder);
+
+        setUniform2f(effectBuilder, "in_origin", params.x->value, params.y->value);
+        setUniform(effectBuilder, "in_radius", params.radius);
+        setUniform(effectBuilder, "in_progress", params.progress);
+        setUniform(effectBuilder, "in_turbulencePhase", params.turbulencePhase);
+
+        SkRuntimeShaderBuilder::BuilderUniform uniform = effectBuilder.uniform("in_color");
+        if (uniform.fVar != nullptr) {
+            uniform = SkV4{SkColorGetR(params.color) / 255.0f, SkColorGetG(params.color) / 255.0f,
+                           SkColorGetB(params.color) / 255.0f, SkColorGetA(params.color) / 255.0f};
+        }
+
+        const float CIRCLE_X_1 = 0.01 * cos(SCALE * 0.55);
+        const float CIRCLE_Y_1 = 0.01 * sin(SCALE * 0.55);
+        const float CIRCLE_X_2 = -0.0066 * cos(SCALE * 0.45);
+        const float CIRCLE_Y_2 = -0.0066 * sin(SCALE * 0.45);
+        const float CIRCLE_X_3 = -0.0066 * cos(SCALE * 0.35);
+        const float CIRCLE_Y_3 = -0.0066 * sin(SCALE * 0.35);
 
         //
         // Keep in sync with:
         // frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java
         //
-        const float turbulencePhase = mTurbulencePhase->value;
-        setUniform2f("in_tCircle1", SCALE * 0.5 + (turbulencePhase * CIRCLE_X_1),
+        const float turbulencePhase = params.turbulencePhase->value;
+        setUniform2f(effectBuilder, "in_tCircle1", SCALE * 0.5 + (turbulencePhase * CIRCLE_X_1),
                      SCALE * 0.5 + (turbulencePhase * CIRCLE_Y_1));
-        setUniform2f("in_tCircle2", SCALE * 0.2 + (turbulencePhase * CIRCLE_X_2),
+        setUniform2f(effectBuilder, "in_tCircle2", SCALE * 0.2 + (turbulencePhase * CIRCLE_X_2),
                      SCALE * 0.2 + (turbulencePhase * CIRCLE_Y_2));
-        setUniform2f("in_tCircle3", SCALE + (turbulencePhase * CIRCLE_X_3),
+        setUniform2f(effectBuilder, "in_tCircle3", SCALE + (turbulencePhase * CIRCLE_X_3),
                      SCALE + (turbulencePhase * CIRCLE_Y_3));
         const float rotation1 = turbulencePhase * PI_ROTATE_RIGHT + 1.7 * PI;
-        setUniform2f("in_tRotation1", cos(rotation1), sin(rotation1));
+        setUniform2f(effectBuilder, "in_tRotation1", cos(rotation1), sin(rotation1));
         const float rotation2 = turbulencePhase * PI_ROTATE_LEFT + 2 * PI;
-        setUniform2f("in_tRotation2", cos(rotation2), sin(rotation2));
+        setUniform2f(effectBuilder, "in_tRotation2", cos(rotation2), sin(rotation2));
         const float rotation3 = turbulencePhase * PI_ROTATE_RIGHT + 2.75 * PI;
-        setUniform2f("in_tRotation3", cos(rotation3), sin(rotation3));
+        setUniform2f(effectBuilder, "in_tRotation3", cos(rotation3), sin(rotation3));
 
-        SkPaint paint = mPaint->value;
-        paint.setShader(mRuntimeEffectBuilder.makeShader(nullptr, false));
-        canvas->drawCircle(mX->value, mY->value, mRadius->value, paint);
+        params.paint->value.setShader(effectBuilder.makeShader(nullptr, false));
+        canvas->drawCircle(params.x->value, params.y->value, params.radius->value,
+                           params.paint->value);
     }
 
 private:
-    sp<uirenderer::CanvasPropertyPrimitive> mX;
-    sp<uirenderer::CanvasPropertyPrimitive> mY;
-    sp<uirenderer::CanvasPropertyPrimitive> mRadius;
-    sp<uirenderer::CanvasPropertyPaint> mPaint;
-    sp<uirenderer::CanvasPropertyPrimitive> mProgress;
-    sp<uirenderer::CanvasPropertyPrimitive> mTurbulencePhase;
-    SkRuntimeShaderBuilder mRuntimeEffectBuilder;
+    static constexpr float PI = 3.1415926535897932384626;
+    static constexpr float PI_ROTATE_RIGHT = PI * 0.0078125;
+    static constexpr float PI_ROTATE_LEFT = PI * -0.0078125;
+    static constexpr float SCALE = 1.5;
 
-    const float PI = 3.1415926535897932384626;
-    const float PI_ROTATE_RIGHT = PI * 0.0078125;
-    const float PI_ROTATE_LEFT = PI * -0.0078125;
-    const float SCALE = 1.5;
-    const float CIRCLE_X_1 = 0.01 * cos(SCALE * 0.55);
-    const float CIRCLE_Y_1 = 0.01 * sin(SCALE * 0.55);
-    const float CIRCLE_X_2 = -0.0066 * cos(SCALE * 0.45);
-    const float CIRCLE_Y_2 = -0.0066 * sin(SCALE * 0.45);
-    const float CIRCLE_X_3 = -0.0066 * cos(SCALE * 0.35);
-    const float CIRCLE_Y_3 = -0.0066 * sin(SCALE * 0.35);
-
-    virtual void setUniform(std::string name, sp<uirenderer::CanvasPropertyPrimitive> property) {
-        SkRuntimeShaderBuilder::BuilderUniform uniform =
-                mRuntimeEffectBuilder.uniform(name.c_str());
+    static void setUniform(SkRuntimeShaderBuilder& effectBuilder, std::string name,
+                           sp<uirenderer::CanvasPropertyPrimitive> property) {
+        SkRuntimeShaderBuilder::BuilderUniform uniform = effectBuilder.uniform(name.c_str());
         if (uniform.fVar != nullptr) {
             uniform = property->value;
         }
     }
 
-    virtual void setUniform2f(std::string name, float a, float b) {
-        SkRuntimeShaderBuilder::BuilderUniform uniform =
-                mRuntimeEffectBuilder.uniform(name.c_str());
+    static void setUniform2f(SkRuntimeShaderBuilder& effectBuilder, std::string name, float a,
+                             float b) {
+        SkRuntimeShaderBuilder::BuilderUniform uniform = effectBuilder.uniform(name.c_str());
         if (uniform.fVar != nullptr) {
             uniform = SkV2{a, b};
         }
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 9e73f04..76c4a03 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -109,15 +109,8 @@
     drawDrawable(mDisplayList->allocateDrawable<AnimatedCircle>(x, y, radius, paint));
 }
 
-void SkiaRecordingCanvas::drawRipple(uirenderer::CanvasPropertyPrimitive* x,
-                                     uirenderer::CanvasPropertyPrimitive* y,
-                                     uirenderer::CanvasPropertyPrimitive* radius,
-                                     uirenderer::CanvasPropertyPaint* paint,
-                                     uirenderer::CanvasPropertyPrimitive* progress,
-                                     uirenderer::CanvasPropertyPrimitive* turbulencePhase,
-                                     const SkRuntimeShaderBuilder& effectBuilder) {
-    drawDrawable(mDisplayList->allocateDrawable<AnimatedRipple>(x, y, radius, paint, progress,
-                                                                turbulencePhase, effectBuilder));
+void SkiaRecordingCanvas::drawRipple(const skiapipeline::RippleDrawableParams& params) {
+    mRecorder.drawRippleDrawable(params);
 }
 
 void SkiaRecordingCanvas::enableZ(bool enableZ) {
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index 4deb3b9..1445a27 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -20,6 +20,7 @@
 #include "ReorderBarrierDrawables.h"
 #include "SkiaCanvas.h"
 #include "SkiaDisplayList.h"
+#include "pipeline/skia/AnimatedDrawables.h"
 
 namespace android {
 namespace uirenderer {
@@ -70,13 +71,7 @@
                             uirenderer::CanvasPropertyPrimitive* y,
                             uirenderer::CanvasPropertyPrimitive* radius,
                             uirenderer::CanvasPropertyPaint* paint) override;
-    virtual void drawRipple(uirenderer::CanvasPropertyPrimitive* x,
-                            uirenderer::CanvasPropertyPrimitive* y,
-                            uirenderer::CanvasPropertyPrimitive* radius,
-                            uirenderer::CanvasPropertyPaint* paint,
-                            uirenderer::CanvasPropertyPrimitive* progress,
-                            uirenderer::CanvasPropertyPrimitive* turbulencePhase,
-                            const SkRuntimeShaderBuilder& effectBuilder) override;
+    virtual void drawRipple(const RippleDrawableParams& params) override;
 
     virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
 
diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp
index a718d46..2cf3456 100644
--- a/libs/hwui/tests/unit/CanvasOpTests.cpp
+++ b/libs/hwui/tests/unit/CanvasOpTests.cpp
@@ -31,6 +31,7 @@
 
 using namespace android;
 using namespace android::uirenderer;
+using namespace android::uirenderer::skiapipeline;
 using namespace android::uirenderer::test;
 
 // We lazy
@@ -569,6 +570,33 @@
     EXPECT_EQ(2, canvas.sumTotalDrawCalls());
 }
 
+TEST(CanvasOp, simpleDrawRipple) {
+    CanvasOpBuffer buffer;
+    EXPECT_EQ(buffer.size(), 0);
+
+    const char* sksl =
+            "half4 main(float2 coord) {"
+            "  return half4(1.);"
+            "}";
+    auto [effect, error] = SkRuntimeEffect::MakeForShader(SkString(sksl));
+    auto params = RippleDrawableParams{
+            .x = sp<CanvasPropertyPrimitive>(new CanvasPropertyPrimitive(100)),
+            .y = sp<CanvasPropertyPrimitive>(new CanvasPropertyPrimitive(200)),
+            .radius = sp<CanvasPropertyPrimitive>(new CanvasPropertyPrimitive(50)),
+            .progress = sp<CanvasPropertyPrimitive>(new CanvasPropertyPrimitive(0.5)),
+            .turbulencePhase = sp<CanvasPropertyPrimitive>(new CanvasPropertyPrimitive(1)),
+            .color = 0xff00ff,
+            .paint = sp<CanvasPropertyPaint>(new CanvasPropertyPaint(SkPaint{})),
+            .effectBuilder = SkRuntimeShaderBuilder(effect)};
+    buffer.push<Op::DrawRippleDrawable>({.params = params});
+
+    CallCountingCanvas canvas;
+    EXPECT_EQ(0, canvas.sumTotalDrawCalls());
+    rasterizeCanvasBuffer(buffer, &canvas);
+    EXPECT_EQ(1, canvas.drawOvalCount);
+    EXPECT_EQ(1, canvas.sumTotalDrawCalls());
+}
+
 TEST(CanvasOp, immediateRendering) {
     auto canvas = std::make_shared<CallCountingCanvas>();
 
diff --git a/media/java/android/media/EncoderProfiles.java b/media/java/android/media/EncoderProfiles.java
index ec8ce29..ac8c65e 100644
--- a/media/java/android/media/EncoderProfiles.java
+++ b/media/java/android/media/EncoderProfiles.java
@@ -304,7 +304,7 @@
             } else if (codec == MediaRecorder.AudioEncoder.AAC_ELD) {
                 return MediaCodecInfo.CodecProfileLevel.AACObjectELD;
             }
-            return 0;
+            return profile;
         }
 
 
@@ -313,17 +313,20 @@
                 int codec,
                 int channels,
                 int sampleRate,
-                int bitrate) {
+                int bitrate,
+                int profile) {
             this.codec = codec;
             this.channels = channels;
             this.sampleRate = sampleRate;
             this.bitrate = bitrate;
+            this.profile = profile;
         }
 
         private int codec;
         private int channels;
         private int sampleRate;
         private int bitrate;
+        private int profile;  // this contains the profile if codec itself does not
     }
 
     private int durationSecs;
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 480e2ea..4c8a8fa 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -78,6 +78,7 @@
 public class MediaRouter {
     private static final String TAG = "MediaRouter";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean DEBUG_RESTORE_ROUTE = true;
 
     static class Static implements DisplayManager.DisplayListener {
         final String mPackageName;
@@ -261,8 +262,8 @@
 
             if (audioRoutesChanged) {
                 Log.v(TAG, "Audio routes updated: " + newRoutes + ", a2dp=" + isBluetoothA2dpOn());
-                if (mSelectedRoute == null || mSelectedRoute == mDefaultAudioVideo
-                        || mSelectedRoute == mBluetoothA2dpRoute) {
+                if (mSelectedRoute == null || mSelectedRoute.isDefault()
+                        || mSelectedRoute.isBluetooth()) {
                     if (forceUseDefaultRoute || mBluetoothA2dpRoute == null) {
                         selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo, false);
                     } else {
@@ -665,12 +666,16 @@
                     // Skip restoring route if the selected route is not a system audio route,
                     // MediaRouter is initializing, or mClient was changed.
                     if (Client.this != mClient || mSelectedRoute == null
-                            || (mSelectedRoute != mDefaultAudioVideo
-                                    && mSelectedRoute != mBluetoothA2dpRoute)) {
+                            || (!mSelectedRoute.isDefault() && !mSelectedRoute.isBluetooth())) {
                         return;
                     }
-                    if (DEBUG) {
-                        Log.d(TAG, "onRestoreRoute() : route=" + mSelectedRoute);
+                    if (DEBUG_RESTORE_ROUTE) {
+                        if (mSelectedRoute.isDefault() && mBluetoothA2dpRoute != null) {
+                            Log.d(TAG, "onRestoreRoute() : selectedRoute=" + mSelectedRoute
+                                    + ", a2dpRoute=" + mBluetoothA2dpRoute);
+                        } else {
+                            Log.d(TAG, "onRestoreRoute() : route=" + mSelectedRoute);
+                        }
                     }
                     mSelectedRoute.select();
                 });
@@ -690,9 +695,12 @@
             @Override
             public void onGlobalA2dpChanged(boolean a2dpOn) {
                 mHandler.post(() -> {
-                    if (mSelectedRoute == mDefaultAudioVideo && a2dpOn) {
+                    if (mSelectedRoute == null) {
+                        return;
+                    }
+                    if (mSelectedRoute.isDefault() && a2dpOn) {
                         setSelectedRoute(mBluetoothA2dpRoute, /*explicit=*/false);
-                    } else if (mSelectedRoute == mBluetoothA2dpRoute && !a2dpOn) {
+                    } else if (mSelectedRoute.isBluetooth() && !a2dpOn) {
                         setSelectedRoute(mDefaultAudioVideo, /*explicit=*/false);
                     }
                 });
@@ -1057,8 +1065,8 @@
         final RouteInfo oldRoute = sStatic.mSelectedRoute;
         final RouteInfo currentSystemRoute = sStatic.isBluetoothA2dpOn()
                 ? sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo;
-        boolean wasDefaultOrBluetoothRoute = (oldRoute == sStatic.mDefaultAudioVideo
-                || oldRoute == sStatic.mBluetoothA2dpRoute);
+        boolean wasDefaultOrBluetoothRoute = (oldRoute != null)
+                && (oldRoute.isDefault() || oldRoute.isBluetooth());
         if (oldRoute == route
                 && (!wasDefaultOrBluetoothRoute || route == currentSystemRoute)) {
             return;
@@ -1070,14 +1078,17 @@
             return;
         }
 
-        final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute;
-        if (sStatic.isPlaybackActive() && btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0
-                && (route == btRoute || route == sStatic.mDefaultAudioVideo)) {
+        if (sStatic.isPlaybackActive() && sStatic.mBluetoothA2dpRoute != null
+                && (types & ROUTE_TYPE_LIVE_AUDIO) != 0
+                && (route.isBluetooth() || route.isDefault())) {
             try {
-                sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute);
+                sStatic.mAudioService.setBluetoothA2dpOn(route.isBluetooth());
             } catch (RemoteException e) {
                 Log.e(TAG, "Error changing Bluetooth A2DP state", e);
             }
+        } else if (DEBUG_RESTORE_ROUTE) {
+            Log.i(TAG, "Skip setBluetoothA2dpOn(): types=" + types + ", isPlaybackActive()="
+                    + sStatic.isPlaybackActive() + ", BT route=" + sStatic.mBluetoothA2dpRoute);
         }
 
         final WifiDisplay activeDisplay =
@@ -1118,7 +1129,8 @@
 
     static void selectDefaultRouteStatic() {
         // TODO: Be smarter about the route types here; this selects for all valid.
-        if (sStatic.mSelectedRoute != sStatic.mBluetoothA2dpRoute && sStatic.isBluetoothA2dpOn()) {
+        if (sStatic.isBluetoothA2dpOn() && sStatic.mSelectedRoute != null
+                && !sStatic.mSelectedRoute.isBluetooth()) {
             selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mBluetoothA2dpRoute, false);
         } else {
             selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mDefaultAudioVideo, false);
@@ -1452,8 +1464,7 @@
         final RouteInfo selectedRoute = sStatic.mSelectedRoute;
         if (selectedRoute == null) return;
 
-        if (selectedRoute == sStatic.mBluetoothA2dpRoute ||
-                selectedRoute == sStatic.mDefaultAudioVideo) {
+        if (selectedRoute.isBluetooth() || selectedRoute.isDefault()) {
             dispatchRouteVolumeChanged(selectedRoute);
         } else if (sStatic.mBluetoothA2dpRoute != null) {
             dispatchRouteVolumeChanged(sStatic.mIsBluetoothA2dpOn
@@ -2225,7 +2236,7 @@
 
         /** @hide */
         public boolean isBluetooth() {
-            return this == sStatic.mBluetoothA2dpRoute;
+            return mDeviceType == RouteInfo.DEVICE_TYPE_BLUETOOTH;
         }
 
         /** @hide */
diff --git a/media/jni/android_media_MediaProfiles.cpp b/media/jni/android_media_MediaProfiles.cpp
index 7e9b6c7..90325e7 100644
--- a/media/jni/android_media_MediaProfiles.cpp
+++ b/media/jni/android_media_MediaProfiles.cpp
@@ -255,7 +255,7 @@
 
     jclass audioProfileClazz = env->FindClass("android/media/EncoderProfiles$AudioProfile");
     jmethodID audioProfileConstructorMethodID =
-        env->GetMethodID(audioProfileClazz, "<init>", "(IIII)V");
+        env->GetMethodID(audioProfileClazz, "<init>", "(IIIII)V");
 
     jobjectArray videoCodecs = (jobjectArray)env->NewObjectArray(
             cp->getVideoCodecs().size(), videoProfileClazz, nullptr);
@@ -269,7 +269,7 @@
                                                 vc->getFrameHeight(),
                                                 vc->getFrameRate(),
                                                 vc->getBitrate(),
-                                                -1 /* profile */);
+                                                vc->getProfile());
             env->SetObjectArrayElement(videoCodecs, i++, videoCodec);
         }
     }
@@ -289,7 +289,8 @@
                                                 ac->getCodec(),
                                                 ac->getChannels(),
                                                 ac->getSampleRate(),
-                                                ac->getBitrate());
+                                                ac->getBitrate(),
+                                                ac->getProfile());
 
             env->SetObjectArrayElement(audioCodecs, i++, audioCodec);
         }
diff --git a/native/android/native_window_jni.cpp b/native/android/native_window_jni.cpp
index 859c550..901b4de 100644
--- a/native/android/native_window_jni.cpp
+++ b/native/android/native_window_jni.cpp
@@ -30,7 +30,7 @@
 ANativeWindow* ANativeWindow_fromSurface(JNIEnv* env, jobject surface) {
     sp<ANativeWindow> win = android_view_Surface_getNativeWindow(env, surface);
     if (win != NULL) {
-        win->incStrong((void*)ANativeWindow_fromSurface);
+        ANativeWindow_acquire(win.get());
     }
     return win.get();
 }
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
index b0c8c61..a0f3098 100644
--- a/native/graphics/jni/imagedecoder.cpp
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -506,7 +506,7 @@
 }
 
 int64_t AImageDecoderFrameInfo_getDuration(const AImageDecoderFrameInfo* info) {
-    if (!info) return 0;
+    if (!info) return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
 
     return toFrameInfo(info)->fDuration * 1'000'000;
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 95f180a..0210079 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -23,6 +23,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.StringRes;
+import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
@@ -632,6 +633,12 @@
                     .setPositiveButton(R.string.ok, (dialog, which) -> getActivity().finish())
                     .create();
         }
+
+        @Override
+        public void onCancel(DialogInterface dialog) {
+            getActivity().setResult(Activity.RESULT_CANCELED);
+            getActivity().finish();
+        }
     }
 
     /**
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 49152e4..11f7db7 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -451,10 +451,8 @@
     <string name="power_remaining_duration_shutdown_imminent" product="tablet" msgid="7703677921000858479">"La tablette va bientôt s\'éteindre (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_remaining_duration_shutdown_imminent" product="device" msgid="4374784375644214578">"L\'appareil va bientôt s\'éteindre (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
-    <!-- no translation found for power_remaining_charging_duration_only (8085099012811384899) -->
-    <skip />
-    <!-- no translation found for power_charging_duration (6127154952524919719) -->
-    <skip />
+    <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Chargée à 100 %% dans <xliff:g id="TIME">%1$s</xliff:g>"</string>
+    <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - chargée à 100 %% dans <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="1956874810658999681">"<xliff:g id="LEVEL">%1$s</xliff:g> - Optimisation pour préserver batterie"</string>
     <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string>
     <string name="battery_info_status_charging" msgid="4279958015430387405">"Batterie en charge"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 24b2cb8..edf653f 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -576,8 +576,8 @@
     <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activado"</string>
     <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"É necesario reiniciar o teu dispositivo para aplicar este cambio. Reiníciao agora ou cancela o cambio."</string>
     <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Auriculares con cable"</string>
-    <string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Activado"</string>
-    <string name="wifi_hotspot_switch_off_text" msgid="7245567251496959764">"Desactivado"</string>
+    <string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Activada"</string>
+    <string name="wifi_hotspot_switch_off_text" msgid="7245567251496959764">"Desactivada"</string>
     <string name="carrier_network_change_mode" msgid="4257621815706644026">"Cambio de rede do operador"</string>
     <string name="data_connection_3g" msgid="931852552688157407">"3G"</string>
     <string name="data_connection_edge" msgid="4625509456544797637">"EDGE"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index c07af12..c312456 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -548,7 +548,7 @@
     <string name="user_add_profile_item_title" msgid="3111051717414643029">"प्रतिबंधित प्रोफ़ाइल"</string>
     <string name="user_add_user_title" msgid="5457079143694924885">"नया उपयोगकर्ता जोड़ें?"</string>
     <string name="user_add_user_message_long" msgid="1527434966294733380">"आप और ज़्यादा उपयोगकर्ता बनाकर इस डिवाइस को दूसरे लोगों के साथ शेयर कर सकते हैं. हर उपयोगकर्ता के पास अपनी जगह होती है, जिसमें वह मनपसंद तरीके से ऐप्लिकेशन, वॉलपेपर और दूसरी चीज़ों में बदलाव कर सकते हैं. उपयोगकर्ता वाई-फ़ाई जैसी डिवाइस सेटिंग में भी बदलाव कर सकते हैं, जिसका असर हर किसी पर पड़ेगा.\n\nजब आप कोई नया उपयोगकर्ता जोड़ते हैं तो उन्हें अपनी जगह सेट करनी होगी.\n\nकोई भी उपयोगकर्ता दूसरे सभी उपयोगकर्ताओं के लिए ऐप्लिकेशन अपडेट कर सकता है. ऐसा भी हो सकता है कि सुलभता सेटिंग और सेवाएं नए उपयोगकर्ता को ट्रांसफ़र न हो पाएं."</string>
-    <string name="user_add_user_message_short" msgid="3295959985795716166">"जब आप कोई नया उपयोगकर्ता जोड़ते हैं तो उसे अपनी जगह सेट करनी होती है.\n\nकोई भी उपयोगकर्ता बाकी सभी उपयोगकर्ताओं के लिए ऐप अपडेट कर सकता है."</string>
+    <string name="user_add_user_message_short" msgid="3295959985795716166">"जब आप कोई नया उपयोगकर्ता जोड़ते हैं, तो उसे अपनी जगह सेट करनी होती है.\n\nकोई भी उपयोगकर्ता बाकी सभी उपयोगकर्ताओं के लिए ऐप्लिकेशन अपडेट कर सकता है."</string>
     <string name="user_setup_dialog_title" msgid="8037342066381939995">"उपयोगकर्ता को अभी सेट करें?"</string>
     <string name="user_setup_dialog_message" msgid="269931619868102841">"पक्का करें कि व्यक्ति डिवाइस का इस्तेमाल करने और अपनी जगह सेट करने के लिए मौजूद है"</string>
     <string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"प्रोफ़ाइल अभी सेट करें?"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index a29462a..798531a 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -565,8 +565,8 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"กำลังสร้างผู้ใช้ใหม่…"</string>
     <string name="add_user_failed" msgid="4809887794313944872">"สร้างผู้ใช้ใหม่ไม่ได้"</string>
     <string name="user_nickname" msgid="262624187455825083">"ชื่อเล่น"</string>
-    <string name="guest_new_guest" msgid="3482026122932643557">"เพิ่มผู้เข้าร่วม"</string>
-    <string name="guest_exit_guest" msgid="5908239569510734136">"นำผู้เข้าร่วมออก"</string>
+    <string name="guest_new_guest" msgid="3482026122932643557">"เพิ่มผู้ใช้ชั่วคราว"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"นำผู้ใช้ชั่วคราวออก"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ผู้ใช้ชั่วคราว"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ถ่ายรูป"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"เลือกรูปภาพ"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixin.java
new file mode 100644
index 0000000..d7e9dc6
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixin.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.core.lifecycle;
+
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.app.Activity;
+import android.provider.Settings;
+import android.view.Window;
+import android.view.WindowManager;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+
+/**
+ * A mixin that adds window flag to prevent non-system overlays showing on top of Settings
+ * activities.
+ */
+public class HideNonSystemOverlayMixin implements LifecycleObserver {
+
+    public static final String SECURE_OVERLAY_SETTINGS = "secure_overlay_settings";
+
+    private final Activity mActivity;
+
+    public HideNonSystemOverlayMixin(Activity activity) {
+        mActivity = activity;
+    }
+
+    @VisibleForTesting
+    boolean isEnabled() {
+        return Settings.Secure.getInt(mActivity.getContentResolver(),
+                SECURE_OVERLAY_SETTINGS, 0 /* defValue */) == 0;
+    }
+
+    /**
+     * Start Lifecycle event
+     */
+    @OnLifecycleEvent(ON_START)
+    public void onStart() {
+        if (mActivity == null || !isEnabled()) {
+            return;
+        }
+        mActivity.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+        android.util.EventLog.writeEvent(0x534e4554, "120484087", -1, "");
+    }
+
+    /**
+     * Stop Lifecycle event
+     */
+    @OnLifecycleEvent(ON_STOP)
+    public void onStop() {
+        if (mActivity == null || !isEnabled()) {
+            return;
+        }
+        final Window window = mActivity.getWindow();
+        final WindowManager.LayoutParams attrs = window.getAttributes();
+        attrs.privateFlags &= ~SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+        window.setAttributes(attrs);
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
new file mode 100644
index 0000000..cf702b53
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.core.lifecycle;
+
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import static com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin.SECURE_OVERLAY_SETTINGS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.provider.Settings;
+import android.view.WindowManager;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.android.settingslib.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.android.controller.ActivityController;
+
+@RunWith(RobolectricTestRunner.class)
+public class HideNonSystemOverlayMixinTest {
+
+    private ActivityController<TestActivity> mActivityController;
+
+    @Before
+    public void setUp() {
+        mActivityController = Robolectric.buildActivity(TestActivity.class);
+    }
+
+    @Test
+    public void startActivity_shouldHideNonSystemOverlay() {
+        mActivityController.setup();
+        TestActivity activity = mActivityController.get();
+
+        // Activity start: HIDE_NON_SYSTEM_OVERLAY should be set.
+        final WindowManager.LayoutParams attrs = activity.getWindow().getAttributes();
+        assertThat(attrs.privateFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+                .isNotEqualTo(0);
+    }
+
+    @Test
+    public void stopActivity_shouldUnhideNonSystemOverlay() {
+        mActivityController.setup().stop();
+        TestActivity activity = mActivityController.get();
+
+        final WindowManager.LayoutParams attrs = activity.getWindow().getAttributes();
+        assertThat(attrs.privateFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+                .isEqualTo(0);
+    }
+
+    @Test
+    public void isEnabled_isAllowedOverlaySettings_returnFalse() {
+        mActivityController.setup();
+        final TestActivity activity = mActivityController.get();
+        Settings.Secure.putInt(activity.getContentResolver(),
+                SECURE_OVERLAY_SETTINGS, 1);
+
+        assertThat(new HideNonSystemOverlayMixin(activity).isEnabled()).isFalse();
+    }
+
+    @Test
+    public void isEnabled_isNotAllowedOverlaySettings_returnTrue() {
+        mActivityController.setup();
+        TestActivity activity = mActivityController.get();
+        Settings.Secure.putInt(activity.getContentResolver(),
+                SECURE_OVERLAY_SETTINGS, 0);
+
+        assertThat(new HideNonSystemOverlayMixin(activity).isEnabled()).isTrue();
+    }
+
+    public static class TestActivity extends AppCompatActivity {
+        @Override
+        protected void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setTheme(R.style.Theme_AppCompat);
+            getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
+        }
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
index 58cef31..0a052df 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
@@ -70,4 +70,17 @@
      */
     public static final Interpolator TOUCH_RESPONSE_REVERSE =
             new PathInterpolator(0.9f, 0f, 0.7f, 1f);
+
+    /**
+     * Interpolate alpha for notifications background scrim during shade expansion.
+     * @param fraction Shade expansion fraction
+     */
+    public static float getNotificationScrimAlpha(float fraction) {
+        fraction = fraction * 1.2f - 0.2f;
+        if (fraction <= 0) {
+            return 0;
+        } else {
+            return (float) (1f - 0.5f * (1f - Math.cos(3.14159f * Math.pow(1f - fraction, 2f))));
+        }
+    }
 }
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index 9bb2dde..69ce275 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -20,10 +20,13 @@
 import android.app.smartspace.SmartspaceAction;
 import android.app.smartspace.SmartspaceTarget;
 import android.content.Intent;
+import android.graphics.drawable.Icon;
 import android.os.Parcelable;
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 
 import java.util.List;
@@ -50,6 +53,11 @@
         return null;
     }
 
+    /**
+     * As the smartspace view becomes available, allow listeners to receive an event.
+     */
+    default void addOnAttachStateChangeListener(View.OnAttachStateChangeListener listener) { }
+
     /** Updates Smartspace data and propagates it to any listeners. */
     void onTargetsAvailable(List<SmartspaceTarget> targets);
 
@@ -83,6 +91,11 @@
          * When on the lockscreen, use the FalsingManager to help detect errant touches
          */
         void setFalsingManager(com.android.systemui.plugins.FalsingManager falsingManager);
+
+        /**
+         * Set or clear any Do Not Disturb information.
+         */
+        void setDnd(@Nullable Icon dndIcon, @Nullable String description);
     }
 
     /** Interface for launching Intents, which can differ on the lockscreen */
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 55dea3d..f1c1477 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -148,9 +148,11 @@
     @ProvidesInterface(version = State.VERSION)
     public static class State {
         public static final int VERSION = 1;
+        public static final int DEFAULT_STATE = Tile.STATE_ACTIVE;
+
         public Icon icon;
         public Supplier<Icon> iconSupplier;
-        public int state = Tile.STATE_ACTIVE;
+        public int state = DEFAULT_STATE;
         public CharSequence label;
         public CharSequence secondaryLabel;
         public CharSequence contentDescription;
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 1cef44b..7c5459c1 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -80,6 +80,6 @@
         android:layout_width="match_parent"
         android:layout_height="@dimen/notification_shelf_height"
         android:layout_below="@id/keyguard_status_area"
-        android:paddingStart="@dimen/below_clock_padding_start"
+        android:paddingStart="@dimen/below_clock_padding_start_extra"
     />
 </com.android.keyguard.KeyguardClockSwitch>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 9b8035d..a166b09 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -94,4 +94,5 @@
     <!-- additional offset for clock switch area items -->
     <dimen name="clock_padding_start">28dp</dimen>
     <dimen name="below_clock_padding_start">32dp</dimen>
+    <dimen name="below_clock_padding_start_extra">36dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/drawable/ic_blank.xml b/packages/SystemUI/res/drawable/ic_blank.xml
new file mode 100644
index 0000000..b94088f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_blank.xml
@@ -0,0 +1,22 @@
+<!--
+Copyright (C) 2021 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="16dp"
+        android:height="16dp"
+        android:viewportWidth="16.0"
+        android:viewportHeight="16.0">
+  <path/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_sim_card.xml b/packages/SystemUI/res/drawable/ic_qs_sim_card.xml
new file mode 100644
index 0000000..6eda929
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_sim_card.xml
@@ -0,0 +1,47 @@
+<!--
+Copyright (C) 2021 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?android:attr/colorControlNormal"
+    >
+
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M18,2h-8L4,8v12c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V4C20,2.9,19.1,2,18,2z M18,4v16H6V8.83L10.83,4L18,4L18,4z" />
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M 7 17 H 9 V 19 H 7 V 17 Z" />
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M 15 17 H 17 V 19 H 15 V 17 Z" />
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M 7 11 H 9 V 15 H 7 V 11 Z" />
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M 11 15 H 13 V 19 H 11 V 15 Z" />
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M 11 11 H 13 V 13 H 11 V 11 Z" />
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M 15 11 H 17 V 15 H 15 V 11 Z" />
+    <path
+        android:pathData="M0,0h24v24H0V0z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/notif_settings_button.xml b/packages/SystemUI/res/drawable/notif_settings_button.xml
new file mode 100644
index 0000000..34a5caf
--- /dev/null
+++ b/packages/SystemUI/res/drawable/notif_settings_button.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/ripple_drawable"/>
+    <item android:drawable="@drawable/ic_settings"/>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_chip_bg.xml b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
index 9f41dbe..99c0ab4b 100644
--- a/packages/SystemUI/res/drawable/privacy_chip_bg.xml
+++ b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
@@ -16,6 +16,6 @@
 -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="@color/privacy_circle" />
+    <solid android:color="@color/privacy_chip_background" />
     <corners android:radius="@dimen/ongoing_appops_chip_bg_corner_radius" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml b/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml
index 54a66e2..f4ff549 100644
--- a/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml
+++ b/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml
@@ -24,7 +24,7 @@
                 android:height="@dimen/ongoing_appops_dialog_circle_size"
                 android:width="@dimen/ongoing_appops_dialog_circle_size"
             />
-            <solid android:color="@color/privacy_circle" />
+            <solid android:color="@color/privacy_chip_background" />
         </shape>
     </item>
     <item android:id="@id/icon"
diff --git a/packages/SystemUI/res/drawable/privacy_item_circle_location.xml b/packages/SystemUI/res/drawable/privacy_item_circle_location.xml
index 65f4396..9a201886 100644
--- a/packages/SystemUI/res/drawable/privacy_item_circle_location.xml
+++ b/packages/SystemUI/res/drawable/privacy_item_circle_location.xml
@@ -24,7 +24,7 @@
                 android:height="@dimen/ongoing_appops_dialog_circle_size"
                 android:width="@dimen/ongoing_appops_dialog_circle_size"
             />
-            <solid android:color="@color/privacy_circle" />
+            <solid android:color="@color/privacy_chip_background" />
         </shape>
     </item>
     <item android:id="@id/icon"
diff --git a/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml b/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml
index 1565d2d..a5f2d32 100644
--- a/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml
+++ b/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml
@@ -24,7 +24,7 @@
                 android:height="@dimen/ongoing_appops_dialog_circle_size"
                 android:width="@dimen/ongoing_appops_dialog_circle_size"
             />
-            <solid android:color="@color/privacy_circle" />
+            <solid android:color="@color/privacy_chip_background" />
         </shape>
     </item>
     <item android:id="@id/icon"
diff --git a/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml b/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml
index 4e9d380..9f81b0d 100644
--- a/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml
+++ b/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml
@@ -18,7 +18,7 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="oval">
     <solid
-        android:color="@color/privacy_circle"/>
+        android:color="@color/privacy_chip_background"/>
     <size
         android:width="6dp"
         android:height="6dp"
diff --git a/packages/SystemUI/res/layout/dock_info_bottom_area_overlay.xml b/packages/SystemUI/res/layout/dock_info_bottom_area_overlay.xml
new file mode 100644
index 0000000..8b70dd7
--- /dev/null
+++ b/packages/SystemUI/res/layout/dock_info_bottom_area_overlay.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!-- empty stub -->
+<merge />
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
index c332f4c..2ec4e73 100644
--- a/packages/SystemUI/res/layout/notification_conversation_info.xml
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -45,11 +45,14 @@
             android:layout_marginEnd="12dp"
         >
 
-            <!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right -->
+            <!-- Big icon: 48x48, 12dp padding top, 16dp padding sides -->
             <ImageView
                 android:id="@+id/conversation_icon"
                 android:layout_width="@*android:dimen/conversation_avatar_size"
                 android:layout_height="@*android:dimen/conversation_avatar_size"
+                android:layout_marginLeft="@*android:dimen/conversation_badge_protrusion"
+                android:layout_marginRight="@*android:dimen/conversation_badge_protrusion"
+                android:layout_marginBottom="@*android:dimen/conversation_badge_protrusion"
                 android:scaleType="centerCrop"
                 android:importantForAccessibility="no"
             />
@@ -58,8 +61,7 @@
                 android:id="@+id/conversation_icon_badge"
                 android:layout_width="@*android:dimen/conversation_icon_size_badged"
                 android:layout_height="@*android:dimen/conversation_icon_size_badged"
-                android:layout_marginLeft="@*android:dimen/conversation_badge_side_margin"
-                android:layout_marginTop="@*android:dimen/conversation_badge_side_margin"
+                android:layout_gravity="end|bottom"
                 android:clipChildren="false"
                 android:clipToPadding="false"
             >
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index b2c968c..7e8112a 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -108,9 +108,9 @@
             android:layout_width="@dimen/notification_importance_toggle_size"
             android:layout_height="@dimen/notification_importance_toggle_size"
             android:layout_centerVertical="true"
-            android:background="@drawable/ripple_drawable"
+            android:background="@android:color/transparent"
             android:contentDescription="@string/notification_more_settings"
-            android:src="@drawable/ic_settings"
+            android:src="@drawable/notif_settings_button"
             android:layout_alignParentEnd="true"
             android:tint="@color/notification_guts_link_icon_tint"/>
 
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 52995ea..f4cb3b1 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -101,6 +101,8 @@
 
     </com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>
 
+    <include layout="@layout/dock_info_bottom_area_overlay" />
+
     <include
         layout="@layout/keyguard_bottom_area"
         android:visibility="gone" />
diff --git a/packages/SystemUI/res/layout/wallet_card_view.xml b/packages/SystemUI/res/layout/wallet_card_view.xml
index 5fd556d..b5a66010 100644
--- a/packages/SystemUI/res/layout/wallet_card_view.xml
+++ b/packages/SystemUI/res/layout/wallet_card_view.xml
@@ -24,6 +24,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_marginHorizontal="@dimen/card_margin"
+        android:layout_marginBottom="@dimen/card_margin"
         android:foreground="?android:attr/selectableItemBackground"
         app:cardBackgroundColor="@android:color/transparent"
         app:cardElevation="12dp">
@@ -32,16 +33,8 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:adjustViewBounds="true"
+            android:layout_gravity="center"
             android:contentDescription="@null"
             android:scaleType="fitXY"/>
-        <ImageView
-            android:id="@+id/add_card_logo"
-            android:layout_width="28dp"
-            android:layout_height="28dp"
-            android:layout_gravity="center"
-            android:drawable="@drawable/ic_qs_plus"
-            android:contentDescription="@null"
-            android:scaleType="fitCenter"
-            android:visibility="gone"/>
     </com.android.systemui.wallet.ui.WalletCardView>
 </FrameLayout>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 8f88950..c473229 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -95,9 +95,6 @@
     <!-- Color of background circle of user avatars in quick settings user switcher -->
     <color name="qs_user_switcher_avatar_background">#3C4043</color>
 
-    <!-- Colors for privacy dialog. These should be changed to the new palette -->
-    <color name="privacy_circle">#81C995</color> <!-- g300 -->
-
     <!-- Accessibility floating menu -->
     <color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% -->
 </resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 4d9b9e1..08a2e19 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -279,7 +279,7 @@
 
     <color name="screenrecord_status_color">#E94235</color>
 
-    <color name="privacy_circle">#5BB974</color> <!-- g400 -->
+    <color name="privacy_chip_background">#3ddc84</color>
 
     <!-- Accessibility floating menu -->
     <color name="accessibility_floating_menu_background">#CCFFFFFF</color> <!-- 80% -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4da2e33..30add20 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1115,16 +1115,16 @@
     <string name="interruption_level_alarms_twoline">Alarms\nonly</string>
 
     <!-- Indication on the keyguard that is shown when the device is wirelessly charging. [CHAR LIMIT=80]-->
-    <string name="keyguard_indication_charging_time_wireless"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging wirelessly (<xliff:g id="charging_time_left" example="4 hours and 2 minutes">%1$s</xliff:g> until full)</string>
+    <string name="keyguard_indication_charging_time_wireless"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging wirelessly • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
 
     <!-- Indication on the keyguard that is shown when the device is charging. [CHAR LIMIT=50]-->
-    <string name="keyguard_indication_charging_time"><xliff:g id="percentage">%2$s</xliff:g> • Charging (<xliff:g id="charging_time_left" example="4 hours and 2 minutes">%1$s</xliff:g> until full)</string>
+    <string name="keyguard_indication_charging_time"><xliff:g id="percentage">%2$s</xliff:g> • Charging • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
 
     <!-- Indication on the keyguard that is shown when the device is charging rapidly. Should match keyguard_plugged_in_charging_fast [CHAR LIMIT=50]-->
-    <string name="keyguard_indication_charging_time_fast"><xliff:g id="percentage">%2$s</xliff:g> • Charging rapidly (<xliff:g id="charging_time_left" example="4 hours and 2 minutes">%1$s</xliff:g> until full)</string>
+    <string name="keyguard_indication_charging_time_fast"><xliff:g id="percentage">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
 
     <!-- Indication on the keyguard that is shown when the device is charging slowly. Should match keyguard_plugged_in_charging_slowly [CHAR LIMIT=50]-->
-    <string name="keyguard_indication_charging_time_slowly"><xliff:g id="percentage">%2$s</xliff:g> • Charging slowly (<xliff:g id="charging_time_left" example="4 hours and 2 minutes">%1$s</xliff:g> until full)</string>
+    <string name="keyguard_indication_charging_time_slowly"><xliff:g id="percentage">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
 
     <!-- Related to user switcher --><skip/>
 
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9fd18c6..abc44a2 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -342,7 +342,6 @@
         <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item>
         <item name="wallpaperTextColorAccent">@*android:color/system_accent1_100</item>
         <item name="android:colorError">@*android:color/error_color_material_dark</item>
-        <item name="android:colorControlHighlight">@*android:color/primary_text_material_dark</item>
         <item name="*android:lockPatternStyle">@style/LockPatternStyle</item>
         <item name="passwordStyle">@style/PasswordTheme</item>
         <item name="numPadKeyStyle">@style/NumPadKey</item>
@@ -359,7 +358,6 @@
         <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_light</item>
         <item name="wallpaperTextColorAccent">@*android:color/system_accent2_600</item>
         <item name="android:colorError">@*android:color/error_color_material_light</item>
-        <item name="android:colorControlHighlight">#40000000</item>
         <item name="shadowRadius">0</item>
 
         <!-- Needed for MediaRoute chooser dialog -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 114472b..277b2e3 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -75,4 +75,10 @@
      * Sent when the split screen is resized
      */
     void onSplitScreenSecondaryBoundsChanged(in Rect bounds, in Rect insets) = 17;
+
+    /**
+     * Sent IME status changes
+     */
+    void onImeWindowStatusChanged(int displayId, IBinder token, int vis, int backDisposition,
+                         boolean showImeSwitcher) = 18;
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java b/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java
index 356e0ca..4394ecb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java
@@ -49,7 +49,7 @@
     private final TraceBuffer<P, S, T> mBuffer;
     private final File mTraceFile;
     private final ProtoTraceParams<P, S, T, R> mParams;
-    private final Choreographer mChoreographer;
+    private Choreographer mChoreographer;
     private final Queue<T> mPool = new LinkedList<>();
     private final ArrayList<ProtoTraceable<R>> mTraceables = new ArrayList<>();
     private final ArrayList<ProtoTraceable<R>> mTmpTraceables = new ArrayList<>();
@@ -94,7 +94,6 @@
             }
         });
         mTraceFile = params.getTraceFile();
-        mChoreographer = Choreographer.getMainThreadInstance();
     }
 
     public void start() {
@@ -140,6 +139,9 @@
         }
 
         // Schedule an update on the next frame
+        if (mChoreographer == null) {
+            mChoreographer = Choreographer.getMainThreadInstance();
+        }
         mChoreographer.postFrameCallback(this);
         mFrameScheduled = true;
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index e92cae4..4b71a3a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -25,7 +25,6 @@
 import android.app.smartspace.SmartspaceManager;
 import android.app.smartspace.SmartspaceSession;
 import android.app.smartspace.SmartspaceTarget;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
@@ -45,7 +44,6 @@
 import com.android.keyguard.clock.ClockManager;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.SystemUIFactory;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -69,6 +67,7 @@
 import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.Locale;
+import java.util.Optional;
 import java.util.TimeZone;
 import java.util.concurrent.Executor;
 
@@ -89,7 +88,6 @@
     private final Executor mUiExecutor;
     private final BatteryController mBatteryController;
     private final FeatureFlags mFeatureFlags;
-    private final SystemUIFactory mSystemUIFactory;
 
     /**
      * Clock for both small and large sizes
@@ -113,7 +111,6 @@
     private boolean mShowSensitiveContentForCurrentUser;
     private boolean mShowSensitiveContentForManagedUser;
     private UserHandle mManagedUserHandle;
-    private UserTracker.Callback mUserTrackerCallback;
 
     /**
      * Listener for changes to the color palette.
@@ -152,6 +149,7 @@
 
     // If set, will replace keyguard_status_area
     private BcSmartspaceDataPlugin.SmartspaceView mSmartspaceView;
+    private Optional<BcSmartspaceDataPlugin> mSmartspacePlugin;
 
     @Inject
     public KeyguardClockSwitchController(
@@ -165,14 +163,14 @@
             @Main Executor uiExecutor,
             BatteryController batteryController,
             ConfigurationController configurationController,
-            SystemUIFactory systemUIFactory,
             ActivityStarter activityStarter,
             FalsingManager falsingManager,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             KeyguardBypassController bypassController,
             @Main Handler handler,
             UserTracker userTracker,
-            SecureSettings secureSettings) {
+            SecureSettings secureSettings,
+            Optional<BcSmartspaceDataPlugin> smartspacePlugin) {
         super(keyguardClockSwitch);
         mStatusBarStateController = statusBarStateController;
         mColorExtractor = colorExtractor;
@@ -184,7 +182,6 @@
         mUiExecutor = uiExecutor;
         mBatteryController = batteryController;
         mConfigurationController = configurationController;
-        mSystemUIFactory = systemUIFactory;
         mActivityStarter = activityStarter;
         mFalsingManager = falsingManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -192,6 +189,7 @@
         mHandler = handler;
         mUserTracker = userTracker;
         mSecureSettings = secureSettings;
+        mSmartspacePlugin = smartspacePlugin;
     }
 
     /**
@@ -237,8 +235,8 @@
         mStatusBarStateController.addCallback(mStatusBarStateListener);
         mConfigurationController.addCallback(mConfigurationListener);
 
-        BcSmartspaceDataPlugin smartspaceDataPlugin = mSystemUIFactory.getSmartspaceDataProvider();
-        if (mFeatureFlags.isSmartspaceEnabled() && smartspaceDataPlugin != null) {
+        if (mFeatureFlags.isSmartspaceEnabled() && mSmartspacePlugin.isPresent()) {
+            BcSmartspaceDataPlugin smartspaceDataPlugin = mSmartspacePlugin.get();
             View ksa = mView.findViewById(R.id.keyguard_status_area);
             int ksaIndex = mView.indexOfChild(ksa);
             ksa.setVisibility(View.GONE);
@@ -294,13 +292,6 @@
                 }
             };
 
-            mUserTrackerCallback = new UserTracker.Callback() {
-                public void onUserChanged(int newUser, Context userContext) {
-                    reloadSmartspace();
-                }
-            };
-            mUserTracker.addCallback(mUserTrackerCallback, mUiExecutor);
-
             getContext().getContentResolver().registerContentObserver(
                     Settings.Secure.getUriFor(
                             Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
@@ -376,10 +367,6 @@
         if (mSettingsObserver != null) {
             getContext().getContentResolver().unregisterContentObserver(mSettingsObserver);
         }
-
-        if (mUserTrackerCallback != null) {
-            mUserTracker.removeCallback(mUserTrackerCallback);
-        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index d07723e..be50eb1 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -91,6 +91,7 @@
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
+import com.android.systemui.util.concurrency.ThreadFactory;
 import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.ArrayList;
@@ -128,6 +129,7 @@
     private CameraAvailabilityListener mCameraListener;
     private final UserTracker mUserTracker;
     private final PrivacyDotViewController mDotViewController;
+    private final ThreadFactory mThreadFactory;
 
     //TODO: These are piecemeal being updated to Points for now to support non-square rounded
     // corners. for now it is only supposed when reading the intrinsic size from the drawables with
@@ -215,7 +217,8 @@
             BroadcastDispatcher broadcastDispatcher,
             TunerService tunerService,
             UserTracker userTracker,
-            PrivacyDotViewController dotViewController) {
+            PrivacyDotViewController dotViewController,
+            ThreadFactory threadFactory) {
         super(context);
         mMainHandler = handler;
         mSecureSettings = secureSettings;
@@ -223,6 +226,7 @@
         mTunerService = tunerService;
         mUserTracker = userTracker;
         mDotViewController = dotViewController;
+        mThreadFactory = threadFactory;
     }
 
     @Override
@@ -233,7 +237,8 @@
         }
         mHandler = startHandlerThread();
         mHandler.post(this::startOnScreenDecorationsThread);
-        mDotViewController.setUiExecutor(mHandler::post);
+        mDotViewController.setUiExecutor(
+                mThreadFactory.buildDelayableExecutorOnLooper(mHandler.getLooper()));
     }
 
     @VisibleForTesting
@@ -643,7 +648,7 @@
 
         int newRotation = mContext.getDisplay().getRotation();
         if (mRotation != newRotation) {
-            mDotViewController.updateRotation(newRotation);
+            mDotViewController.setNewRotation(newRotation);
         }
 
         if (mPendingRotationChange) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index af064e1..cc167b9 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -29,7 +29,6 @@
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.navigationbar.gestural.BackGestureTfClassifierProvider;
-import com.android.systemui.plugins.BcSmartspaceDataPlugin;
 import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider;
 import com.android.wm.shell.transition.Transitions;
 
@@ -210,8 +209,4 @@
             AssetManager am, String modelName) {
         return new BackGestureTfClassifierProvider();
     }
-
-    public BcSmartspaceDataPlugin getSmartspaceDataProvider() {
-        return null;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 126724c..7fa48d4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -38,6 +38,7 @@
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.log.dagger.LogModule;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.plugins.BcSmartspaceDataPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.dagger.PowerModule;
 import com.android.systemui.recents.Recents;
@@ -151,6 +152,9 @@
     abstract HeadsUpManager optionalHeadsUpManager();
 
     @BindsOptionalOf
+    abstract BcSmartspaceDataPlugin optionalBcSmartspaceDataPlugin();
+
+    @BindsOptionalOf
     abstract Recents optionalRecents();
 
     @BindsOptionalOf
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 2450367..644876c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -106,7 +106,7 @@
     private var carouselMeasureHeight: Int = 0
     private var desiredHostState: MediaHostState? = null
     private val mediaCarousel: MediaScrollView
-    private val mediaCarouselScrollHandler: MediaCarouselScrollHandler
+    val mediaCarouselScrollHandler: MediaCarouselScrollHandler
     val mediaFrame: ViewGroup
     private lateinit var settingsButton: View
     private val mediaContent: ViewGroup
@@ -115,6 +115,7 @@
     private var needsReordering: Boolean = false
     private var keysNeedRemoval = mutableSetOf<String>()
     private var bgColor = getBackgroundColor()
+    private var shouldScrollToActivePlayer: Boolean = false
     private var isRtl: Boolean = false
         set(value) {
             if (value != field) {
@@ -155,20 +156,13 @@
         }
     }
 
-    var visibleToUser: Boolean = false
-        set(value) {
-            if (field != value) {
-                field = value
-            }
-        }
-
     init {
         mediaFrame = inflateMediaCarousel()
         mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
         pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
         mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator,
-                executor, mediaManager::onSwipeToDismiss, this::updatePageIndicatorLocation,
-                this::closeGuts, falsingCollector, falsingManager)
+                executor, this::onSwipeToDismiss, this::updatePageIndicatorLocation,
+                this::closeGuts, falsingCollector, falsingManager, this::logSmartspaceImpression)
         isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
         inflateSettingsButton()
         mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
@@ -209,7 +203,7 @@
             override fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) {
                 Log.d(TAG, "My Smartspace media update is here")
                 addSmartspaceMediaRecommendations(key, data)
-                if (visibleToUser) {
+                if (mediaCarouselScrollHandler.visibleToUser) {
                     logSmartspaceImpression()
                 }
             }
@@ -271,6 +265,15 @@
             }
         }
         mediaCarouselScrollHandler.onPlayersChanged()
+
+        // Automatically scroll to the active player if needed
+        if (shouldScrollToActivePlayer) {
+            shouldScrollToActivePlayer = false
+            val activeMediaIndex = MediaPlayerData.getActiveMediaIndex()
+            if (activeMediaIndex != -1) {
+                mediaCarouselScrollHandler.scrollToActivePlayer(activeMediaIndex)
+            }
+        }
     }
 
     private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData) {
@@ -322,8 +325,8 @@
         val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
             ViewGroup.LayoutParams.WRAP_CONTENT)
         newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp)
-        newRecs.bindRecommendation(data, bgColor)
-        MediaPlayerData.addMediaPlayer(key, newRecs)
+        newRecs.bindRecommendation(data, bgColor, { v -> shouldScrollToActivePlayer = true })
+        MediaPlayerData.addMediaRecommendation(key, newRecs)
         updatePlayerToState(newRecs, noAnimation = true)
         reorderAllPlayers()
         updatePageIndicator()
@@ -570,32 +573,77 @@
      * Log the user impression for media card.
      */
     fun logSmartspaceImpression() {
-        MediaPlayerData.players().forEach {
-            // Log every impression of media recommendation card since it will only be shown
-            // for 1 minute after each connection.
-            if (it.recommendationViewHolder?.recommendations?.visibility == View.VISIBLE) {
-                /* ktlint-disable max-line-length */
-                SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
-                        800, // SMARTSPACE_CARD_SEEN
-                        it.getInstanceId(),
-                        SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS,
-                        it.getSurfaceForSmartspaceLogging(),
-                        /* rank */ 0,
-                        /* cardinality */ 1)
-                /* ktlint-disable max-line-length */
+        val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex
+        if (MediaPlayerData.players().size > visibleMediaIndex) {
+            val mediaControlPanel = MediaPlayerData.players().elementAt(visibleMediaIndex)
+            val isMediaActive =
+                    MediaPlayerData.playerKeys().elementAt(visibleMediaIndex).data?.active
+            val isRecommendationCard = mediaControlPanel.recommendationViewHolder != null
+            if (!isRecommendationCard && !isMediaActive) {
+                // Media control card time out or swiped away
+                return
             }
-
-            // TODO(shijieru): add logging for media control card
+            logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
+                    mediaControlPanel.mInstanceId,
+                    isRecommendationCard,
+                    mediaControlPanel.surfaceForSmartspaceLogging)
         }
     }
+
+    @JvmOverloads
+    fun logSmartspaceCardReported(
+        eventId: Int,
+        instanceId: Int,
+        isRecommendationCard: Boolean,
+        surface: Int,
+        rank: Int = mediaCarouselScrollHandler.visibleMediaIndex
+    ) {
+        /* ktlint-disable max-line-length */
+        SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
+                eventId,
+                instanceId,
+                if (isRecommendationCard)
+                    SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS
+                else
+                    SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_RESUME_MEDIA,
+                surface,
+                rank,
+                mediaContent.getChildCount())
+        /* ktlint-disable max-line-length */
+    }
+
+    private fun onSwipeToDismiss() {
+        val recommendation = MediaPlayerData.players().filter {
+            it.recommendationViewHolder != null
+        }
+        // Use -1 as rank value to indicate user swipe to dismiss the card
+        if (!recommendation.isEmpty()) {
+            logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
+                    recommendation.get(0).mInstanceId,
+                    true,
+                    recommendation.get(0).surfaceForSmartspaceLogging,
+            /* rank */-1)
+        } else {
+            val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex
+            if (MediaPlayerData.players().size > visibleMediaIndex) {
+                val player = MediaPlayerData.players().elementAt(visibleMediaIndex)
+                logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
+                        player.mInstanceId,
+                false,
+                        player.surfaceForSmartspaceLogging,
+                /* rank */-1)
+            }
+        }
+        mediaManager.onSwipeToDismiss()
+    }
 }
 
 @VisibleForTesting
 internal object MediaPlayerData {
     private val EMPTY = MediaData(-1, false, 0, null, null, null, null, null,
-        emptyList(), emptyList(), "INVALID", null, null, null, true, null)
+        emptyList(), emptyList(), "INVALID", null, null, null, false, null)
 
-    private data class MediaSortKey(
+    data class MediaSortKey(
         // Is Smartspace media recommendation. When the Smartspace media is present, it should
         // always be the first card in carousel.
         val isSsMediaRec: Boolean,
@@ -620,7 +668,7 @@
         mediaPlayers.put(sortKey, player)
     }
 
-    fun addMediaPlayer(key: String, player: MediaControlPanel) {
+    fun addMediaRecommendation(key: String, player: MediaControlPanel) {
         removeMediaPlayer(key)
         val sortKey = MediaSortKey(isSsMediaRec = true, EMPTY, System.currentTimeMillis())
         mediaData.put(key, sortKey)
@@ -643,6 +691,18 @@
 
     fun players() = mediaPlayers.values
 
+    /** Returns the index of the first non-timeout media. */
+    fun getActiveMediaIndex(): Int {
+        mediaPlayers.entries.forEachIndexed { index, e ->
+            if (e.key.data.active) {
+                return index
+            }
+        }
+        return -1
+    }
+
+    fun playerKeys() = mediaPlayers.keys
+
     @VisibleForTesting
     fun clear() {
         mediaData.clear()
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
index f0b78dd..e5a6271 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
@@ -28,16 +28,17 @@
 import androidx.dynamicanimation.animation.SpringForce
 import com.android.settingslib.Utils
 import com.android.systemui.Gefingerpoken
-import com.android.systemui.qs.PageIndicator
 import com.android.systemui.R
 import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.plugins.FalsingManager
-import com.android.wm.shell.animation.PhysicsAnimator
+import com.android.systemui.qs.PageIndicator
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.wm.shell.animation.PhysicsAnimator
 
 private const val FLING_SLOP = 1000000
 private const val DISMISS_DELAY = 100L
+private const val SCROLL_DELAY = 100L
 private const val RUBBERBAND_FACTOR = 0.2f
 private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f
 
@@ -60,7 +61,8 @@
     private var translationChangedListener: () -> Unit,
     private val closeGuts: () -> Unit,
     private val falsingCollector: FalsingCollector,
-    private val falsingManager: FalsingManager
+    private val falsingManager: FalsingManager,
+    private val logSmartspaceImpression: () -> Unit
 ) {
     /**
      * Is the view in RTL
@@ -100,10 +102,11 @@
     private lateinit var settingsButton: View
 
     /**
-     * What's the currently active player index?
+     * What's the currently visible player index?
      */
-    var activeMediaIndex: Int = 0
+    var visibleMediaIndex: Int = 0
         private set
+
     /**
      * How much are we scrolled into the current media?
      */
@@ -129,7 +132,7 @@
             field = value
             // The player width has changed, let's update the scroll position to make sure
             // it's still at the same place
-            var newRelativeScroll = activeMediaIndex * playerWidthPlusPadding
+            var newRelativeScroll = visibleMediaIndex * playerWidthPlusPadding
             if (scrollIntoCurrentMedia > playerWidthPlusPadding) {
                 newRelativeScroll += playerWidthPlusPadding -
                         (scrollIntoCurrentMedia - playerWidthPlusPadding)
@@ -198,6 +201,13 @@
         }
     }
 
+    var visibleToUser: Boolean = false
+        set(value) {
+            if (field != value) {
+                field = value
+            }
+        }
+
     init {
         gestureDetector = GestureDetectorCompat(scrollView.context, gestureListener)
         scrollView.touchListener = touchListener
@@ -457,12 +467,16 @@
         val wasScrolledIn = scrollIntoCurrentMedia != 0
         scrollIntoCurrentMedia = scrollInAmount
         val nowScrolledIn = scrollIntoCurrentMedia != 0
-        if (newIndex != activeMediaIndex || wasScrolledIn != nowScrolledIn) {
-            activeMediaIndex = newIndex
+        if (newIndex != visibleMediaIndex || wasScrolledIn != nowScrolledIn) {
+            val oldIndex = visibleMediaIndex
+            visibleMediaIndex = newIndex
+            if (oldIndex != visibleMediaIndex && visibleToUser) {
+                logSmartspaceImpression()
+            }
             closeGuts()
             updatePlayerVisibilities()
         }
-        val relativeLocation = activeMediaIndex.toFloat() + if (playerWidthPlusPadding > 0)
+        val relativeLocation = visibleMediaIndex.toFloat() + if (playerWidthPlusPadding > 0)
             scrollInAmount.toFloat() / playerWidthPlusPadding else 0f
         // Fix the location, because PageIndicator does not handle RTL internally
         val location = if (isRtl) {
@@ -500,7 +514,7 @@
         val scrolledIn = scrollIntoCurrentMedia != 0
         for (i in 0 until mediaContent.childCount) {
             val view = mediaContent.getChildAt(i)
-            val visible = (i == activeMediaIndex) || ((i == (activeMediaIndex + 1)) && scrolledIn)
+            val visible = (i == visibleMediaIndex) || ((i == (visibleMediaIndex + 1)) && scrolledIn)
             view.visibility = if (visible) View.VISIBLE else View.INVISIBLE
         }
     }
@@ -511,12 +525,12 @@
      */
     fun onPrePlayerRemoved(removed: MediaControlPanel) {
         val removedIndex = mediaContent.indexOfChild(removed.playerViewHolder?.player)
-        // If the removed index is less than the activeMediaIndex, then we need to decrement it.
+        // If the removed index is less than the visibleMediaIndex, then we need to decrement it.
         // RTL has no effect on this, because indices are always relative (start-to-end).
         // Update the index 'manually' since we won't always get a call to onMediaScrollingChanged
-        val beforeActive = removedIndex <= activeMediaIndex
+        val beforeActive = removedIndex <= visibleMediaIndex
         if (beforeActive) {
-            activeMediaIndex = Math.max(0, activeMediaIndex - 1)
+            visibleMediaIndex = Math.max(0, visibleMediaIndex - 1)
         }
         // If the removed media item is "left of" the active one (in an absolute sense), we need to
         // scroll the view to keep that player in view.  This is because scroll position is always
@@ -545,6 +559,17 @@
         scrollView.relativeScrollX = 0
     }
 
+    fun scrollToActivePlayer(activePlayerIndex: Int) {
+        var destIndex = activePlayerIndex
+        destIndex = Math.min(mediaContent.getChildCount() - 1, destIndex)
+        val view = mediaContent.getChildAt(destIndex)
+        // We need to post this to wait for the active player becomes visible.
+        mainExecutor.executeDelayed({
+            visibleMediaIndex = activePlayerIndex
+            scrollView.smoothScrollTo(view.left, scrollView.scrollY)
+        }, SCROLL_DELAY)
+    }
+
     companion object {
         private val CONTENT_TRANSLATION = object : FloatPropertyCompat<MediaCarouselScrollHandler>(
                 "contentTranslation") {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 1e9cc8c..fe3463f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -105,7 +105,8 @@
     private int mDevicePadding;
     private int mAlbumArtSize;
     // Instance id for logging purpose.
-    private int mInstanceId;
+    protected int mInstanceId = -1;
+    private MediaCarouselController mMediaCarouselController;
     private final MediaOutputDialogFactory mMediaOutputDialogFactory;
 
     /**
@@ -119,7 +120,7 @@
             ActivityStarter activityStarter, MediaViewController mediaViewController,
             SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
             KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory
-            mediaOutputDialogFactory) {
+            mediaOutputDialogFactory, MediaCarouselController mediaCarouselController) {
         mContext = context;
         mBackgroundExecutor = backgroundExecutor;
         mActivityStarter = activityStarter;
@@ -128,6 +129,7 @@
         mMediaDataManagerLazy = lazyMediaDataManager;
         mKeyguardDismissUtil = keyguardDismissUtil;
         mMediaOutputDialogFactory = mediaOutputDialogFactory;
+        mMediaCarouselController = mediaCarouselController;
         loadDimens();
     }
 
@@ -251,6 +253,8 @@
         }
         mKey = key;
         MediaSession.Token token = data.getToken();
+        mInstanceId = data.getPackageName().hashCode();
+
         mBackgroundColor = data.getBackgroundColor();
         if (mToken == null || !mToken.equals(token)) {
             mToken = token;
@@ -270,6 +274,9 @@
         if (clickIntent != null) {
             mPlayerViewHolder.getPlayer().setOnClickListener(v -> {
                 if (mMediaViewController.isGutsVisible()) return;
+
+                logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
+                        false);
                 mActivityStarter.postStartActivityDismissingKeyguard(clickIntent,
                         buildLaunchAnimatorController(mPlayerViewHolder.getPlayer()));
             });
@@ -376,6 +383,8 @@
             } else {
                 button.setEnabled(true);
                 button.setOnClickListener(v -> {
+                    logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
+                            false);
                     action.run();
                 });
             }
@@ -408,6 +417,9 @@
         mPlayerViewHolder.getDismissLabel().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
         mPlayerViewHolder.getDismiss().setEnabled(isDismissible);
         mPlayerViewHolder.getDismiss().setOnClickListener(v -> {
+            logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
+                    false);
+
             if (mKey != null) {
                 closeGuts();
                 mKeyguardDismissUtil.executeWhenUnlocked(() -> {
@@ -462,7 +474,8 @@
     /** Bind this recommendation view based on the data given. */
     public void bindRecommendation(
             @NonNull SmartspaceTarget target,
-            @NonNull int backgroundColor) {
+            @NonNull int backgroundColor,
+            @Nullable View.OnClickListener callback) {
         if (mRecommendationViewHolder == null) {
             return;
         }
@@ -526,7 +539,7 @@
             setSmartspaceRecItemOnClickListener(
                     mediaCoverImageView,
                     recommendation,
-                    null);
+                    callback);
 
             if (uiComponentIndex < MEDIA_RECOMMENDATION_ITEMS_PER_ROW) {
                 setVisibleAndAlpha(collapsedSet,
@@ -549,6 +562,8 @@
 
         // Set up long press to show guts setting panel.
         mRecommendationViewHolder.getDismiss().setOnClickListener(v -> {
+            logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
+                    true);
             closeGuts();
             mKeyguardDismissUtil.executeWhenUnlocked(() -> {
                 mMediaDataManagerLazy.get().dismissSmartspaceRecommendation(
@@ -651,15 +666,9 @@
         }
 
         view.setOnClickListener(v -> {
-            // When media recommendation card is shown, there could be only one card.
-            SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
-                    760, // SMARTSPACE_CARD_CLICK
-                    mInstanceId,
-                    SysUiStatsLog
-                            .SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS,
-                    getSurfaceForSmartspaceLogging(),
-                    /* rank */ 0,
-                    /* cardinality */ 1);
+            // When media recommendation card is shown, it will always be the top card.
+            logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
+                    true);
 
             if (shouldSmartspaceRecItemOpenInForeground(action)) {
                 // Request to unlock the device if the activity needs to be opened in foreground.
@@ -717,7 +726,10 @@
         return SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DEFAULT_SURFACE;
     }
 
-    protected int getInstanceId() {
-        return mInstanceId;
+    private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard) {
+        mMediaCarouselController.logSmartspaceCardReported(eventId,
+                mInstanceId,
+                isRecommendationCard,
+                getSurfaceForSmartspaceLogging());
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index a80a410..3c28f6e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -171,16 +171,15 @@
             if (field != value) {
                 field = value
             }
-            // Pull down shade from lock screen (exclude the case when shade is brought out by
-            // tapping twice on lock screen)
-            if (value && isLockScreenShadeVisibleToUser()) {
+            // qs is expanded on LS shade and HS shade
+            if (value && (isLockScreenShadeVisibleToUser() || isHomeScreenShadeVisibleToUser())) {
                 mediaCarouselController.logSmartspaceImpression()
             }
             // Release shade and back to lock screen
             if (isLockScreenVisibleToUser()) {
                 mediaCarouselController.logSmartspaceImpression()
             }
-            mediaCarouselController.visibleToUser = isVisibleToUser()
+            mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
         }
 
     /**
@@ -257,7 +256,7 @@
                 if (newState == StatusBarState.SHADE_LOCKED && isLockScreenShadeVisibleToUser()) {
                     mediaCarouselController.logSmartspaceImpression()
                 }
-                mediaCarouselController.visibleToUser = isVisibleToUser()
+                mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
             }
 
             override fun onDozeAmountChanged(linear: Float, eased: Float) {
@@ -275,7 +274,7 @@
                     updateDesiredLocation()
                     qsExpanded = false
                 }
-                mediaCarouselController.visibleToUser = isVisibleToUser()
+                mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
             }
 
             override fun onExpandedChanged(isExpanded: Boolean) {
@@ -287,7 +286,7 @@
                 if (isLockScreenVisibleToUser()) {
                     mediaCarouselController.logSmartspaceImpression()
                 }
-                mediaCarouselController.visibleToUser = isVisibleToUser()
+                mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
             }
         })
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 56375ad..4e41d75 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -101,6 +101,7 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
+import android.view.inputmethod.InputMethodManager;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -1175,6 +1176,9 @@
         accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
         updateAccessibilityServicesState(mAccessibilityManager);
 
+        ButtonDispatcher imeSwitcherButton = mNavigationBarView.getImeSwitchButton();
+        imeSwitcherButton.setOnClickListener(this::onImeSwitcherClick);
+
         updateScreenPinningGestures();
     }
 
@@ -1274,6 +1278,11 @@
         mCommandQueue.toggleRecentApps();
     }
 
+    private void onImeSwitcherClick(View v) {
+        mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem(
+                true /* showAuxiliarySubtypes */, mDisplayId);
+    };
+
     private boolean onLongPressBackHome(View v) {
         return onLongPressNavigationButtons(v, R.id.back, R.id.home);
     }
@@ -1282,7 +1291,6 @@
         return onLongPressNavigationButtons(v, R.id.back, R.id.recent_apps);
     }
 
-
     /**
      * This handles long-press of both back and recents/home. Back is the common button with
      * combination of recents if it is visible or home if recents is invisible.
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 3544f60..9c3cb2e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -113,6 +113,7 @@
     private final Handler mHandler;
     private final DisplayManager mDisplayManager;
     private final NavigationBarOverlayController mNavBarOverlayController;
+    private final TaskbarDelegate mTaskbarDelegate;
     private int mNavMode;
     private boolean mIsTablet;
 
@@ -182,6 +183,7 @@
         mNavBarOverlayController = navBarOverlayController;
         mNavMode = mNavigationModeController.addListener(this);
         mNavigationModeController.addListener(this);
+        mTaskbarDelegate = new TaskbarDelegate(mOverviewProxyService);
     }
 
     @Override
@@ -190,17 +192,7 @@
         mIsTablet = isTablet(newConfig);
         boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
         // If we folded/unfolded while in 3 button, show navbar in folded state, hide in unfolded
-        if (isThreeButtonTaskbarFlagEnabled() &&
-                largeScreenChanged && mNavMode == NAV_BAR_MODE_3BUTTON) {
-            if (!mIsTablet) {
-                // Folded state, show 3 button nav bar
-                createNavigationBar(mContext.getDisplay(), null, null);
-            } else {
-                // Unfolded state, hide 3 button nav bars
-                for (int i = 0; i < mNavigationBars.size(); i++) {
-                    removeNavigationBar(mNavigationBars.keyAt(i));
-                }
-            }
+        if (largeScreenChanged && updateNavbarForTaskbar()) {
             return;
         }
 
@@ -217,18 +209,15 @@
 
     @Override
     public void onNavigationModeChanged(int mode) {
+        if (mNavMode == mode) {
+            return;
+        }
         final int oldMode = mNavMode;
         mNavMode = mode;
         mHandler.post(() -> {
             // create/destroy nav bar based on nav mode only in unfolded state
-            if (isThreeButtonTaskbarFlagEnabled() && oldMode != mNavMode && mIsTablet) {
-                if (oldMode == NAV_BAR_MODE_3BUTTON &&
-                        mNavigationBars.get(mContext.getDisplayId()) == null) {
-                    // We remove navbar for 3 button unfolded, add it back in
-                    createNavigationBar(mContext.getDisplay(), null, null);
-                } else if (mNavMode == NAV_BAR_MODE_3BUTTON) {
-                    removeNavigationBar(mContext.getDisplayId());
-                }
+            if (oldMode != mNavMode) {
+                updateNavbarForTaskbar();
             }
             for (int i = 0; i < mNavigationBars.size(); i++) {
                 NavigationBar navBar = mNavigationBars.valueAt(i);
@@ -243,6 +232,27 @@
         });
     }
 
+    /**
+     * @return {@code true} if navbar was added/removed, false otherwise
+     */
+    public boolean updateNavbarForTaskbar() {
+        if (!isThreeButtonTaskbarFlagEnabled()) {
+            return false;
+        }
+
+        if (mIsTablet && mNavMode == NAV_BAR_MODE_3BUTTON) {
+            // Remove navigation bar when taskbar is showing, currently only for 3 button mode
+            removeNavigationBar(mContext.getDisplayId());
+            mCommandQueue.addCallback(mTaskbarDelegate);
+        } else if (mNavigationBars.get(mContext.getDisplayId()) == null) {
+            // Add navigation bar after taskbar goes away
+            createNavigationBar(mContext.getDisplay(), null, null);
+            mCommandQueue.removeCallback(mTaskbarDelegate);
+        }
+
+        return true;
+    }
+
     @Override
     public void onDisplayRemoved(int displayId) {
         removeNavigationBar(displayId);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
index 7342f91..4d9175b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
@@ -158,7 +158,6 @@
     }
 
     public void onLikelyDefaultLayoutChange() {
-
         // Reevaluate new layout
         final String newValue = getDefaultLayout();
         if (!Objects.equals(mCurrentLayout, newValue)) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 0ed4d86..f82d265d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -166,6 +166,7 @@
     private NavigationBarInflaterView mNavigationInflaterView;
     private RecentsOnboarding mRecentsOnboarding;
     private NotificationPanelViewController mPanelView;
+    private RotationContextButton mRotationContextButton;
     private FloatingRotationButton mFloatingRotationButton;
     private RotationButtonController mRotationButtonController;
     private NavigationBarOverlayController mNavBarOverlayController;
@@ -233,14 +234,6 @@
         }
     }
 
-    private final OnClickListener mImeSwitcherClickListener = new OnClickListener() {
-        @Override
-        public void onClick(View view) {
-            mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem(
-                    true /* showAuxiliarySubtypes */, getContext().getDisplayId());
-        }
-    };
-
     private final AccessibilityDelegate mQuickStepAccessibilityDelegate =
             new AccessibilityDelegate() {
                 private AccessibilityAction mToggleOverviewAction;
@@ -311,33 +304,26 @@
         mIsVertical = false;
         mLongClickableAccessibilityButton = false;
         mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
-        boolean isGesturalMode = isGesturalMode(mNavBarMode);
 
         mSysUiFlagContainer = Dependency.get(SysUiState.class);
         // Set up the context group of buttons
         mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
         final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,
                 mLightContext, R.drawable.ic_ime_switcher_default);
-        final RotationContextButton rotateSuggestionButton = new RotationContextButton(
-                R.id.rotate_suggestion, mLightContext,
-                R.drawable.ic_sysbar_rotate_button_ccw_start_0);
         final ContextualButton accessibilityButton =
                 new ContextualButton(R.id.accessibility_button, mLightContext,
                         R.drawable.ic_sysbar_accessibility_button);
         mContextualButtonGroup.addButton(imeSwitcherButton);
-        if (!isGesturalMode) {
-            mContextualButtonGroup.addButton(rotateSuggestionButton);
-        }
         mContextualButtonGroup.addButton(accessibilityButton);
+        mRotationContextButton = new RotationContextButton(R.id.rotate_suggestion,
+                mLightContext, R.drawable.ic_sysbar_rotate_button_ccw_start_0);
+        mFloatingRotationButton = new FloatingRotationButton(context);
+        mRotationButtonController = new RotationButtonController(mLightContext,
+                mLightIconColor, mDarkIconColor);
+        updateRotationButton();
 
         mOverviewProxyService = Dependency.get(OverviewProxyService.class);
-        mFloatingRotationButton = new FloatingRotationButton(context);
         mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
-        mRotationButtonController = new RotationButtonController(mLightContext,
-                mLightIconColor, mDarkIconColor,
-                isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton,
-                mRotationButtonListener);
-
         mNavBarOverlayController = Dependency.get(NavigationBarOverlayController.class);
         if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) {
             mNavBarOverlayController.init(
@@ -357,7 +343,6 @@
         mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
         mButtonDispatchers.put(R.id.ime_switcher, imeSwitcherButton);
         mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton);
-        mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton);
         mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
         mDeadZone = new DeadZone(this);
 
@@ -555,6 +540,23 @@
         }
     }
 
+    /**
+     * Updates the rotation button based on the current navigation mode.
+     */
+    private void updateRotationButton() {
+        if (isGesturalMode(mNavBarMode)) {
+            mContextualButtonGroup.removeButton(R.id.rotate_suggestion);
+            mButtonDispatchers.remove(R.id.rotate_suggestion);
+            mRotationButtonController.setRotationButton(mFloatingRotationButton,
+                    mRotationButtonListener);
+        } else if (mContextualButtonGroup.getContextButton(R.id.rotate_suggestion) == null) {
+            mContextualButtonGroup.addButton(mRotationContextButton);
+            mButtonDispatchers.put(R.id.rotate_suggestion, mRotationContextButton);
+            mRotationButtonController.setRotationButton(mRotationContextButton,
+                    mRotationButtonListener);
+        }
+    }
+
     public KeyButtonDrawable getBackDrawable() {
         KeyButtonDrawable drawable = getDrawable(getBackDrawableRes());
         orientBackButton(drawable);
@@ -908,6 +910,7 @@
         mBarTransitions.onNavigationModeChanged(mNavBarMode);
         mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode);
         mRecentsOnboarding.onNavigationModeChanged(mNavBarMode);
+        updateRotationButton();
 
         if (isGesturalMode(mNavBarMode)) {
             mRegionSamplingHelper.start(mSamplingBounds);
@@ -932,7 +935,6 @@
         mNavigationInflaterView = findViewById(R.id.navigation_inflater);
         mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
 
-        getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
         updateOrientationViews();
         reloadNavIcons();
     }
@@ -1027,6 +1029,9 @@
 
     private void updateButtonLocation(ButtonDispatcher button, boolean inScreenSpace,
             boolean useNearestRegion) {
+        if (button == null) {
+            return;
+        }
         View view = button.getCurrentView();
         if (view == null || !button.isVisible()) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
index 4bcb019..ddf089b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
@@ -66,10 +66,10 @@
     private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
 
     private final Context mContext;
-    private final RotationButton mRotationButton;
     private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
     private final UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
     private final ViewRippler mViewRippler = new ViewRippler();
+    private RotationButton mRotationButton;
 
     private int mLastRotationSuggestion;
     private boolean mPendingRotationSuggestion;
@@ -125,20 +125,21 @@
     }
 
     RotationButtonController(Context context, @ColorInt int lightIconColor,
-            @ColorInt int darkIconColor, RotationButton rotationButton,
-            Consumer<Boolean> visibilityChangedCallback) {
+            @ColorInt int darkIconColor) {
         mContext = context;
         mLightIconColor = lightIconColor;
         mDarkIconColor = darkIconColor;
-        mRotationButton = rotationButton;
-        mRotationButton.setRotationButtonController(this);
 
         mIsNavigationBarShowing = true;
         mRotationLockController = Dependency.get(RotationLockController.class);
         mAccessibilityManagerWrapper = Dependency.get(AccessibilityManagerWrapper.class);
-
-        // Register the task stack listener
         mTaskStackListener = new TaskStackListenerImpl();
+    }
+
+    void setRotationButton(RotationButton rotationButton,
+            Consumer<Boolean> visibilityChangedCallback) {
+        mRotationButton = rotationButton;
+        mRotationButton.setRotationButtonController(this);
         mRotationButton.setOnClickListener(this::onRotateSuggestionClick);
         mRotationButton.setOnHoverListener(this::onRotateSuggestionHover);
         mRotationButton.setVisibilityChangedCallback(visibilityChangedCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
new file mode 100644
index 0000000..03147d8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar;
+
+import android.os.IBinder;
+
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.statusbar.CommandQueue;
+
+public class TaskbarDelegate implements CommandQueue.Callbacks {
+
+    private final OverviewProxyService mOverviewProxyService;
+
+    public TaskbarDelegate(OverviewProxyService overviewProxyService) {
+        mOverviewProxyService = overviewProxyService;
+    }
+
+    @Override
+    public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
+            boolean showImeSwitcher) {
+        mOverviewProxyService.notifyImeWindowStatus(displayId, token, vis, backDisposition,
+                showImeSwitcher);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
index 50b638b..2ace303 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
@@ -41,10 +41,23 @@
      * @param button the button added to the group
      */
     public void addButton(@NonNull ContextualButton button) {
+        // By default buttons in the context group are not visible until
+        // {@link #setButtonVisibility()) is called to show one of the buttons
+        button.setVisibility(View.INVISIBLE);
         button.attachToGroup(this);
         mButtonData.add(new ButtonData(button));
     }
 
+    /**
+     * Removes a contextual button from the group.
+     */
+    public void removeButton(@IdRes int buttonResId) {
+        int index = getContextButtonIndex(buttonResId);
+        if (index != INVALID_INDEX) {
+            mButtonData.remove(index);
+        }
+    }
+
     public ContextualButton getContextButton(@IdRes int buttonResId) {
         int index = getContextButtonIndex(buttonResId);
         if (index != INVALID_INDEX) {
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index a6c6103..29685a4 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -203,7 +203,7 @@
         if (DEBUG) {
             Log.i(TAG, "Removing any notification stored for tile Id: " + tile.getId());
         }
-        return tile
+        PeopleSpaceTile.Builder updatedTile = tile
                 .toBuilder()
                 // Reset notification content.
                 .setNotificationKey(null)
@@ -212,8 +212,15 @@
                 .setNotificationDataUri(null)
                 .setMessagesCount(0)
                 // Reset missed calls category.
-                .setNotificationCategory(null)
-                .build();
+                .setNotificationCategory(null);
+
+        // Only set last interaction to now if we are clearing a notification.
+        if (!TextUtils.isEmpty(tile.getNotificationKey())) {
+            long currentTimeMillis = System.currentTimeMillis();
+            if (DEBUG) Log.d(TAG, "Set last interaction on clear: " + currentTimeMillis);
+            updatedTile.setLastInteractionTimestamp(currentTimeMillis);
+        }
+        return updatedTile.build();
     }
 
     /**
@@ -227,10 +234,11 @@
             if (DEBUG) Log.d(TAG, "Tile key: " + key.toString() + ". Notification is null");
             return removeNotificationFields(tile);
         }
-        Notification notification = notificationEntry.getSbn().getNotification();
+        StatusBarNotification sbn = notificationEntry.getSbn();
+        Notification notification = sbn.getNotification();
 
         PeopleSpaceTile.Builder updatedTile = tile.toBuilder();
-        String uriFromNotification = getContactUri(notificationEntry.getSbn());
+        String uriFromNotification = getContactUri(sbn);
         if (appWidgetId.isPresent() && tile.getContactUri() == null && !TextUtils.isEmpty(
                 uriFromNotification)) {
             if (DEBUG) Log.d(TAG, "Add uri from notification to tile: " + uriFromNotification);
@@ -241,7 +249,6 @@
             // Update cached tile in-memory.
             updatedTile.setContactUri(contactUri);
         }
-
         boolean isMissedCall = isMissedCall(notification);
         List<Notification.MessagingStyle.Message> messages =
                 getMessagingStyleMessages(notification);
@@ -261,12 +268,13 @@
         Uri dataUri = message != null ? message.getDataUri() : null;
         if (DEBUG) {
             Log.d(TAG, "Tile key: " + key.toString() + ". Notification message has text: "
-                    + hasMessageText);
+                    + hasMessageText + " Has last interaction: " + sbn.getPostTime());
         }
         CharSequence sender = getSenderIfGroupConversation(notification, message);
 
         return updatedTile
-                .setNotificationKey(notificationEntry.getSbn().getKey())
+                .setLastInteractionTimestamp(sbn.getPostTime())
+                .setNotificationKey(sbn.getKey())
                 .setNotificationCategory(notification.category)
                 .setNotificationContent(content)
                 .setNotificationSender(sender)
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index 6980d72..06f8a60 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -835,7 +835,7 @@
             return null;
         } else if (durationSinceLastInteraction.toDays() < DAYS_IN_A_WEEK) {
             return context.getString(R.string.timestamp, formatter.formatMeasures(
-                    new Measure(durationSinceLastInteraction.toHours(),
+                    new Measure(durationSinceLastInteraction.toDays(),
                             MeasureUnit.DAY)));
         } else if (durationSinceLastInteraction.toDays() <= DAYS_IN_A_WEEK * 2) {
             return context.getString(durationSinceLastInteraction.toDays() == DAYS_IN_A_WEEK
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/AppWidgetOptionsHelper.java b/packages/SystemUI/src/com/android/systemui/people/widget/AppWidgetOptionsHelper.java
index 73c43eb..b49533e 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/AppWidgetOptionsHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/AppWidgetOptionsHelper.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.people.widget;
 
-import static com.android.systemui.people.PeopleSpaceUtils.DEBUG;
 import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_KEY;
 import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_STRING;
 import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
@@ -24,38 +23,13 @@
 import static com.android.systemui.people.PeopleSpaceUtils.SHORTCUT_ID;
 import static com.android.systemui.people.PeopleSpaceUtils.USER_ID;
 
-import android.app.people.PeopleSpaceTile;
 import android.appwidget.AppWidgetManager;
 import android.os.Bundle;
-import android.util.Log;
 
 /** Helper class encapsulating AppWidgetOptions for People Tile. */
 public class AppWidgetOptionsHelper {
     private static final String TAG = "AppWidgetOptionsHelper";
 
-    /** Key to store {@link PeopleSpaceTile} in AppWidgetOptions Bundle. */
-    public static final String OPTIONS_PEOPLE_TILE = "options_people_tile";
-
-    /** Sets {@link PeopleSpaceTile} in AppWidgetOptions. */
-    public static Bundle setPeopleTile(AppWidgetManager appWidgetManager, int appWidgetId,
-            PeopleSpaceTile tile) {
-        Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
-        if (tile == null) {
-            if (DEBUG) Log.w(TAG, "Requested to store null tile");
-            return options;
-        }
-        options.putParcelable(OPTIONS_PEOPLE_TILE, tile);
-        appWidgetManager.updateAppWidgetOptions(appWidgetId, options);
-        return options;
-    }
-
-    /** Gets {@link PeopleSpaceTile} from AppWidgetOptions. */
-    public static PeopleSpaceTile getPeopleTile(AppWidgetManager appWidgetManager,
-            int appWidgetId) {
-        Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
-        return options != null ? options.getParcelable(OPTIONS_PEOPLE_TILE) : null;
-    }
-
     /** Sets {@link PeopleTileKey} in AppWidgetOptions. */
     public static void setPeopleTileKey(AppWidgetManager appWidgetManager, int appWidgetId,
             PeopleTileKey key) {
@@ -66,16 +40,6 @@
         appWidgetManager.updateAppWidgetOptions(appWidgetId, options);
     }
 
-    /** Gets {@link PeopleTileKey} from AppWidgetOptions. */
-    public static PeopleTileKey getPeopleTileKey(AppWidgetManager appWidgetManager,
-            int appWidgetId) {
-        Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
-        if (options == null) {
-            return EMPTY_KEY;
-        }
-        return getPeopleTileKeyFromBundle(options);
-    }
-
     /** Gets {@link PeopleTileKey} from Bundle {@code options}. */
     public static PeopleTileKey getPeopleTileKeyFromBundle(Bundle options) {
         String pkg = options.getString(PACKAGE_NAME, EMPTY_STRING);
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index ea1724f..9e0dd72 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -136,6 +136,9 @@
     private Map<String, Set<String>> mNotificationKeyToWidgetIdsMatchedByUri = new HashMap<>();
     private boolean mRegisteredReceivers;
 
+    @GuardedBy("mLock")
+    public static Map<Integer, PeopleSpaceTile> mTiles = new HashMap<>();
+
     @Inject
     public PeopleSpaceWidgetManager(Context context, LauncherApps launcherApps,
             NotificationEntryManager notificationEntryManager,
@@ -252,8 +255,7 @@
             if (tile == null) {
                 Log.e(TAG, "Matching conversation not found for shortcut ID");
             }
-            Bundle options = mAppWidgetManager.getAppWidgetOptions(appWidgetId);
-            updateAppWidgetViews(appWidgetId, tile, options);
+            updateAppWidgetOptionsAndView(appWidgetId, tile);
             widgetIdToTile.put(appWidgetId, tile);
             if (tile != null) {
                 registerConversationListenerIfNeeded(appWidgetId,
@@ -289,7 +291,14 @@
 
     /** Updates tile in app widget options and the current view. */
     public void updateAppWidgetOptionsAndView(int appWidgetId, PeopleSpaceTile tile) {
-        Bundle options = AppWidgetOptionsHelper.setPeopleTile(mAppWidgetManager, appWidgetId, tile);
+        if (tile == null) {
+            if (DEBUG) Log.w(TAG, "Requested to store null tile");
+            return;
+        }
+        synchronized (mTiles) {
+            mTiles.put(appWidgetId, tile);
+        }
+        Bundle options = mAppWidgetManager.getAppWidgetOptions(appWidgetId);
         updateAppWidgetViews(appWidgetId, tile, options);
     }
 
@@ -299,8 +308,11 @@
      */
     @Nullable
     public PeopleSpaceTile getTileForExistingWidget(int appWidgetId) {
-        // First, check if tile is cached in AppWidgetOptions.
-        PeopleSpaceTile tile = AppWidgetOptionsHelper.getPeopleTile(mAppWidgetManager, appWidgetId);
+        // First, check if tile is cached in memory.
+        PeopleSpaceTile tile;
+        synchronized (mTiles) {
+            tile = mTiles.get(appWidgetId);
+        }
         if (tile != null) {
             if (DEBUG) Log.d(TAG, "People Tile is cached for widget: " + appWidgetId);
             return tile;
@@ -781,7 +793,6 @@
         } catch (Exception e) {
             Log.w(TAG, "Exception caching shortcut:" + e);
         }
-
         updateAppWidgetOptionsAndView(appWidgetId, tile);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 3b43676..dd1a4af 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -297,9 +297,7 @@
                 // start margin of next page).
                 qsPanelController.setPageMargin(2 * mSideMargins);
             } else if (view == mHeader) {
-                // The header contains the QQS panel which needs to have special padding, to
-                // visually align them.
-                quickStatusBarHeaderController.setContentMargins(mContentPadding, mContentPadding);
+                // No content padding for the header.
             } else {
                 view.setPaddingRelative(
                         mContentPadding,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index c69956f..73a6b34 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -77,8 +77,6 @@
 
     private int mStatusBarPaddingTop = 0;
     private int mRoundedCornerPadding = 0;
-    private int mContentMarginStart;
-    private int mContentMarginEnd;
     private int mWaterfallTopInset;
     private int mCutOutPaddingLeft;
     private int mCutOutPaddingRight;
@@ -242,35 +240,26 @@
                 .addFloat(mDateView, "alpha", 0, 1)
                 .addFloat(mSecurityHeaderView, "alpha", 0, 1)
                 .addFloat(mQSCarriers, "alpha", 0, 1);
-
-        if (noCallingIcon != null || callStrengthIcon != null) {
-            if (noCallingIcon != null) {
-                builder.addFloat(noCallingIcon, "alpha", 1, 0);
+        builder.setListener(new TouchAnimator.ListenerAdapter() {
+            @Override
+            public void onAnimationAtEnd() {
+                mIconContainer.addIgnoredSlot(mMobileSlotName);
+                mIconContainer.addIgnoredSlot(mCallStrengthSlotName);
             }
-            if (callStrengthIcon != null) {
-                builder.addFloat(callStrengthIcon, "alpha", 1, 0);
+
+            @Override
+            public void onAnimationStarted() {
+                mIconContainer.addIgnoredSlot(mMobileSlotName);
+                mIconContainer.addIgnoredSlot(mCallStrengthSlotName);
             }
-            builder.setListener(new TouchAnimator.ListenerAdapter() {
-                @Override
-                public void onAnimationAtEnd() {
-                    mIconContainer.addIgnoredSlot(mMobileSlotName);
-                    mIconContainer.addIgnoredSlot(mCallStrengthSlotName);
-                }
 
-                @Override
-                public void onAnimationStarted() {
-                    mIconContainer.removeIgnoredSlot(mMobileSlotName);
-                    mIconContainer.removeIgnoredSlot(mCallStrengthSlotName);
-                }
-
-                @Override
-                public void onAnimationAtStart() {
-                    super.onAnimationAtStart();
-                    mIconContainer.removeIgnoredSlot(mMobileSlotName);
-                    mIconContainer.removeIgnoredSlot(mCallStrengthSlotName);
-                }
-            });
-        }
+            @Override
+            public void onAnimationAtStart() {
+                super.onAnimationAtStart();
+                mIconContainer.removeIgnoredSlot(mMobileSlotName);
+                mIconContainer.removeIgnoredSlot(mCallStrengthSlotName);
+            }
+        });
         mAlphaAnimator = builder.build();
     }
 
@@ -370,6 +359,8 @@
     }
 
     private void updateHeadersPadding() {
+        setContentMargins(mDatePrivacyView, 0, 0);
+        setContentMargins(mClockIconsView, 0, 0);
         int paddingLeft = 0;
         int paddingRight = 0;
 
@@ -383,14 +374,12 @@
         if (mCutOutPaddingLeft > 0) {
             // if there's a cutout, let's use at least the rounded corner inset
             int cutoutPadding = Math.max(mCutOutPaddingLeft, mRoundedCornerPadding);
-            int contentMarginLeft = isLayoutRtl() ? mContentMarginEnd : mContentMarginStart;
-            paddingLeft = Math.max(cutoutPadding - contentMarginLeft - leftMargin, 0);
+            paddingLeft = Math.max(cutoutPadding - leftMargin, 0);
         }
         if (mCutOutPaddingRight > 0) {
             // if there's a cutout, let's use at least the rounded corner inset
             int cutoutPadding = Math.max(mCutOutPaddingRight, mRoundedCornerPadding);
-            int contentMarginRight = isLayoutRtl() ? mContentMarginStart : mContentMarginEnd;
-            paddingRight = Math.max(cutoutPadding - contentMarginRight - rightMargin, 0);
+            paddingRight = Math.max(cutoutPadding - rightMargin, 0);
         }
 
         mDatePrivacyView.setPadding(paddingLeft,
@@ -411,19 +400,6 @@
         mHeaderQsPanel.setCallback(qsPanelCallback);
     }
 
-    /** */
-    public void setContentMargins(int marginStart, int marginEnd,
-            QuickQSPanelController quickQSPanelController) {
-        mContentMarginStart = marginStart;
-        mContentMarginEnd = marginEnd;
-        // The clock and QQS are not direct children, but the container should be just a wrapper to
-        // be able to move them together. So we set the margins to the actual views.
-        quickQSPanelController.setContentMargins(0, 0);
-        setContentMargins(mDatePrivacyView, marginStart, marginEnd);
-        setContentMargins(mClockIconsView, marginStart, marginEnd);
-        updateHeadersPadding();
-    }
-
     private void setContentMargins(View view, int marginStart, int marginEnd) {
         MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
         lp.setMarginStart(marginStart);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index 617b067..1a828e4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -183,6 +183,7 @@
         mView.onAttach(mIconManager, mQSExpansionPathInterpolator);
 
         mDemoModeController.addCallback(mDemoModeReceiver);
+        mHeaderQsPanelController.setContentMargins(0, 0);
     }
 
     @Override
@@ -259,11 +260,6 @@
         return mMicCameraIndicatorsEnabled || mLocationIndicatorsEnabled;
     }
 
-    public void setContentMargins(int contentPaddingStart, int contentPaddingEnd) {
-        mView.setContentMargins(contentPaddingStart, contentPaddingEnd, mHeaderQsPanelController);
-    }
-
-
     private static class ClockDemoModeReceiver implements DemoMode {
         private Clock mClockView;
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index 5afe1c8..c49e054 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -43,6 +43,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
+import com.android.systemui.util.CarrierConfigTracker;
 
 import java.util.function.Consumer;
 
@@ -71,6 +72,7 @@
     private int[] mLastSignalLevel = new int[SIM_SLOTS];
     private String[] mLastSignalLevelDescription = new String[SIM_SLOTS];
     private final boolean mProviderModel;
+    private final CarrierConfigTracker mCarrierConfigTracker;
 
     private final NetworkController.SignalCallback mSignalCallback =
             new NetworkController.SignalCallback() {
@@ -112,6 +114,10 @@
                         Log.e(TAG, "Invalid SIM slot index for subscription: " + subId);
                         return;
                     }
+
+                    boolean displayCallStrengthIcon =
+                            mCarrierConfigTracker.getCallStrengthConfig(subId);
+
                     if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) {
                         if (statusIcon.visible) {
                             mInfos[slotIndex] = new CellSignalState(true,
@@ -119,9 +125,14 @@
                         } else {
                             // Whenever the no Calling & SMS state is cleared, switched to the last
                             // known call strength icon.
-                            mInfos[slotIndex] = new CellSignalState(
-                                    true, mLastSignalLevel[slotIndex],
-                                    mLastSignalLevelDescription[slotIndex], "", false);
+                            if (displayCallStrengthIcon) {
+                                mInfos[slotIndex] = new CellSignalState(
+                                        true, mLastSignalLevel[slotIndex],
+                                        mLastSignalLevelDescription[slotIndex], "", false);
+                            } else {
+                                mInfos[slotIndex] = new CellSignalState(
+                                        true, R.drawable.ic_qs_sim_card, "", "", false);
+                            }
                         }
                         mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
                     } else {
@@ -131,8 +142,13 @@
                         // shown.
                         if (mInfos[slotIndex].mobileSignalIconId
                                 != R.drawable.ic_qs_no_calling_sms) {
-                            mInfos[slotIndex] = new CellSignalState(true, statusIcon.icon,
-                                    statusIcon.contentDescription, "", false);
+                            if (displayCallStrengthIcon) {
+                                mInfos[slotIndex] = new CellSignalState(true, statusIcon.icon,
+                                        statusIcon.contentDescription, "", false);
+                            } else {
+                                mInfos[slotIndex] = new CellSignalState(
+                                        true, R.drawable.ic_qs_sim_card, "", "", false);
+                            }
                             mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
                         }
                     }
@@ -165,7 +181,8 @@
     private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter,
             @Background Handler bgHandler, @Main Looper mainLooper,
             NetworkController networkController,
-            CarrierTextManager.Builder carrierTextManagerBuilder, Context context) {
+            CarrierTextManager.Builder carrierTextManagerBuilder, Context context,
+            CarrierConfigTracker carrierConfigTracker) {
         if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
             mProviderModel = true;
         } else {
@@ -178,7 +195,7 @@
                 .setShowAirplaneMode(false)
                 .setShowMissingSim(false)
                 .build();
-
+        mCarrierConfigTracker = carrierConfigTracker;
         View.OnClickListener onClickListener = v -> {
             if (!v.isVisibleToUser()) {
                 return;
@@ -228,6 +245,17 @@
         return SubscriptionManager.getSlotIndex(subscriptionId);
     }
 
+    private boolean isSingleCarrier() {
+        int carrierCount = 0;
+        for (int i = 0; i < SIM_SLOTS; i++) {
+
+            if (mInfos[i].visible) {
+                carrierCount++;
+            }
+        }
+        return carrierCount == 1;
+    }
+
     public void setListening(boolean listening) {
         if (listening == mListening) {
             return;
@@ -257,6 +285,15 @@
             return;
         }
 
+        if (isSingleCarrier()) {
+            for (int i = 0; i < SIM_SLOTS; i++) {
+                if (mInfos[i].visible
+                        && mInfos[i].mobileSignalIconId == R.drawable.ic_qs_sim_card) {
+                    mInfos[i] = new CellSignalState(true, R.drawable.ic_blank, "", "", false);
+                }
+            }
+        }
+
         for (int i = 0; i < SIM_SLOTS; i++) {
             mCarrierGroups[i].updateState(mInfos[i]);
         }
@@ -363,17 +400,20 @@
         private final NetworkController mNetworkController;
         private final CarrierTextManager.Builder mCarrierTextControllerBuilder;
         private final Context mContext;
+        private final CarrierConfigTracker mCarrierConfigTracker;
 
         @Inject
         public Builder(ActivityStarter activityStarter, @Background Handler handler,
                 @Main Looper looper, NetworkController networkController,
-                CarrierTextManager.Builder carrierTextControllerBuilder, Context context) {
+                CarrierTextManager.Builder carrierTextControllerBuilder, Context context,
+                CarrierConfigTracker carrierConfigTracker) {
             mActivityStarter = activityStarter;
             mHandler = handler;
             mLooper = looper;
             mNetworkController = networkController;
             mCarrierTextControllerBuilder = carrierTextControllerBuilder;
             mContext = context;
+            mCarrierConfigTracker = carrierConfigTracker;
         }
 
         public Builder setQSCarrierGroup(QSCarrierGroup view) {
@@ -383,7 +423,8 @@
 
         public QSCarrierGroupController build() {
             return new QSCarrierGroupController(mView, mActivityStarter, mHandler, mLooper,
-                    mNetworkController, mCarrierTextControllerBuilder, mContext);
+                    mNetworkController, mCarrierTextControllerBuilder, mContext,
+                    mCarrierConfigTracker);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 3ad95d2..2d777a5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -21,7 +21,6 @@
 import android.content.res.ColorStateList
 import android.content.res.Configuration
 import android.content.res.Resources.ID_NULL
-import android.graphics.Color
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.RippleDrawable
 import android.service.quicksettings.Tile
@@ -83,7 +82,7 @@
 
     private lateinit var ripple: RippleDrawable
     private lateinit var colorBackgroundDrawable: Drawable
-    private var paintColor = Color.WHITE
+    private var paintColor: Int = 0
     private var paintAnimator: ValueAnimator? = null
     private var labelAnimator: ValueAnimator? = null
     private var secondaryLabelAnimator: ValueAnimator? = null
@@ -105,6 +104,8 @@
         clipToPadding = false
         isFocusable = true
         background = createTileBackground()
+        paintColor = getCircleColor(QSTile.State.DEFAULT_STATE)
+        colorBackgroundDrawable.setTint(paintColor)
 
         val padding = resources.getDimensionPixelSize(R.dimen.qs_tile_padding)
         val startPadding = resources.getDimensionPixelSize(R.dimen.qs_tile_start_padding)
@@ -165,8 +166,8 @@
             labelContainer.ignoreLastView = true
             secondaryLabel.alpha = 0f
         }
-        label.setTextColor(getLabelColor(0)) // Default state
-        secondaryLabel.setTextColor(getSecondaryLabelColor(0))
+        label.setTextColor(getLabelColor(QSTile.State.DEFAULT_STATE))
+        secondaryLabel.setTextColor(getSecondaryLabelColor(QSTile.State.DEFAULT_STATE))
         addView(labelContainer)
     }
 
@@ -314,7 +315,7 @@
     // HANDLE STATE CHANGES RELATED METHODS
 
     protected open fun handleStateChanged(state: QSTile.State) {
-        val allowAnimations = animationsEnabled() && paintColor != Color.WHITE
+        val allowAnimations = animationsEnabled()
         showRippleEffect = state.showRippleEffect
         isClickable = state.state != Tile.STATE_UNAVAILABLE
         isLongClickable = state.handlesLongClick
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 1ec785d47..63adbd0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -933,6 +933,21 @@
         }
     }
 
+    public void notifyImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
+            boolean showImeSwitcher) {
+        try {
+            if (mOverviewProxy != null) {
+                mOverviewProxy.onImeWindowStatusChanged(displayId, token, vis, backDisposition,
+                        showImeSwitcher);
+            } else {
+                Log.e(TAG_OPS, "Failed to get overview proxy for setting IME status.");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG_OPS, "Failed to call notifyImeWindowStatus()", e);
+        }
+
+    }
+
     private void updateEnabledState() {
         final int currentUser = ActivityManagerWrapper.getInstance().getCurrentUserId();
         mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent,
@@ -983,5 +998,7 @@
         default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
         default void onAssistantGestureCompletion(float velocity) {}
         default void startAssistant(Bundle bundle) {}
+        default void onImeWindowStatusChanged(int displayId, IBinder token, int vis,
+                int backDisposition, boolean showImeSwitcher) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
index df9fc63..17e94c4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
@@ -88,7 +88,7 @@
                     ? ACTION_TYPE_EDIT
                     : ACTION_TYPE_SHARE;
             mScreenshotSmartActions.notifyScreenshotAction(
-                    context, intent.getStringExtra(EXTRA_ID), actionType, false);
+                    context, intent.getStringExtra(EXTRA_ID), actionType, false, null);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java
index 35839f3..8d44205 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java
@@ -62,7 +62,7 @@
         });
         if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
             mScreenshotSmartActions.notifyScreenshotAction(
-                    context, intent.getStringExtra(EXTRA_ID), ACTION_TYPE_DELETE, false);
+                    context, intent.getStringExtra(EXTRA_ID), ACTION_TYPE_DELETE, false, null);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java
index 6ebab8a..3eafbfb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java
@@ -21,6 +21,7 @@
 
 import android.app.Notification;
 import android.content.ComponentName;
+import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.UserHandle;
@@ -107,7 +108,8 @@
      * @param action        type of notification action invoked.
      * @param isSmartAction whether action invoked was a smart action.
      */
-    public void notifyAction(String screenshotId, String action, boolean isSmartAction) {
+    public void notifyAction(String screenshotId, String action, boolean isSmartAction,
+            Intent intent) {
         if (DEBUG_ACTIONS) {
             Log.d(TAG, "SmartActions: notifyAction: return without notify");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
index 99238cd..0527818 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
@@ -26,6 +26,7 @@
 import android.app.Notification;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Handler;
@@ -165,7 +166,7 @@
     }
 
     void notifyScreenshotAction(Context context, String screenshotId, String action,
-            boolean isSmartAction) {
+            boolean isSmartAction, Intent intent) {
         try {
             ScreenshotNotificationSmartActionsProvider provider =
                     SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
@@ -174,7 +175,7 @@
                 Log.d(TAG, String.format("%s notifyAction: %s id=%s, isSmartAction=%b",
                         provider.getClass(), action, screenshotId, isSmartAction));
             }
-            provider.notifyAction(screenshotId, action, isSmartAction);
+            provider.notifyAction(screenshotId, action, isSmartAction, intent);
         } catch (Throwable e) {
             Log.e(TAG, "Error in notifyScreenshotAction: ", e);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
index 3ad922b..f703058 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
@@ -60,6 +60,7 @@
         }
 
         mScreenshotSmartActions.notifyScreenshotAction(
-                context, intent.getStringExtra(EXTRA_ID), actionType, true);
+                context, intent.getStringExtra(EXTRA_ID), actionType, true,
+                pendingIntent.getIntent());
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index a072de8..5daee6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -492,8 +492,12 @@
         return UserHandle.USER_NULL;
     }
 
-    @VisibleForTesting
-    protected void setVisible(boolean visible) {
+    /**
+     * Sets the visibility of keyguard bottom area, and if the indications are updatable.
+     *
+     * @param visible true to make the area visible and update the indication, false otherwise.
+     */
+    public void setVisible(boolean visible) {
         mVisible = visible;
         mIndicationArea.setVisibility(visible ? VISIBLE : GONE);
         if (visible) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 8f462fe..e49ca13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -31,6 +31,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -176,7 +177,13 @@
             viewState.height = getIntrinsicHeight();
             viewState.zTranslation = ambientState.getBaseZHeight();
             viewState.clipTopAmount = 0;
-            viewState.alpha = 1f - ambientState.getHideAmount();
+
+            if (ambientState.isExpansionChanging() && !ambientState.isOnKeyguard()) {
+                viewState.alpha = Interpolators.getNotificationScrimAlpha(
+                        ambientState.getExpansionFraction());
+            } else {
+                viewState.alpha = 1f - ambientState.getHideAmount();
+            }
             viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0;
             viewState.hideSensitive = false;
             viewState.xTranslation = getTranslationX();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index fad7480..eb7854e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.dagger;
 
+import android.app.IActivityManager;
 import android.app.NotificationManager;
 import android.content.Context;
 import android.os.Handler;
@@ -68,6 +69,7 @@
 import com.android.wm.shell.bubbles.Bubbles;
 
 import java.util.Optional;
+import java.util.concurrent.Executor;
 
 import dagger.Binds;
 import dagger.Lazy;
@@ -239,10 +241,13 @@
             CommonNotifCollection notifCollection,
             FeatureFlags featureFlags,
             SystemClock systemClock,
-            ActivityStarter activityStarter) {
+            ActivityStarter activityStarter,
+            @Main Executor mainExecutor,
+            IActivityManager iActivityManager) {
         OngoingCallController ongoingCallController =
                 new OngoingCallController(
-                        notifCollection, featureFlags, systemClock, activityStarter);
+                        notifCollection, featureFlags, systemClock, activityStarter, mainExecutor,
+                        iActivityManager);
         ongoingCallController.init();
         return ongoingCallController;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index eb3a17f..2481ed4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -17,20 +17,25 @@
 package com.android.systemui.statusbar.events
 
 import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.ObjectAnimator
 import android.annotation.UiThread
 import android.util.Log
 import android.view.Gravity
 import android.view.View
 import android.widget.FrameLayout
 
+import com.android.internal.annotations.GuardedBy
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.phone.StatusBarLocationPublisher
 import com.android.systemui.statusbar.phone.StatusBarMarginUpdatedListener
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE
+import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE
+import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
+import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
 
 import java.lang.IllegalStateException
 import java.util.concurrent.Executor
@@ -54,31 +59,33 @@
 @SysUISingleton
 class PrivacyDotViewController @Inject constructor(
     @Main private val mainExecutor: Executor,
+    private val stateController: StatusBarStateController,
     private val locationPublisher: StatusBarLocationPublisher,
     private val animationScheduler: SystemStatusAnimationScheduler
 ) {
-    private var rotation = 0
-    private var leftSize = 0
-    private var rightSize = 0
-
     private var sbHeightPortrait = 0
     private var sbHeightLandscape = 0
 
-    private var hasMultipleHeights = false
-    private var needsHeightUpdate = false
-    private var needsRotationUpdate = false
-    private var needsMarginUpdate = false
-
     private lateinit var tl: View
     private lateinit var tr: View
     private lateinit var bl: View
     private lateinit var br: View
 
-    // Track which corner is active (based on orientation + RTL)
-    private var designatedCorner: View? = null
+    // Only can be modified on @UiThread
+    private var currentViewState: ViewState = ViewState()
+
+    @GuardedBy("lock")
+    private var nextViewState: ViewState = currentViewState.copy()
+        set(value) {
+            field = value
+            scheduleUpdate()
+        }
+    private val lock = Object()
+    private var cancelRunnable: Runnable? = null
 
     // Privacy dots are created in ScreenDecoration's UiThread, which is not the main thread
-    private var uiExecutor: Executor? = null
+    private var uiExecutor: DelayableExecutor? = null
+    private var e: DelayableExecutor? = null
 
     private val views: Sequence<View>
         get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl)
@@ -89,29 +96,78 @@
                 setStatusBarMargins(marginLeft, marginRight)
             }
         })
+
+        stateController.addCallback(object : StatusBarStateController.StateListener {
+            override fun onExpandedChanged(isExpanded: Boolean) {
+                setStatusBarExpanded(isExpanded)
+            }
+        })
     }
 
-    fun setUiExecutor(e: Executor) {
+    fun setUiExecutor(e: DelayableExecutor) {
         uiExecutor = e
     }
 
     @UiThread
-    fun updateRotation(rot: Int) {
-        dlog("updateRotation: ")
-        if (rot == rotation) {
-            return
+    fun setNewRotation(rot: Int) {
+        dlog("updateRotation: $rot")
+
+        synchronized(lock) {
+            if (rot == nextViewState.rotation) {
+                return
+            }
         }
 
-        // A rotation has started, hide the views to avoid flicker
+        // If we rotated, hide all dotes until the next state resolves
         setCornerVisibilities(View.INVISIBLE)
 
-        if (hasMultipleHeights && (rotation % 2) != (rot % 2)) {
-            // we've changed from vertical to horizontal; update status bar height
-            needsHeightUpdate = true
-        }
+        val newCorner = selectDesignatedCorner(rot)
+        val index = newCorner.cornerIndex()
 
-        rotation = rot
-        needsRotationUpdate = true
+        val h = when (rot) {
+            ROTATION_NONE, ROTATION_UPSIDE_DOWN -> sbHeightPortrait
+            ROTATION_LANDSCAPE, ROTATION_SEASCAPE -> sbHeightLandscape
+            else -> 0
+        }
+        synchronized(lock) {
+            nextViewState = nextViewState.copy(
+                    rotation = rot,
+                    height = h,
+                    designatedCorner = newCorner,
+                    cornerIndex = index)
+        }
+    }
+
+    @UiThread
+    private fun hideDotView(dot: View, animate: Boolean) {
+        dot.clearAnimation()
+        if (animate) {
+            dot.animate()
+                    .setDuration(DURATION)
+                    .setInterpolator(Interpolators.ALPHA_OUT)
+                    .alpha(0f)
+                    .withEndAction { dot.visibility = View.INVISIBLE }
+                    .start()
+        } else {
+            dot.visibility = View.INVISIBLE
+        }
+    }
+
+    @UiThread
+    private fun showDotView(dot: View, animate: Boolean) {
+        dot.clearAnimation()
+        if (animate) {
+            dot.visibility = View.VISIBLE
+            dot.alpha = 0f
+            dot.animate()
+                    .alpha(1f)
+                    .setDuration(DURATION)
+                    .setInterpolator(Interpolators.ALPHA_IN)
+                    .start()
+        } else {
+            dot.visibility = View.VISIBLE
+            dot.alpha = 1f
+        }
     }
 
     @UiThread
@@ -127,14 +183,14 @@
 
     // Update the gravity and margins of the privacy views
     @UiThread
-    private fun updateRotations() {
+    private fun updateRotations(rotation: Int) {
         // To keep a view in the corner, its gravity is always the description of its current corner
         // Therefore, just figure out which view is in which corner. This turns out to be something
         // like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and
         // rotating the device counter-clockwise increments rotation by 1
 
         views.forEach { corner ->
-            val rotatedCorner = rotatedCorner(cornerForView(corner))
+            val rotatedCorner = rotatedCorner(cornerForView(corner), rotation)
             (corner.layoutParams as FrameLayout.LayoutParams).apply {
                 gravity = rotatedCorner.toGravity()
             }
@@ -147,26 +203,24 @@
     }
 
     @UiThread
-    private fun updateCornerSizes() {
+    private fun updateCornerSizes(l: Int, r: Int, rotation: Int) {
         views.forEach { corner ->
-            val rotatedCorner = rotatedCorner(cornerForView(corner))
-            val w = widthForCorner(rotatedCorner)
-            Log.d(TAG, "updateCornerSizes: setting (${cornerForView(corner)}) to $w")
+            val rotatedCorner = rotatedCorner(cornerForView(corner), rotation)
+            val w = widthForCorner(rotatedCorner, l, r)
             (corner.layoutParams as FrameLayout.LayoutParams).width = w
-            corner.requestLayout()
         }
     }
 
     // Designated view will be the one at statusbar's view.END
     @UiThread
-    private fun selectDesignatedCorner(): View? {
+    private fun selectDesignatedCorner(r: Int): View? {
         if (!this::tl.isInitialized) {
             return null
         }
 
         val isRtl = tl.isLayoutRtl
 
-        return when (rotation) {
+        return when (r) {
             0 -> if (isRtl) tl else tr
             1 -> if (isRtl) tr else br
             2 -> if (isRtl) br else bl
@@ -177,23 +231,17 @@
 
     // Track the current designated corner and maybe animate to a new rotation
     @UiThread
-    private fun updateDesignatedCorner(newCorner: View) {
-        designatedCorner = newCorner
-
-        if (animationScheduler.hasPersistentDot) {
-            fadeInDot()
-        }
-    }
-
-    @UiThread
-    private fun fadeInDot() {
-        designatedCorner?.let { dot ->
-            dot.visibility = View.VISIBLE
-            dot.alpha = 0f
-            dot.animate()
-                .alpha(1.0f)
-                .setDuration(300)
-                .start()
+    private fun updateDesignatedCorner(newCorner: View?, shouldShowDot: Boolean) {
+        if (shouldShowDot) {
+            newCorner?.apply {
+                clearAnimation()
+                visibility = View.VISIBLE
+                alpha = 0f
+                animate()
+                    .alpha(1.0f)
+                    .setDuration(300)
+                    .start()
+            }
         }
     }
 
@@ -214,7 +262,7 @@
         }
     }
 
-    private fun rotatedCorner(corner: Int): Int {
+    private fun rotatedCorner(corner: Int, rotation: Int): Int {
         var modded = corner - rotation
         if (modded < 0) {
             modded += 4
@@ -223,10 +271,10 @@
         return modded
     }
 
-    private fun widthForCorner(corner: Int): Int {
+    private fun widthForCorner(corner: Int, left: Int, right: Int): Int {
         return when (corner) {
-            TOP_LEFT, BOTTOM_LEFT -> leftSize
-            TOP_RIGHT, BOTTOM_RIGHT -> rightSize
+            TOP_LEFT, BOTTOM_LEFT -> left
+            TOP_RIGHT, BOTTOM_RIGHT -> right
             else -> throw IllegalArgumentException("Unknown corner")
         }
     }
@@ -244,10 +292,16 @@
         bl = bottomLeft
         br = bottomRight
 
-        designatedCorner = selectDesignatedCorner()
+        val dc = selectDesignatedCorner(0)
+        val index = dc.cornerIndex()
+
         mainExecutor.execute {
             animationScheduler.addCallback(systemStatusAnimationCallback)
         }
+
+        synchronized(lock) {
+            nextViewState = nextViewState.copy(designatedCorner = dc, cornerIndex = index)
+        }
     }
 
     /**
@@ -257,8 +311,6 @@
     fun setStatusBarHeights(portrait: Int, landscape: Int) {
         sbHeightPortrait = portrait
         sbHeightLandscape = landscape
-
-        hasMultipleHeights = portrait != landscape
     }
 
     /**
@@ -268,85 +320,109 @@
      * @param right space between the status bar contents and the right side of the screen
      */
     private fun setStatusBarMargins(left: Int, right: Int) {
-        leftSize = left
-        rightSize = right
-
-        needsMarginUpdate = true
-
-        // Margins come after PhoneStatusBarView does a layout pass, and so will always happen
-        // after rotation changes. It is safe to execute the updates from here
-        uiExecutor?.execute {
-            doUpdates(needsRotationUpdate, needsHeightUpdate, needsMarginUpdate)
+        dlog("setStatusBarMargins l=$left r=$right")
+        synchronized(lock) {
+            nextViewState = nextViewState.copy(marginLeft = left, marginRight = right)
         }
     }
 
-    private fun doUpdates(rot: Boolean, height: Boolean, width: Boolean) {
-        dlog("doUpdates: ")
-        var newDesignatedCorner: View? = null
+    /**
+     *  We won't show the dot when quick settings is showing
+     */
+    private fun setStatusBarExpanded(expanded: Boolean) {
+        synchronized(lock) {
+            nextViewState = nextViewState.copy(hideDotForQuickSettings = expanded)
+        }
+    }
 
-        if (rot) {
-            needsRotationUpdate = false
-            updateRotations()
-            newDesignatedCorner = selectDesignatedCorner()
+    private fun scheduleUpdate() {
+        dlog("scheduleUpdate: ")
+
+        cancelRunnable?.run()
+        cancelRunnable = uiExecutor?.executeDelayed({
+            processNextViewState()
+        }, 100)
+    }
+
+    @UiThread
+    private fun processNextViewState() {
+        dlog("processNextViewState: ")
+
+        val newState: ViewState
+        synchronized(lock) {
+            newState = nextViewState.copy()
         }
 
-        if (height) {
-            needsHeightUpdate = false
-            updateHeights(rotation)
+        resolveState(newState)
+    }
+
+    @UiThread
+    private fun resolveState(state: ViewState) {
+        dlog("resolveState $state")
+        if (state == currentViewState) {
+            dlog("resolveState: skipping")
+            return
         }
 
-        if (width) {
-            needsMarginUpdate = false
-            updateCornerSizes()
+        if (state.rotation != currentViewState.rotation) {
+            // A rotation has started, hide the views to avoid flicker
+            updateRotations(state.rotation)
         }
 
-        if (newDesignatedCorner != null && newDesignatedCorner != designatedCorner) {
-            updateDesignatedCorner(newDesignatedCorner)
+        if (state.height != currentViewState.height) {
+            updateHeights(state.rotation)
         }
+
+        if (state.marginLeft != currentViewState.marginLeft ||
+                state.marginRight != currentViewState.marginRight) {
+            updateCornerSizes(state.marginLeft, state.marginRight, state.rotation)
+        }
+
+        if (state.designatedCorner != currentViewState.designatedCorner) {
+            updateDesignatedCorner(state.designatedCorner, state.shouldShowDot())
+        }
+
+        if (state.needsLayout(currentViewState)) {
+            views.forEach { it.requestLayout() }
+        }
+
+        val shouldShow = state.shouldShowDot()
+        if (shouldShow != currentViewState.shouldShowDot()) {
+            if (shouldShow && state.designatedCorner != null) {
+                showDotView(state.designatedCorner, true)
+            } else if (!shouldShow && state.designatedCorner != null) {
+                hideDotView(state.designatedCorner, true)
+            }
+        }
+
+        currentViewState = state
     }
 
     private val systemStatusAnimationCallback: SystemStatusAnimationCallback =
             object : SystemStatusAnimationCallback {
-        override fun onSystemStatusAnimationTransitionToPersistentDot(
-            showAnimation: Boolean
-        ): Animator? {
-            if (designatedCorner == null) {
-                return null
-            } else if (!showAnimation) {
-                uiExecutor?.execute { fadeInDot() }
-                return null
+        override fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? {
+            synchronized(lock) {
+                nextViewState = nextViewState.copy(systemPrivacyEventIsActive = true)
             }
 
-            val alpha = ObjectAnimator.ofFloat(
-                    designatedCorner, "alpha", 0f, 1f)
-            alpha.duration = DURATION
-            alpha.interpolator = Interpolators.ALPHA_OUT
-            alpha.addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationStart(animator: Animator) {
-                    uiExecutor?.execute { designatedCorner?.visibility = View.VISIBLE }
-                }
-            })
-            return alpha
+            return null
         }
 
         override fun onHidePersistentDot(): Animator? {
-            if (designatedCorner == null) {
-                return null
+            synchronized(lock) {
+                nextViewState = nextViewState.copy(systemPrivacyEventIsActive = false)
             }
 
-            val alpha = ObjectAnimator.ofFloat(
-                    designatedCorner, "alpha", 1f, 0f)
-            alpha.duration = DURATION
-            alpha.interpolator = Interpolators.ALPHA_OUT
-            alpha.addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animator: Animator) {
-                    uiExecutor?.execute { designatedCorner?.visibility = View.INVISIBLE }
-                }
-            })
-            alpha.start()
             return null
         }
     }
+
+    private fun View?.cornerIndex(): Int {
+        if (this != null) {
+            return cornerForView(this)
+        }
+        return -1
+    }
 }
 
 private fun dlog(s: String) {
@@ -382,3 +458,26 @@
         else -> throw IllegalArgumentException("Not a corner")
     }
 }
+
+private data class ViewState(
+    // don't @ me with names
+    val systemPrivacyEventIsActive: Boolean = false,
+    val hideDotForQuickSettings: Boolean = false,
+    val statusBarExpanded: Boolean = false,
+    val rotation: Int = 0,
+    val height: Int = 0,
+    val marginLeft: Int = 0,
+    val marginRight: Int = 0,
+    val cornerIndex: Int = -1,
+    val designatedCorner: View? = null
+) {
+    fun shouldShowDot(): Boolean {
+        return systemPrivacyEventIsActive && !hideDotForQuickSettings
+    }
+
+    fun needsLayout(other: ViewState): Boolean {
+        return rotation != other.rotation ||
+                marginRight != other.marginRight ||
+                height != other.height
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
index b6f0416..655ed41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
@@ -107,7 +107,7 @@
                 scheduleEvent(event)
             } else if (event.forceVisible) {
                 hasPersistentDot = true
-                notifyTransitionToPersistentDot(showAnimation = false)
+                notifyTransitionToPersistentDot()
             }
         } else {
             if (DEBUG) {
@@ -202,7 +202,7 @@
 
                 aSet2.play(chipAnimator).before(systemAnimator)
                 if (hasPersistentDot) {
-                    val dotAnim = notifyTransitionToPersistentDot(showAnimation = true)
+                    val dotAnim = notifyTransitionToPersistentDot()
                     if (dotAnim != null) aSet2.playTogether(systemAnimator, dotAnim)
                 }
 
@@ -214,9 +214,9 @@
         }, DELAY)
     }
 
-    private fun notifyTransitionToPersistentDot(showAnimation: Boolean): Animator? {
+    private fun notifyTransitionToPersistentDot(): Animator? {
         val anims: List<Animator> = listeners.mapNotNull {
-            it.onSystemStatusAnimationTransitionToPersistentDot(showAnimation)
+            it.onSystemStatusAnimationTransitionToPersistentDot()
         }
         if (anims.isNotEmpty()) {
             val aSet = AnimatorSet()
@@ -326,9 +326,7 @@
     @JvmDefault fun onSystemChromeAnimationEnd() {}
 
     // Best method name, change my mind
-    @JvmDefault fun onSystemStatusAnimationTransitionToPersistentDot(
-        showAnimation: Boolean
-    ): Animator? { return null }
+    @JvmDefault fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? { return null }
     @JvmDefault fun onHidePersistentDot(): Animator? { return null }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 506d8a1..120f973 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -2068,6 +2068,7 @@
         int height = 0;
         float previousPaddingRequest = mPaddingBetweenElements;
         int numShownItems = 0;
+        int numShownNotifs = 0;
         boolean finish = false;
         int maxDisplayedNotifications = mMaxDisplayedNotifications;
         ExpandableView previousView = null;
@@ -2077,7 +2078,7 @@
             if (expandableView.getVisibility() != View.GONE
                     && !expandableView.hasNoContentHeight() && !footerViewOnLockScreen) {
                 boolean limitReached = maxDisplayedNotifications != -1
-                        && numShownItems >= maxDisplayedNotifications;
+                        && numShownNotifs >= maxDisplayedNotifications;
                 final float viewHeight;
                 if (limitReached) {
                     viewHeight = mShelf.getIntrinsicHeight();
@@ -2090,7 +2091,11 @@
                 }
                 height += calculateGapHeight(previousView, expandableView, numShownItems);
                 height += viewHeight;
+
                 numShownItems++;
+                if (!(expandableView instanceof MediaHeaderView)) {
+                    numShownNotifs++;
+                }
                 previousView = expandableView;
                 if (finish) {
                     break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 413048d..d94d030 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -24,6 +24,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.dagger.SilentHeader;
@@ -374,7 +375,13 @@
         ExpandableView view = algorithmState.visibleChildren.get(i);
         ExpandableViewState viewState = view.getViewState();
         viewState.location = ExpandableViewState.LOCATION_UNKNOWN;
-        viewState.alpha = 1f - ambientState.getHideAmount();
+
+        if (ambientState.isExpansionChanging() && !ambientState.isOnKeyguard()) {
+            viewState.alpha = Interpolators.getNotificationScrimAlpha(
+                    ambientState.getExpansionFraction());
+        } else {
+            viewState.alpha = 1f - ambientState.getHideAmount();
+        }
 
         if (view.mustStayOnScreen() && viewState.yTranslation >= 0) {
             // Even if we're not scrolled away we're in view and we're also not in the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 16bed6f..2b51b56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -105,12 +105,7 @@
 
     private final OngoingCallListener mOngoingCallListener = new OngoingCallListener() {
         @Override
-        public void onOngoingCallStarted(boolean animate) {
-            disable(getContext().getDisplayId(), mDisabled1, mDisabled2, animate);
-        }
-
-        @Override
-        public void onOngoingCallEnded(boolean animate) {
+        public void onOngoingCallStateChanged(boolean animate) {
             disable(getContext().getDisplayId(), mDisabled1, mDisabled2, animate);
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index f9a644f..a3efcd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -697,16 +697,6 @@
         if (isNaN(h)) {
             Log.wtf(TAG, "ExpandedHeight set to NaN");
         }
-        if (mAmbientState.isExpansionChanging()
-                && !mIsFlinging  // Fling already uses interpolated height from end of swipe
-                && !mAmbientState.isOnKeyguard()
-                && !mAmbientState.isDozing()
-                && !mAmbientState.isPulsing()) {
-            final float fraction = h / mView.getHeight();
-            final float interpolatedFraction = new PathInterpolator(0.2f, 0.8f, 0.8f, 1f)
-                    .getInterpolation(fraction);
-            h = interpolatedFraction * mView.getHeight();
-        }
         maybeOverScrollForShadeFlingOpen(h);
         if (mExpandLatencyTracking && h != 0f) {
             DejankUtils.postAfterTraversal(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index f1405de..ed7ab6cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -673,11 +673,14 @@
             }
         }
 
+        // Disabling for now, but keeping the log
+        /*
         mIconController.setIconVisibility(mSlotCamera, showCamera);
         mIconController.setIconVisibility(mSlotMicrophone, showMicrophone);
         if (mPrivacyItemController.getLocationAvailable()) {
             mIconController.setIconVisibility(mSlotLocation, showLocation);
         }
+         */
         mPrivacyLogger.logStatusBarIconsVisible(showCamera, showMicrophone,  showLocation);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index a952db2..c34fa2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -42,6 +42,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.settingslib.Utils;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
@@ -810,15 +811,7 @@
     }
 
     private float getInterpolatedFraction() {
-        float frac = mPanelExpansion;
-        // let's start this 20% of the way down the screen
-        frac = frac * 1.2f - 0.2f;
-        if (frac <= 0) {
-            return 0;
-        } else {
-            // woo, special effects
-            return (float) (1f - 0.5f * (1f - Math.cos(3.14159f * Math.pow(1f - frac, 2f))));
-        }
+        return Interpolators.getNotificationScrimAlpha(mPanelExpansion);
     }
 
     private void setScrimAlpha(ScrimView scrim, float alpha) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index bd17d00..cae7e35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -393,7 +393,6 @@
 
     private final Object mQueueLock = new Object();
 
-    private final StatusBarIconController mIconController;
     private final PulseExpansionHandler mPulseExpansionHandler;
     private final NotificationWakeUpCoordinator mWakeUpCoordinator;
     private final KeyguardBypassController mKeyguardBypassController;
@@ -720,7 +719,7 @@
             LightBarController lightBarController,
             AutoHideController autoHideController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            StatusBarIconController statusBarIconController,
+            StatusBarSignalPolicy signalPolicy,
             PulseExpansionHandler pulseExpansionHandler,
             NotificationWakeUpCoordinator notificationWakeUpCoordinator,
             KeyguardBypassController keyguardBypassController,
@@ -806,7 +805,7 @@
         mLightBarController = lightBarController;
         mAutoHideController = autoHideController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mIconController = statusBarIconController;
+        mSignalPolicy = signalPolicy;
         mPulseExpansionHandler = pulseExpansionHandler;
         mWakeUpCoordinator = notificationWakeUpCoordinator;
         mKeyguardBypassController = keyguardBypassController;
@@ -1015,7 +1014,6 @@
 
         // Lastly, call to the icon policy to install/update all the icons.
         mIconPolicy.init();
-        mSignalPolicy = new StatusBarSignalPolicy(mContext, mIconController);
 
         mKeyguardStateController.addCallback(this);
         startKeyguard();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 3445826..142cf21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -25,6 +25,7 @@
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
@@ -33,12 +34,16 @@
 import com.android.systemui.statusbar.policy.SecurityController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
+import com.android.systemui.util.CarrierConfigTracker;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
+import javax.inject.Inject;
 
+/** Controls the signal policies for icons shown in the StatusBar. **/
+@SysUISingleton
 public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallback,
         SecurityController.SecurityControllerCallback, Tunable {
     private static final String TAG = "StatusBarSignalPolicy";
@@ -57,6 +62,7 @@
     private final NetworkController mNetworkController;
     private final SecurityController mSecurityController;
     private final Handler mHandler = Handler.getMain();
+    private final CarrierConfigTracker mCarrierConfigTracker;
 
     private boolean mHideAirplane;
     private boolean mHideMobile;
@@ -75,7 +81,9 @@
             new ArrayList<CallIndicatorIconState>();
     private WifiIconState mWifiIconState = new WifiIconState();
 
-    public StatusBarSignalPolicy(Context context, StatusBarIconController iconController) {
+    @Inject
+    public StatusBarSignalPolicy(Context context, StatusBarIconController iconController,
+            CarrierConfigTracker carrierConfigTracker) {
         mContext = context;
 
         mSlotAirplane = mContext.getString(com.android.internal.R.string.status_bar_airplane);
@@ -95,6 +103,7 @@
         Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_HIDE_LIST);
         mNetworkController.addCallback(this);
         mSecurityController.addCallback(this);
+        mCarrierConfigTracker = carrierConfigTracker;
     }
 
     public void destroy() {
@@ -215,8 +224,12 @@
             state.callStrengthResId = statusIcon.icon;
             state.callStrengthDescription = statusIcon.contentDescription;
         }
-        mIconController.setCallStrengthIcons(mSlotCallStrength,
-                CallIndicatorIconState.copyStates(mCallIndicatorStates));
+        if (mCarrierConfigTracker.getCallStrengthConfig(subId)) {
+            mIconController.setCallStrengthIcons(mSlotCallStrength,
+                    CallIndicatorIconState.copyStates(mCallIndicatorStates));
+        } else {
+            mIconController.removeIcon(mSlotCallStrength, subId);
+        }
         mIconController.setNoCallingIcons(mSlotNoCalling,
                 CallIndicatorIconState.copyStates(mCallIndicatorStates));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index ae11a74..d0d2cb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -88,10 +88,10 @@
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy;
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -134,7 +134,7 @@
             LightBarController lightBarController,
             AutoHideController autoHideController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            StatusBarIconController statusBarIconController,
+            StatusBarSignalPolicy signalPolicy,
             PulseExpansionHandler pulseExpansionHandler,
             NotificationWakeUpCoordinator notificationWakeUpCoordinator,
             KeyguardBypassController keyguardBypassController,
@@ -221,7 +221,7 @@
                 lightBarController,
                 autoHideController,
                 keyguardUpdateMonitor,
-                statusBarIconController,
+                signalPolicy,
                 pulseExpansionHandler,
                 notificationWakeUpCoordinator,
                 keyguardBypassController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 51bb643..6d1df5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.phone.ongoingcall
 
+import android.app.ActivityManager
+import android.app.IActivityManager
+import android.app.IUidObserver
 import android.app.Notification
 import android.app.Notification.CallStyle.CALL_TYPE_ONGOING
 import android.content.Intent
@@ -25,6 +28,7 @@
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.FeatureFlags
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -32,6 +36,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.policy.CallbackController
 import com.android.systemui.util.time.SystemClock
+import java.util.concurrent.Executor
 import javax.inject.Inject
 
 /**
@@ -42,12 +47,17 @@
     private val notifCollection: CommonNotifCollection,
     private val featureFlags: FeatureFlags,
     private val systemClock: SystemClock,
-    private val activityStarter: ActivityStarter
+    private val activityStarter: ActivityStarter,
+    @Main private val mainExecutor: Executor,
+    private val iActivityManager: IActivityManager
 ) : CallbackController<OngoingCallListener> {
 
     /** Null if there's no ongoing call. */
     private var ongoingCallInfo: OngoingCallInfo? = null
+    /** True if the application managing the call is visible to the user. */
+    private var isCallAppVisible: Boolean = true
     private var chipView: ViewGroup? = null
+    private var uidObserver: IUidObserver.Stub? = null
 
     private val mListeners: MutableList<OngoingCallListener> = mutableListOf()
 
@@ -67,8 +77,9 @@
         override fun onEntryUpdated(entry: NotificationEntry) {
             if (isOngoingCallNotification(entry)) {
                 ongoingCallInfo = OngoingCallInfo(
-                entry.sbn.notification.`when`,
-                        entry.sbn.notification.contentIntent.intent)
+                    entry.sbn.notification.`when`,
+                    entry.sbn.notification.contentIntent.intent,
+                    entry.sbn.uid)
                 updateChip()
             }
         }
@@ -76,7 +87,10 @@
         override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
             if (isOngoingCallNotification(entry)) {
                 ongoingCallInfo = null
-                mListeners.forEach { l -> l.onOngoingCallEnded(animate = true) }
+                mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
+                if (uidObserver != null) {
+                    iActivityManager.unregisterUidObserver(uidObserver)
+                }
             }
         }
     }
@@ -100,9 +114,13 @@
     }
 
     /**
-     * Returns true if there's an active ongoing call that can be displayed in a status bar chip.
+     * Returns true if there's an active ongoing call that should be displayed in a status bar chip.
      */
-    fun hasOngoingCall(): Boolean = ongoingCallInfo != null
+    fun hasOngoingCall(): Boolean {
+        return ongoingCallInfo != null &&
+                // When the user is in the phone app, don't show the chip.
+                !isCallAppVisible
+    }
 
     override fun addCallback(listener: OngoingCallListener) {
         synchronized(mListeners) {
@@ -137,7 +155,9 @@
                         ActivityLaunchAnimator.Controller.fromView(it))
             }
 
-            mListeners.forEach { l -> l.onOngoingCallStarted(animate = true) }
+            setUpUidObserver(currentOngoingCallInfo)
+
+            mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
         } else {
             // If we failed to update the chip, don't store the ongoing call info. Then
             // [hasOngoingCall] will return false and we fall back to typical notification handling.
@@ -150,9 +170,52 @@
         }
     }
 
+    /**
+     * Sets up an [IUidObserver] to monitor the status of the application managing the ongoing call.
+     */
+    private fun setUpUidObserver(currentOngoingCallInfo: OngoingCallInfo) {
+        isCallAppVisible = isProcessVisibleToUser(
+                iActivityManager.getUidProcessState(currentOngoingCallInfo.uid, null))
+
+        uidObserver = object : IUidObserver.Stub() {
+            override fun onUidStateChanged(
+                    uid: Int, procState: Int, procStateSeq: Long, capability: Int) {
+                if (uid == currentOngoingCallInfo.uid) {
+                    val oldIsCallAppVisible = isCallAppVisible
+                    isCallAppVisible = isProcessVisibleToUser(procState)
+                    if (oldIsCallAppVisible != isCallAppVisible) {
+                        // Animations may be run as a result of the call's state change, so ensure
+                        // the listener is notified on the main thread.
+                        mainExecutor.execute {
+                            mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
+                        }
+                    }
+                }
+            }
+
+            override fun onUidGone(uid: Int, disabled: Boolean) {}
+            override fun onUidActive(uid: Int) {}
+            override fun onUidIdle(uid: Int, disabled: Boolean) {}
+            override fun onUidCachedChanged(uid: Int, cached: Boolean) {}
+        }
+
+        iActivityManager.registerUidObserver(
+                uidObserver,
+                ActivityManager.UID_OBSERVER_PROCSTATE,
+                ActivityManager.PROCESS_STATE_UNKNOWN,
+                null
+        )
+    }
+
+    /** Returns true if the given [procState] represents a process that's visible to the user. */
+    private fun isProcessVisibleToUser(procState: Int): Boolean {
+        return procState <= ActivityManager.PROCESS_STATE_TOP
+    }
+
     private class OngoingCallInfo(
         val callStartTime: Long,
-        val intent: Intent
+        val intent: Intent,
+        val uid: Int
     )
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallListener.kt
index 7c583a1..7a12430 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallListener.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.statusbar.phone.ongoingcall
 
-/** A listener that's notified when an ongoing call is started or ended. */
+/** A listener that's notified when the state of an ongoing call has changed. */
 interface OngoingCallListener {
-    /** Called when an ongoing call is started. */
-    fun onOngoingCallStarted(animate: Boolean)
 
-    /** Called when an ongoing call is ended. */
-    fun onOngoingCallEnded(animate: Boolean)
+    /**
+     * Called when the state of an ongoing call has changed in any way that may affect view
+     * visibility (including call starting, call stopping, application managing the call becoming
+     * visible or invisible).
+     */
+    fun onOngoingCallStateChanged(animate: Boolean)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 044f52f..ce08075 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -61,6 +61,7 @@
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.util.CarrierConfigTracker;
 
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
@@ -78,6 +79,7 @@
     private static final int IMS_TYPE_WLAN = 2;
     private static final int IMS_TYPE_WLAN_CROSS_SIM = 3;
     private final TelephonyManager mPhone;
+    private final CarrierConfigTracker mCarrierConfigTracker;
     private final ImsMmTelManager mImsMmTelManager;
     private final SubscriptionDefaults mDefaults;
     private final String mNetworkNameDefault;
@@ -123,10 +125,12 @@
     public MobileSignalController(Context context, Config config, boolean hasMobileData,
             TelephonyManager phone, CallbackHandler callbackHandler,
             NetworkControllerImpl networkController, SubscriptionInfo info,
-            SubscriptionDefaults defaults, Looper receiverLooper) {
+            SubscriptionDefaults defaults, Looper receiverLooper,
+            CarrierConfigTracker carrierConfigTracker) {
         super("MobileSignalController(" + info.getSubscriptionId() + ")", context,
                 NetworkCapabilities.TRANSPORT_CELLULAR, callbackHandler,
                 networkController);
+        mCarrierConfigTracker = carrierConfigTracker;
         mConfig = config;
         mPhone = phone;
         mDefaults = defaults;
@@ -583,7 +587,7 @@
         mTelephonyDisplayInfo = mobileStatus.telephonyDisplayInfo;
         int lastVoiceState = mServiceState != null ? mServiceState.getState() : -1;
         mServiceState = mobileStatus.serviceState;
-        int currentVoiceState =  mServiceState != null ? mServiceState.getState() : -1;
+        int currentVoiceState = mServiceState != null ? mServiceState.getState() : -1;
         // Only update the no calling Status in the below scenarios
         // 1. The first valid voice state has been received
         // 2. The voice state has been changed and either the last or current state is
@@ -594,12 +598,29 @@
                         || (lastVoiceState == ServiceState.STATE_IN_SERVICE
                                 || currentVoiceState == ServiceState.STATE_IN_SERVICE))) {
             boolean isNoCalling = currentVoiceState != ServiceState.STATE_IN_SERVICE;
-            IconState statusIcon = new IconState(isNoCalling, R.drawable.ic_qs_no_calling_sms,
+            isNoCalling &= !hideNoCalling();
+            IconState statusIcon = new IconState(isNoCalling,
+                    R.drawable.ic_qs_no_calling_sms,
                     getTextIfExists(AccessibilityContentDescriptions.NO_CALLING).toString());
             notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
         }
     }
 
+    void updateNoCallingState() {
+        int currentVoiceState = mServiceState != null ? mServiceState.getState() : -1;
+        boolean isNoCalling = currentVoiceState != ServiceState.STATE_IN_SERVICE;
+        isNoCalling &= !hideNoCalling();
+        IconState statusIcon = new IconState(isNoCalling,
+                R.drawable.ic_qs_no_calling_sms,
+                getTextIfExists(AccessibilityContentDescriptions.NO_CALLING).toString());
+        notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+    }
+
+    private boolean hideNoCalling() {
+        return mNetworkController.hasDefaultNetwork()
+                && mCarrierConfigTracker.getNoCallingConfig(mSubscriptionInfo.getSubscriptionId());
+    }
+
     private int getCallStrengthIcon(int level, boolean isWifi) {
         return isWifi ? TelephonyIcons.WIFI_CALL_STRENGTH_ICONS[level]
                 : TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[level];
@@ -616,7 +637,9 @@
     void refreshCallIndicator(SignalCallback callback) {
         boolean isNoCalling = mServiceState != null
                 && mServiceState.getState() != ServiceState.STATE_IN_SERVICE;
-        IconState statusIcon = new IconState(isNoCalling, R.drawable.ic_qs_no_calling_sms,
+        isNoCalling &= !hideNoCalling();
+        IconState statusIcon = new IconState(isNoCalling,
+                R.drawable.ic_qs_no_calling_sms,
                 getTextIfExists(AccessibilityContentDescriptions.NO_CALLING).toString());
         callback.setCallIndicator(statusIcon, mSubscriptionInfo.getSubscriptionId());
 
@@ -646,7 +669,6 @@
         if (!mProviderModel) {
             return;
         }
-        Log.d("mTag", "notifyWifiLevelChange " + mImsType);
         mLastWlanLevel = level;
         if (mImsType != IMS_TYPE_WLAN) {
             return;
@@ -662,7 +684,6 @@
         if (!mProviderModel) {
             return;
         }
-        Log.d("mTag", "notifyDefaultMobileLevelChange " + mImsType);
         mLastWlanCrossSimLevel = level;
         if (mImsType != IMS_TYPE_WLAN_CROSS_SIM) {
             return;
@@ -681,7 +702,6 @@
         int newLevel = getSignalLevel(signalStrength);
         if (newLevel != mLastLevel) {
             mLastLevel = newLevel;
-            Log.d("mTag", "notifyMobileLevelChangeIfNecessary " + mImsType);
             mLastWwanLevel = newLevel;
             if (mImsType == IMS_TYPE_WWAN) {
                 IconState statusIcon = new IconState(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index f683603..f45218d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -74,6 +74,7 @@
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.util.CarrierConfigTracker;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -122,6 +123,7 @@
     private final Object mLock = new Object();
     private final boolean mProviderModel;
     private Config mConfig;
+    private final CarrierConfigTracker mCarrierConfigTracker;
 
     private TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -212,7 +214,8 @@
             @Nullable WifiManager wifiManager,
             NetworkScoreManager networkScoreManager,
             AccessPointControllerImpl accessPointController,
-            DemoModeController demoModeController) {
+            DemoModeController demoModeController,
+            CarrierConfigTracker carrierConfigTracker) {
         this(context, connectivityManager,
                 telephonyManager,
                 telephonyListenerManager,
@@ -228,7 +231,8 @@
                 new SubscriptionDefaults(),
                 deviceProvisionedController,
                 broadcastDispatcher,
-                demoModeController);
+                demoModeController,
+                carrierConfigTracker);
         mReceiverHandler.post(mRegisterListeners);
     }
 
@@ -246,7 +250,8 @@
             SubscriptionDefaults defaultsHandler,
             DeviceProvisionedController deviceProvisionedController,
             BroadcastDispatcher broadcastDispatcher,
-            DemoModeController demoModeController) {
+            DemoModeController demoModeController,
+            CarrierConfigTracker carrierConfigTracker) {
         mContext = context;
         mTelephonyListenerManager = telephonyListenerManager;
         mConfig = config;
@@ -262,6 +267,7 @@
         mConnectivityManager = connectivityManager;
         mHasMobileDataFeature = telephonyManager.isDataCapable();
         mDemoModeController = demoModeController;
+        mCarrierConfigTracker = carrierConfigTracker;
 
         // telephony
         mPhone = telephonyManager;
@@ -574,6 +580,10 @@
         return mWifiSignalController.isCarrierMergedWifi(subId);
     }
 
+    boolean hasDefaultNetwork() {
+        return !mNoDefaultNetwork;
+    }
+
     boolean isNonCarrierWifiNetworkAvailable() {
         return !mNoNetworksAvailable;
     }
@@ -884,7 +894,7 @@
                 MobileSignalController controller = new MobileSignalController(mContext, mConfig,
                         mHasMobileDataFeature, mPhone.createForSubscriptionId(subId),
                         mCallbackHandler, this, subscriptions.get(i),
-                        mSubDefaults, mReceiverHandler.getLooper());
+                        mSubDefaults, mReceiverHandler.getLooper(), mCarrierConfigTracker);
                 controller.setUserSetupComplete(mUserSetup);
                 mMobileSignalControllers.put(subId, controller);
                 if (subscriptions.get(i).getSimSlotIndex() == 0) {
@@ -1027,6 +1037,10 @@
             mNoDefaultNetwork = mConnectedTransports.isEmpty();
             mCallbackHandler.setConnectivityStatus(mNoDefaultNetwork, !mInetCondition,
                     mNoNetworksAvailable);
+            for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+                MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
+                mobileSignalController.updateNoCallingState();
+            }
             notifyAllListeners();
         }
     }
@@ -1334,8 +1348,8 @@
                 null, null, null, "", false, null, null);
         MobileSignalController controller = new MobileSignalController(mContext,
                 mConfig, mHasMobileDataFeature,
-                mPhone.createForSubscriptionId(info.getSubscriptionId()), mCallbackHandler, this, info,
-                mSubDefaults, mReceiverHandler.getLooper());
+                mPhone.createForSubscriptionId(info.getSubscriptionId()), mCallbackHandler, this,
+                info, mSubDefaults, mReceiverHandler.getLooper(), mCarrierConfigTracker);
         mMobileSignalControllers.put(id, controller);
         controller.getState().userSetup = true;
         return info;
diff --git a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
new file mode 100644
index 0000000..02a07e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.util.SparseArray;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Tracks the Carrier Config values.
+ */
+@SysUISingleton
+public class CarrierConfigTracker extends BroadcastReceiver {
+    private final SparseArray<Boolean> mCallStrengthConfigs = new SparseArray<>();
+    private final SparseArray<Boolean> mNoCallingConfigs = new SparseArray<>();
+    private final CarrierConfigManager mCarrierConfigManager;
+    private final boolean mDefaultCallStrengthConfig;
+    private final boolean mDefaultNoCallingConfig;
+
+    @Inject
+    public CarrierConfigTracker(Context context) {
+        mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
+        context.registerReceiver(
+                this, new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+        mDefaultCallStrengthConfig =
+                CarrierConfigManager.getDefaultConfig().getBoolean(
+                        CarrierConfigManager.KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL);
+        mDefaultNoCallingConfig =
+                CarrierConfigManager.getDefaultConfig().getBoolean(
+                        CarrierConfigManager.KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL);
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (intent.getAction() == CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED) {
+            int subId = intent.getIntExtra(
+                    CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
+                    SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+            if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+                return;
+            }
+            PersistableBundle b = mCarrierConfigManager.getConfigForSubId(subId);
+            if (b != null) {
+                boolean hideNoCallingConfig = b.getBoolean(
+                        CarrierConfigManager.KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL);
+                boolean displayCallStrengthIcon = b.getBoolean(
+                        CarrierConfigManager.KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL);
+                mCallStrengthConfigs.put(subId, displayCallStrengthIcon);
+                mNoCallingConfigs.put(subId, hideNoCallingConfig);
+            }
+        }
+    }
+
+    /**
+     * Returns the KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL value for the given subId.
+     */
+    public boolean getCallStrengthConfig(int subId) {
+        if (mCallStrengthConfigs.indexOfKey(subId) >= 0) {
+            return mCallStrengthConfigs.get(subId);
+        }
+        return mDefaultCallStrengthConfig;
+    }
+
+    /**
+     * Returns the KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL value for the given subId.
+     */
+    public boolean getNoCallingConfig(int subId) {
+        if (mNoCallingConfigs.indexOfKey(subId) >= 0) {
+            return mNoCallingConfigs.get(subId);
+        }
+        return mDefaultNoCallingConfig;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactory.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactory.java
index 0352fb5..2270f96 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactory.java
@@ -16,13 +16,15 @@
 
 package com.android.systemui.util.concurrency;
 
+import android.os.Looper;
+
 import java.util.concurrent.Executor;
 
 /**
  * Factory for building Executors running on a unique named thread.
  *
  * Use this when our generally available @Main, @Background, @UiBackground, @LongRunning, or
- * similar global qualifiers don't quite cut it. Note that the methods here create entirely new
+ * similar global qualifiers don't quite cut it. Note that the methods here can create entirely new
  * threads; there are no singletons here. Use responsibly.
  */
 public interface ThreadFactory {
@@ -41,4 +43,9 @@
      * implementation. Assume this is the case and use responsibly.
      **/
     DelayableExecutor buildDelayableExecutorOnNewThread(String threadName);
+
+    /**
+     * Return an {@link DelayableExecutor} running the given Looper
+     **/
+    DelayableExecutor buildDelayableExecutorOnLooper(Looper looper);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactoryImpl.java
index ca8d836..2d9f2b4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactoryImpl.java
@@ -17,6 +17,7 @@
 package com.android.systemui.util.concurrency;
 
 import android.os.HandlerThread;
+import android.os.Looper;
 
 import java.util.concurrent.Executor;
 
@@ -35,4 +36,8 @@
         handlerThread.start();
         return new ExecutorImpl(handlerThread.getLooper());
     }
+
+    public DelayableExecutor buildDelayableExecutorOnLooper(Looper looper) {
+        return new ExecutorImpl(looper);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardCarousel.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardCarousel.java
index 4200241..1e1b459 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardCarousel.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardCarousel.java
@@ -61,7 +61,7 @@
     static final int CARD_ANIM_ALPHA_DELAY = 50;
 
     private final Rect mSystemGestureExclusionZone = new Rect();
-    private WalletCardCarouselAdapter mWalletCardCarouselAdapter;
+    private final WalletCardCarouselAdapter mWalletCardCarouselAdapter;
     private int mExpectedViewWidth;
     private int mCardMarginPx;
     private int mCardWidthPx;
@@ -79,12 +79,6 @@
     // also be used in DotIndicatorDecoration.
     float mEdgeToCenterDistance = Float.MAX_VALUE;
     private float mCardCenterToScreenCenterDistancePx = Float.MAX_VALUE;
-    // When card data is loaded, this many cards should be animated as data is bound to them.
-    private int mNumCardsToAnimate;
-    // When card data is loaded, this is the position of the leftmost card to be animated.
-    private int mCardAnimationStartPosition;
-    // When card data is loaded, the animations may be delayed so that other animations can complete
-    private int mExtraAnimationDelay;
 
     interface OnSelectionListener {
         /**
@@ -179,10 +173,6 @@
         }
     }
 
-    void setExtraAnimationDelay(int extraAnimationDelay) {
-        mExtraAnimationDelay = extraAnimationDelay;
-    }
-
     void setSelectionListener(OnSelectionListener selectionListener) {
         mSelectionListener = selectionListener;
     }
@@ -200,19 +190,14 @@
     }
 
     /**
-     * Set card data. Returns true if carousel was empty, indicating that views will be animated
+     * Returns true if the data set is changed.
      */
-    boolean setData(List<WalletCardViewInfo> data, int selectedIndex) {
-        boolean wasEmpty = mWalletCardCarouselAdapter.getItemCount() == 0;
-        mWalletCardCarouselAdapter.setData(data);
+    boolean setData(List<WalletCardViewInfo> data, int selectedIndex, boolean hasLockStateChanged) {
+        boolean hasDataChanged = mWalletCardCarouselAdapter.setData(data, hasLockStateChanged);
         scrollToPosition(selectedIndex);
-        if (wasEmpty) {
-            mNumCardsToAnimate = numCardsOnScreen(data.size(), selectedIndex);
-            mCardAnimationStartPosition = Math.max(selectedIndex - 1, 0);
-        }
         WalletCardViewInfo selectedCard = data.get(selectedIndex);
         mCardScrollListener.onCardScroll(selectedCard, selectedCard, 0);
-        return wasEmpty;
+        return hasDataChanged;
     }
 
     @Override
@@ -222,19 +207,6 @@
     }
 
     /**
-     * The number of cards shown on screen when one of the cards is position in the center. This is
-     * also the num
-     */
-    private static int numCardsOnScreen(int numCards, int selectedIndex) {
-        if (numCards <= 2) {
-            return numCards;
-        }
-        // When there are 3 or more cards, 3 cards will be shown unless the first or last card is
-        // centered on screen.
-        return selectedIndex > 0 && selectedIndex < (numCards - 1) ? 3 : 2;
-    }
-
-    /**
      * The padding pushes the first and last cards in the list to the center when they are
      * selected.
      */
@@ -439,9 +411,29 @@
             return mData.get(position).getCardId().hashCode();
         }
 
-        void setData(List<WalletCardViewInfo> data) {
+        private boolean setData(List<WalletCardViewInfo> data, boolean hasLockedStateChanged) {
+            List<WalletCardViewInfo> oldData = mData;
             mData = data;
-            notifyDataSetChanged();
+            if (hasLockedStateChanged || !isUiEquivalent(oldData, data)) {
+                notifyDataSetChanged();
+                return true;
+            }
+            return false;
+        }
+
+        private boolean isUiEquivalent(
+                List<WalletCardViewInfo> oldData, List<WalletCardViewInfo> newData) {
+            if (oldData.size() != newData.size()) {
+                return false;
+            }
+            for (int i = 0; i < newData.size(); i++) {
+                WalletCardViewInfo oldItem = oldData.get(i);
+                WalletCardViewInfo newItem = newData.get(i);
+                if (!oldItem.isUiEquivalent(newItem)) {
+                    return false;
+                }
+            }
+            return true;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardViewInfo.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardViewInfo.java
index 3d37320..08fbe4a 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardViewInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardViewInfo.java
@@ -54,4 +54,11 @@
      */
     @NonNull
     PendingIntent getPendingIntent();
+
+    default boolean isUiEquivalent(WalletCardViewInfo other) {
+        if (other == null) {
+            return false;
+        }
+        return getCardId().equals(other.getCardId());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
index 44074f70..c547bb3 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
@@ -19,8 +19,6 @@
 import static com.android.systemui.wallet.ui.WalletCardCarousel.CARD_ANIM_ALPHA_DELAY;
 import static com.android.systemui.wallet.ui.WalletCardCarousel.CARD_ANIM_ALPHA_DURATION;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -29,6 +27,7 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
@@ -46,9 +45,8 @@
 public class WalletView extends FrameLayout implements WalletCardCarousel.OnCardScrollListener {
 
     private static final String TAG = "WalletView";
-    private static final int CAROUSEL_IN_ANIMATION_DURATION = 300;
+    private static final int CAROUSEL_IN_ANIMATION_DURATION = 100;
     private static final int CAROUSEL_OUT_ANIMATION_DURATION = 200;
-    private static final int CARD_LABEL_ANIM_DELAY = 133;
 
     private final WalletCardCarousel mCardCarousel;
     private final ImageView mIcon;
@@ -57,14 +55,12 @@
     private final Button mAppButton;
     // Displays underneath the carousel, allow user to unlock device, verify card, etc.
     private final Button mActionButton;
-    private final Interpolator mInInterpolator;
     private final Interpolator mOutInterpolator;
     private final float mAnimationTranslationX;
     private final ViewGroup mCardCarouselContainer;
     private final TextView mErrorView;
     private final ViewGroup mEmptyStateView;
     private CharSequence mCenterCardText;
-    private Drawable mCenterCardIcon;
     private boolean mIsDeviceLocked = false;
 
     public WalletView(Context context) {
@@ -83,8 +79,6 @@
         mActionButton = requireViewById(R.id.wallet_action_button);
         mErrorView = requireViewById(R.id.error_view);
         mEmptyStateView = requireViewById(R.id.wallet_empty_state);
-        mInInterpolator =
-                AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
         mOutInterpolator =
                 AnimationUtils.loadInterpolator(context, android.R.interpolator.accelerate_cubic);
         mAnimationTranslationX = mCardCarousel.getCardWidthPx() / 4f;
@@ -109,7 +103,6 @@
         Drawable centerCardIcon = centerCard.getIcon();
         if (!TextUtils.equals(mCenterCardText, centerCardText)) {
             mCenterCardText = centerCardText;
-            mCenterCardIcon = centerCardIcon;
             mCardLabel.setText(centerCardText);
             mIcon.setImageDrawable(centerCardIcon);
         }
@@ -134,39 +127,15 @@
      */
     void showCardCarousel(
             List<WalletCardViewInfo> data, int selectedIndex, boolean isDeviceLocked) {
+        boolean shouldAnimate =
+                mCardCarousel.setData(data, selectedIndex, mIsDeviceLocked != isDeviceLocked);
         mIsDeviceLocked = isDeviceLocked;
-        boolean shouldAnimate = mCardCarousel.setData(data, selectedIndex);
         mCardCarouselContainer.setVisibility(VISIBLE);
         mErrorView.setVisibility(GONE);
+        mEmptyStateView.setVisibility(GONE);
         renderHeaderIconAndActionButton(data.get(selectedIndex), isDeviceLocked);
         if (shouldAnimate) {
-            // If the empty state is visible, animate it away and delay the card carousel animation
-            int emptyStateAnimDelay = 0;
-            if (mEmptyStateView.getVisibility() == VISIBLE) {
-                emptyStateAnimDelay = CARD_ANIM_ALPHA_DURATION;
-                mEmptyStateView.animate()
-                        .alpha(0)
-                        .setDuration(emptyStateAnimDelay)
-                        .setListener(new AnimatorListenerAdapter() {
-                            @Override
-                            public void onAnimationEnd(Animator animation) {
-                                mEmptyStateView.setVisibility(GONE);
-                            }
-                        })
-                        .start();
-            }
-            mCardLabel.setAlpha(0f);
-            mCardLabel.animate().alpha(1f)
-                    .setStartDelay(CARD_LABEL_ANIM_DELAY + emptyStateAnimDelay)
-                    .setDuration(CARD_ANIM_ALPHA_DURATION)
-                    .start();
-            mCardCarousel.setExtraAnimationDelay(emptyStateAnimDelay);
-            mCardCarousel.setTranslationX(mAnimationTranslationX);
-            mCardCarousel.animate().translationX(0)
-                    .setInterpolator(mInInterpolator)
-                    .setDuration(CAROUSEL_IN_ANIMATION_DURATION)
-                    .setStartDelay(emptyStateAnimDelay)
-                    .start();
+            animateViewsShown(mIcon, mCardLabel, mActionButton);
         }
     }
 
@@ -277,6 +246,15 @@
         }
     }
 
+    private static void animateViewsShown(View... uiElements) {
+        for (View view : uiElements) {
+            if (view.getVisibility() == VISIBLE) {
+                view.setAlpha(0f);
+                view.animate().alpha(1f).setDuration(CAROUSEL_IN_ANIMATION_DURATION).start();
+            }
+        }
+    }
+
     private static CharSequence getLabelText(WalletCardViewInfo card) {
         String[] rawLabel = card.getLabel().toString().split("\\n");
         return rawLabel.length == 2 ? rawLabel[0] : card.getLabel();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index d07a8da..495489f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -32,6 +32,7 @@
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
+import android.graphics.drawable.Icon;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -41,10 +42,11 @@
 import android.widget.FrameLayout;
 import android.widget.RelativeLayout;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.keyguard.clock.ClockManager;
 import com.android.systemui.R;
-import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -74,6 +76,7 @@
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.Executor;
 
 @SmallTest
@@ -117,12 +120,12 @@
     @Mock
     ConfigurationController mConfigurationController;
     @Mock
+    Optional<BcSmartspaceDataPlugin> mOptionalSmartspaceDataProvider;
+    @Mock
     BcSmartspaceDataPlugin mSmartspaceDataProvider;
     @Mock
     SmartspaceView mSmartspaceView;
     @Mock
-    SystemUIFactory mSystemUIFactory;
-    @Mock
     ActivityStarter mActivityStarter;
     @Mock
     FalsingManager mFalsingManager;
@@ -162,7 +165,6 @@
         when(mFeatureFlags.isSmartspaceEnabled()).thenReturn(true);
         when(mView.isAttachedToWindow()).thenReturn(true);
         when(mResources.getString(anyInt())).thenReturn("h:mm");
-        when(mSystemUIFactory.getSmartspaceDataProvider()).thenReturn(mSmartspaceDataProvider);
         mController = new KeyguardClockSwitchController(
                 mView,
                 mStatusBarStateController,
@@ -175,14 +177,14 @@
                 mExecutor,
                 mBatteryController,
                 mConfigurationController,
-                mSystemUIFactory,
                 mActivityStarter,
                 mFalsingManager,
                 mKeyguardUpdateMonitor,
                 mBypassController,
                 mHandler,
                 mUserTracker,
-                mSecureSettings
+                mSecureSettings,
+                mOptionalSmartspaceDataProvider
         );
 
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
@@ -190,6 +192,8 @@
 
         mStatusArea = new View(getContext());
         when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea);
+        when(mOptionalSmartspaceDataProvider.isPresent()).thenReturn(true);
+        when(mOptionalSmartspaceDataProvider.get()).thenReturn(mSmartspaceDataProvider);
         when(mSmartspaceDataProvider.getView(any())).thenReturn(mSmartspaceView);
     }
 
@@ -260,7 +264,7 @@
     @Test
     public void testSmartspaceEnabledNoDataProviderShowsKeyguardStatusArea() {
         when(mFeatureFlags.isSmartspaceEnabled()).thenReturn(true);
-        when(mSystemUIFactory.getSmartspaceDataProvider()).thenReturn(null);
+        when(mOptionalSmartspaceDataProvider.isPresent()).thenReturn(false);
         mController.init();
 
         assertEquals(View.VISIBLE, mStatusArea.getVisibility());
@@ -389,5 +393,7 @@
         public void setIntentStarter(IntentStarter intentStarter) { }
 
         public void setFalsingManager(FalsingManager falsingManager) { }
+
+        public void setDnd(@Nullable Icon dndIcon, @Nullable String description) { }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 3252750..b9ce203 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -65,8 +65,12 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
 import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.concurrency.FakeThreadFactory;
+import com.android.systemui.util.concurrency.ThreadFactory;
 import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -89,6 +93,7 @@
     private DisplayManager mDisplayManager;
     private SecureSettings mSecureSettings;
     private Handler mMainHandler;
+    private ThreadFactory mThreadFactory;
     @Mock
     private TunerService mTunerService;
     @Mock
@@ -105,6 +110,7 @@
         mTestableLooper = TestableLooper.get(this);
         mMainHandler = new Handler(mTestableLooper.getLooper());
         mSecureSettings = new FakeSettings();
+        mThreadFactory = new FakeThreadFactory(new FakeExecutor(new FakeSystemClock()));
 
         mWindowManager = mock(WindowManager.class);
         WindowMetrics metrics = mContext.getSystemService(WindowManager.class)
@@ -119,7 +125,8 @@
         mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
 
         mScreenDecorations = spy(new ScreenDecorations(mContext, mMainHandler, mSecureSettings,
-                mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController) {
+                mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController,
+                mThreadFactory) {
             @Override
             public void start() {
                 super.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 800daa1..dd3a192 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -93,6 +93,7 @@
     @Mock private lateinit var expandedSet: ConstraintSet
     @Mock private lateinit var collapsedSet: ConstraintSet
     @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
+    @Mock private lateinit var mediaCarouselController: MediaCarouselController
     private lateinit var appIcon: ImageView
     private lateinit var albumView: ImageView
     private lateinit var titleText: TextView
@@ -129,7 +130,7 @@
 
         player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
                 seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil,
-                mediaOutputDialogFactory)
+                mediaOutputDialogFactory, mediaCarouselController)
         whenever(seekBarViewModel.progress).thenReturn(seekBarData)
 
         // Mock out a view holder for the player to attach to.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
index 47f4183..eac68f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
@@ -62,8 +62,10 @@
 
         final View view = new View(mContext);
         mRotationButton = mock(RotationButton.class);
-        mRotationButtonController = spy(new RotationButtonController(mContext, 0, 0,
-                mRotationButton, (visibility) -> {}));
+        mRotationButtonController = new RotationButtonController(mContext, 0, 0);
+        mRotationButtonController.setRotationButton(mRotationButton, (visibility) -> {});
+        // Due to a mockito issue, only spy the object after setting the initial state
+        mRotationButtonController = spy(mRotationButtonController);
         final KeyButtonDrawable kbd = mock(KeyButtonDrawable.class);
         doReturn(view).when(mRotationButton).getCurrentView();
         doReturn(true).when(mRotationButton).acceptRotationProposal();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index 0dd1f68..8983ff8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -17,7 +17,6 @@
 package com.android.systemui.people;
 
 import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
-import static com.android.systemui.people.widget.AppWidgetOptionsHelper.OPTIONS_PEOPLE_TILE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -200,7 +199,6 @@
 
         int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
         mOptions = new Bundle();
-        mOptions.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE);
 
         when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
         when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
index 228e5e8..5c70a4ef2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
@@ -29,7 +29,6 @@
 
 import static com.android.systemui.people.PeopleSpaceUtils.STARRED_CONTACT;
 import static com.android.systemui.people.PeopleSpaceUtils.VALID_CONTACT;
-import static com.android.systemui.people.widget.AppWidgetOptionsHelper.OPTIONS_PEOPLE_TILE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -140,7 +139,6 @@
         MockitoAnnotations.initMocks(this);
 
         mOptions = new Bundle();
-        mOptions.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE);
 
         when(mMockContext.getString(R.string.birthday_status)).thenReturn(
                 mContext.getString(R.string.birthday_status));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index f31f326..107ce28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -51,7 +51,6 @@
 import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
 import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
 import static com.android.systemui.people.PeopleSpaceUtils.USER_ID;
-import static com.android.systemui.people.widget.AppWidgetOptionsHelper.OPTIONS_PEOPLE_TILE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -192,6 +191,7 @@
             | SUPPRESSED_EFFECT_LIGHTS
             | SUPPRESSED_EFFECT_PEEK
             | SUPPRESSED_EFFECT_NOTIFICATION_LIST;
+    private static final long SBN_POST_TIME = 567L;
 
     private ShortcutInfo mShortcutInfo;
     private NotificationEntry mNotificationEntry;
@@ -474,8 +474,6 @@
                 .setId(1));
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, never())
-                .updateAppWidgetOptions(anyInt(), any());
         verify(mAppWidgetManager, never()).updateAppWidget(anyInt(),
                 any());
     }
@@ -495,8 +493,6 @@
                 .setId(1));
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, never())
-                .updateAppWidgetOptions(anyInt(), any());
         verify(mAppWidgetManager, never()).updateAppWidget(anyInt(),
                 any());
     }
@@ -515,8 +511,6 @@
         NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn, 0);
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, never())
-                .updateAppWidgetOptions(anyInt(), any());
         verify(mAppWidgetManager, never()).updateAppWidget(anyInt(),
                 any());
     }
@@ -538,8 +532,6 @@
         NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn, 0);
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, never())
-                .updateAppWidgetOptions(anyInt(), any());
         verify(mAppWidgetManager, never()).updateAppWidget(anyInt(),
                 any());
     }
@@ -559,8 +551,6 @@
         mManager.updateWidgetsWithConversationChanged(conversationChannel);
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, never())
-                .updateAppWidgetOptions(anyInt(), any());
         verify(mAppWidgetManager, never()).updateAppWidget(anyInt(),
                 any());
     }
@@ -578,11 +568,7 @@
         mManager.updateWidgetsWithConversationChanged(conversationChannel);
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        Bundle bundle = mBundleArgumentCaptor.getValue();
-        PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tile = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile.getStatuses()).containsExactly(status);
         verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
@@ -599,14 +585,8 @@
         mManager.updateWidgetsWithConversationChanged(conversationChannel);
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        any());
         verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT),
-                        any());
         verify(mAppWidgetManager, times(1)).updateAppWidget(eq(SECOND_WIDGET_ID_WITH_SHORTCUT),
                 any());
     }
@@ -626,12 +606,9 @@
         NotifEvent notif1 = mNoMan.postNotif(builder);
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        Bundle bundle = mBundleArgumentCaptor.getValue();
-        PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tile = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
+        assertThat(tile.getLastInteractionTimestamp()).isEqualTo(SBN_POST_TIME);
         assertThat(tile.getNotificationContent()).isEqualTo(NOTIFICATION_CONTENT_1);
         verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
@@ -647,14 +624,8 @@
                 .setId(1));
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        any());
         verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT),
-                        any());
         verify(mAppWidgetManager, times(1)).updateAppWidget(eq(SECOND_WIDGET_ID_WITH_SHORTCUT),
                 any());
     }
@@ -672,14 +643,8 @@
                 .setId(1));
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        any());
         verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
-        verify(mAppWidgetManager, never())
-                .updateAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT),
-                        any());
         verify(mAppWidgetManager, never()).updateAppWidget(eq(SECOND_WIDGET_ID_WITH_SHORTCUT),
                 any());
     }
@@ -700,12 +665,7 @@
         NotifEvent notif1 = mNoMan.postNotif(builder);
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue());
-
-        PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tile = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
         assertThat(tile.getNotificationContent())
                 .isEqualTo(mContext.getString(R.string.missed_call));
@@ -729,12 +689,7 @@
         NotifEvent notif1 = mNoMan.postNotif(builder);
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue());
-
-        PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tile = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
         assertThat(tile.getNotificationContent()).isEqualTo(NOTIFICATION_CONTENT_1);
         verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
@@ -758,21 +713,13 @@
         NotifEvent notif1 = mNoMan.postNotif(builder);
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue());
-        PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tileWithMissedCallOrigin = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
         assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(
                 NOTIFICATION_CONTENT_1);
         verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI),
-                        mBundleArgumentCaptor.capture());
-        Bundle bundleForSameUriTile = requireNonNull(mBundleArgumentCaptor.getValue());
-        PeopleSpaceTile tileWithSameUri = bundleForSameUriTile.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tileWithSameUri = mManager.mTiles.get(WIDGET_ID_WITH_SAME_URI);
         assertThat(tileWithSameUri.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
         assertThat(tileWithSameUri.getNotificationContent()).isEqualTo(NOTIFICATION_CONTENT_1);
         verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI),
@@ -801,19 +748,12 @@
         NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn.cloneLight(), 0);
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, times(2)).updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                mBundleArgumentCaptor.capture());
-        Bundle bundle = mBundleArgumentCaptor.getValue();
-        PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tileWithMissedCallOrigin = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(null);
         assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(null);
         verify(mAppWidgetManager, times(2)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
-        verify(mAppWidgetManager, times(2))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI),
-                        mBundleArgumentCaptor.capture());
-        Bundle bundleForSameUriTile = requireNonNull(mBundleArgumentCaptor.getValue());
-        PeopleSpaceTile tileWithSameUri = bundleForSameUriTile.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tileWithSameUri = mManager.mTiles.get(WIDGET_ID_WITH_SAME_URI);
         assertThat(tileWithSameUri.getNotificationKey()).isEqualTo(null);
         assertThat(tileWithSameUri.getNotificationContent()).isEqualTo(null);
         verify(mAppWidgetManager, times(2)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI),
@@ -847,21 +787,13 @@
         NotifEvent notif1 = mNoMan.postNotif(builder);
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue());
-        PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tileWithMissedCallOrigin = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
         assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(
                 NOTIFICATION_CONTENT_1);
         verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI),
-                        mBundleArgumentCaptor.capture());
-        Bundle bundleForSameUriTile = requireNonNull(mBundleArgumentCaptor.getValue());
-        PeopleSpaceTile tileWithSameUri = bundleForSameUriTile.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tileWithSameUri = mManager.mTiles.get(WIDGET_ID_WITH_SAME_URI);
         assertThat(tileWithSameUri.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
         assertThat(tileWithSameUri.getNotificationContent()).isEqualTo(NOTIFICATION_CONTENT_1);
         verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI),
@@ -900,20 +832,13 @@
 
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue());
-        PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tileWithMissedCallOrigin = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
         assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(
                 NOTIFICATION_CONTENT_1);
         verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
         // Do not update since notification doesn't include a Person reference.
-        verify(mAppWidgetManager, times(0))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI),
-                        any());
         verify(mAppWidgetManager, times(0)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI),
                 any());
     }
@@ -939,20 +864,13 @@
         NotifEvent notif1 = mNoMan.postNotif(builder);
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue());
-        PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tileWithMissedCallOrigin = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
         assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(
                 NOTIFICATION_CONTENT_1);
         verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
         // Do not update since missing permission to read contacts.
-        verify(mAppWidgetManager, times(0))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_DIFFERENT_URI),
-                        any());
         verify(mAppWidgetManager, times(0)).updateAppWidget(eq(WIDGET_ID_WITH_DIFFERENT_URI),
                 any());
     }
@@ -977,23 +895,14 @@
         NotifEvent notif1 = mNoMan.postNotif(builder);
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue());
-        PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tileWithMissedCallOrigin = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
         assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(
                 NOTIFICATION_CONTENT_1);
         verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
         // Do not update since missing permission to read contacts.
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI),
-                        mBundleArgumentCaptor.capture());
-        Bundle noNotificationBundle = requireNonNull(mBundleArgumentCaptor.getValue());
-        PeopleSpaceTile tileNoNotification =
-                noNotificationBundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tileNoNotification = mManager.mTiles.get(WIDGET_ID_WITH_SAME_URI);
         assertThat(tileNoNotification.getNotificationKey()).isNull();
         verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI),
                 any());
@@ -1010,21 +919,20 @@
                 .setSbn(sbn)
                 .setId(1));
         mClock.advanceTime(MIN_LINGER_DURATION);
+        long timestampBeforeNotificationClear = System.currentTimeMillis();
         NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn, 0);
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, times(2)).updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                mBundleArgumentCaptor.capture());
-        Bundle bundle = mBundleArgumentCaptor.getValue();
-        PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tile = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile.getNotificationKey()).isEqualTo(null);
+        assertThat(tile.getLastInteractionTimestamp()).isLessThan(
+                timestampBeforeNotificationClear);
         assertThat(tile.getNotificationContent()).isEqualTo(null);
         assertThat(tile.getNotificationDataUri()).isEqualTo(null);
         verify(mAppWidgetManager, times(2)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
     }
 
-
     @Test
     public void testAddThenReconfigureWidgetsUpdatesStorageCacheAndListeners()
             throws Exception {
@@ -1131,7 +1039,6 @@
     @Test
     public void testOnAppWidgetOptionsChangedNoWidgetAdded() {
         Bundle newOptions = new Bundle();
-        newOptions.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE);
         mManager.onAppWidgetOptionsChanged(SECOND_WIDGET_ID_WITH_SHORTCUT, newOptions);
 
 
@@ -1166,10 +1073,9 @@
 
         mManager.onAppWidgetOptionsChanged(SECOND_WIDGET_ID_WITH_SHORTCUT, newOptions);
 
-        verify(mAppWidgetManager, times(2)).updateAppWidgetOptions(
+        verify(mAppWidgetManager, times(1)).updateAppWidgetOptions(
                 eq(SECOND_WIDGET_ID_WITH_SHORTCUT), mBundleArgumentCaptor.capture());
-        List<Bundle> bundles = mBundleArgumentCaptor.getAllValues();
-        Bundle first = bundles.get(0);
+        Bundle first = mBundleArgumentCaptor.getValue();
         assertThat(first.getString(PeopleSpaceUtils.SHORTCUT_ID, EMPTY_STRING))
                 .isEqualTo(EMPTY_STRING);
         assertThat(first.getInt(USER_ID, INVALID_USER_ID)).isEqualTo(INVALID_USER_ID);
@@ -1177,21 +1083,6 @@
         verify(mLauncherApps, times(1)).cacheShortcuts(eq(TEST_PACKAGE_A),
                 eq(Arrays.asList(SHORTCUT_ID)), eq(UserHandle.of(0)),
                 eq(LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS));
-
-        Bundle second = bundles.get(1);
-        PeopleSpaceTile tile = second.getParcelable(OPTIONS_PEOPLE_TILE);
-        assertThat(tile.getId()).isEqualTo(SHORTCUT_ID);
-
-        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
-        assertThat(sp.getStringSet(KEY.toString(), new HashSet<>())).contains(
-                String.valueOf(SECOND_WIDGET_ID_WITH_SHORTCUT));
-        SharedPreferences widgetSp = mContext.getSharedPreferences(
-                String.valueOf(SECOND_WIDGET_ID_WITH_SHORTCUT),
-                Context.MODE_PRIVATE);
-        assertThat(widgetSp.getString(PACKAGE_NAME, EMPTY_STRING)).isEqualTo(TEST_PACKAGE_A);
-        assertThat(widgetSp.getString(PeopleSpaceUtils.SHORTCUT_ID, EMPTY_STRING))
-                .isEqualTo(SHORTCUT_ID);
-        assertThat(widgetSp.getInt(USER_ID, INVALID_USER_ID)).isEqualTo(0);
     }
 
     @Test
@@ -1306,11 +1197,7 @@
     public void testUpdateWidgetsOnStateChange() {
         mManager.updateWidgetsOnStateChange(ACTION_BOOT_COMPLETED);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        Bundle bundle = mBundleArgumentCaptor.getValue();
-        PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tile = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile.isPackageSuspended()).isFalse();
         assertThat(tile.isUserQuieted()).isFalse();
         assertThat(tile.canBypassDnd()).isFalse();
@@ -1325,11 +1212,7 @@
 
         mManager.updateWidgetsOnStateChange(ACTION_BOOT_COMPLETED);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        Bundle bundle = mBundleArgumentCaptor.getValue();
-        PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tile = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile.isPackageSuspended()).isFalse();
         assertThat(tile.isUserQuieted()).isTrue();
         assertThat(tile.getNotificationPolicyState()).isEqualTo(SHOW_CONVERSATIONS);
@@ -1341,11 +1224,7 @@
 
         mManager.updateWidgetsOnStateChange(ACTION_PACKAGES_SUSPENDED);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        Bundle bundle = mBundleArgumentCaptor.getValue();
-        PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tile = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile.isPackageSuspended()).isTrue();
         assertThat(tile.isUserQuieted()).isFalse();
         assertThat(tile.getNotificationPolicyState()).isEqualTo(SHOW_CONVERSATIONS);
@@ -1357,10 +1236,7 @@
         mManager.updateWidgetsOnStateChange(NotificationManager
                 .ACTION_INTERRUPTION_FILTER_CHANGED);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tile = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | SHOW_CONVERSATIONS);
     }
 
@@ -1375,10 +1251,7 @@
         mManager.updateWidgetsOnStateChange(NotificationManager
                 .ACTION_INTERRUPTION_FILTER_CHANGED);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tile = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | SHOW_CONVERSATIONS);
     }
 
@@ -1394,10 +1267,7 @@
         mManager.updateWidgetsOnStateChange(NotificationManager
                 .ACTION_INTERRUPTION_FILTER_CHANGED);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tile = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile.getNotificationPolicyState()).isEqualTo(
                 expected | SHOW_IMPORTANT_CONVERSATIONS);
     }
@@ -1412,10 +1282,7 @@
         mManager.updateWidgetsOnStateChange(NotificationManager
                 .ACTION_INTERRUPTION_FILTER_CHANGED);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tile = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | BLOCK_CONVERSATIONS);
     }
 
@@ -1430,10 +1297,7 @@
 
         mManager.updateWidgetsOnStateChange(ACTION_BOOT_COMPLETED);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tile = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | SHOW_CONTACTS);
     }
 
@@ -1448,10 +1312,7 @@
 
         mManager.updateWidgetsOnStateChange(ACTION_BOOT_COMPLETED);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tile = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | SHOW_STARRED_CONTACTS);
     }
 
@@ -1464,10 +1325,7 @@
         mManager.updateWidgetsOnStateChange(NotificationManager
                 .ACTION_INTERRUPTION_FILTER_CHANGED);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tile = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | BLOCK_CONVERSATIONS);
     }
 
@@ -1482,10 +1340,7 @@
 
         mManager.updateWidgetsOnStateChange(ACTION_BOOT_COMPLETED);
 
-        verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
-                        mBundleArgumentCaptor.capture());
-        PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+        PeopleSpaceTile tile = mManager.mTiles.get(WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | SHOW_CONVERSATIONS);
     }
 
@@ -1513,7 +1368,6 @@
     private void addTileForWidget(PeopleSpaceTile tile, int widgetId) throws Exception {
         setStorageForTile(tile.getId(), tile.getPackageName(), widgetId, tile.getContactUri());
         Bundle options = new Bundle();
-        options.putParcelable(OPTIONS_PEOPLE_TILE, tile);
         ConversationChannel channel = getConversationWithShortcutId(new PeopleTileKey(tile));
         when(mAppWidgetManager.getAppWidgetOptions(eq(widgetId)))
                 .thenReturn(options);
@@ -1596,6 +1450,7 @@
                 .setNotification(notification)
                 .setPkg(TEST_PACKAGE_A)
                 .setUid(0)
+                .setPostTime(SBN_POST_TIME)
                 .setUser(new UserHandle(0))
                 .build();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index 59a5f03..876acc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
+import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 import com.android.systemui.utils.os.FakeHandler;
 
@@ -66,6 +67,8 @@
     private CarrierTextManager.Builder mCarrierTextControllerBuilder;
     @Mock
     private CarrierTextManager mCarrierTextManager;
+    @Mock
+    private CarrierConfigTracker mCarrierConfigTracker;
     private TestableLooper mTestableLooper;
 
     @Before
@@ -99,7 +102,7 @@
 
         mQSCarrierGroupController = new QSCarrierGroupController.Builder(
                 mActivityStarter, handler, TestableLooper.get(this).getLooper(),
-                mNetworkController, mCarrierTextControllerBuilder, mContext)
+                mNetworkController, mCarrierTextControllerBuilder, mContext, mCarrierConfigTracker)
                 .setQSCarrierGroup(mQSCarrierGroup)
                 .build();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
index 9a1126f..bfe875c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
@@ -115,7 +115,8 @@
         actionProxyReceiver.onReceive(mContext, mIntent);
 
         verify(mMockScreenshotSmartActions, never())
-                .notifyScreenshotAction(any(Context.class), anyString(), anyString(), anyBoolean());
+                .notifyScreenshotAction(any(Context.class), anyString(), anyString(), anyBoolean(),
+                        any(Intent.class));
     }
 
     @Test
@@ -128,7 +129,7 @@
         actionProxyReceiver.onReceive(mContext, mIntent);
 
         verify(mMockScreenshotSmartActions).notifyScreenshotAction(
-                mContext, testId, ACTION_TYPE_SHARE, false);
+                mContext, testId, ACTION_TYPE_SHARE, false, null);
     }
 
     private ActionProxyReceiver constructActionProxyReceiver(boolean withStatusBar) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java
index 14c7679..664c125 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java
@@ -81,7 +81,8 @@
 
         verify(mMockExecutor, never()).execute(any(Runnable.class));
         verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction(
-                any(Context.class), any(String.class), any(String.class), anyBoolean());
+                any(Context.class), any(String.class), any(String.class), anyBoolean(),
+                any(Intent.class));
     }
 
     @Test
@@ -112,8 +113,8 @@
         }
 
         // ensure smart actions not called by default
-        verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction(
-                any(Context.class), any(String.class), any(String.class), anyBoolean());
+        verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction(any(Context.class),
+                any(String.class), any(String.class), anyBoolean(), any(Intent.class));
     }
 
     @Test
@@ -128,8 +129,8 @@
         mDeleteScreenshotReceiver.onReceive(mContext, intent);
 
         verify(mMockExecutor).execute(any(Runnable.class));
-        verify(mMockScreenshotSmartActions).notifyScreenshotAction(
-                mContext, testId, ACTION_TYPE_DELETE, false);
+        verify(mMockScreenshotSmartActions).notifyScreenshotAction(mContext, testId,
+                ACTION_TYPE_DELETE, false, null);
     }
 
     private static ContentValues getFakeContentValues() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java
index 6f3a4a1..011e6b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.PendingIntent;
 import android.content.Intent;
@@ -65,12 +66,14 @@
         String testActionType = "testActionType";
         mIntent.putExtra(EXTRA_ID, testId);
         mIntent.putExtra(EXTRA_ACTION_TYPE, testActionType);
+        Intent intent = new Intent();
+        when(mMockPendingIntent.getIntent()).thenReturn(intent);
 
         mSmartActionsReceiver.onReceive(mContext, mIntent);
 
         verify(mMockPendingIntent).send(
                 eq(mContext), eq(0), isNull(), isNull(), isNull(), isNull(), any(Bundle.class));
         verify(mMockScreenshotSmartActions).notifyScreenshotAction(
-                mContext, testId, testActionType, true);
+                mContext, testId, testActionType, true, intent);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index b2487e8..b3d52b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -178,7 +178,7 @@
 
     @Mock private NotificationsController mNotificationsController;
     @Mock private LightBarController mLightBarController;
-    @Mock private StatusBarIconController mStatusBarIconController;
+    @Mock private StatusBarSignalPolicy mStatusBarSignalPolicy;
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock private KeyguardStateController mKeyguardStateController;
     @Mock private KeyguardIndicationController mKeyguardIndicationController;
@@ -356,7 +356,7 @@
                 mLightBarController,
                 mAutoHideController,
                 mKeyguardUpdateMonitor,
-                mStatusBarIconController,
+                mStatusBarSignalPolicy,
                 mPulseExpansionHandler,
                 mNotificationWakeUpCoordinator,
                 mKeyguardBypassController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index c244290..896e330 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.phone.ongoingcall
 
+import android.app.ActivityManager
+import android.app.IActivityManager
+import android.app.IUidObserver
 import android.app.Notification
 import android.app.PendingIntent
 import android.app.Person
@@ -34,31 +37,46 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.*
 import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.eq
 import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
-import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+private const val CALL_UID = 900
+
+// A process state that represents the process being visible to the user.
+private const val PROC_STATE_VISIBLE = ActivityManager.PROCESS_STATE_TOP
+
+// A process state that represents the process being invisible to the user.
+private const val PROC_STATE_INVISIBLE = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class OngoingCallControllerTest : SysuiTestCase() {
 
+    private val clock = FakeSystemClock()
+    private val mainExecutor = FakeExecutor(clock)
+
     private lateinit var controller: OngoingCallController
     private lateinit var notifCollectionListener: NotifCollectionListener
 
     @Mock private lateinit var mockOngoingCallListener: OngoingCallListener
     @Mock private lateinit var mockActivityStarter: ActivityStarter
+    @Mock private lateinit var mockIActivityManager: IActivityManager
 
     private lateinit var chipView: LinearLayout
 
@@ -76,7 +94,12 @@
         val notificationCollection = mock(CommonNotifCollection::class.java)
 
         controller = OngoingCallController(
-                notificationCollection, featureFlags, FakeSystemClock(), mockActivityStarter)
+                notificationCollection,
+                featureFlags,
+                clock,
+                mockActivityStarter,
+                mainExecutor,
+                mockIActivityManager)
         controller.init()
         controller.addCallback(mockOngoingCallListener)
         controller.setChipView(chipView)
@@ -84,34 +107,37 @@
         val collectionListenerCaptor = ArgumentCaptor.forClass(NotifCollectionListener::class.java)
         verify(notificationCollection).addCollectionListener(collectionListenerCaptor.capture())
         notifCollectionListener = collectionListenerCaptor.value!!
+
+        `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
+                .thenReturn(PROC_STATE_INVISIBLE)
     }
 
     @Test
     fun onEntryUpdated_isOngoingCallNotif_listenerNotifiedWithRightCallTime() {
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
 
-        verify(mockOngoingCallListener).onOngoingCallStarted(anyBoolean())
+        verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
     }
 
     @Test
     fun onEntryUpdated_notOngoingCallNotif_listenerNotNotified() {
         notifCollectionListener.onEntryUpdated(createNotCallNotifEntry())
 
-        verify(mockOngoingCallListener, never()).onOngoingCallStarted(anyBoolean())
+        verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean())
     }
 
     @Test
     fun onEntryRemoved_ongoingCallNotif_listenerNotified() {
         notifCollectionListener.onEntryRemoved(createOngoingCallNotifEntry(), REASON_USER_STOPPED)
 
-        verify(mockOngoingCallListener).onOngoingCallEnded(anyBoolean())
+        verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
     }
 
     @Test
     fun onEntryRemoved_notOngoingCallNotif_listenerNotNotified() {
         notifCollectionListener.onEntryRemoved(createNotCallNotifEntry(), REASON_USER_STOPPED)
 
-        verify(mockOngoingCallListener, never()).onOngoingCallEnded(anyBoolean())
+        verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean())
     }
 
     @Test
@@ -120,13 +146,26 @@
     }
 
     @Test
-    fun hasOngoingCall_ongoingCallNotifSentAndChipViewSet_returnsTrue() {
+    fun hasOngoingCall_ongoingCallNotifSentAndCallAppNotVisible_returnsTrue() {
+        `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
+                .thenReturn(PROC_STATE_INVISIBLE)
+
         notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
 
         assertThat(controller.hasOngoingCall()).isTrue()
     }
 
     @Test
+    fun hasOngoingCall_ongoingCallNotifSentButCallAppVisible_returnsFalse() {
+        `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
+                .thenReturn(PROC_STATE_VISIBLE)
+
+        notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+        assertThat(controller.hasOngoingCall()).isFalse()
+    }
+
+    @Test
     fun hasOngoingCall_ongoingCallNotifSentButInvalidChipView_returnsFalse() {
         val invalidChipView = LinearLayout(context)
         controller.setChipView(invalidChipView)
@@ -169,7 +208,52 @@
 
         // Verify the listener was notified once for the initial call and again when the new view
         // was set.
-        verify(mockOngoingCallListener, times(2)).onOngoingCallStarted(anyBoolean())
+        verify(mockOngoingCallListener, times(2))
+                .onOngoingCallStateChanged(anyBoolean())
+    }
+
+    @Test
+    fun callProcessChangesToVisible_listenerNotified() {
+        // Start the call while the process is invisible.
+        `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
+                .thenReturn(PROC_STATE_INVISIBLE)
+        notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+        val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java)
+        verify(mockIActivityManager).registerUidObserver(
+                captor.capture(), any(), any(), nullable(String::class.java))
+        val uidObserver = captor.value
+
+        // Update the process to visible.
+        uidObserver.onUidStateChanged(CALL_UID, PROC_STATE_VISIBLE, 0, 0)
+        mainExecutor.advanceClockToLast()
+        mainExecutor.runAllReady();
+
+        // Once for when the call was started, and another time when the process visibility changes.
+        verify(mockOngoingCallListener, times(2))
+                .onOngoingCallStateChanged(anyBoolean())
+    }
+
+    @Test
+    fun callProcessChangesToInvisible_listenerNotified() {
+        // Start the call while the process is visible.
+        `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
+                .thenReturn(PROC_STATE_VISIBLE)
+        notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+        val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java)
+        verify(mockIActivityManager).registerUidObserver(
+                captor.capture(), any(), any(), nullable(String::class.java))
+        val uidObserver = captor.value
+
+        // Update the process to invisible.
+        uidObserver.onUidStateChanged(CALL_UID, PROC_STATE_INVISIBLE, 0, 0)
+        mainExecutor.advanceClockToLast()
+        mainExecutor.runAllReady();
+
+        // Once for when the call was started, and another time when the process visibility changes.
+        verify(mockOngoingCallListener, times(2))
+                .onOngoingCallStateChanged(anyBoolean())
     }
 
     private fun createOngoingCallNotifEntry(): NotificationEntry {
@@ -179,6 +263,7 @@
         val contentIntent = mock(PendingIntent::class.java)
         `when`(contentIntent.intent).thenReturn(mock(Intent::class.java))
         notificationEntryBuilder.modifyNotification(context).setContentIntent(contentIntent)
+        notificationEntryBuilder.setUid(CALL_UID)
         return notificationEntryBuilder.build()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index f33c9e8..abc66db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -76,6 +76,7 @@
 import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
 import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -124,6 +125,7 @@
     protected DeviceProvisionedListener mUserCallback;
     protected Instrumentation mInstrumentation;
     protected DemoModeController mDemoModeController;
+    protected CarrierConfigTracker mCarrierConfigTracker;
     protected FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
 
     protected int mSubId;
@@ -174,6 +176,7 @@
         mMockBd = mock(BroadcastDispatcher.class);
         mMockNsm = mock(NetworkScoreManager.class);
         mMockSubDefaults = mock(SubscriptionDefaults.class);
+        mCarrierConfigTracker = mock(CarrierConfigTracker.class);
         mNetCapabilities = new NetworkCapabilities();
         when(mMockTm.isDataCapable()).thenReturn(true);
         when(mMockTm.createForSubscriptionId(anyInt())).thenReturn(mMockTm);
@@ -231,7 +234,8 @@
                 mMockSubDefaults,
                 mMockProvisionController,
                 mMockBd,
-                mDemoModeController);
+                mDemoModeController,
+                mCarrierConfigTracker);
         setupNetworkController();
 
         // Trigger blank callbacks to always get the current state (some tests don't trigger
@@ -298,7 +302,8 @@
                         mCallbackHandler,
                         mock(AccessPointControllerImpl.class),
                         mock(DataUsageController.class), mMockSubDefaults,
-                        mock(DeviceProvisionedController.class), mMockBd, mDemoModeController);
+                        mock(DeviceProvisionedController.class), mMockBd, mDemoModeController,
+                        mCarrierConfigTracker);
 
         setupNetworkController();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 6219faf..09554e717 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -21,6 +21,7 @@
 
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.settingslib.net.DataUsageController;
+import com.android.systemui.util.CarrierConfigTracker;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -111,7 +112,8 @@
                 mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mFakeExecutor, mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
                 mock(DataUsageController.class), mMockSubDefaults,
-                mock(DeviceProvisionedController.class), mMockBd, mDemoModeController);
+                mock(DeviceProvisionedController.class), mMockBd, mDemoModeController,
+                mock(CarrierConfigTracker.class));
         setupNetworkController();
 
         setupDefaultSignal();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index 8d3e403..1e7801d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -41,6 +41,7 @@
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.R;
+import com.android.systemui.util.CarrierConfigTracker;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -66,7 +67,7 @@
                 Looper.getMainLooper(), mFakeExecutor, mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
                 mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
-                mDemoModeController);
+                mDemoModeController, mock(CarrierConfigTracker.class));
         setupNetworkController();
 
         verifyLastMobileDataIndicators(false, -1, 0);
@@ -86,7 +87,7 @@
                 Looper.getMainLooper(), mFakeExecutor, mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
                 mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
-                mDemoModeController);
+                mDemoModeController, mock(CarrierConfigTracker.class));
         mNetworkController.registerListeners();
 
         // Wait for the main looper to execute the previous command
@@ -154,7 +155,7 @@
                 Looper.getMainLooper(), mFakeExecutor, mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
                 mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
-                mDemoModeController);
+                mDemoModeController, mock(CarrierConfigTracker.class));
         setupNetworkController();
 
         // No Subscriptions.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index bfb98de..687ca60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -269,9 +269,9 @@
         }
         // Set the ImsType to be IMS_TYPE_WWAN
         setImsType(1);
+        setupDefaultSignal();
         for (int testStrength = 0;
                 testStrength < CellSignalStrength.getNumSignalStrengthLevels(); testStrength++) {
-            setupDefaultSignal();
             setLevel(testStrength);
             verifyLastCallStrength(TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[testStrength]);
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeThreadFactory.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeThreadFactory.java
index 8c92482..ce71ac8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeThreadFactory.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeThreadFactory.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.util.concurrency;
 
+import android.os.Looper;
+
 import java.util.concurrent.Executor;
 
 /**
@@ -37,4 +39,9 @@
     public DelayableExecutor buildDelayableExecutorOnNewThread(String threadName) {
         return mFakeExecutor;
     }
+
+    @Override
+    public DelayableExecutor buildDelayableExecutorOnLooper(Looper looper) {
+        return mFakeExecutor;
+    }
 }
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index d922d2b..3bb6e08 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -1839,6 +1839,8 @@
                 // For a full update we replace the RemoteViews completely.
                 widget.views = views;
             }
+            widget.views.setProviderInstanceId(UPDATE_COUNTER.get());
+
             int memoryUsage;
             if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) &&
                     (widget.views != null) &&
@@ -1939,6 +1941,9 @@
                 || widget.host.callbacks == null || widget.host.zombie) {
             return;
         }
+        if (updateViews != null) {
+            updateViews.setProviderInstanceId(requestId);
+        }
 
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = widget.host;
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index ab3060a..dc8f84a 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -33,7 +33,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
-import android.app.IOnProjectionStateChangeListener;
+import android.app.IOnProjectionStateChangedListener;
 import android.app.IUiModeManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -181,7 +181,7 @@
     private SparseArray<List<ProjectionHolder>> mProjectionHolders;
     @GuardedBy("mLock")
     @Nullable
-    private SparseArray<RemoteCallbackList<IOnProjectionStateChangeListener>> mProjectionListeners;
+    private SparseArray<RemoteCallbackList<IOnProjectionStateChangedListener>> mProjectionListeners;
 
     public UiModeManagerService(Context context) {
         this(context, /* setupWizardComplete= */ false, /* tm= */ null, new Injector());
@@ -993,11 +993,11 @@
             }
         }
 
-        public void addOnProjectionStateChangeListener(IOnProjectionStateChangeListener listener,
+        public void addOnProjectionStateChangedListener(IOnProjectionStateChangedListener listener,
                 @UiModeManager.ProjectionType int projectionType) {
             getContext().enforceCallingOrSelfPermission(
                     android.Manifest.permission.READ_PROJECTION_STATE,
-                    "registerProjectionStateListener");
+                    "addOnProjectionStateChangedListener");
             if (projectionType == PROJECTION_TYPE_NONE) {
                 return;
             }
@@ -1027,11 +1027,11 @@
         }
 
 
-        public void removeOnProjectionStateChangeListener(
-                IOnProjectionStateChangeListener listener) {
+        public void removeOnProjectionStateChangedListener(
+                IOnProjectionStateChangedListener listener) {
             getContext().enforceCallingOrSelfPermission(
                     android.Manifest.permission.READ_PROJECTION_STATE,
-                    "unregisterProjectionStateListener");
+                    "removeOnProjectionStateChangedListener");
             synchronized (mLock) {
                 if (mProjectionListeners != null) {
                     for (int i = 0; i < mProjectionListeners.size(); ++i) {
@@ -1191,7 +1191,7 @@
             // Every listener that is affected must be called back with all the state they are
             // listening for.
             if ((changedProjectionType & listenerProjectionType) != 0) {
-                RemoteCallbackList<IOnProjectionStateChangeListener> listeners =
+                RemoteCallbackList<IOnProjectionStateChangedListener> listeners =
                         mProjectionListeners.valueAt(i);
                 List<String> packageNames = new ArrayList<>();
                 @UiModeManager.ProjectionType int activeProjectionTypes =
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index b261231..5700bb36 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -155,7 +155,6 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.am.ActivityManagerService.ItemMatcher;
-import com.android.server.notification.NotificationManagerInternal;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.wm.ActivityServiceConnectionsHolder;
 
@@ -1978,17 +1977,6 @@
             showNow = isLegacyApp && mAm.mConstants.mFlagFgsNotificationDeferralApiGated;
         }
         if (!showNow) {
-            // did we already show it?
-            showNow = r.mFgsNotificationShown;
-        }
-        if (!showNow) {
-            // Is the notification already showing for any reason?
-            final NotificationManagerInternal nmi =
-                    LocalServices.getService(NotificationManagerInternal.class);
-            showNow = nmi.isNotificationShown(r.appInfo.packageName, null,
-                    r.foregroundId, UserHandle.getUserId(uid));
-        }
-        if (!showNow) {
             // has the app forced deferral?
             if (!r.foregroundNoti.isForegroundDisplayForceDeferred()) {
                 // is the notification such that it should show right away?
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 5a59eabd..bcb42bb 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -32,6 +32,7 @@
 import android.app.AnrController;
 import android.app.ApplicationErrorReport;
 import android.app.ApplicationExitInfo;
+import android.app.usage.UsageStatsManager;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
@@ -57,7 +58,9 @@
 import com.android.internal.app.ProcessMap;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
+import com.android.server.LocalServices;
 import com.android.server.PackageWatchdog;
+import com.android.server.usage.AppStandbyInternal;
 import com.android.server.wm.WindowProcessController;
 
 import java.io.FileDescriptor;
@@ -885,6 +888,16 @@
                 }
                 errState.setBad(true);
                 app.setRemoved(true);
+                final AppStandbyInternal appStandbyInternal =
+                        LocalServices.getService(AppStandbyInternal.class);
+                if (appStandbyInternal != null) {
+                    appStandbyInternal.restrictApp(
+                            // Sometimes the processName is the same as the package name, so use
+                            // that if we don't have the ApplicationInfo object.
+                            // AppStandbyController will just return if it can't find the app.
+                            app.info != null ? app.info.packageName : processName,
+                            userId, UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
+                }
                 // Don't let services in this process be restarted and potentially
                 // annoy the user repeatedly.  Unless it is persistent, since those
                 // processes run critical code.
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index cc98abf..af3f658 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3418,6 +3418,11 @@
             return;
         }
 
+        if (app.getPid() == 0 && !app.isPendingStart()) {
+            // This process has been killed and its cleanup is done, don't proceed the LRU update.
+            return;
+        }
+
         synchronized (mProcLock) {
             updateLruProcessLSP(app, client, hasActivity, hasService);
         }
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 0314510..fb919fb 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1659,13 +1659,31 @@
         final UsageStatsManagerInternal usmi =
                 LocalServices.getService(UsageStatsManagerInternal.class);
         if (usmi != null) {
+            // This method name is unfortunate. It elevates apps to a higher bucket, so it ideally
+            // should be called before we attempt to schedule the job (especially as EJ).
             usmi.reportSyncScheduled(syncOperation.owningPackage,
                     UserHandle.getUserId(syncOperation.owningUid),
                     syncOperation.isAppStandbyExempted());
         }
 
-        getJobScheduler().scheduleAsPackage(b.build(), syncOperation.owningPackage,
+        final JobInfo ji = b.build();
+        int result = getJobScheduler().scheduleAsPackage(ji, syncOperation.owningPackage,
                 syncOperation.target.userId, syncOperation.wakeLockName());
+        if (result == JobScheduler.RESULT_FAILURE && ji.isExpedited()) {
+            if (isLoggable) {
+                Slog.i(TAG, "Failed to schedule EJ for " + syncOperation.owningPackage
+                        + ". Downgrading to regular");
+            }
+            syncOperation.scheduleEjAsRegularJob = true;
+            b.setExpedited(false).setExtras(syncOperation.toJobInfoExtras());
+            result = getJobScheduler().scheduleAsPackage(b.build(), syncOperation.owningPackage,
+                    syncOperation.target.userId, syncOperation.wakeLockName());
+        }
+        if (result == JobScheduler.RESULT_FAILURE) {
+            Slog.e(TAG, "Failed to schedule job for " + syncOperation.owningPackage);
+            // TODO: notify AppStandbyController that the sync isn't actually scheduled so the
+            // bucket doesn't stay elevated
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index c51571a..1c27c65 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.input;
 
+import static android.view.Surface.ROTATION_0;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Notification;
@@ -38,6 +40,7 @@
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayViewport;
@@ -97,6 +100,7 @@
 import android.view.InputEvent;
 import android.view.InputMonitor;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.view.Surface;
 import android.view.VerifiedInputEvent;
@@ -820,6 +824,28 @@
                 && mode != InputEventInjectionSync.WAIT_FOR_RESULT) {
             throw new IllegalArgumentException("mode is invalid");
         }
+        if (ENABLE_PER_WINDOW_INPUT_ROTATION) {
+            if (event instanceof MotionEvent) {
+                final Context dispCtx = getContextForDisplay(event.getDisplayId());
+                final Display display = dispCtx.getDisplay();
+                final int rotation = display.getRotation();
+                if (rotation != ROTATION_0) {
+                    final MotionEvent motion = (MotionEvent) event;
+                    // Injections are currently expected to be in the space of the injector (ie.
+                    // usually assumed to be post-rotated). Thus we need to unrotate into raw
+                    // input coordinates for dispatch.
+                    final Point sz = new Point();
+                    display.getRealSize(sz);
+                    if ((rotation % 2) != 0) {
+                        final int tmpX = sz.x;
+                        sz.x = sz.y;
+                        sz.y = tmpX;
+                    }
+                    motion.applyTransform(MotionEvent.createRotateMatrix(
+                            (4 - rotation), sz.x, sz.y));
+                }
+            }
+        }
 
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 2ffc62a..66b23c4 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -58,13 +58,17 @@
     private static final int EVENT_LOCATION_ENABLED = 2;
     private static final int EVENT_PROVIDER_ENABLED = 3;
     private static final int EVENT_PROVIDER_MOCKED = 4;
-    private static final int EVENT_PROVIDER_REGISTER_CLIENT = 5;
-    private static final int EVENT_PROVIDER_UNREGISTER_CLIENT = 6;
-    private static final int EVENT_PROVIDER_UPDATE_REQUEST = 7;
-    private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 8;
-    private static final int EVENT_PROVIDER_DELIVER_LOCATION = 9;
-    private static final int EVENT_PROVIDER_STATIONARY_THROTTLED = 10;
-    private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 11;
+    private static final int EVENT_PROVIDER_CLIENT_REGISTER = 5;
+    private static final int EVENT_PROVIDER_CLIENT_UNREGISTER = 6;
+    private static final int EVENT_PROVIDER_CLIENT_FOREGROUND = 7;
+    private static final int EVENT_PROVIDER_CLIENT_BACKGROUND = 8;
+    private static final int EVENT_PROVIDER_CLIENT_PERMITTED = 9;
+    private static final int EVENT_PROVIDER_CLIENT_UNPERMITTED = 10;
+    private static final int EVENT_PROVIDER_UPDATE_REQUEST = 11;
+    private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 12;
+    private static final int EVENT_PROVIDER_DELIVER_LOCATION = 13;
+    private static final int EVENT_PROVIDER_STATIONARY_THROTTLED = 14;
+    private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 15;
 
     @GuardedBy("mAggregateStats")
     private final ArrayMap<String, ArrayMap<CallerIdentity, AggregateStats>> mAggregateStats;
@@ -126,13 +130,13 @@
     /** Logs a new client registration for a location provider. */
     public void logProviderClientRegistered(String provider, CallerIdentity identity,
             LocationRequest request) {
-        addLogEvent(EVENT_PROVIDER_REGISTER_CLIENT, provider, identity, request);
+        addLogEvent(EVENT_PROVIDER_CLIENT_REGISTER, provider, identity, request);
         getAggregateStats(provider, identity).markRequestAdded(request.getIntervalMillis());
     }
 
     /** Logs a client unregistration for a location provider. */
     public void logProviderClientUnregistered(String provider, CallerIdentity identity) {
-        addLogEvent(EVENT_PROVIDER_UNREGISTER_CLIENT, provider, identity);
+        addLogEvent(EVENT_PROVIDER_CLIENT_UNREGISTER, provider, identity);
         getAggregateStats(provider, identity).markRequestRemoved();
     }
 
@@ -148,14 +152,34 @@
 
     /** Logs a client for a location provider entering the foreground state. */
     public void logProviderClientForeground(String provider, CallerIdentity identity) {
+        if (Build.IS_DEBUGGABLE || D) {
+            addLogEvent(EVENT_PROVIDER_CLIENT_FOREGROUND, provider, identity);
+        }
         getAggregateStats(provider, identity).markRequestForeground();
     }
 
     /** Logs a client for a location provider leaving the foreground state. */
     public void logProviderClientBackground(String provider, CallerIdentity identity) {
+        if (Build.IS_DEBUGGABLE || D) {
+            addLogEvent(EVENT_PROVIDER_CLIENT_BACKGROUND, provider, identity);
+        }
         getAggregateStats(provider, identity).markRequestBackground();
     }
 
+    /** Logs a client for a location provider entering the permitted state. */
+    public void logProviderClientPermitted(String provider, CallerIdentity identity) {
+        if (Build.IS_DEBUGGABLE || D) {
+            addLogEvent(EVENT_PROVIDER_CLIENT_PERMITTED, provider, identity);
+        }
+    }
+
+    /** Logs a client for a location provider leaving the permitted state. */
+    public void logProviderClientUnpermitted(String provider, CallerIdentity identity) {
+        if (Build.IS_DEBUGGABLE || D) {
+            addLogEvent(EVENT_PROVIDER_CLIENT_UNPERMITTED, provider, identity);
+        }
+    }
+
     /** Logs a change to the provider request for a location provider. */
     public void logProviderUpdateRequest(String provider, ProviderRequest request) {
         addLogEvent(EVENT_PROVIDER_UPDATE_REQUEST, provider, request);
@@ -201,12 +225,24 @@
                         (Boolean) args[2]);
             case EVENT_PROVIDER_MOCKED:
                 return new ProviderMockedEvent(timeDelta, (String) args[0], (Boolean) args[1]);
-            case EVENT_PROVIDER_REGISTER_CLIENT:
-                return new ProviderRegisterEvent(timeDelta, (String) args[0], true,
+            case EVENT_PROVIDER_CLIENT_REGISTER:
+                return new ProviderClientRegisterEvent(timeDelta, (String) args[0], true,
                         (CallerIdentity) args[1], (LocationRequest) args[2]);
-            case EVENT_PROVIDER_UNREGISTER_CLIENT:
-                return new ProviderRegisterEvent(timeDelta, (String) args[0], false,
+            case EVENT_PROVIDER_CLIENT_UNREGISTER:
+                return new ProviderClientRegisterEvent(timeDelta, (String) args[0], false,
                         (CallerIdentity) args[1], null);
+            case EVENT_PROVIDER_CLIENT_FOREGROUND:
+                return new ProviderClientForegroundEvent(timeDelta, (String) args[0], true,
+                        (CallerIdentity) args[1]);
+            case EVENT_PROVIDER_CLIENT_BACKGROUND:
+                return new ProviderClientForegroundEvent(timeDelta, (String) args[0], false,
+                        (CallerIdentity) args[1]);
+            case EVENT_PROVIDER_CLIENT_PERMITTED:
+                return new ProviderClientPermittedEvent(timeDelta, (String) args[0], true,
+                        (CallerIdentity) args[1]);
+            case EVENT_PROVIDER_CLIENT_UNPERMITTED:
+                return new ProviderClientPermittedEvent(timeDelta, (String) args[0], false,
+                        (CallerIdentity) args[1]);
             case EVENT_PROVIDER_UPDATE_REQUEST:
                 return new ProviderUpdateEvent(timeDelta, (String) args[0],
                         (ProviderRequest) args[1]);
@@ -279,13 +315,13 @@
         }
     }
 
-    private static final class ProviderRegisterEvent extends ProviderEvent {
+    private static final class ProviderClientRegisterEvent extends ProviderEvent {
 
         private final boolean mRegistered;
         private final CallerIdentity mIdentity;
         @Nullable private final LocationRequest mLocationRequest;
 
-        ProviderRegisterEvent(long timeDelta, String provider, boolean registered,
+        ProviderClientRegisterEvent(long timeDelta, String provider, boolean registered,
                 CallerIdentity identity, @Nullable LocationRequest locationRequest) {
             super(timeDelta, provider);
             mRegistered = registered;
@@ -296,14 +332,52 @@
         @Override
         public String getLogString() {
             if (mRegistered) {
-                return mProvider + " provider " + "+registration " + mIdentity + " -> "
+                return mProvider + " provider +registration " + mIdentity + " -> "
                         + mLocationRequest;
             } else {
-                return mProvider + " provider " + "-registration " + mIdentity;
+                return mProvider + " provider -registration " + mIdentity;
             }
         }
     }
 
+    private static final class ProviderClientForegroundEvent extends ProviderEvent {
+
+        private final boolean mForeground;
+        private final CallerIdentity mIdentity;
+
+        ProviderClientForegroundEvent(long timeDelta, String provider, boolean foreground,
+                CallerIdentity identity) {
+            super(timeDelta, provider);
+            mForeground = foreground;
+            mIdentity = identity;
+        }
+
+        @Override
+        public String getLogString() {
+            return mProvider + " provider client " + mIdentity + " -> "
+                    + (mForeground ? "foreground" : "background");
+        }
+    }
+
+    private static final class ProviderClientPermittedEvent extends ProviderEvent {
+
+        private final boolean mPermitted;
+        private final CallerIdentity mIdentity;
+
+        ProviderClientPermittedEvent(long timeDelta, String provider, boolean permitted,
+                CallerIdentity identity) {
+            super(timeDelta, provider);
+            mPermitted = permitted;
+            mIdentity = identity;
+        }
+
+        @Override
+        public String getLogString() {
+            return mProvider + " provider client " + mIdentity + " -> "
+                    + (mPermitted ? "permitted" : "unpermitted");
+        }
+    }
+
     private static final class ProviderUpdateEvent extends ProviderEvent {
 
         private final ProviderRequest mRequest;
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 4b772f2..9474f5d 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -523,6 +523,13 @@
                 }
 
                 mPermitted = permitted;
+
+                if (mForeground) {
+                    EVENT_LOG.logProviderClientPermitted(mName, getIdentity());
+                } else {
+                    EVENT_LOG.logProviderClientUnpermitted(mName, getIdentity());
+                }
+
                 return true;
             }
 
diff --git a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
index b9822fcb..d285c43 100644
--- a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
+++ b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
@@ -258,16 +258,16 @@
                 if (mActiveAudioUids.size() > 0
                         && !mActiveAudioUids.contains(mSortedAudioPlaybackClientUids.get(0))) {
                     int firstActiveUid = -1;
-                    int firatActiveUidIndex = -1;
+                    int firstActiveUidIndex = -1;
                     for (int i = 1; i < mSortedAudioPlaybackClientUids.size(); ++i) {
                         int uid = mSortedAudioPlaybackClientUids.get(i);
                         if (mActiveAudioUids.contains(uid)) {
-                            firatActiveUidIndex = i;
+                            firstActiveUidIndex = i;
                             firstActiveUid = uid;
                             break;
                         }
                     }
-                    for (int i = firatActiveUidIndex; i > 0; --i) {
+                    for (int i = firstActiveUidIndex; i > 0; --i) {
                         mSortedAudioPlaybackClientUids.set(i,
                                 mSortedAudioPlaybackClientUids.get(i - 1));
                     }
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 384bc99..9f02c3c 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -907,6 +907,8 @@
                     mActiveBluetoothDevice = btDevice;
                     mGlobalBluetoothA2dpOn = btDevice != null;
                     if (wasA2dpOn != mGlobalBluetoothA2dpOn) {
+                        Slog.d(TAG, "GlobalBluetoothA2dpOn is changed to '"
+                                + mGlobalBluetoothA2dpOn + "'");
                         UserRecord userRecord = mUserRecords.get(mCurrentUserId);
                         if (userRecord != null) {
                             for (ClientRecord cr : userRecord.mClientRecords) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index 0528b95..dc9839c 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -30,9 +30,6 @@
     void cancelNotification(String pkg, String basePkg, int callingUid, int callingPid,
             String tag, int id, int userId);
 
-    /** is the given notification currently showing? */
-    boolean isNotificationShown(String pkg, String tag, int notificationId, int userId);
-
     void removeForegroundServiceFlagFromNotification(String pkg, int notificationId, int userId);
 
     void onConversationRemoved(String pkg, int uid, Set<String> shortcuts);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 76aba25..53e3a0e 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6054,13 +6054,6 @@
         }
 
         @Override
-        public boolean isNotificationShown(String pkg, String tag, int notificationId, int userId) {
-            synchronized (mNotificationLock) {
-                return findNotificationLocked(pkg, tag, notificationId, userId) != null;
-            }
-        }
-
-        @Override
         public void removeForegroundServiceFlagFromNotification(String pkg, int notificationId,
                 int userId) {
             checkCallerIsSystem();
@@ -8310,6 +8303,21 @@
             int rank, int count, boolean wasPosted, String listenerName) {
         final String canceledKey = r.getKey();
 
+        // Get pending intent used to create alarm, use FLAG_NO_CREATE if PendingIntent
+        // does not already exist, then null will be returned.
+        final PendingIntent pi = PendingIntent.getBroadcast(getContext(),
+                REQUEST_CODE_TIMEOUT,
+                new Intent(ACTION_NOTIFICATION_TIMEOUT)
+                        .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
+                                .appendPath(r.getKey()).build())
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
+                PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE);
+
+        // Cancel alarm corresponding to pi.
+        if (pi != null) {
+            mAlarmManager.cancel(pi);
+        }
+
         // Record caller.
         recordCallerLocked(r);
 
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index b3fa98e..1bfa72f 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2180,23 +2180,30 @@
         }
 
         final int callingUid = Binder.getCallingUid();
-        final int userId = UserHandle.getUserId(newPackage.getUid());
-        int numRequestedPermissions = newPackage.getRequestedPermissions().size();
-        for (int i = 0; i < numRequestedPermissions; i++) {
-            PermissionInfo permInfo = getPermissionInfo(newPackage.getRequestedPermissions().get(i),
-                    newPackage.getPackageName(), 0);
-            if (permInfo == null || !STORAGE_PERMISSIONS.contains(permInfo.name)) {
-                continue;
+        for (int userId: getAllUserIds()) {
+            int numRequestedPermissions = newPackage.getRequestedPermissions().size();
+            for (int i = 0; i < numRequestedPermissions; i++) {
+                PermissionInfo permInfo = getPermissionInfo(
+                        newPackage.getRequestedPermissions().get(i),
+                        newPackage.getPackageName(), 0);
+                if (permInfo == null || !STORAGE_PERMISSIONS.contains(permInfo.name)) {
+                    continue;
+                }
+
+                EventLog.writeEvent(0x534e4554, "171430330", newPackage.getUid(),
+                        "Revoking permission " + permInfo.name + " from package "
+                                + newPackage.getPackageName() + " as either the sdk downgraded "
+                                + downgradedSdk + " or newly requested legacy full storage "
+                                + newlyRequestsLegacy);
+
+                try {
+                    revokeRuntimePermissionInternal(newPackage.getPackageName(), permInfo.name,
+                            false, callingUid, userId, null, mDefaultPermissionCallback);
+                } catch (IllegalStateException | SecurityException e) {
+                    Log.e(TAG, "unable to revoke " + permInfo.name + " for "
+                            + newPackage.getPackageName() + " user " + userId, e);
+                }
             }
-
-            EventLog.writeEvent(0x534e4554, "171430330", newPackage.getUid(),
-                    "Revoking permission " + permInfo.name + " from package "
-                            + newPackage.getPackageName() + " as either the sdk downgraded "
-                            + downgradedSdk + " or newly requested legacy full storage "
-                            + newlyRequestsLegacy);
-
-            revokeRuntimePermissionInternal(newPackage.getPackageName(), permInfo.name,
-                    false, callingUid, userId, null, mDefaultPermissionCallback);
         }
 
     }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 27f5350..0a65c23 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -379,6 +379,7 @@
     private DisplayFoldController mDisplayFoldController;
     AppOpsManager mAppOpsManager;
     PackageManager mPackageManager;
+    SideFpsEventHandler mSideFpsEventHandler;
     private boolean mHasFeatureAuto;
     private boolean mHasFeatureWatch;
     private boolean mHasFeatureLeanback;
@@ -928,6 +929,11 @@
         } else if (count == 3) {
             powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior);
         } else if (interactive && !beganFromNonInteractive) {
+            if (mSideFpsEventHandler.onSinglePressDetected(eventTime)) {
+                Slog.i(TAG, "Suppressing power key because the user is interacting with the "
+                        + "fingerprint sensor");
+                return;
+            }
             switch (mShortPressOnPowerBehavior) {
                 case SHORT_PRESS_POWER_NOTHING:
                     break;
@@ -1803,6 +1809,7 @@
                 });
         initKeyCombinationRules();
         initSingleKeyGestureRules();
+        mSideFpsEventHandler = new SideFpsEventHandler(mContext, mHandler, mPowerManager);
     }
 
     private void initKeyCombinationRules() {
@@ -4668,6 +4675,7 @@
                 mKeyguardDelegate.onBootCompleted();
             }
         }
+        mSideFpsEventHandler.onFingerprintSensorReady();
         startedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
         finishedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
 
diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
new file mode 100644
index 0000000..7c0005c
--- /dev/null
+++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import static android.hardware.fingerprint.FingerprintStateListener.STATE_ENROLLING;
+import static android.hardware.fingerprint.FingerprintStateListener.STATE_IDLE;
+
+import android.annotation.NonNull;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintStateListener;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.view.WindowManager;
+
+import com.android.internal.R;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Defines behavior for handling interactions between power button events and
+ * fingerprint-related operations, for devices where the fingerprint sensor (side fps)
+ * lives on the power button.
+ */
+public class SideFpsEventHandler {
+    @NonNull private final Context mContext;
+    @NonNull private final Handler mHandler;
+    @NonNull private final PowerManager mPowerManager;
+    @NonNull private final AtomicBoolean mIsSideFps;
+    @NonNull private final AtomicBoolean mSideFpsEventHandlerReady;
+
+    private @FingerprintStateListener.State int mFingerprintState;
+
+    SideFpsEventHandler(Context context, Handler handler, PowerManager powerManager) {
+        mContext = context;
+        mHandler = handler;
+        mPowerManager = powerManager;
+        mFingerprintState = STATE_IDLE;
+        mIsSideFps = new AtomicBoolean(false);
+        mSideFpsEventHandlerReady = new AtomicBoolean(false);
+    }
+
+    /**
+     * Called from {@link PhoneWindowManager} after power button is pressed. Checks fingerprint
+     * sensor state and if mFingerprintState = STATE_ENROLLING, displays a dialog confirming intent
+     * to turn screen off. If confirmed, the device goes to sleep, and if canceled, the dialog is
+     * dismissed.
+     * @param eventTime powerPress event time
+     * @return true if powerPress was consumed, false otherwise
+     */
+    public boolean onSinglePressDetected(long eventTime) {
+        if (!mSideFpsEventHandlerReady.get() || !mIsSideFps.get()
+                || mFingerprintState != STATE_ENROLLING) {
+            return false;
+        }
+        mHandler.post(() -> {
+            Dialog confirmScreenOffDialog = new AlertDialog.Builder(mContext)
+                    .setTitle(R.string.fp_enrollment_powerbutton_intent_title)
+                    .setMessage(R.string.fp_enrollment_powerbutton_intent_message)
+                    .setPositiveButton(
+                            R.string.fp_enrollment_powerbutton_intent_positive_button,
+                            new DialogInterface.OnClickListener() {
+                                @Override
+                                public void onClick(DialogInterface dialog, int which) {
+                                    dialog.dismiss();
+                                    mPowerManager.goToSleep(
+                                            eventTime,
+                                            PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
+                                            0 /* flags */
+                                    );
+                                }
+                            })
+                    .setNegativeButton(
+                            R.string.fp_enrollment_powerbutton_intent_negative_button,
+                            new DialogInterface.OnClickListener() {
+                                @Override
+                                public void onClick(DialogInterface dialog, int which) {
+                                    dialog.dismiss();
+                                }
+                            })
+                    .setCancelable(false)
+                    .create();
+            confirmScreenOffDialog.getWindow().setType(
+                    WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+            confirmScreenOffDialog.show();
+        });
+        return true;
+    }
+
+    /**
+     * Awaits notification from PhoneWindowManager that fingerprint service is ready
+     * to send updates about power button fps sensor state. Then configures a
+     * FingerprintStateListener to receive and record updates to fps state, and
+     * registers the FingerprintStateListener in FingerprintManager.
+     */
+    public void onFingerprintSensorReady() {
+        final PackageManager pm = mContext.getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) return;
+        FingerprintManager fingerprintManager =
+                mContext.getSystemService(FingerprintManager.class);
+        fingerprintManager.addAuthenticatorsRegisteredCallback(
+                new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+                    @Override
+                    public void onAllAuthenticatorsRegistered(
+                            List<FingerprintSensorPropertiesInternal> sensors) {
+                        mIsSideFps.set(fingerprintManager.isPowerbuttonFps());
+                        FingerprintStateListener fingerprintStateListener =
+                                new FingerprintStateListener() {
+                            @Override
+                            public void onStateChanged(
+                                    @FingerprintStateListener.State int newState) {
+                                mFingerprintState = newState;
+                            }
+                        };
+                        fingerprintManager.registerFingerprintStateListener(
+                                fingerprintStateListener);
+                        mSideFpsEventHandlerReady.set(true);
+                    }
+                });
+    }
+}
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
index 03584b9..68e7bdb 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
@@ -256,7 +256,7 @@
     private final ContentResolver mContentResolver;
     private final BatterySavingStats mBatterySavingStats;
 
-    private final UiModeManager.OnProjectionStateChangeListener mOnProjectionStateChangeListener =
+    private final UiModeManager.OnProjectionStateChangedListener mOnProjectionStateChangedListener =
             (t, pkgs) -> mAutomotiveProjectionActive.update(!pkgs.isEmpty());
 
     @GuardedBy("mLock")
@@ -292,8 +292,8 @@
         mAccessibilityEnabled.initialize(acm.isEnabled());
 
         UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class);
-        uiModeManager.addOnProjectionStateChangeListener(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE,
-                mContext.getMainExecutor(), mOnProjectionStateChangeListener);
+        uiModeManager.addOnProjectionStateChangedListener(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE,
+                mContext.getMainExecutor(), mOnProjectionStateChangedListener);
         mAutomotiveProjectionActive.initialize(
                 uiModeManager.getActiveProjectionTypes() != UiModeManager.PROJECTION_TYPE_NONE);
 
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 203214d..30f69dd79 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2861,6 +2861,10 @@
         mImmersiveModeConfirmation.onLockTaskModeChangedLw(lockTaskState);
     }
 
+    boolean onSystemUiSettingsChanged() {
+        return mImmersiveModeConfirmation.onSettingChanged(mService.mCurrentUserId);
+    }
+
     /**
      * Request a screenshot be taken.
      *
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index b94bc5b..747d365 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -148,6 +148,15 @@
         }
     }
 
+    boolean onSettingChanged(int currentUserId) {
+        final boolean changed = loadSetting(currentUserId, mContext);
+        // Remove the window if the setting changes to be confirmed.
+        if (changed && sConfirmed) {
+            mHandler.sendEmptyMessage(H.HIDE);
+        }
+        return changed;
+    }
+
     void immersiveModeChangedLw(int rootDisplayAreaId, boolean isImmersiveMode,
             boolean userSetupComplete, boolean navBarEmpty) {
         mHandler.removeMessages(H.SHOW);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1657a13..d43a763 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -818,7 +818,7 @@
             }
 
             if (mImmersiveModeConfirmationsUri.equals(uri) || mPolicyControlUri.equals(uri)) {
-                updateSystemUiSettings();
+                updateSystemUiSettings(true /* handleChange */);
                 return;
             }
 
@@ -874,17 +874,22 @@
         }
 
         void loadSettings() {
-            updateSystemUiSettings();
+            updateSystemUiSettings(false /* handleChange */);
             updatePointerLocation();
         }
 
-        void updateSystemUiSettings() {
-            boolean changed;
+        void updateSystemUiSettings(boolean handleChange) {
             synchronized (mGlobalLock) {
-                changed = ImmersiveModeConfirmation.loadSetting(mCurrentUserId, mContext);
-            }
-            if (changed) {
-                updateRotation(false /* alwaysSendConfiguration */, false /* forceRelayout */);
+                boolean changed = false;
+                if (handleChange) {
+                    changed = getDefaultDisplayContentLocked().getDisplayPolicy()
+                            .onSystemUiSettingsChanged();
+                } else {
+                    ImmersiveModeConfirmation.loadSetting(mCurrentUserId, mContext);
+                }
+                if (changed) {
+                    mWindowPlacerLocked.requestTraversal();
+                }
             }
         }
 
@@ -2990,7 +2995,7 @@
 
     @Override
     public void onUserSwitched() {
-        mSettingsObserver.updateSystemUiSettings();
+        mSettingsObserver.updateSystemUiSettings(true /* handleChange */);
         synchronized (mGlobalLock) {
             // force a re-application of focused window sysui visibility on each display.
             mRoot.forAllDisplayPolicies(DisplayPolicy::resetSystemUiVisibilityLw);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9de5058..ef7360d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4547,30 +4547,6 @@
     }
 
     @Override
-    public boolean isProfileActivePasswordSufficientForParent(int userHandle) {
-        if (!mHasFeature) {
-            return true;
-        }
-        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
-
-        final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
-        Preconditions.checkCallAuthorization(isManagedProfile(userHandle),
-                "can not call APIs refering to the parent profile outside a managed profile, "
-                        + "userId = %d", userHandle);
-
-        synchronized (getLockObject()) {
-            final int targetUser = getProfileParentId(userHandle);
-            enforceUserUnlocked(targetUser, false);
-            int credentialOwner = getCredentialOwner(userHandle, false);
-            DevicePolicyData policy = getUserDataUnchecked(credentialOwner);
-            PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner);
-            return isActivePasswordSufficientForUserLocked(
-                    policy.mPasswordValidAtLastCheckpoint, metrics, targetUser);
-        }
-    }
-
-    @Override
     public boolean isPasswordSufficientAfterProfileUnification(int userHandle, int profileUser) {
         if (!mHasFeature) {
             return true;
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index d913d4e..7189fb4 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -1149,11 +1149,11 @@
                 if (conversationInfo == null) {
                     return;
                 }
+                if (DEBUG) Log.d(TAG, "Last event from notification: " + sbn.getPostTime());
                 ConversationInfo updated = new ConversationInfo.Builder(conversationInfo)
                         .setLastEventTimestamp(sbn.getPostTime())
                         .setParentNotificationChannelId(sbn.getNotification().getChannelId())
                         .build();
-                // Don't update listeners on notifications posted.
                 packageData.getConversationStore().addOrUpdate(updated);
 
                 EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
@@ -1186,9 +1186,19 @@
             if (reason != REASON_CLICK || packageData == null) {
                 return;
             }
+            long currentTime = System.currentTimeMillis();
+            ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
+            if (conversationInfo == null) {
+                return;
+            }
+            if (DEBUG) Log.d(TAG, "Last event from notification removed: " + currentTime);
+            ConversationInfo updated = new ConversationInfo.Builder(conversationInfo)
+                    .setLastEventTimestamp(currentTime)
+                    .build();
+            packageData.getConversationStore().addOrUpdate(updated);
+
             EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
                     EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
-            long currentTime = System.currentTimeMillis();
             eventHistory.addEvent(new Event(currentTime, Event.TYPE_NOTIFICATION_OPENED));
         }
 
@@ -1265,6 +1275,7 @@
         public void onEvent(PackageData packageData, ConversationInfo conversationInfo,
                 Event event) {
             if (event.getType() == Event.TYPE_IN_APP_CONVERSATION) {
+                if (DEBUG) Log.d(TAG, "Last event from in-app: " + event.getTimestamp());
                 ConversationInfo updated = new ConversationInfo.Builder(conversationInfo)
                         .setLastEventTimestamp(event.getTimestamp())
                         .build();
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 6468790..029930a 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
@@ -68,7 +68,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
@@ -475,7 +474,6 @@
         expectedRegular.add(thr);
         expectedRegular.add(two);
         expectedRegular.add(one);
-        expectedEJ.add(fiv); // EJ list should be unaffected
         expectedEJ.add(fou);
         expectedEJ.add(one);
         mQuotaController.saveTimingSession(0, "com.android.test", fiv, false);
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 6c1e915b..b112f3fc 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -693,7 +693,6 @@
         NotificationListenerService listenerService =
                 mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
         listenerService.onNotificationPosted(mStatusBarNotification);
-
         ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
                 TEST_SHORTCUT_ID);
 
@@ -1361,6 +1360,27 @@
     }
 
     @Test
+    public void testNotificationRemoved() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+
+        NotificationListenerService listenerService =
+                mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+        listenerService.onNotificationPosted(mStatusBarNotification);
+        listenerService.onNotificationRemoved(mStatusBarNotification, null,
+                NotificationListenerService.REASON_CANCEL);
+
+        ConversationInfo conversationInfo = mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)
+                .getConversationStore()
+                .getConversation(TEST_SHORTCUT_ID);
+        assertEquals(conversationInfo.getLastEventTimestamp(), System.currentTimeMillis());
+    }
+
+    @Test
     public void testRemoveRecentConversation() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 81c237b..4b3771b 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -55,7 +55,7 @@
 
 import android.Manifest;
 import android.app.AlarmManager;
-import android.app.IOnProjectionStateChangeListener;
+import android.app.IOnProjectionStateChangedListener;
 import android.app.IUiModeManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -669,46 +669,46 @@
     }
 
     @Test
-    public void addOnProjectionStateChangeListener_enforcesReadProjStatePermission() {
+    public void addOnProjectionStateChangedListener_enforcesReadProjStatePermission() {
         doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
                 eq(android.Manifest.permission.READ_PROJECTION_STATE), any());
-        IOnProjectionStateChangeListener listener = mock(IOnProjectionStateChangeListener.class);
+        IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class);
 
-        assertThrows(SecurityException.class, () -> mService.addOnProjectionStateChangeListener(
+        assertThrows(SecurityException.class, () -> mService.addOnProjectionStateChangedListener(
                 listener, PROJECTION_TYPE_ALL));
     }
 
     @Test
-    public void addOnProjectionStateChangeListener_callsListenerIfProjectionActive()
+    public void addOnProjectionStateChangedListener_callsListenerIfProjectionActive()
             throws Exception {
         when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
         mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
         assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
 
-        IOnProjectionStateChangeListener listener = mock(IOnProjectionStateChangeListener.class);
+        IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class);
         when(listener.asBinder()).thenReturn(mBinder);  // Any binder will do
-        mService.addOnProjectionStateChangeListener(listener, PROJECTION_TYPE_ALL);
+        mService.addOnProjectionStateChangedListener(listener, PROJECTION_TYPE_ALL);
         verify(listener).onProjectionStateChanged(eq(PROJECTION_TYPE_AUTOMOTIVE),
                 eq(List.of(PACKAGE_NAME)));
     }
 
     @Test
-    public void removeOnProjectionStateChangeListener_enforcesReadProjStatePermission() {
+    public void removeOnProjectionStateChangedListener_enforcesReadProjStatePermission() {
         doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
                 eq(android.Manifest.permission.READ_PROJECTION_STATE), any());
-        IOnProjectionStateChangeListener listener = mock(IOnProjectionStateChangeListener.class);
+        IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class);
 
-        assertThrows(SecurityException.class, () -> mService.removeOnProjectionStateChangeListener(
+        assertThrows(SecurityException.class, () -> mService.removeOnProjectionStateChangedListener(
                 listener));
     }
 
     @Test
-    public void removeOnProjectionStateChangeListener() throws Exception {
-        IOnProjectionStateChangeListener listener = mock(IOnProjectionStateChangeListener.class);
+    public void removeOnProjectionStateChangedListener() throws Exception {
+        IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class);
         when(listener.asBinder()).thenReturn(mBinder); // Any binder will do.
-        mService.addOnProjectionStateChangeListener(listener, PROJECTION_TYPE_ALL);
+        mService.addOnProjectionStateChangedListener(listener, PROJECTION_TYPE_ALL);
 
-        mService.removeOnProjectionStateChangeListener(listener);
+        mService.removeOnProjectionStateChangedListener(listener);
         // Now set automotive projection, should not call back.
         when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
         mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
@@ -716,10 +716,10 @@
     }
 
     @Test
-    public void projectionStateChangeListener_calledWhenStateChanges() throws Exception {
-        IOnProjectionStateChangeListener listener = mock(IOnProjectionStateChangeListener.class);
+    public void projectionStateChangedListener_calledWhenStateChanges() throws Exception {
+        IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class);
         when(listener.asBinder()).thenReturn(mBinder); // Any binder will do.
-        mService.addOnProjectionStateChangeListener(listener, PROJECTION_TYPE_ALL);
+        mService.addOnProjectionStateChangedListener(listener, PROJECTION_TYPE_ALL);
         verify(listener, atLeastOnce()).asBinder(); // Called twice during register.
 
         // No calls initially, no projection state set.
@@ -748,19 +748,19 @@
     }
 
     @Test
-    public void projectionStateChangeListener_calledForAnyRelevantStateChange() throws Exception {
+    public void projectionStateChangedListener_calledForAnyRelevantStateChange() throws Exception {
         int fakeProjectionType = 0x0002;
         int otherFakeProjectionType = 0x0004;
         String otherPackageName = "Internet Arms";
         when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
         when(mPackageManager.getPackageUid(otherPackageName, 0))
                 .thenReturn(TestInjector.CALLING_UID);
-        IOnProjectionStateChangeListener listener = mock(IOnProjectionStateChangeListener.class);
+        IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class);
         when(listener.asBinder()).thenReturn(mBinder); // Any binder will do.
-        IOnProjectionStateChangeListener listener2 = mock(IOnProjectionStateChangeListener.class);
+        IOnProjectionStateChangedListener listener2 = mock(IOnProjectionStateChangedListener.class);
         when(listener2.asBinder()).thenReturn(mBinder); // Any binder will do.
-        mService.addOnProjectionStateChangeListener(listener, fakeProjectionType);
-        mService.addOnProjectionStateChangeListener(listener2,
+        mService.addOnProjectionStateChangedListener(listener, fakeProjectionType);
+        mService.addOnProjectionStateChangedListener(listener2,
                 fakeProjectionType | otherFakeProjectionType);
         verify(listener, atLeastOnce()).asBinder(); // Called twice during register.
         verify(listener2, atLeastOnce()).asBinder(); // Called twice during register.
@@ -795,11 +795,11 @@
     }
 
     @Test
-    public void projectionStateChangeListener_unregisteredOnDeath() throws Exception {
-        IOnProjectionStateChangeListener listener = mock(IOnProjectionStateChangeListener.class);
+    public void projectionStateChangedListener_unregisteredOnDeath() throws Exception {
+        IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class);
         IBinder listenerBinder = mock(IBinder.class);
         when(listener.asBinder()).thenReturn(listenerBinder);
-        mService.addOnProjectionStateChangeListener(listener, PROJECTION_TYPE_ALL);
+        mService.addOnProjectionStateChangedListener(listener, PROJECTION_TYPE_ALL);
         ArgumentCaptor<IBinder.DeathRecipient> listenerDeathRecipient = ArgumentCaptor.forClass(
                 IBinder.DeathRecipient.class);
         verify(listenerBinder).linkToDeath(listenerDeathRecipient.capture(), anyInt());
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 04db686..de4698d 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -54,11 +54,8 @@
         <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityRequestedOrientationChange" />
         <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityTaskChangeCallbacks" />
         <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityTaskDescriptionChange" />
-        <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityViewTestActivity" />
-        <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityInActivityView"
+        <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityInVirtualDisplay"
                   android:resizeableActivity="true" />
-        <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityLaunchesNewActivityInActivityView"
-            android:resizeableActivity="true" />
         <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$LandscapeActivity"
                   android:screenOrientation="sensorLandscape"
                   android:showWhenLocked="true"
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index d139141..a1d0eb8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -16,7 +16,9 @@
 
 package com.android.server.wm;
 
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
@@ -31,7 +33,6 @@
 import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
-import android.app.ActivityView;
 import android.app.ITaskStackListener;
 import android.app.Instrumentation.ActivityMonitor;
 import android.app.TaskStackListener;
@@ -39,6 +40,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -46,13 +51,16 @@
 import android.text.TextUtils;
 import android.view.Display;
 import android.view.ViewGroup;
+import android.widget.LinearLayout;
 
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 
 import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 
+import java.util.Arrays;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -65,15 +73,49 @@
 @MediumTest
 public class TaskStackChangedListenerTest {
 
+    private static final int VIRTUAL_DISPLAY_WIDTH = 800;
+    private static final int VIRTUAL_DISPLAY_HEIGHT = 600;
+    private static final int VIRTUAL_DISPLAY_DENSITY = 160;
+
     private ITaskStackListener mTaskStackListener;
+    private DisplayManager mDisplayManager;
+    private VirtualDisplay mVirtualDisplay;
 
     private static final int WAIT_TIMEOUT_MS = 5000;
     private static final Object sLock = new Object();
 
+    @Before
+    public void setUp() {
+        mDisplayManager = getInstrumentation().getContext().getSystemService(
+                DisplayManager.class);
+        mVirtualDisplay = createVirtualDisplay(
+                getClass().getSimpleName() + "_virtualDisplay",
+                VIRTUAL_DISPLAY_WIDTH, VIRTUAL_DISPLAY_HEIGHT, VIRTUAL_DISPLAY_DENSITY);
+    }
+
     @After
     public void tearDown() throws Exception {
         ActivityTaskManager.getService().unregisterTaskStackListener(mTaskStackListener);
         mTaskStackListener = null;
+        mVirtualDisplay.release();
+    }
+
+    private VirtualDisplay createVirtualDisplay(String name, int width, int height, int density) {
+        VirtualDisplay virtualDisplay = null;
+        try (ImageReader reader = ImageReader.newInstance(width, height,
+                /* format= */ PixelFormat.RGBA_8888, /* maxImages= */ 2)) {
+            int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+                    | VIRTUAL_DISPLAY_FLAG_PUBLIC;
+            virtualDisplay = mDisplayManager.createVirtualDisplay(
+                    name, width, height, density, reader.getSurface(), flags);
+            virtualDisplay.setSurface(reader.getSurface());
+        }
+        assertTrue("display id must be unique",
+                virtualDisplay.getDisplay().getDisplayId() != Display.DEFAULT_DISPLAY);
+        assertNotNull("display must be registered",
+                Arrays.asList(mDisplayManager.getDisplays()).stream().filter(
+                        d -> d.getName().equals(name)).findAny());
+        return virtualDisplay;
     }
 
     @Test
@@ -235,48 +277,19 @@
         assertTrue(activity.mOnDetachedFromWindowCalled);
     }
 
-    public static class ActivityLaunchesNewActivityInActivityView extends TestActivity {
-        private boolean mActivityBLaunched = false;
-
-        @Override
-        protected void onPostResume() {
-            super.onPostResume();
-            if (mActivityBLaunched) {
-                return;
-            }
-            mActivityBLaunched = true;
-            startActivity(new Intent(this, ActivityB.class));
-        }
-    }
-
     @Test
     public void testTaskDisplayChanged() throws Exception {
-        final CountDownLatch activityViewReadyLatch = new CountDownLatch(1);
-        final ActivityViewTestActivity activity =
-                (ActivityViewTestActivity) startTestActivity(ActivityViewTestActivity.class);
-        final ActivityView activityView = activity.getActivityView();
-        activityView.setCallback(new ActivityView.StateCallback() {
-            @Override
-            public void onActivityViewReady(ActivityView view) {
-                activityViewReadyLatch.countDown();
-            }
-            @Override
-            public void onActivityViewDestroyed(ActivityView view) {}
-        });
-        waitForCallback(activityViewReadyLatch);
+        int virtualDisplayId = mVirtualDisplay.getDisplay().getDisplayId();
 
-        // Launch a Activity inside ActivityView.
+        // Launch a Activity inside VirtualDisplay
+        CountDownLatch displayChangedLatch1 = new CountDownLatch(1);
         final Object[] params1 = new Object[1];
-        final CountDownLatch displayChangedLatch1 = new CountDownLatch(1);
-        final int activityViewDisplayId = activityView.getVirtualDisplayId();
-        registerTaskStackChangedListener(
-                new TaskDisplayChangedListener(
-                        activityViewDisplayId, params1, displayChangedLatch1));
+        registerTaskStackChangedListener(new TaskDisplayChangedListener(
+                virtualDisplayId, params1, displayChangedLatch1));
+        ActivityOptions options1 = ActivityOptions.makeBasic().setLaunchDisplayId(virtualDisplayId);
         int taskId1;
-        ActivityOptions options1 = ActivityOptions.makeBasic()
-                .setLaunchDisplayId(activityView.getVirtualDisplayId());
         synchronized (sLock) {
-            taskId1 = startTestActivity(ActivityInActivityView.class, options1).getTaskId();
+            taskId1 = startTestActivity(ActivityInVirtualDisplay.class, options1).getTaskId();
         }
         waitForCallback(displayChangedLatch1);
 
@@ -292,7 +305,7 @@
         ActivityOptions options2 = ActivityOptions.makeBasic()
                 .setLaunchDisplayId(Display.DEFAULT_DISPLAY);
         synchronized (sLock) {
-            taskId2 = startTestActivity(ActivityInActivityView.class, options2).getTaskId();
+            taskId2 = startTestActivity(ActivityInVirtualDisplay.class, options2).getTaskId();
         }
         waitForCallback(displayChangedLatch2);
 
@@ -447,8 +460,7 @@
         }
     }
 
-    public static class ActivityA extends TestActivity {
-    }
+    public static class ActivityA extends TestActivity {}
 
     public static class ActivityB extends TestActivity {
 
@@ -500,30 +512,20 @@
         }
     }
 
-    public static class ActivityViewTestActivity extends TestActivity {
-        private ActivityView mActivityView;
+    public static class ActivityInVirtualDisplay extends TestActivity {
 
         @Override
         public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
 
-            mActivityView = new ActivityView.Builder(this).build();
-            setContentView(mActivityView);
-
-            ViewGroup.LayoutParams layoutParams = mActivityView.getLayoutParams();
-            layoutParams.width = MATCH_PARENT;
-            layoutParams.height = MATCH_PARENT;
-            mActivityView.requestLayout();
-        }
-
-        ActivityView getActivityView() {
-            return mActivityView;
+            LinearLayout layout = new LinearLayout(this);
+            layout.setLayoutParams(new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT));
+            setContentView(layout);
         }
     }
 
-    // Activity that has {@link android.R.attr#resizeableActivity} attribute set to {@code true}
-    public static class ActivityInActivityView extends TestActivity {}
-
     public static class ResumeWhilePausingActivity extends TestActivity {}
 
     public static class LandscapeActivity extends TestActivity {}
diff --git a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
index a9e1a8f..bf49f3c 100644
--- a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
+++ b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
@@ -483,7 +483,16 @@
 
         // check to see if these are recognized numbers, and use shortcuts if we can.
         TelephonyManager tm = context.getSystemService(TelephonyManager.class);
-        if (tm.isEmergencyNumber(number)) {
+        boolean isEmergencyNumber = false;
+        try {
+            isEmergencyNumber = tm.isEmergencyNumber(number);
+        } catch (IllegalStateException ise) {
+            // Ignore the exception that Telephony is not up. Use PhoneNumberUtils API now.
+            // Ideally the PhoneNumberUtils API needs to be removed once the
+            // telphony service not up issue can be fixed (b/187412989)
+            isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(context, number);
+        }
+        if (isEmergencyNumber) {
             cw.event = EVENT_EMERGENCY_NUMBER;
         } else if (PhoneNumberUtils.isVoiceMailNumber(context, subId, number)) {
             cw.event = EVENT_VOICEMAIL_NUMBER;
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index d361db2..4d81b5e 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -263,7 +263,7 @@
             return true;
         }
         return checkPrivilegedReadPermissionOrCarrierPrivilegePermission(
-                context, subId, callingPackage, callingFeatureId, message, true);
+                context, subId, callingPackage, callingFeatureId, message, true, true);
     }
 
     /**
@@ -286,14 +286,28 @@
      */
     public static boolean checkCallingOrSelfReadSubscriberIdentifiers(Context context, int subId,
             String callingPackage, @Nullable String callingFeatureId, String message) {
+        return checkCallingOrSelfReadSubscriberIdentifiers(context, subId, callingPackage,
+                callingFeatureId, message, true);
+    }
+
+    /**
+     * Same as {@link #checkCallingOrSelfReadSubscriberIdentifiers(Context, int, String, String,
+     * String)} except this allows an additional parameter reportFailure. Caller may not want to
+     * report a failure when this is an internal/intermediate check, for example,
+     * SubscriptionController calls this with an INVALID_SUBID to check if caller has the required
+     * permissions to bypass carrier privilege checks.
+     * @param reportFailure Indicates if failure should be reported.
+     */
+    public static boolean checkCallingOrSelfReadSubscriberIdentifiers(Context context, int subId,
+            String callingPackage, @Nullable String callingFeatureId, String message,
+            boolean reportFailure) {
         if (checkCallingOrSelfUseIccAuthWithDeviceIdentifier(context, callingPackage,
                 callingFeatureId, message)) {
             return true;
         }
         return checkPrivilegedReadPermissionOrCarrierPrivilegePermission(
-                context, subId, callingPackage, callingFeatureId, message, false);
+                context, subId, callingPackage, callingFeatureId, message, false, reportFailure);
     }
-
     /**
      * Checks whether the app with the given pid/uid can read device identifiers.
      *
@@ -314,7 +328,7 @@
      */
     private static boolean checkPrivilegedReadPermissionOrCarrierPrivilegePermission(
             Context context, int subId, String callingPackage, @Nullable String callingFeatureId,
-            String message, boolean allowCarrierPrivilegeOnAnySub) {
+            String message, boolean allowCarrierPrivilegeOnAnySub, boolean reportFailure) {
         int uid = Binder.getCallingUid();
         int pid = Binder.getCallingPid();
 
@@ -334,8 +348,12 @@
             return true;
         }
 
-        return reportAccessDeniedToReadIdentifiers(context, subId, pid, uid, callingPackage,
-                message);
+        if (reportFailure) {
+            return reportAccessDeniedToReadIdentifiers(context, subId, pid, uid, callingPackage,
+                    message);
+        } else {
+            return false;
+        }
     }
 
     /**
diff --git a/telephony/java/android/telephony/ims/RcsConfig.java b/telephony/java/android/telephony/ims/RcsConfig.java
index 6867c86..fd8d8a7 100644
--- a/telephony/java/android/telephony/ims/RcsConfig.java
+++ b/telephony/java/android/telephony/ims/RcsConfig.java
@@ -357,9 +357,9 @@
     /**
      * Check whether Rcs Volte single registration is supported by the config.
      */
-    public boolean isRcsVolteSingleRegistrationSupported() {
-        return getBoolean(PARM_SINGLE_REGISTRATION, false)
-                || getInteger(PARM_SINGLE_REGISTRATION, 0) != 0;
+    public boolean isRcsVolteSingleRegistrationSupported(boolean isRoaming) {
+        int val = getInteger(PARM_SINGLE_REGISTRATION, 1);
+        return isRoaming ? val == 1 : val > 0;
     }
 
     @Override
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java
index 79410cf..d925541 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.CanvasProperty;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.RecordingCanvas;
 import android.graphics.RuntimeShader;
@@ -48,6 +49,7 @@
     static class RippleView extends View {
         static final int DURATION = 1000;
         static final int MAX_RADIUS = 250;
+        private final int mColor = Color.RED;
 
         private boolean mToggle = false;
         ArrayList<RenderNodeAnimator> mRunningAnimations = new ArrayList<RenderNodeAnimator>();
@@ -104,7 +106,7 @@
 
             Paint p = new Paint();
             p.setAntiAlias(true);
-            p.setColor(0xFFFF0000);
+            p.setColor(mColor);
             mPaint = CanvasProperty.createPaint(p);
 
             mRuntimeShader = new RuntimeShader(sSkSL, false);
@@ -118,7 +120,7 @@
             if (canvas.isHardwareAccelerated()) {
                 RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
                 recordingCanvas.drawRipple(mX, mY, mRadius, mPaint, mProgress, mNoisePhase,
-                        mRuntimeShader);
+                        mColor, mRuntimeShader);
             }
         }
 
diff --git a/tests/TouchLatency/gradle/wrapper/gradle-wrapper.jar b/tests/TouchLatency/gradle/wrapper/gradle-wrapper.jar
index 8c0fb64..758de96 100644
--- a/tests/TouchLatency/gradle/wrapper/gradle-wrapper.jar
+++ b/tests/TouchLatency/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/tests/TouchLatency/gradle/wrapper/gradle-wrapper.properties b/tests/TouchLatency/gradle/wrapper/gradle-wrapper.properties
index 111992a..2d80b69 100644
--- a/tests/TouchLatency/gradle/wrapper/gradle-wrapper.properties
+++ b/tests/TouchLatency/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,5 @@
-#Tue Nov 27 13:37:59 PST 2018
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
diff --git a/tests/TouchLatency/gradlew b/tests/TouchLatency/gradlew
index 91a7e26..cccdd3d 100755
--- a/tests/TouchLatency/gradlew
+++ b/tests/TouchLatency/gradlew
@@ -1,4 +1,4 @@
-#!/usr/bin/env bash
+#!/usr/bin/env sh
 
 ##############################################################################
 ##
@@ -6,47 +6,6 @@
 ##
 ##############################################################################
 
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
-    echo "$*"
-}
-
-die ( ) {
-    echo
-    echo "$*"
-    echo
-    exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
-  CYGWIN* )
-    cygwin=true
-    ;;
-  Darwin* )
-    darwin=true
-    ;;
-  MINGW* )
-    msys=true
-    ;;
-esac
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched.
-if $cygwin ; then
-    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
-fi
-
 # Attempt to set APP_HOME
 # Resolve links: $0 may be a link
 PRG="$0"
@@ -61,9 +20,49 @@
     fi
 done
 SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >&-
+cd "`dirname \"$PRG\"`/" >/dev/null
 APP_HOME="`pwd -P`"
-cd "$SAVED" >&-
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
 
 CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
 
@@ -90,7 +89,7 @@
 fi
 
 # Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
     MAX_FD_LIMIT=`ulimit -H -n`
     if [ $? -eq 0 ] ; then
         if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -114,6 +113,7 @@
 if $cygwin ; then
     APP_HOME=`cygpath --path --mixed "$APP_HOME"`
     CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
 
     # We build the pattern for arguments to be converted via cygpath
     ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
@@ -154,11 +154,19 @@
     esac
 fi
 
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
-    JVM_OPTS=("$@")
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
 }
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+APP_ARGS=$(save "$@")
 
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/tests/TouchLatency/gradlew.bat b/tests/TouchLatency/gradlew.bat
index aec9973..e95643d 100644
--- a/tests/TouchLatency/gradlew.bat
+++ b/tests/TouchLatency/gradlew.bat
@@ -8,14 +8,14 @@
 @rem Set local scope for the variables with windows NT shell

 if "%OS%"=="Windows_NT" setlocal

 

-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

-set DEFAULT_JVM_OPTS=

-

 set DIRNAME=%~dp0

 if "%DIRNAME%" == "" set DIRNAME=.

 set APP_BASE_NAME=%~n0

 set APP_HOME=%DIRNAME%

 

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

 @rem Find java.exe

 if defined JAVA_HOME goto findJavaFromJavaHome

 

@@ -46,10 +46,9 @@
 goto fail

 

 :init

-@rem Get command-line arguments, handling Windowz variants

+@rem Get command-line arguments, handling Windows variants

 

 if not "%OS%" == "Windows_NT" goto win9xME_args

-if "%@eval[2+2]" == "4" goto 4NT_args

 

 :win9xME_args

 @rem Slurp the command line arguments.

@@ -60,11 +59,6 @@
 if "x%~1" == "x" goto execute

 

 set CMD_LINE_ARGS=%*

-goto execute

-

-:4NT_args

-@rem Get arguments from the 4NT Shell from JP Software

-set CMD_LINE_ARGS=%$

 

 :execute

 @rem Setup the command line

diff --git a/tests/UiBench/res/layout/recycler_view.xml b/tests/UiBench/res/layout/recycler_view.xml
index 53eab68..c8a85de 100644
--- a/tests/UiBench/res/layout/recycler_view.xml
+++ b/tests/UiBench/res/layout/recycler_view.xml
@@ -17,5 +17,6 @@
 <androidx.recyclerview.widget.RecyclerView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/recyclerView"
+    android:overScrollMode="never"
     android:layout_width="match_parent"
     android:layout_height="match_parent"/>
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
index a44ad1e..eff6658 100644
--- a/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
@@ -61,7 +61,6 @@
     private class NetworkMonitorDeps(private val privateDnsBypassNetwork: Network) :
             NetworkMonitor.Dependencies() {
         override fun getPrivateDnsBypassNetwork(network: Network?) = privateDnsBypassNetwork
-        override fun sendNetworkConditionsBroadcast(context: Context, broadcast: Intent) = Unit
     }
 
     private inner class TestNetworkStackConnector(context: Context) : NetworkStackConnector(
@@ -98,4 +97,4 @@
             cb.onNetworkMonitorCreated(NetworkMonitorConnector(nm, TestPermissionChecker()))
         }
     }
-}
\ No newline at end of file
+}