Merge "Check FontFamily null before adding it." into sc-dev
diff --git a/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java b/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java
index afd8e29..ac63653 100644
--- a/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java
+++ b/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java
@@ -144,7 +144,7 @@
         while (state.keepRunning()) {
             state.pauseTiming();
             // Invalidate cache.
-            resourcesManager.applyConfigurationToResourcesLocked(
+            resourcesManager.applyConfigurationToResources(
                     resourcesManager.getConfiguration(), null);
             state.resumeTiming();
 
diff --git a/apex/appsearch/Android.bp b/apex/appsearch/Android.bp
index 87f65a9..c592d13 100644
--- a/apex/appsearch/Android.bp
+++ b/apex/appsearch/Android.bp
@@ -24,8 +24,8 @@
 apex {
     name: "com.android.appsearch",
     manifest: "apex_manifest.json",
+    bootclasspath_fragments: ["com.android.appsearch-bootclasspath-fragment"],
     java_libs: [
-        "framework-appsearch",
         "service-appsearch",
     ],
     key: "com.android.appsearch.key",
@@ -45,3 +45,10 @@
     // com.android.appsearch.pk8 (the private key)
     certificate: "com.android.appsearch",
 }
+
+// Encapsulate the contributions made by the com.android.appsearch to the bootclasspath.
+bootclasspath_fragment {
+    name: "com.android.appsearch-bootclasspath-fragment",
+    contents: ["framework-appsearch"],
+    apex_available: ["com.android.appsearch"],
+}
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index b096537..8b824e8 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -152,6 +152,8 @@
      * this broadcast will be sent. Applications can reschedule all the necessary alarms when
      * receiving it.
      *
+     * <p>This broadcast will <em>not</em> be sent when the user revokes the permission.
+     *
      * <p><em>Note:</em>
      * Applications are still required to check {@link #canScheduleExactAlarms()}
      * before using the above APIs after receiving this broadcast,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 452be30..96cbed7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -2629,8 +2629,7 @@
                 }
             } catch (NameNotFoundException e) {
                 throw new IllegalArgumentException(
-                        "Tried to schedule job for non-existent package: "
-                                + service.getPackageName());
+                        "Tried to schedule job for non-existent component: " + service);
             }
         }
 
diff --git a/core/api/current.txt b/core/api/current.txt
index cb61bbc..98c2d40 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -11154,6 +11154,7 @@
     field public static final String ACTION_VIEW = "android.intent.action.VIEW";
     field public static final String ACTION_VIEW_LOCUS = "android.intent.action.VIEW_LOCUS";
     field @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE) public static final String ACTION_VIEW_PERMISSION_USAGE = "android.intent.action.VIEW_PERMISSION_USAGE";
+    field @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE) public static final String ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD = "android.intent.action.VIEW_PERMISSION_USAGE_FOR_PERIOD";
     field public static final String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
     field @Deprecated public static final String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
     field public static final String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
@@ -11206,6 +11207,7 @@
     field public static final String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD";
     field public static final String EXTRA_ASSIST_PACKAGE = "android.intent.extra.ASSIST_PACKAGE";
     field public static final String EXTRA_ASSIST_UID = "android.intent.extra.ASSIST_UID";
+    field public static final String EXTRA_ATTRIBUTION_TAGS = "android.intent.extra.ATTRIBUTION_TAGS";
     field public static final String EXTRA_AUTO_LAUNCH_SINGLE_CHOICE = "android.intent.extra.AUTO_LAUNCH_SINGLE_CHOICE";
     field public static final String EXTRA_BCC = "android.intent.extra.BCC";
     field public static final String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";
@@ -11231,6 +11233,7 @@
     field public static final String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP";
     field public static final String EXTRA_DURATION_MILLIS = "android.intent.extra.DURATION_MILLIS";
     field public static final String EXTRA_EMAIL = "android.intent.extra.EMAIL";
+    field public static final String EXTRA_END_TIME = "android.intent.extra.END_TIME";
     field public static final String EXTRA_EXCLUDE_COMPONENTS = "android.intent.extra.EXCLUDE_COMPONENTS";
     field public static final String EXTRA_FROM_STORAGE = "android.intent.extra.FROM_STORAGE";
     field public static final String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
@@ -11245,6 +11248,7 @@
     field public static final String EXTRA_NOT_UNKNOWN_SOURCE = "android.intent.extra.NOT_UNKNOWN_SOURCE";
     field public static final String EXTRA_ORIGINATING_URI = "android.intent.extra.ORIGINATING_URI";
     field public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";
+    field public static final String EXTRA_PERMISSION_GROUP_NAME = "android.intent.extra.PERMISSION_GROUP_NAME";
     field public static final String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
     field public static final String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT";
     field public static final String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY";
@@ -11267,6 +11271,7 @@
     field @Deprecated public static final String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME";
     field public static final String EXTRA_SHUTDOWN_USERSPACE_ONLY = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
     field public static final String EXTRA_SPLIT_NAME = "android.intent.extra.SPLIT_NAME";
+    field public static final String EXTRA_START_TIME = "android.intent.extra.START_TIME";
     field public static final String EXTRA_STREAM = "android.intent.extra.STREAM";
     field public static final String EXTRA_SUBJECT = "android.intent.extra.SUBJECT";
     field public static final String EXTRA_SUSPENDED_PACKAGE_EXTRAS = "android.intent.extra.SUSPENDED_PACKAGE_EXTRAS";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 96a23b2..d30d59f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2399,12 +2399,9 @@
     field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
     field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED";
     field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
-    field @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE) public static final String ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD = "android.intent.action.VIEW_PERMISSION_USAGE_FOR_PERIOD";
     field public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
     field public static final String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS";
-    field public static final String EXTRA_ATTRIBUTION_TAGS = "android.intent.extra.ATTRIBUTION_TAGS";
     field public static final String EXTRA_CALLING_PACKAGE = "android.intent.extra.CALLING_PACKAGE";
-    field public static final String EXTRA_END_TIME = "android.intent.extra.END_TIME";
     field public static final String EXTRA_FORCE_FACTORY_RESET = "android.intent.extra.FORCE_FACTORY_RESET";
     field public static final String EXTRA_INSTANT_APP_ACTION = "android.intent.extra.INSTANT_APP_ACTION";
     field public static final String EXTRA_INSTANT_APP_BUNDLES = "android.intent.extra.INSTANT_APP_BUNDLES";
@@ -2416,13 +2413,11 @@
     field public static final String EXTRA_LONG_VERSION_CODE = "android.intent.extra.LONG_VERSION_CODE";
     field public static final String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID";
     field public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
-    field public static final String EXTRA_PERMISSION_GROUP_NAME = "android.intent.extra.PERMISSION_GROUP_NAME";
     field public static final String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME";
     field public static final String EXTRA_REASON = "android.intent.extra.REASON";
     field public static final String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";
     field public static final String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED";
     field public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME";
-    field public static final String EXTRA_START_TIME = "android.intent.extra.START_TIME";
     field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP";
     field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE";
     field public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 67108864; // 0x4000000
@@ -10473,7 +10468,7 @@
     method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int setParameter(int, int);
     method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int);
     method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition();
-    method @Nullable public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle);
+    method public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle);
     method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition();
     method public final void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
     field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
@@ -10498,7 +10493,7 @@
     method public abstract void onAvailabilityChanged(int);
     method public void onHotwordDetectionServiceInitialized(int);
     method public void onHotwordDetectionServiceRestarted();
-    method public void onRejected(@Nullable android.service.voice.HotwordRejectedResult);
+    method public void onRejected(@NonNull android.service.voice.HotwordRejectedResult);
   }
 
   public static class AlwaysOnHotwordDetector.EventPayload {
@@ -10559,8 +10554,8 @@
   }
 
   public static final class HotwordDetectionService.Callback {
-    method public void onDetected(@Nullable android.service.voice.HotwordDetectedResult);
-    method public void onRejected(@Nullable android.service.voice.HotwordRejectedResult);
+    method public void onDetected(@NonNull android.service.voice.HotwordDetectedResult);
+    method public void onRejected(@NonNull android.service.voice.HotwordRejectedResult);
   }
 
   public interface HotwordDetector {
@@ -10581,7 +10576,7 @@
     method public void onHotwordDetectionServiceRestarted();
     method public void onRecognitionPaused();
     method public void onRecognitionResumed();
-    method public void onRejected(@Nullable android.service.voice.HotwordRejectedResult);
+    method public void onRejected(@NonNull android.service.voice.HotwordRejectedResult);
   }
 
   public final class HotwordRejectedResult implements android.os.Parcelable {
@@ -14919,6 +14914,7 @@
 
   public static interface WebViewProvider.ViewDelegate {
     method public default void autofill(android.util.SparseArray<android.view.autofill.AutofillValue>);
+    method public default void dispatchCreateViewTranslationRequest(@NonNull java.util.Map<android.view.autofill.AutofillId,long[]>, @NonNull int[], @Nullable android.view.translation.TranslationCapability, @NonNull java.util.List<android.view.translation.ViewTranslationRequest>);
     method public boolean dispatchKeyEvent(android.view.KeyEvent);
     method public android.view.View findFocus(android.view.View);
     method public android.view.accessibility.AccessibilityNodeProvider getAccessibilityNodeProvider();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index f458107..c8e365e 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -865,6 +865,10 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.ProviderInfoList> CREATOR;
   }
 
+  public final class SharedLibraryInfo implements android.os.Parcelable {
+    method @NonNull public java.util.List<java.lang.String> getAllCodePaths();
+  }
+
   public final class ShortcutInfo implements android.os.Parcelable {
     method public boolean isVisibleToPublisher();
   }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ff210e1..a667767 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5887,7 +5887,7 @@
 
     public final void applyConfigurationToResources(Configuration config) {
         synchronized (mResourcesManager) {
-            mResourcesManager.applyConfigurationToResourcesLocked(config, null);
+            mResourcesManager.applyConfigurationToResources(config, null);
         }
     }
 
@@ -5975,7 +5975,7 @@
 
         synchronized (mResourcesManager) {
             // Update all affected Resources objects to use new ResourcesImpl
-            mResourcesManager.applyNewResourceDirsLocked(ai, oldResDirs);
+            mResourcesManager.applyNewResourceDirs(ai, oldResDirs);
         }
     }
 
@@ -6231,7 +6231,7 @@
 
                                 synchronized (mResourcesManager) {
                                     // Update affected Resources objects to use new ResourcesImpl
-                                    mResourcesManager.applyNewResourceDirsLocked(aInfo, oldResDirs);
+                                    mResourcesManager.applyNewResourceDirs(aInfo, oldResDirs);
                                 }
                             } catch (RemoteException e) {
                             }
@@ -6474,7 +6474,7 @@
              * reflect configuration changes. The configuration object passed
              * in AppBindData can be safely assumed to be up to date
              */
-            mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo);
+            mResourcesManager.applyConfigurationToResources(data.config, data.compatInfo);
             mCurDefaultDisplayDpi = data.config.densityDpi;
 
             // This calls mResourcesManager so keep it within the synchronized block.
@@ -7509,7 +7509,7 @@
 
                 // We need to apply this change to the resources immediately, because upon returning
                 // the view hierarchy will be informed about it.
-                if (mResourcesManager.applyConfigurationToResourcesLocked(globalConfig,
+                if (mResourcesManager.applyConfigurationToResources(globalConfig,
                         null /* compat */,
                         mInitialApplication.getResources().getDisplayAdjustments())) {
                     mConfigurationController.updateLocaleListFromAppContext(
diff --git a/core/java/android/app/ConfigurationController.java b/core/java/android/app/ConfigurationController.java
index 0dbe3ba..6d92201 100644
--- a/core/java/android/app/ConfigurationController.java
+++ b/core/java/android/app/ConfigurationController.java
@@ -107,8 +107,7 @@
             mCompatConfiguration = new Configuration();
         }
         mCompatConfiguration.setTo(mConfiguration);
-        if (mResourcesManager.applyCompatConfigurationLocked(displayDensity,
-                mCompatConfiguration)) {
+        if (mResourcesManager.applyCompatConfiguration(displayDensity, mCompatConfiguration)) {
             config = mCompatConfiguration;
         }
         return config;
@@ -199,7 +198,7 @@
                 // configuration also needs to set to the adjustments for consistency.
                 appResources.getDisplayAdjustments().getConfiguration().updateFrom(config);
             }
-            mResourcesManager.applyConfigurationToResourcesLocked(config, compat,
+            mResourcesManager.applyConfigurationToResources(config, compat,
                     appResources.getDisplayAdjustments());
             updateLocaleListFromAppContext(app.getApplicationContext());
 
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index c6847aa..c6989ad 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -174,6 +174,7 @@
      */
     public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFERRED = 2;
 
+    @ServiceNotificationPolicy
     private int mFgsDeferBehavior;
 
     /**
@@ -4614,9 +4615,9 @@
          * foreground service.  By default, the system can choose to defer
          * visibility of the notification for a short time after the service is
          * started.  Pass
-         * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE BEHAVIOR_IMMEDIATE_DISPLAY}
+         * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE}
          * to this method in order to guarantee that visibility is never deferred.  Pass
-         * {@link Notification#FOREGROUND_SERVICE_DEFERRED BEHAVIOR_DEFERRED_DISPLAY}
+         * {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED}
          * to request that visibility is deferred whenever possible.
          *
          * <p class="note">Note that deferred visibility is not guaranteed.  There
@@ -4624,13 +4625,13 @@
          * service's associated Notification immediately even when the app has used
          * this method to explicitly request deferred display.</p>
          * @param behavior One of
-         * {@link Notification#FOREGROUND_SERVICE_DEFAULT BEHAVIOR_DEFAULT},
-         * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE BEHAVIOR_IMMEDIATE_DISPLAY},
-         * or {@link Notification#FOREGROUND_SERVICE_DEFERRED BEHAVIOR_DEFERRED_DISPLAY}
+         * {@link Notification#FOREGROUND_SERVICE_DEFAULT FOREGROUND_SERVICE_DEFAULT},
+         * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE},
+         * or {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED}
          * @return
          */
         @NonNull
-        public Builder setForegroundServiceBehavior(int behavior) {
+        public Builder setForegroundServiceBehavior(@ServiceNotificationPolicy int behavior) {
             mN.mFgsDeferBehavior = behavior;
             return this;
         }
@@ -5309,8 +5310,7 @@
         // the change's state in NotificationManagerService were very complex. These behavior
         // changes are entirely visual, and should otherwise be undetectable by apps.
         @SuppressWarnings("AndroidFrameworkCompatChange")
-        private void calculateLargeIconDimens(boolean largeIconShown,
-                @NonNull StandardTemplateParams p,
+        private void calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture,
                 @NonNull TemplateBindResult result) {
             final Resources resources = mContext.getResources();
             final float density = resources.getDisplayMetrics().density;
@@ -5323,9 +5323,9 @@
             final float viewHeightDp = resources.getDimension(
                     R.dimen.notification_right_icon_size) / density;
             float viewWidthDp = viewHeightDp;  // icons are 1:1 by default
-            if (largeIconShown && (p.mPromotePicture
+            if (rightIcon != null && (isPromotedPicture
                     || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S)) {
-                Drawable drawable = mN.mLargeIcon.loadDrawable(mContext);
+                Drawable drawable = rightIcon.loadDrawable(mContext);
                 if (drawable != null) {
                     int iconWidth = drawable.getIntrinsicWidth();
                     int iconHeight = drawable.getIntrinsicHeight();
@@ -5337,7 +5337,7 @@
                 }
             }
             final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp;
-            result.setRightIconState(largeIconShown, viewWidthDp,
+            result.setRightIconState(rightIcon != null /* visible */, viewWidthDp,
                     extraMarginEndDpIfVisible, expanderSizeDp);
         }
 
@@ -5349,19 +5349,43 @@
             if (mN.mLargeIcon == null && mN.largeIcon != null) {
                 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
             }
-            boolean showLargeIcon = mN.mLargeIcon != null && !p.hideLargeIcon;
-            calculateLargeIconDimens(showLargeIcon, p, result);
-            if (showLargeIcon) {
+
+            // Determine the left and right icons
+            Icon leftIcon = p.mHideLeftIcon ? null : mN.mLargeIcon;
+            Icon rightIcon = p.mHideRightIcon ? null
+                    : (p.mPromotedPicture != null ? p.mPromotedPicture : mN.mLargeIcon);
+
+            // Apply the left icon (without duplicating the bitmap)
+            if (leftIcon != rightIcon || leftIcon == null) {
+                // If the leftIcon is explicitly hidden or different from the rightIcon, then set it
+                // explicitly and make sure it won't take the right_icon drawable.
+                contentView.setImageViewIcon(R.id.left_icon, leftIcon);
+                contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 0);
+            } else {
+                // If the leftIcon equals the rightIcon, just set the flag to use the right_icon
+                // drawable.  This avoids the view having two copies of the same bitmap.
+                contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 1);
+            }
+
+            // Always calculate dimens to populate `result` for the GONE case
+            boolean isPromotedPicture = p.mPromotedPicture != null;
+            calculateRightIconDimens(rightIcon, isPromotedPicture, result);
+
+            // Bind the right icon
+            if (rightIcon != null) {
                 contentView.setViewLayoutWidth(R.id.right_icon,
                         result.mRightIconWidthDp, TypedValue.COMPLEX_UNIT_DIP);
                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
-                contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
-                processLargeLegacyIcon(mN.mLargeIcon, contentView, p);
+                contentView.setImageViewIcon(R.id.right_icon, rightIcon);
+                contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon,
+                        isPromotedPicture ? 1 : 0);
+                processLargeLegacyIcon(rightIcon, contentView, p);
             } else {
                 // The "reset" doesn't clear the drawable, so we do it here.  This clear is
                 // important because the presence of a drawable in this view (regardless of the
                 // visibility) is used by NotificationGroupingUtil to set the visibility.
                 contentView.setImageViewIcon(R.id.right_icon, null);
+                contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 0);
             }
         }
 
@@ -6048,11 +6072,13 @@
                     .viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED)
                     .highlightExpander(false)
                     .fillTextsFrom(this);
-            if (!useRegularSubtext || TextUtils.isEmpty(mParams.summaryText)) {
+            if (!useRegularSubtext || TextUtils.isEmpty(p.summaryText)) {
                 p.summaryText(createSummaryText());
             }
             RemoteViews header = makeNotificationHeader(p);
             header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true);
+            // The low priority header has no app name and shows the text
+            header.setBoolean(R.id.notification_header, "styleTextAsTitle", true);
             return header;
         }
 
@@ -7397,25 +7423,11 @@
                 return super.makeContentView(increasedHeight);
             }
 
-            Icon oldLargeIcon = mBuilder.mN.mLargeIcon;
-            mBuilder.mN.mLargeIcon = mPictureIcon;
-            // The legacy largeIcon might not allow us to clear the image, as it's taken in
-            // replacement if the other one is null. Because we're restoring these legacy icons
-            // for old listeners, this is in general non-null.
-            Bitmap largeIconLegacy = mBuilder.mN.largeIcon;
-            mBuilder.mN.largeIcon = null;
-
             StandardTemplateParams p = mBuilder.mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
                     .fillTextsFrom(mBuilder)
-                    .promotePicture(true);
-            RemoteViews contentView = getStandardView(mBuilder.getBaseLayoutResource(),
-                    p, null /* result */);
-
-            mBuilder.mN.mLargeIcon = oldLargeIcon;
-            mBuilder.mN.largeIcon = largeIconLegacy;
-
-            return contentView;
+                    .promotedPicture(mPictureIcon);
+            return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */);
         }
 
         /**
@@ -7427,25 +7439,11 @@
                 return super.makeHeadsUpContentView(increasedHeight);
             }
 
-            Icon oldLargeIcon = mBuilder.mN.mLargeIcon;
-            mBuilder.mN.mLargeIcon = mPictureIcon;
-            // The legacy largeIcon might not allow us to clear the image, as it's taken in
-            // replacement if the other one is null. Because we're restoring these legacy icons
-            // for old listeners, this is in general non-null.
-            Bitmap largeIconLegacy = mBuilder.mN.largeIcon;
-            mBuilder.mN.largeIcon = null;
-
             StandardTemplateParams p = mBuilder.mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
                     .fillTextsFrom(mBuilder)
-                    .promotePicture(true);
-            RemoteViews contentView = getStandardView(mBuilder.getHeadsUpBaseLayoutResource(),
-                    p, null /* result */);
-
-            mBuilder.mN.mLargeIcon = oldLargeIcon;
-            mBuilder.mN.largeIcon = largeIconLegacy;
-
-            return contentView;
+                    .promotedPicture(mPictureIcon);
+            return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */);
         }
 
         /**
@@ -7540,14 +7538,21 @@
 
             mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED);
 
+            mPictureIcon = getPictureIcon(extras);
+        }
+
+        /** @hide */
+        @Nullable
+        public static Icon getPictureIcon(@Nullable Bundle extras) {
+            if (extras == null) return null;
             // When this style adds a picture, we only add one of the keys.  If both were added,
             // it would most likely be a legacy app trying to override the picture in some way.
             // Because of that case it's better to give precedence to the legacy field.
             Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE);
             if (bitmapPicture != null) {
-                mPictureIcon = Icon.createWithBitmap(bitmapPicture);
+                return Icon.createWithBitmap(bitmapPicture);
             } else {
-                mPictureIcon = extras.getParcelable(EXTRA_PICTURE_ICON);
+                return extras.getParcelable(EXTRA_PICTURE_ICON);
             }
         }
 
@@ -8323,7 +8328,8 @@
                     .hideProgress(true)
                     .title(isHeaderless ? conversationTitle : null)
                     .text(null)
-                    .hideLargeIcon(hideRightIcons || isOneToOne)
+                    .hideLeftIcon(isOneToOne)
+                    .hideRightIcon(hideRightIcons || isOneToOne)
                     .headerTextSecondary(isHeaderless ? null : conversationTitle);
             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
                     isConversationLayout
@@ -9121,9 +9127,10 @@
 
             StandardTemplateParams p = mBuilder.mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
-                    .hideTime(numActionsToShow > 1)    // hide if actions wider than a large icon
-                    .hideSubText(numActionsToShow > 1) // hide if actions wider than a large icon
-                    .hideLargeIcon(numActionsToShow > 0)  // large icon or actions; not both
+                    .hideTime(numActionsToShow > 1)       // hide if actions wider than a right icon
+                    .hideSubText(numActionsToShow > 1)    // hide if actions wider than a right icon
+                    .hideLeftIcon(false)                  // allow large icon on left when grouped
+                    .hideRightIcon(numActionsToShow > 0)  // right icon or actions; not both
                     .hideProgress(true)
                     .fillTextsFrom(mBuilder);
             TemplateBindResult result = new TemplateBindResult();
@@ -9527,7 +9534,8 @@
                     .viewType(viewType)
                     .callStyleActions(true)
                     .allowTextWithProgress(true)
-                    .hideLargeIcon(true)
+                    .hideLeftIcon(true)
+                    .hideRightIcon(true)
                     .hideAppName(isCollapsed)
                     .titleViewId(R.id.conversation_text)
                     .title(title)
@@ -12237,7 +12245,9 @@
         boolean mHideActions;
         boolean mHideProgress;
         boolean mHideSnoozeButton;
-        boolean mPromotePicture;
+        boolean mHideLeftIcon;
+        boolean mHideRightIcon;
+        Icon mPromotedPicture;
         boolean mCallStyleActions;
         boolean mAllowTextWithProgress;
         int mTitleViewId;
@@ -12247,7 +12257,6 @@
         CharSequence headerTextSecondary;
         CharSequence summaryText;
         int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
-        boolean hideLargeIcon;
         boolean allowColorization  = true;
         boolean mHighlightExpander = false;
 
@@ -12261,7 +12270,9 @@
             mHideActions = false;
             mHideProgress = false;
             mHideSnoozeButton = false;
-            mPromotePicture = false;
+            mHideLeftIcon = false;
+            mHideRightIcon = false;
+            mPromotedPicture = null;
             mCallStyleActions = false;
             mAllowTextWithProgress = false;
             mTitleViewId = R.id.title;
@@ -12271,7 +12282,6 @@
             summaryText = null;
             headerTextSecondary = null;
             maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
-            hideLargeIcon = false;
             allowColorization = true;
             mHighlightExpander = false;
             return this;
@@ -12336,8 +12346,8 @@
             return this;
         }
 
-        final StandardTemplateParams promotePicture(boolean promotePicture) {
-            this.mPromotePicture = promotePicture;
+        final StandardTemplateParams promotedPicture(Icon promotedPicture) {
+            this.mPromotedPicture = promotedPicture;
             return this;
         }
 
@@ -12371,8 +12381,14 @@
             return this;
         }
 
-        final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) {
-            this.hideLargeIcon = hideLargeIcon;
+
+        final StandardTemplateParams hideLeftIcon(boolean hideLeftIcon) {
+            this.mHideLeftIcon = hideLeftIcon;
+            return this;
+        }
+
+        final StandardTemplateParams hideRightIcon(boolean hideRightIcon) {
+            this.mHideRightIcon = hideRightIcon;
             return this;
         }
 
@@ -12409,7 +12425,8 @@
             // Minimally decorated custom views do not show certain pieces of chrome that have
             // always been shown when using DecoratedCustomViewStyle.
             boolean hideOtherFields = decorationType <= DECORATION_MINIMAL;
-            hideLargeIcon(hideOtherFields);
+            hideLeftIcon(false);  // The left icon decoration is better than showing nothing.
+            hideRightIcon(hideOtherFields);
             hideProgress(hideOtherFields);
             hideActions(hideOtherFields);
             return this;
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 74134e1..792336d 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -75,6 +75,11 @@
     private static ResourcesManager sResourcesManager;
 
     /**
+     * Internal lock object
+     */
+    private final Object mLock = new Object();
+
+    /**
      * The global compatibility settings.
      */
     private CompatibilityInfo mResCompatibilityInfo;
@@ -275,7 +280,7 @@
      * try as hard as possible to release any open FDs.
      */
     public void invalidatePath(String path) {
-        synchronized (this) {
+        synchronized (mLock) {
             int count = 0;
 
             for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
@@ -304,7 +309,7 @@
     }
 
     public Configuration getConfiguration() {
-        synchronized (this) {
+        synchronized (mLock) {
             return mResConfiguration;
         }
     }
@@ -351,13 +356,15 @@
         config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp;
     }
 
-    public boolean applyCompatConfigurationLocked(int displayDensity,
+    public boolean applyCompatConfiguration(int displayDensity,
             @NonNull Configuration compatConfiguration) {
-        if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
-            mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration);
-            return true;
+        synchronized (mLock) {
+            if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
+                mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration);
+                return true;
+            }
+            return false;
         }
-        return false;
     }
 
     /**
@@ -376,7 +383,7 @@
         final Pair<Integer, DisplayAdjustments> key =
                 Pair.create(displayId, displayAdjustmentsCopy);
         SoftReference<Display> sd;
-        synchronized (this) {
+        synchronized (mLock) {
             sd = mAdjustedDisplays.get(key);
         }
         if (sd != null) {
@@ -392,7 +399,7 @@
         }
         final Display display = dm.getCompatibleDisplay(displayId, key.second);
         if (display != null) {
-            synchronized (this) {
+            synchronized (mLock) {
                 mAdjustedDisplays.put(key, new SoftReference<>(display));
             }
         }
@@ -407,7 +414,7 @@
      * @param resources The {@link Resources} backing the display adjustments.
      */
     public Display getAdjustedDisplay(final int displayId, Resources resources) {
-        synchronized (this) {
+        synchronized (mLock) {
             final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
             if (dm == null) {
                 // may be null early in system startup
@@ -425,7 +432,7 @@
         ApkAssets apkAssets;
 
         // Optimistically check if this ApkAssets exists somewhere else.
-        synchronized (this) {
+        synchronized (mLock) {
             final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(key);
             if (apkAssetsRef != null) {
                 apkAssets = apkAssetsRef.get();
@@ -447,7 +454,7 @@
                     key.sharedLib ? ApkAssets.PROPERTY_DYNAMIC : 0);
         }
 
-        synchronized (this) {
+        synchronized (mLock) {
             mCachedApkAssets.put(key, new WeakReference<>(apkAssets));
         }
 
@@ -559,7 +566,7 @@
      * @hide
      */
     public void dump(String prefix, PrintWriter printWriter) {
-        synchronized (this) {
+        synchronized (mLock) {
             IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
             for (int i = 0; i < prefix.length() / 2; i++) {
                 pw.increaseIndent();
@@ -688,7 +695,7 @@
      */
     boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken,
             @Nullable Configuration overrideConfig) {
-        synchronized (this) {
+        synchronized (mLock) {
             final ActivityResources activityResources
                     = activityToken != null ? mActivityResourceReferences.get(activityToken) : null;
             if (activityResources == null) {
@@ -834,7 +841,7 @@
                         + " with key=" + key);
             }
 
-            synchronized (this) {
+            synchronized (mLock) {
                 // Force the creation of an ActivityResourcesStruct.
                 getOrCreateActivityResourcesStructLocked(token);
             }
@@ -842,7 +849,7 @@
             // Update any existing Activity Resources references.
             updateResourcesForActivity(token, overrideConfig, displayId);
 
-            synchronized (this) {
+            synchronized (mLock) {
                 Resources resources = findResourcesForActivityLocked(token, key,
                         classLoader);
                 if (resources != null) {
@@ -868,7 +875,7 @@
      */
     private void rebaseKeyForActivity(IBinder activityToken, ResourcesKey key,
             boolean overridesActivityDisplay) {
-        synchronized (this) {
+        synchronized (mLock) {
             final ActivityResources activityResources =
                     getOrCreateActivityResourcesStructLocked(activityToken);
 
@@ -960,7 +967,7 @@
         Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
                 "ResourcesManager#createApkAssetsSupplierNotLocked");
         try {
-            if (DEBUG && Thread.holdsLock(this)) {
+            if (DEBUG && Thread.holdsLock(mLock)) {
                 Slog.w(TAG, "Calling thread " + Thread.currentThread().getName()
                     + " is holding mLock", new Throwable());
             }
@@ -994,7 +1001,7 @@
     @Nullable
     private Resources createResources(@NonNull ResourcesKey key, @NonNull ClassLoader classLoader,
             @Nullable ApkAssetsSupplier apkSupplier) {
-        synchronized (this) {
+        synchronized (mLock) {
             if (DEBUG) {
                 Throwable here = new Throwable();
                 here.fillInStackTrace();
@@ -1015,7 +1022,7 @@
             @NonNull ResourcesKey key, @NonNull Configuration initialOverrideConfig,
             @Nullable Integer overrideDisplayId, @NonNull ClassLoader classLoader,
             @Nullable ApkAssetsSupplier apkSupplier) {
-        synchronized (this) {
+        synchronized (mLock) {
             if (DEBUG) {
                 Throwable here = new Throwable();
                 here.fillInStackTrace();
@@ -1130,7 +1137,7 @@
             if (displayId == INVALID_DISPLAY) {
                 throw new IllegalArgumentException("displayId can not be INVALID_DISPLAY");
             }
-            synchronized (this) {
+            synchronized (mLock) {
                 final ActivityResources activityResources =
                         getOrCreateActivityResourcesStructLocked(activityToken);
 
@@ -1269,67 +1276,64 @@
 
     public final boolean applyConfigurationToResources(@NonNull Configuration config,
             @Nullable CompatibilityInfo compat) {
-        synchronized(this) {
-            return applyConfigurationToResourcesLocked(config, compat, null /* adjustments */);
-        }
-    }
-
-    public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
-            @Nullable CompatibilityInfo compat) {
-        return applyConfigurationToResourcesLocked(config, compat, null /* adjustments */);
+        return applyConfigurationToResources(config, compat, null /* adjustments */);
     }
 
     /** Applies the global configuration to the managed resources. */
-    public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
+    public final boolean applyConfigurationToResources(@NonNull Configuration config,
             @Nullable CompatibilityInfo compat, @Nullable DisplayAdjustments adjustments) {
-        try {
-            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
-                    "ResourcesManager#applyConfigurationToResourcesLocked");
+        synchronized (mLock) {
+            try {
+                Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
+                        "ResourcesManager#applyConfigurationToResources");
 
-            if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
-                if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
-                        + mResConfiguration.seq + ", newSeq=" + config.seq);
-                return false;
-            }
-
-            // Things might have changed in display manager, so clear the cached displays.
-            mAdjustedDisplays.clear();
-
-            int changes = mResConfiguration.updateFrom(config);
-            if (compat != null && (mResCompatibilityInfo == null ||
-                    !mResCompatibilityInfo.equals(compat))) {
-                mResCompatibilityInfo = compat;
-                changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
-                        | ActivityInfo.CONFIG_SCREEN_SIZE
-                        | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
-            }
-
-            DisplayMetrics displayMetrics = getDisplayMetrics();
-            if (adjustments != null) {
-                // Currently the only case where the adjustment takes effect is to simulate placing
-                // an app in a rotated display.
-                adjustments.adjustGlobalAppMetrics(displayMetrics);
-            }
-            Resources.updateSystemConfiguration(config, displayMetrics, compat);
-
-            ApplicationPackageManager.configurationChanged();
-
-            Configuration tmpConfig = new Configuration();
-
-            for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
-                ResourcesKey key = mResourceImpls.keyAt(i);
-                WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
-                ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null;
-                if (r != null) {
-                    applyConfigurationToResourcesLocked(config, compat, tmpConfig, key, r);
-                } else {
-                    mResourceImpls.removeAt(i);
+                if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
+                    if (DEBUG || DEBUG_CONFIGURATION) {
+                        Slog.v(TAG, "Skipping new config: curSeq="
+                                + mResConfiguration.seq + ", newSeq=" + config.seq);
+                    }
+                    return false;
                 }
-            }
 
-            return changes != 0;
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+                // Things might have changed in display manager, so clear the cached displays.
+                mAdjustedDisplays.clear();
+
+                int changes = mResConfiguration.updateFrom(config);
+                if (compat != null && (mResCompatibilityInfo == null
+                        || !mResCompatibilityInfo.equals(compat))) {
+                    mResCompatibilityInfo = compat;
+                    changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
+                            | ActivityInfo.CONFIG_SCREEN_SIZE
+                            | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
+                }
+
+                DisplayMetrics displayMetrics = getDisplayMetrics();
+                if (adjustments != null) {
+                    // Currently the only case where the adjustment takes effect is to simulate
+                    // placing an app in a rotated display.
+                    adjustments.adjustGlobalAppMetrics(displayMetrics);
+                }
+                Resources.updateSystemConfiguration(config, displayMetrics, compat);
+
+                ApplicationPackageManager.configurationChanged();
+
+                Configuration tmpConfig = new Configuration();
+
+                for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
+                    ResourcesKey key = mResourceImpls.keyAt(i);
+                    WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
+                    ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null;
+                    if (r != null) {
+                        applyConfigurationToResourcesLocked(config, compat, tmpConfig, key, r);
+                    } else {
+                        mResourceImpls.removeAt(i);
+                    }
+                }
+
+                return changes != 0;
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+            }
         }
     }
 
@@ -1378,7 +1382,7 @@
      * @param libAssets The library asset paths to add.
      */
     public void appendLibAssetsForMainAssetPath(String assetPath, String[] libAssets) {
-        synchronized (this) {
+        synchronized (mLock) {
             // Record which ResourcesImpl need updating
             // (and what ResourcesKey they should update to).
             final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
@@ -1414,54 +1418,56 @@
     }
 
     // TODO(adamlesinski): Make this accept more than just overlay directories.
-    final void applyNewResourceDirsLocked(@NonNull final ApplicationInfo appInfo,
+    void applyNewResourceDirs(@NonNull final ApplicationInfo appInfo,
             @Nullable final String[] oldPaths) {
-        try {
-            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
-                    "ResourcesManager#applyNewResourceDirsLocked");
+        synchronized (mLock) {
+            try {
+                Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
+                        "ResourcesManager#applyNewResourceDirsLocked");
 
-            String baseCodePath = appInfo.getBaseCodePath();
+                String baseCodePath = appInfo.getBaseCodePath();
 
-            final int myUid = Process.myUid();
-            String[] newSplitDirs = appInfo.uid == myUid
-                    ? appInfo.splitSourceDirs
-                    : appInfo.splitPublicSourceDirs;
+                final int myUid = Process.myUid();
+                String[] newSplitDirs = appInfo.uid == myUid
+                        ? appInfo.splitSourceDirs
+                        : appInfo.splitPublicSourceDirs;
 
-            // ApplicationInfo is mutable, so clone the arrays to prevent outside modification
-            String[] copiedSplitDirs = ArrayUtils.cloneOrNull(newSplitDirs);
-            String[] copiedResourceDirs = combinedOverlayPaths(appInfo.resourceDirs,
-                    appInfo.overlayPaths);
+                // ApplicationInfo is mutable, so clone the arrays to prevent outside modification
+                String[] copiedSplitDirs = ArrayUtils.cloneOrNull(newSplitDirs);
+                String[] copiedResourceDirs = combinedOverlayPaths(appInfo.resourceDirs,
+                        appInfo.overlayPaths);
 
-            final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
-            final int implCount = mResourceImpls.size();
-            for (int i = 0; i < implCount; i++) {
-                final ResourcesKey key = mResourceImpls.keyAt(i);
-                final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
-                final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
+                final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
+                final int implCount = mResourceImpls.size();
+                for (int i = 0; i < implCount; i++) {
+                    final ResourcesKey key = mResourceImpls.keyAt(i);
+                    final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
+                    final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
 
-                if (impl == null) {
-                    continue;
+                    if (impl == null) {
+                        continue;
+                    }
+
+                    if (key.mResDir == null
+                            || key.mResDir.equals(baseCodePath)
+                            || ArrayUtils.contains(oldPaths, key.mResDir)) {
+                        updatedResourceKeys.put(impl, new ResourcesKey(
+                                baseCodePath,
+                                copiedSplitDirs,
+                                copiedResourceDirs,
+                                key.mLibDirs,
+                                key.mDisplayId,
+                                key.mOverrideConfiguration,
+                                key.mCompatInfo,
+                                key.mLoaders
+                        ));
+                    }
                 }
 
-                if (key.mResDir == null
-                        || key.mResDir.equals(baseCodePath)
-                        || ArrayUtils.contains(oldPaths, key.mResDir)) {
-                    updatedResourceKeys.put(impl, new ResourcesKey(
-                            baseCodePath,
-                            copiedSplitDirs,
-                            copiedResourceDirs,
-                            key.mLibDirs,
-                            key.mDisplayId,
-                            key.mOverrideConfiguration,
-                            key.mCompatInfo,
-                            key.mLoaders
-                    ));
-                }
+                redirectResourcesToNewImplLocked(updatedResourceKeys);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
             }
-
-            redirectResourcesToNewImplLocked(updatedResourceKeys);
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
         }
     }
 
@@ -1556,7 +1562,7 @@
     public boolean overrideTokenDisplayAdjustments(IBinder token,
             @Nullable Consumer<DisplayAdjustments> override) {
         boolean handled = false;
-        synchronized (this) {
+        synchronized (mLock) {
             final ActivityResources tokenResources = mActivityResourceReferences.get(token);
             if (tokenResources == null) {
                 return false;
@@ -1589,7 +1595,7 @@
         @Override
         public void onLoadersChanged(@NonNull Resources resources,
                 @NonNull List<ResourcesLoader> newLoader) {
-            synchronized (ResourcesManager.this) {
+            synchronized (mLock) {
                 final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
                 if (oldKey == null) {
                     throw new IllegalArgumentException("Cannot modify resource loaders of"
@@ -1617,7 +1623,7 @@
          **/
         @Override
         public void onLoaderUpdated(@NonNull ResourcesLoader loader) {
-            synchronized (ResourcesManager.this) {
+            synchronized (mLock) {
                 final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceImplKeys =
                         new ArrayMap<>();
 
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 7c7cfdb..bbb49fb 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2022,12 +2022,9 @@
      * <p>
      * Output: Nothing.
      * </p>
-     *
-     * @hide
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE)
-    @SystemApi
     public static final String ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD =
             "android.intent.action.VIEW_PERMISSION_USAGE_FOR_PERIOD";
 
@@ -2188,12 +2185,7 @@
      * <p>
      * Type: String
      * </p>
-     *
-     * E.g. {@link android.Manifest.permission_group.CONTACTS}
-     *
-     * @hide
      */
-    @SystemApi
     public static final String EXTRA_PERMISSION_GROUP_NAME =
             "android.intent.extra.PERMISSION_GROUP_NAME";
 
@@ -5342,28 +5334,19 @@
      * {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD}
      *
      * E.g. an attribution tag could be location_provider, com.google.android.gms.*, etc.
-     *
-     * @hide
      */
-    @SystemApi
     public static final String EXTRA_ATTRIBUTION_TAGS = "android.intent.extra.ATTRIBUTION_TAGS";
 
     /**
      * A long representing the start timestamp (epoch time in millis) of the permission usage
      * when used with {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD}
-     *
-     * @hide
      */
-    @SystemApi
     public static final String EXTRA_START_TIME = "android.intent.extra.START_TIME";
 
     /**
      * A long representing the end timestamp (epoch time in millis) of the permission usage when
      * used with {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD}
-     *
-     * @hide
      */
-    @SystemApi
     public static final String EXTRA_END_TIME = "android.intent.extra.END_TIME";
 
     /**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a1d419e..edf0e57 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4775,8 +4775,7 @@
      * @param flags Additional option flags to modify the data returned.
      * @return A {@link ServiceInfo} object containing information about the
      *         service.
-     * @throws NameNotFoundException if a package with the given name cannot be
-     *             found on the system.
+     * @throws NameNotFoundException if the component cannot be found on the system.
      */
     @NonNull
     public abstract ServiceInfo getServiceInfo(@NonNull ComponentName component,
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index a60e642..13ff602 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -20,6 +20,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -29,6 +30,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * This class provides information for a shared library. There are
@@ -190,7 +192,8 @@
      *
      * @hide
      */
-    public List<String> getAllCodePaths() {
+    @TestApi
+    public @NonNull List<String> getAllCodePaths() {
         if (getPath() != null) {
             // Builtin library.
             ArrayList<String> list = new ArrayList<>();
@@ -198,7 +201,7 @@
             return list;
         } else {
             // Static or dynamic library.
-            return mCodePaths;
+            return Objects.requireNonNull(mCodePaths);
         }
     }
 
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 8bc3734..1eb4504 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -49,6 +49,9 @@
           "include-filter": "android.appsecurity.cts.AppSecurityTests#testPermissionDiffCert"
         }
       ]
+    },
+    {
+      "name": "CtsPackageManagerBootTestCases"
     }
   ]
 }
diff --git a/core/java/android/hardware/hdmi/OWNERS b/core/java/android/hardware/hdmi/OWNERS
index 16c15e3..60d43fd 100644
--- a/core/java/android/hardware/hdmi/OWNERS
+++ b/core/java/android/hardware/hdmi/OWNERS
@@ -4,3 +4,4 @@
 
 marvinramin@google.com
 nchalko@google.com
+lcnathalie@google.com
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index d41c0b4..caab152 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -52,12 +52,17 @@
     private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs";
     @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs;
 
+    private static final String IS_TEST_MODE_PROFILE_KEY = "mIsTestModeProfile";
+    private final boolean mIsTestModeProfile;
+
     private VcnConfig(
             @NonNull String packageName,
-            @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs) {
+            @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs,
+            boolean isTestModeProfile) {
         mPackageName = packageName;
         mGatewayConnectionConfigs =
                 Collections.unmodifiableSet(new ArraySet<>(gatewayConnectionConfigs));
+        mIsTestModeProfile = isTestModeProfile;
 
         validate();
     }
@@ -77,6 +82,7 @@
                 new ArraySet<>(
                         PersistableBundleUtils.toList(
                                 gatewayConnectionConfigsBundle, VcnGatewayConnectionConfig::new));
+        mIsTestModeProfile = in.getBoolean(IS_TEST_MODE_PROFILE_KEY);
 
         validate();
     }
@@ -104,6 +110,15 @@
     }
 
     /**
+     * Returns whether or not this VcnConfig is restricted to test networks.
+     *
+     * @hide
+     */
+    public boolean isTestModeProfile() {
+        return mIsTestModeProfile;
+    }
+
+    /**
      * Serializes this object to a PersistableBundle.
      *
      * @hide
@@ -119,13 +134,14 @@
                         new ArrayList<>(mGatewayConnectionConfigs),
                         VcnGatewayConnectionConfig::toPersistableBundle);
         result.putPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY, gatewayConnectionConfigsBundle);
+        result.putBoolean(IS_TEST_MODE_PROFILE_KEY, mIsTestModeProfile);
 
         return result;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mPackageName, mGatewayConnectionConfigs);
+        return Objects.hash(mPackageName, mGatewayConnectionConfigs, mIsTestModeProfile);
     }
 
     @Override
@@ -136,7 +152,8 @@
 
         final VcnConfig rhs = (VcnConfig) other;
         return mPackageName.equals(rhs.mPackageName)
-                && mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs);
+                && mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs)
+                && mIsTestModeProfile == rhs.mIsTestModeProfile;
     }
 
     // Parcelable methods
@@ -172,6 +189,8 @@
         @NonNull
         private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>();
 
+        private boolean mIsTestModeProfile = false;
+
         public Builder(@NonNull Context context) {
             Objects.requireNonNull(context, "context was null");
 
@@ -207,13 +226,29 @@
         }
 
         /**
+         * Restricts this VcnConfig to matching with test networks (only).
+         *
+         * <p>This method is for testing only, and must not be used by apps. Calling {@link
+         * VcnManager#setVcnConfig(ParcelUuid, VcnConfig)} with a VcnConfig where test-network usage
+         * is enabled will require the MANAGE_TEST_NETWORKS permission.
+         *
+         * @return this {@link Builder} instance, for chaining
+         * @hide
+         */
+        @NonNull
+        public Builder setIsTestModeProfile() {
+            mIsTestModeProfile = true;
+            return this;
+        }
+
+        /**
          * Builds and validates the VcnConfig.
          *
          * @return an immutable VcnConfig instance
          */
         @NonNull
         public VcnConfig build() {
-            return new VcnConfig(mPackageName, mGatewayConnectionConfigs);
+            return new VcnConfig(mPackageName, mGatewayConnectionConfigs, mIsTestModeProfile);
         }
     }
 }
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index f02346b..7eea0b1 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -15,6 +15,8 @@
  */
 package android.net.vcn;
 
+import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
+
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 
 import android.annotation.IntDef;
@@ -438,6 +440,8 @@
          *     distinguish between VcnGatewayConnectionConfigs configured on a single {@link
          *     VcnConfig}. This will be used as the identifier in VcnStatusCallback invocations.
          * @param tunnelConnectionParams the IKE tunnel connection configuration
+         * @throws IllegalArgumentException if the provided IkeTunnelConnectionParams is not
+         *     configured to support MOBIKE
          * @see IkeTunnelConnectionParams
          * @see VcnManager.VcnStatusCallback#onGatewayConnectionError
          */
@@ -446,6 +450,10 @@
                 @NonNull IkeTunnelConnectionParams tunnelConnectionParams) {
             Objects.requireNonNull(gatewayConnectionName, "gatewayConnectionName was null");
             Objects.requireNonNull(tunnelConnectionParams, "tunnelConnectionParams was null");
+            if (!tunnelConnectionParams.getIkeSessionParams().hasIkeOption(IKE_OPTION_MOBIKE)) {
+                throw new IllegalArgumentException(
+                        "MOBIKE must be configured for the provided IkeSessionParams");
+            }
 
             mGatewayConnectionName = gatewayConnectionName;
             mTunnelConnectionParams = tunnelConnectionParams;
diff --git a/core/java/android/net/vcn/VcnTransportInfo.java b/core/java/android/net/vcn/VcnTransportInfo.java
index 0e9ccf1..1f18184 100644
--- a/core/java/android/net/vcn/VcnTransportInfo.java
+++ b/core/java/android/net/vcn/VcnTransportInfo.java
@@ -16,23 +16,17 @@
 
 package android.net.vcn;
 
-import static android.net.NetworkCapabilities.REDACT_ALL;
-import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
+import static android.net.NetworkCapabilities.REDACT_NONE;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.net.NetworkCapabilities;
 import android.net.TransportInfo;
 import android.net.wifi.WifiInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.SubscriptionManager;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 import java.util.Objects;
 
 /**
@@ -55,32 +49,17 @@
     @Nullable private final WifiInfo mWifiInfo;
     private final int mSubId;
 
-    /**
-     * The redaction scheme to use when parcelling.
-     *
-     * <p>The TransportInfo/NetworkCapabilities redaction mechanisms rely on redaction being
-     * performed at parcelling time. This means that the redaction scheme must be stored for later
-     * use.
-     *
-     * <p>Since the redaction scheme itself is not parcelled, this field is listed as a transient.
-     *
-     * <p>Defaults to REDACT_ALL when constructed using public constructors, or creating from
-     * parcels.
-     */
-    private final transient long mRedactions;
-
     public VcnTransportInfo(@NonNull WifiInfo wifiInfo) {
-        this(wifiInfo, INVALID_SUBSCRIPTION_ID, REDACT_ALL);
+        this(wifiInfo, INVALID_SUBSCRIPTION_ID);
     }
 
     public VcnTransportInfo(int subId) {
-        this(null /* wifiInfo */, subId, REDACT_ALL);
+        this(null /* wifiInfo */, subId);
     }
 
-    private VcnTransportInfo(@Nullable WifiInfo wifiInfo, int subId, long redactions) {
+    private VcnTransportInfo(@Nullable WifiInfo wifiInfo, int subId) {
         mWifiInfo = wifiInfo;
         mSubId = subId;
-        mRedactions = redactions;
     }
 
     /**
@@ -102,25 +81,14 @@
      * SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
      *
      * @return the Subscription ID if a cellular underlying Network is present, else {@link
-     *     android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID}.
+     *     android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
      */
     public int getSubId() {
         return mSubId;
     }
 
-    /**
-     * Gets the redaction scheme
-     *
-     * @hide
-     */
-    @VisibleForTesting(visibility = PRIVATE)
-    public long getRedaction() {
-        return mRedactions;
-    }
-
     @Override
     public int hashCode() {
-        // mRedactions not hashed, as it is a transient, for control of parcelling
         return Objects.hash(mWifiInfo, mSubId);
     }
 
@@ -128,8 +96,6 @@
     public boolean equals(Object o) {
         if (!(o instanceof VcnTransportInfo)) return false;
         final VcnTransportInfo that = (VcnTransportInfo) o;
-
-        // mRedactions not compared, as it is a transient, for control of parcelling
         return Objects.equals(mWifiInfo, that.mWifiInfo) && mSubId == that.mSubId;
     }
 
@@ -143,31 +109,19 @@
     @NonNull
     public TransportInfo makeCopy(long redactions) {
         return new VcnTransportInfo(
-                mWifiInfo == null ? null : mWifiInfo.makeCopy(redactions), mSubId, redactions);
+                (mWifiInfo == null) ? null : mWifiInfo.makeCopy(redactions), mSubId);
     }
 
     @Override
     public long getApplicableRedactions() {
-        long redactions = REDACT_FOR_NETWORK_SETTINGS;
-
-        // Add additional wifi redactions if necessary
-        if (mWifiInfo != null) {
-            redactions |= mWifiInfo.getApplicableRedactions();
-        }
-
-        return redactions;
-    }
-
-    private boolean shouldParcelNetworkSettingsFields() {
-        return (mRedactions & NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS) == 0;
+        return (mWifiInfo == null) ? REDACT_NONE : mWifiInfo.getApplicableRedactions();
     }
 
     /** {@inheritDoc} */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(shouldParcelNetworkSettingsFields() ? mSubId : INVALID_SUBSCRIPTION_ID);
-        dest.writeParcelable(
-                shouldParcelNetworkSettingsFields() ? (Parcelable) mWifiInfo : null, flags);
+        dest.writeInt(mSubId);
+        dest.writeParcelable(mWifiInfo, flags);
     }
 
     @Override
@@ -181,17 +135,7 @@
                 public VcnTransportInfo createFromParcel(Parcel in) {
                     final int subId = in.readInt();
                     final WifiInfo wifiInfo = in.readParcelable(null);
-
-                    // If all fields are their null values, return null TransportInfo to avoid
-                    // leaking information about this being a VCN Network (instead of macro
-                    // cellular, etc)
-                    if (wifiInfo == null && subId == INVALID_SUBSCRIPTION_ID) {
-                        return null;
-                    }
-
-                    // Prevent further forwarding by redacting everything in future parcels from
-                    // this VcnTransportInfo
-                    return new VcnTransportInfo(wifiInfo, subId, REDACT_ALL);
+                    return new VcnTransportInfo(wifiInfo, subId);
                 }
 
                 public VcnTransportInfo[] newArray(int size) {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index b8ad068..326012d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2026,6 +2026,9 @@
             case USER_TYPE_SYSTEM_HEADLESS:
                 return FrameworkStatsLog
                         .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__SYSTEM_HEADLESS;
+            case USER_TYPE_PROFILE_CLONE:
+                return FrameworkStatsLog
+                        .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_CLONE;
             default:
                 return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
         }
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index d7d1902..1092adf 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -580,6 +580,13 @@
      */
     public static final String NAMESPACE_GAME_OVERLAY = "game_overlay";
 
+    /**
+     * Namespace for Constrain Display APIs related features.
+     *
+     * @hide
+     */
+    public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b879082..e410e50 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8636,7 +8636,6 @@
          */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         @TestApi
-        @Readable
         public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
 
         /**
diff --git a/core/java/android/service/voice/AbstractHotwordDetector.java b/core/java/android/service/voice/AbstractHotwordDetector.java
index 4896748..54ccf30 100644
--- a/core/java/android/service/voice/AbstractHotwordDetector.java
+++ b/core/java/android/service/voice/AbstractHotwordDetector.java
@@ -55,10 +55,8 @@
     /**
      * Detect hotword from an externally supplied stream of data.
      *
-     * @return a writeable file descriptor that clients can start writing data in the given format.
-     * In order to stop detection, clients can close the given stream.
+     * @return true if the request to start recognition succeeded
      */
-    @Nullable
     @Override
     public boolean startRecognition(
             @NonNull ParcelFileDescriptor audioStream,
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index bacc6ec..e813017 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -524,7 +524,7 @@
          * @param result Info about the second stage detection result, provided by the
          *         {@link HotwordDetectionService}.
          */
-        public void onRejected(@Nullable HotwordRejectedResult result) {
+        public void onRejected(@NonNull HotwordRejectedResult result) {
         }
 
         /**
@@ -1164,7 +1164,7 @@
         }
 
         @Override
-        public void onRejected(HotwordRejectedResult result) {
+        public void onRejected(@NonNull HotwordRejectedResult result) {
             if (DBG) {
                 Slog.d(TAG, "onRejected(" + result + ")");
             } else {
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index 473e7ae..ea01e09 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -16,6 +16,8 @@
 
 package android.service.voice;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.DurationMillisLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -414,11 +416,15 @@
         }
 
         /**
-         * Called when the detected result is valid.
+         * Informs the {@link HotwordDetector} that the keyphrase was detected.
+         *
+         * @param result Info about the detection result. This is provided to the
+         *         {@link HotwordDetector}.
          */
-        public void onDetected(@Nullable HotwordDetectedResult hotwordDetectedResult) {
+        public void onDetected(@NonNull HotwordDetectedResult result) {
+            requireNonNull(result);
             try {
-                mRemoteCallback.onDetected(hotwordDetectedResult);
+                mRemoteCallback.onDetected(result);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -433,7 +439,8 @@
          * @param result Info about the second stage detection result. This is provided to
          *         the {@link HotwordDetector}.
          */
-        public void onRejected(@Nullable HotwordRejectedResult result) {
+        public void onRejected(@NonNull HotwordRejectedResult result) {
+            requireNonNull(result);
             try {
                 mRemoteCallback.onRejected(result);
             } catch (RemoteException e) {
diff --git a/core/java/android/service/voice/HotwordDetector.java b/core/java/android/service/voice/HotwordDetector.java
index 2fb4dbc..d3c10ea 100644
--- a/core/java/android/service/voice/HotwordDetector.java
+++ b/core/java/android/service/voice/HotwordDetector.java
@@ -160,7 +160,7 @@
          * @param result Info about the second stage detection result, provided by the
          *         {@link HotwordDetectionService}.
          */
-        void onRejected(@Nullable HotwordRejectedResult result);
+        void onRejected(@NonNull HotwordRejectedResult result);
 
         /**
          * Called when the {@link HotwordDetectionService} is created by the system and given a
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 6c8753b..000c685 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -29,6 +29,7 @@
 import android.util.AttributeSet;
 import android.widget.RelativeLayout;
 import android.widget.RemoteViews;
+import android.widget.TextView;
 
 import com.android.internal.R;
 import com.android.internal.widget.CachingIconView;
@@ -175,6 +176,28 @@
     }
 
     /**
+     * This is used to make the low-priority header show the bolded text of a title.
+     *
+     * @param styleTextAsTitle true if this header's text is to have the style of a title
+     */
+    @RemotableViewMethod
+    public void styleTextAsTitle(boolean styleTextAsTitle) {
+        int styleResId = styleTextAsTitle
+                ? R.style.TextAppearance_DeviceDefault_Notification_Title
+                : R.style.TextAppearance_DeviceDefault_Notification_Info;
+        // Most of the time, we're showing text in the minimized state
+        View headerText = findViewById(R.id.header_text);
+        if (headerText instanceof TextView) {
+            ((TextView) headerText).setTextAppearance(styleResId);
+        }
+        // If there's no summary or text, we show the app name instead of nothing
+        View appNameText = findViewById(R.id.app_name_text);
+        if (appNameText instanceof TextView) {
+            ((TextView) appNameText).setTextAppearance(styleResId);
+        }
+    }
+
+    /**
      * Get the current margin end value for the header text.
      * Add this to {@link #getTopLineBaseMarginEnd()} to get the total margin of the top line.
      *
diff --git a/core/java/android/view/translation/UiTranslationSpec.java b/core/java/android/view/translation/UiTranslationSpec.java
index b43dbce..3d1ebfd 100644
--- a/core/java/android/view/translation/UiTranslationSpec.java
+++ b/core/java/android/view/translation/UiTranslationSpec.java
@@ -20,6 +20,7 @@
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.widget.TextView;
 
 import com.android.internal.util.DataClass;
 
@@ -35,36 +36,38 @@
 public final class UiTranslationSpec implements Parcelable {
 
     /**
-     * Whether the original content of the view should be directly modified to include padding that
-     * makes it the same size as the translated content. Defaults to {@code false}.
+     * Whether the original content of the view should be made to appear as if it is the same size
+     * as the translated content. Defaults to {@code false}.
      * <p>
-     * For {@link android.widget.TextView}, the system does not directly modify the original text,
-     * rather changes the displayed content using a
-     * {@link android.text.method.TransformationMethod}.
-     * This can cause issues in apps that do not account for TransformationMethods. For example, an
-     * app using DynamicLayout may use the calculated text offsets to operate on the original text,
-     * but this can be problematic when the layout was calculated on translated text with a
-     * different length.
+     * For {@link TextView}, the system does not directly modify the original text, rather
+     * changes the displayed content using a {@link android.text.method.TransformationMethod}. This
+     * can cause issues in apps that do not account for length-changing TransformationMethods. For
+     * example, an app using DynamicLayout may use the calculated line-offsets to operate on the
+     * original text, but this can cause crashes when the layout was calculated on translated text
+     * with a different length.
      * <p>
-     * If this is {@code true}, for a TextView the default implementation will append spaces to the
-     * text to make the length the same as the translated text.
+     * If this is {@code true}, for a TextView the default implementation appends spaces to the
+     * result of {@link TextView#getText()} to make the length the same as the translated text.
+     * <p>
+     * This only affects apps with target SDK R or lower.
      */
     private boolean mShouldPadContentForCompat = false;
 
     /**
-     * Whether the original content of the view should be directly modified to include padding that
-     * makes it the same size as the translated content.
+     * Whether the original content of the view should be made to appear as if it is the same size
+     * as the translated content.
      * <p>
-     * For {@link android.widget.TextView}, the system does not directly modify the original text,
-     * rather changes the displayed content using a
-     * {@link android.text.method.TransformationMethod}.
-     * This can cause issues in apps that do not account for TransformationMethods. For example, an
-     * app using DynamicLayout may use the calculated text offsets to operate on the original text,
-     * but this can be problematic when the layout was calculated on translated text with a
-     * different length.
+     * For {@link TextView}, the system does not directly modify the original text, rather
+     * changes the displayed content using a {@link android.text.method.TransformationMethod}. This
+     * can cause issues in apps that do not account for length-changing TransformationMethods. For
+     * example, an app using DynamicLayout may use the calculated line-offsets to operate on the
+     * original text, but this can cause crashes when the layout was calculated on translated text
+     * with a different length.
      * <p>
-     * If this is {@code true}, for a TextView the default implementation will append spaces to the
-     * text to make the length the same as the translated text.
+     * If this is {@code true}, for a TextView the default implementation appends spaces to the
+     * result of {@link TextView#getText()} to make the length the same as the translated text.
+     * <p>
+     * This only affects apps with target SDK R or lower.
      */
     public boolean shouldPadContentForCompat() {
         return mShouldPadContentForCompat;
@@ -190,19 +193,20 @@
         }
 
         /**
-         * Whether the original content of the view should be directly modified to include padding that
-         * makes it the same size as the translated content. Defaults to {@code false}.
+         * Whether the original content of the view should be made to appear as if it is the same size
+         * as the translated content. Defaults to {@code false}.
          * <p>
-         * For {@link android.widget.TextView}, the system does not directly modify the original text,
-         * rather changes the displayed content using a
-         * {@link android.text.method.TransformationMethod}.
-         * This can cause issues in apps that do not account for TransformationMethods. For example, an
-         * app using DynamicLayout may use the calculated text offsets to operate on the original text,
-         * but this can be problematic when the layout was calculated on translated text with a
-         * different length.
+         * For {@link TextView}, the system does not directly modify the original text, rather
+         * changes the displayed content using a {@link android.text.method.TransformationMethod}. This
+         * can cause issues in apps that do not account for length-changing TransformationMethods. For
+         * example, an app using DynamicLayout may use the calculated line-offsets to operate on the
+         * original text, but this can cause crashes when the layout was calculated on translated text
+         * with a different length.
          * <p>
-         * If this is {@code true}, for a TextView the default implementation will append spaces to the
-         * text to make the length the same as the translated text.
+         * If this is {@code true}, for a TextView the default implementation appends spaces to the
+         * result of {@link TextView#getText()} to make the length the same as the translated text.
+         * <p>
+         * This only affects apps with target SDK R or lower.
          */
         @DataClass.Generated.Member
         public @NonNull Builder setShouldPadContentForCompat(boolean value) {
@@ -234,7 +238,7 @@
     }
 
     @DataClass.Generated(
-            time = 1619034161701L,
+            time = 1620790033058L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/translation/UiTranslationSpec.java",
             inputSignatures = "private  boolean mShouldPadContentForCompat\npublic  boolean shouldPadContentForCompat()\nclass UiTranslationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genToString=true)")
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index a1d4822..0bbaac0f 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -60,11 +60,13 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.view.inspector.InspectableProperty;
 import android.view.textclassifier.TextClassifier;
+import android.view.translation.TranslationCapability;
 import android.view.translation.TranslationSpec.DataFormat;
 import android.view.translation.ViewTranslationRequest;
 import android.view.translation.ViewTranslationResponse;
@@ -2869,6 +2871,16 @@
     }
 
     @Override
+    public void dispatchCreateViewTranslationRequest(@NonNull Map<AutofillId, long[]> viewIds,
+            @NonNull @DataFormat int[] supportedFormats,
+            @Nullable TranslationCapability capability,
+            @NonNull List<ViewTranslationRequest> requests) {
+        super.dispatchCreateViewTranslationRequest(viewIds, supportedFormats, capability, requests);
+        mProvider.getViewDelegate().dispatchCreateViewTranslationRequest(viewIds, supportedFormats,
+                capability, requests);
+    }
+
+    @Override
     public void onVirtualViewTranslationResponses(
             @NonNull LongSparseArray<ViewTranslationResponse> response) {
         mProvider.getViewDelegate().onVirtualViewTranslationResponses(response);
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 8d996ee..f9f823b 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -45,10 +45,12 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.view.textclassifier.TextClassifier;
+import android.view.translation.TranslationCapability;
 import android.view.translation.TranslationSpec.DataFormat;
 import android.view.translation.ViewTranslationRequest;
 import android.view.translation.ViewTranslationResponse;
@@ -59,6 +61,7 @@
 
 import java.io.BufferedWriter;
 import java.io.File;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -377,6 +380,14 @@
                         LongSparseArray<ViewTranslationResponse> response) {
         }
 
+        default void dispatchCreateViewTranslationRequest(
+                @NonNull @SuppressWarnings("unused") Map<AutofillId, long[]> viewIds,
+                @NonNull @SuppressWarnings("unused") @DataFormat int[] supportedFormats,
+                @Nullable @SuppressWarnings("unused") TranslationCapability capability,
+                @NonNull @SuppressWarnings("unused") List<ViewTranslationRequest> requests) {
+
+        }
+
         public AccessibilityNodeProvider getAccessibilityNodeProvider();
 
         public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 9eebf06..786e6fc 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -24,7 +24,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.ColorStateList;
-import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.BlendMode;
 import android.graphics.Canvas;
@@ -40,8 +39,9 @@
 
 import java.time.Clock;
 import java.time.DateTimeException;
+import java.time.Duration;
 import java.time.Instant;
-import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.time.ZoneId;
 import java.util.Formatter;
 import java.util.Locale;
@@ -61,8 +61,8 @@
 @Deprecated
 public class AnalogClock extends View {
     private static final String LOG_TAG = "AnalogClock";
-    /** How often the clock should refresh to make the seconds hand advance at ~15 FPS. */
-    private static final long SECONDS_TICK_FREQUENCY_MS = 1000 / 15;
+    /** How many times per second that the seconds hand advances. */
+    private static final long SECONDS_HAND_FPS = 30;
 
     private Clock mClock;
     @Nullable
@@ -106,7 +106,6 @@
     public AnalogClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
 
-        final Resources r = context.getResources();
         final TypedArray a = context.obtainStyledAttributes(
                 attrs, com.android.internal.R.styleable.AnalogClock, defStyleAttr, defStyleRes);
         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.AnalogClock,
@@ -716,25 +715,34 @@
     }
 
     private void onTimeChanged() {
-        long nowMillis = mClock.millis();
-        LocalDateTime localDateTime = toLocalDateTime(nowMillis, mClock.getZone());
+        Instant now = mClock.instant();
+        onTimeChanged(now.atZone(mClock.getZone()).toLocalTime(), now.toEpochMilli());
+    }
 
-        int hour = localDateTime.getHour();
-        int minute = localDateTime.getMinute();
-        int second = localDateTime.getSecond();
+    private void onTimeChanged(LocalTime localTime, long nowMillis) {
+        float previousHour = mHour;
+        float previousMinutes = mMinutes;
 
-        mSeconds = second + localDateTime.getNano() / 1_000_000_000f;
-        mMinutes = minute + second / 60.0f;
-        mHour = hour + mMinutes / 60.0f;
+        float rawSeconds = localTime.getSecond() + localTime.getNano() / 1_000_000_000f;
+        // We round the fraction of the second so that the seconds hand always occupies the same
+        // n positions between two given numbers, where n is the number of ticks per second. This
+        // ensures the second hand advances by a consistent distance despite our handler callbacks
+        // occurring at inconsistent frequencies.
+        mSeconds = Math.round(rawSeconds * SECONDS_HAND_FPS) / (float) SECONDS_HAND_FPS;
+        mMinutes = localTime.getMinute() + mSeconds / 60.0f;
+        mHour = localTime.getHour() + mMinutes / 60.0f;
         mChanged = true;
 
-        updateContentDescription(nowMillis);
+        // Update the content description only if the announced hours and minutes have changed.
+        if ((int) previousHour != (int) mHour || (int) previousMinutes != (int) mMinutes) {
+            updateContentDescription(nowMillis);
+        }
     }
 
     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
+            if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
                 createClock();
             }
 
@@ -747,15 +755,32 @@
     private final Runnable mSecondsTick = new Runnable() {
         @Override
         public void run() {
+            removeCallbacks(this);
             if (!mVisible || mSecondHand == null) {
                 return;
             }
 
-            onTimeChanged();
+            Instant now = mClock.instant();
+            LocalTime localTime = now.atZone(mClock.getZone()).toLocalTime();
+            // How many milliseconds through the second we currently are.
+            long millisOfSecond = Duration.ofNanos(localTime.getNano()).toMillis();
+            // How many milliseconds there are between tick positions for the seconds hand.
+            double millisPerTick = 1000 / (double) SECONDS_HAND_FPS;
+            // How many milliseconds we are past the last tick position.
+            long millisPastLastTick = Math.round(millisOfSecond % millisPerTick);
+            // How many milliseconds there are until the next tick position.
+            long millisUntilNextTick = Math.round(millisPerTick - millisPastLastTick);
+            // If we are exactly at the tick position, this could be 0 milliseconds due to rounding.
+            // In this case, advance by the full amount of millis to the next position.
+            if (millisUntilNextTick <= 0) {
+                millisUntilNextTick = Math.round(millisPerTick);
+            }
+            // Schedule a callback for when the next tick should occur.
+            postDelayed(this, millisUntilNextTick);
+
+            onTimeChanged(localTime, now.toEpochMilli());
 
             invalidate();
-
-            postDelayed(this, SECONDS_TICK_FREQUENCY_MS);
         }
     };
 
@@ -782,14 +807,6 @@
         setContentDescription(contentDescription);
     }
 
-    private static LocalDateTime toLocalDateTime(long timeMillis, ZoneId zoneId) {
-        // java.time types like LocalDateTime / Instant can support the full range of "long millis"
-        // with room to spare so we do not need to worry about overflow / underflow and the
-        // resulting exceptions while the input to this class is a long.
-        Instant instant = Instant.ofEpochMilli(timeMillis);
-        return LocalDateTime.ofInstant(instant, zoneId);
-    }
-
     /**
      * Tries to parse a {@link ZoneId} from {@code timeZone}, returning null if it is null or there
      * is an error parsing.
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index db67bab..56f81e2 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -11194,7 +11194,7 @@
         final long elapsedRealtimeUs = elapsedRealtimeMillis * 1000;
         mStartCount = 0;
         initTimes(uptimeUs, elapsedRealtimeUs);
-        mScreenOnTimer.reset(false, uptimeUs);
+        mScreenOnTimer.reset(false, elapsedRealtimeUs);
         mScreenDozeTimer.reset(false, elapsedRealtimeUs);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i].reset(false, elapsedRealtimeUs);
diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
index 6e99bbb..c322258 100644
--- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java
+++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
@@ -51,7 +51,7 @@
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
-        if (!mHasBluetoothPowerController || !batteryStats.hasBluetoothActivityReporting()) {
+        if (!batteryStats.hasBluetoothActivityReporting()) {
             return;
         }
 
@@ -69,8 +69,8 @@
         final ControllerActivityCounter activityCounter =
                 batteryStats.getBluetoothControllerActivity();
         final long systemDurationMs = calculateDuration(activityCounter);
-        final double systemPowerMah =
-                calculatePowerMah(powerModel, measuredChargeUC, activityCounter);
+        final double systemPowerMah = calculatePowerMah(powerModel, measuredChargeUC,
+                activityCounter, query.shouldForceUsePowerProfileModel());
 
         // Subtract what the apps used, but clamp to 0.
         final long systemComponentDurationMs = Math.max(0, systemDurationMs - total.durationMs);
@@ -100,7 +100,8 @@
         final ControllerActivityCounter activityCounter =
                 app.getBatteryStatsUid().getBluetoothControllerActivity();
         final long durationMs = calculateDuration(activityCounter);
-        final double powerMah = calculatePowerMah(powerModel, measuredChargeUC, activityCounter);
+        final double powerMah = calculatePowerMah(powerModel, measuredChargeUC, activityCounter,
+                query.shouldForceUsePowerProfileModel());
 
         app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, durationMs)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerMah, powerModel);
@@ -132,7 +133,7 @@
                 batteryStats.getBluetoothControllerActivity();
         final long systemDurationMs = calculateDuration(activityCounter);
         final double systemPowerMah =
-                calculatePowerMah(powerModel, measuredChargeUC, activityCounter);
+                calculatePowerMah(powerModel, measuredChargeUC, activityCounter, false);
 
         // Subtract what the apps used, but clamp to 0.
         final double powerMah = Math.max(0, systemPowerMah - total.powerMah);
@@ -165,7 +166,8 @@
         final int powerModel = getPowerModel(measuredChargeUC);
         final ControllerActivityCounter activityCounter = u.getBluetoothControllerActivity();
         final long durationMs = calculateDuration(activityCounter);
-        final double powerMah = calculatePowerMah(powerModel, measuredChargeUC, activityCounter);
+        final double powerMah = calculatePowerMah(powerModel, measuredChargeUC, activityCounter,
+                false);
 
         app.bluetoothRunningTimeMs = durationMs;
         app.bluetoothPowerMah = powerMah;
@@ -188,7 +190,7 @@
 
     /** Returns bluetooth power usage based on the best data available. */
     private double calculatePowerMah(@BatteryConsumer.PowerModel int powerModel,
-            long measuredChargeUC, ControllerActivityCounter counter) {
+            long measuredChargeUC, ControllerActivityCounter counter, boolean ignoreReportedPower) {
         if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
             return uCtoMah(measuredChargeUC);
         }
@@ -197,12 +199,17 @@
             return 0;
         }
 
-        final double powerMah =
-                counter.getPowerCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED)
-                        / (double) (1000 * 60 * 60);
+        if (!ignoreReportedPower) {
+            final double powerMah =
+                    counter.getPowerCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED)
+                            / (double) (1000 * 60 * 60);
+            if (powerMah != 0) {
+                return powerMah;
+            }
+        }
 
-        if (powerMah != 0) {
-            return powerMah;
+        if (!mHasBluetoothPowerController) {
+            return 0;
         }
 
         final long idleTimeMs =
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index e670178..50df166 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -477,18 +477,17 @@
             }
             copyToCurTimes();
             boolean notify = false;
-            boolean valid = true;
             for (int i = 0; i < mFreqCount; i++) {
                 // Unit is 10ms.
                 mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
                 if (mDeltaTimes[i] < 0) {
                     Slog.e(mTag, "Negative delta from freq time for uid: " + uid
                             + ", delta: " + mDeltaTimes[i]);
-                    valid = false;
+                    return;
                 }
                 notify |= mDeltaTimes[i] > 0;
             }
-            if (notify && valid) {
+            if (notify) {
                 System.arraycopy(mCurTimes, 0, lastTimes, 0, mFreqCount);
                 if (cb != null) {
                     cb.onUidCpuTime(uid, mDeltaTimes);
@@ -826,11 +825,11 @@
                 if (mDeltaTime[i] < 0) {
                     Slog.e(mTag, "Negative delta from cluster time for uid: " + uid
                             + ", delta: " + mDeltaTime[i]);
-                    valid = false;
+                    return;
                 }
                 notify |= mDeltaTime[i] > 0;
             }
-            if (notify && valid) {
+            if (notify) {
                 System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters);
                 if (cb != null) {
                     cb.onUidCpuTime(uid, mDeltaTime);
diff --git a/core/java/com/android/internal/os/WakelockPowerCalculator.java b/core/java/com/android/internal/os/WakelockPowerCalculator.java
index d594107..e0ef129 100644
--- a/core/java/com/android/internal/os/WakelockPowerCalculator.java
+++ b/core/java/com/android/internal/os/WakelockPowerCalculator.java
@@ -75,22 +75,29 @@
         // this remainder to the OS, if possible.
         calculateRemaining(result, batteryStats, rawRealtimeUs, rawUptimeUs,
                 BatteryStats.STATS_SINCE_CHARGED, osPowerMah, osDurationMs, totalAppDurationMs);
+        final double remainingPowerMah = result.powerMah;
         if (osBatteryConsumer != null) {
             osBatteryConsumer.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WAKELOCK,
                     result.durationMs)
-                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, result.powerMah);
+                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, remainingPowerMah);
         }
 
-        final long wakeTimeMillis =
-                calculateWakeTimeMillis(batteryStats, rawRealtimeUs, rawUptimeUs);
-        final double powerMah = mPowerEstimator.calculatePower(wakeTimeMillis);
+        long wakeTimeMs = calculateWakeTimeMillis(batteryStats, rawRealtimeUs, rawUptimeUs);
+        if (wakeTimeMs < 0) {
+            wakeTimeMs = 0;
+        }
         builder.getAggregateBatteryConsumerBuilder(
                 BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
-                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WAKELOCK, wakeTimeMillis)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, powerMah);
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WAKELOCK,
+                        wakeTimeMs)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK,
+                        appPowerMah + remainingPowerMah);
         builder.getAggregateBatteryConsumerBuilder(
                 BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, appPowerMah);
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WAKELOCK,
+                        totalAppDurationMs)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK,
+                        appPowerMah);
     }
 
     @Override
@@ -167,9 +174,16 @@
             }
             result.durationMs = osDurationMs + wakeTimeMillis;
             result.powerMah = osPowerMah + power;
+        } else {
+            result.durationMs = 0;
+            result.powerMah = 0;
         }
     }
 
+    /**
+     * Return on-battery/screen-off time.  May be negative if the screen-on time exceeds
+     * the on-battery time.
+     */
     private long calculateWakeTimeMillis(BatteryStats batteryStats, long rawRealtimeUs,
             long rawUptimeUs) {
         final long batteryUptimeUs = batteryStats.getBatteryUptime(rawUptimeUs);
diff --git a/core/res/Android.bp b/core/res/Android.bp
index b988b5a..6063062 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -100,8 +100,7 @@
         + "RES_DIR=$$(dirname $$(dirname $${INPUTS[0]})) && "
         + "mkdir -p $$RES_DIR/values && "
         + "cp $${INPUTS[1]} $$RES_DIR/values && "
-        + "$(location soong_zip) -o $(out) -C $$RES_DIR -D $$RES_DIR && "
-        + "cp $(out) ."
+        + "$(location soong_zip) -o $(out) -C $$RES_DIR -D $$RES_DIR"
 }
 
 android_app {
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index c3b35c8..c4838b8 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -206,6 +206,12 @@
   <!-- A tag used to store the margin end for this view when the right icon is gone -->
   <item type="id" name="tag_margin_end_when_icon_visible" />
 
+  <!-- A tag used on the notification @id/left_icon to indicate that this view should be pupulated with the drawable from @id/right_icon when visible. -->
+  <item type="id" name="tag_uses_right_icon_drawable" />
+
+  <!-- A tag used on notification @id/right_icon to indicate that this view should remain visible even when the @id/left_icon is shown. -->
+  <item type="id" name="tag_keep_when_showing_left_icon" />
+
   <!-- Marks the "copy to clipboard" button in the ChooserActivity -->
   <item type="id" name="chooser_copy_button" />
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d8620a7..d440173 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3167,6 +3167,8 @@
   <java-symbol type="dimen" name="notification_action_disabled_alpha" />
   <java-symbol type="id" name="tag_margin_end_when_icon_visible" />
   <java-symbol type="id" name="tag_margin_end_when_icon_gone" />
+  <java-symbol type="id" name="tag_uses_right_icon_drawable" />
+  <java-symbol type="id" name="tag_keep_when_showing_left_icon" />
 
   <!-- Override Wake Key Behavior When Screen is Off -->
   <java-symbol type="bool" name="config_wakeOnDpadKeyPress" />
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 46dbe0f..e7ee9dc 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -166,7 +166,7 @@
 
         Configuration newConfig = new Configuration();
         newConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
-        mResourcesManager.applyConfigurationToResourcesLocked(newConfig, null);
+        mResourcesManager.applyConfigurationToResources(newConfig, null);
 
         final Configuration expectedConfig = new Configuration();
         expectedConfig.setToDefaults();
diff --git a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
index 4a5528d..79f7a5c 100644
--- a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
@@ -40,6 +40,7 @@
 
     @Test
     public void testMeasuredEnergyBasedModel() {
+        mStatsRule.initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
         stats.updateDisplayMeasuredEnergyStatsLocked(300_000_000, Display.STATE_ON, 0);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index f168b3c..464412f 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -506,6 +506,7 @@
     public void testUpdateDisplayMeasuredEnergyStatsLocked() {
         final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
         final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.initMeasuredEnergyStats();
 
         clocks.realtime = 0;
         int screen = Display.STATE_OFF;
@@ -590,6 +591,7 @@
     public void testUpdateCustomMeasuredEnergyStatsLocked_neverCalled() {
         final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
         final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.initMeasuredEnergyStats();
         bi.setOnBatteryInternal(true);
 
         final int uid1 = 11500;
@@ -603,6 +605,7 @@
     public void testUpdateCustomMeasuredEnergyStatsLocked() {
         final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
         final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.initMeasuredEnergyStats();
 
         final int bucketA = 0; // Custom bucket 0
         final int bucketB = 1; // Custom bucket 1
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
index 1a6408f..8c9591c 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -51,12 +51,7 @@
 
     private final PowerProfile mPowerProfile;
     private final MockClocks mMockClocks = new MockClocks();
-    private final MockBatteryStatsImpl mBatteryStats = new MockBatteryStatsImpl(mMockClocks) {
-        @Override
-        public boolean hasBluetoothActivityReporting() {
-            return true;
-        }
-    };
+    private final MockBatteryStatsImpl mBatteryStats;
 
     private BatteryUsageStats mBatteryUsageStats;
     private boolean mScreenOn;
@@ -64,6 +59,7 @@
     public BatteryUsageStatsRule() {
         Context context = InstrumentationRegistry.getContext();
         mPowerProfile = spy(new PowerProfile(context, true /* forTest */));
+        mBatteryStats = new MockBatteryStatsImpl(mMockClocks);
         mBatteryStats.setPowerProfile(mPowerProfile);
     }
 
@@ -110,10 +106,17 @@
 
     /** Call only after setting the power profile information. */
     public BatteryUsageStatsRule initMeasuredEnergyStatsLocked() {
+        return initMeasuredEnergyStatsLocked(new String[0]);
+    }
+
+    /** Call only after setting the power profile information. */
+    public BatteryUsageStatsRule initMeasuredEnergyStatsLocked(
+            String[] customPowerComponentNames) {
         final boolean[] supportedStandardBuckets =
                 new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
         Arrays.fill(supportedStandardBuckets, true);
-        mBatteryStats.initMeasuredEnergyStatsLocked(supportedStandardBuckets, new String[0]);
+        mBatteryStats.initMeasuredEnergyStatsLocked(supportedStandardBuckets,
+                customPowerComponentNames);
         mBatteryStats.informThatAllExternalStatsAreFlushed();
         return this;
     }
diff --git a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
index 2de621c..5c84794 100644
--- a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
@@ -22,6 +22,7 @@
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.UidTraffic;
 import android.os.BatteryConsumer;
+import android.os.BatteryStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.Process;
 
@@ -43,111 +44,89 @@
             .setAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE, 10.0)
             .setAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX, 50.0)
             .setAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX, 100.0)
-            .initMeasuredEnergyStatsLocked();
+            .setAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE, 3700);
 
     @Test
     public void testTimerBasedModel() {
-        setDurationsAndPower(mStatsRule.getUidStats(Process.BLUETOOTH_UID)
-                        .getOrCreateBluetoothControllerActivityLocked(),
-                1000, 2000, 3000, 0);
-
-        setDurationsAndPower(mStatsRule.getUidStats(APP_UID)
-                        .getOrCreateBluetoothControllerActivityLocked(),
-                4000, 5000, 6000, 0);
-
-        setDurationsAndPower((BatteryStatsImpl.ControllerActivityCounterImpl)
-                        mStatsRule.getBatteryStats().getBluetoothControllerActivity(),
-                6000, 8000, 10000, 0);
+        setupBluetoothEnergyInfo(0, BatteryStats.POWER_DATA_UNAVAILABLE);
 
         BluetoothPowerCalculator calculator =
                 new BluetoothPowerCalculator(mStatsRule.getPowerProfile());
 
         mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
 
-        assertBluetoothPowerAndDuration(
-                mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
-                0.11388, 6000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-        assertBluetoothPowerAndDuration(
-                mStatsRule.getUidBatteryConsumer(APP_UID),
-                0.24722, 15000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-        assertBluetoothPowerAndDuration(
-                mStatsRule.getDeviceBatteryConsumer(),
-                0.40555, 24000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-        assertBluetoothPowerAndDuration(
-                mStatsRule.getAppsBatteryConsumer(),
-                0.36111, 21000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+        assertCalculatedPower(0.08216, 0.18169, 0.26388, 0.26386,
+                BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
-    public void testReportedPowerBasedModel() {
-        setDurationsAndPower(mStatsRule.getUidStats(Process.BLUETOOTH_UID)
-                        .getOrCreateBluetoothControllerActivityLocked(),
-                1000, 2000, 3000, 360000);
-
-        setDurationsAndPower(mStatsRule.getUidStats(APP_UID)
-                        .getOrCreateBluetoothControllerActivityLocked(),
-                4000, 5000, 6000, 720000);
-
-        setDurationsAndPower((BatteryStatsImpl.ControllerActivityCounterImpl)
-                        mStatsRule.getBatteryStats().getBluetoothControllerActivity(),
-                6000, 8000, 10000, 1260000);
+    public void testReportedEnergyBasedModel() {
+        setupBluetoothEnergyInfo(4000000, BatteryStats.POWER_DATA_UNAVAILABLE);
 
         BluetoothPowerCalculator calculator =
                 new BluetoothPowerCalculator(mStatsRule.getPowerProfile());
 
-        mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
-
-        assertBluetoothPowerAndDuration(
-                mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
-                0.1, 6000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-        assertBluetoothPowerAndDuration(
-                mStatsRule.getUidBatteryConsumer(APP_UID),
-                0.2, 15000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-        assertBluetoothPowerAndDuration(
-                mStatsRule.getDeviceBatteryConsumer(),
-                0.35, 24000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-        assertBluetoothPowerAndDuration(
-                mStatsRule.getAppsBatteryConsumer(),
-                0.3, 21000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-    }
-
-    @Test
-    public void testMeasuredEnergyBasedModel() {
-        final BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(1000,
-                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 7000, 5000, 0, 100000);
-        info.setUidTraffic(new UidTraffic[]{
-                new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000),
-                new UidTraffic(APP_UID, 3000, 4000)
-        });
-        mStatsRule.getBatteryStats().updateBluetoothStateLocked(info, 1200000, 1000, 1000);
-
-        final BluetoothPowerCalculator calculator =
-                new BluetoothPowerCalculator(mStatsRule.getPowerProfile());
-
         mStatsRule.apply(new BatteryUsageStatsQuery.Builder().includePowerModels().build(),
                 calculator);
 
-        assertBluetoothPowerAndDuration(
-                mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
-                0.10378, 3583, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
-        assertBluetoothPowerAndDuration(
-                mStatsRule.getUidBatteryConsumer(APP_UID),
-                0.22950, 8416, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
-        assertBluetoothPowerAndDuration(
-                mStatsRule.getDeviceBatteryConsumer(),
-                0.33333, 12000, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
-        assertBluetoothPowerAndDuration(
-                mStatsRule.getAppsBatteryConsumer(),
-                0.33329, 11999, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+        assertCalculatedPower(0.08216, 0.18169, 0.30030, 0.26386,
+                BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
-    private void setDurationsAndPower(
-            BatteryStatsImpl.ControllerActivityCounterImpl controllerActivity, int idleDurationMs,
-            int rxDurationMs, int txDurationMs, long powerMaMs) {
-        controllerActivity.getIdleTimeCounter().addCountLocked(idleDurationMs);
-        controllerActivity.getRxTimeCounter().addCountLocked(rxDurationMs);
-        controllerActivity.getTxTimeCounters()[0].addCountLocked(txDurationMs);
-        controllerActivity.getPowerCounter().addCountLocked(powerMaMs);
+    @Test
+    public void testMeasuredEnergyBasedModel() {
+        mStatsRule.initMeasuredEnergyStatsLocked();
+        setupBluetoothEnergyInfo(0, 1200000);
+
+        final BluetoothPowerCalculator calculator =
+                new BluetoothPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(calculator);
+
+        assertCalculatedPower(0.10378, 0.22950, 0.33333, 0.33329,
+                BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+    }
+
+    @Test
+    public void testIgnoreMeasuredEnergyBasedModel() {
+        mStatsRule.initMeasuredEnergyStatsLocked();
+        setupBluetoothEnergyInfo(4000000, 1200000);
+
+        BluetoothPowerCalculator calculator =
+                new BluetoothPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
+
+        assertCalculatedPower(0.08216, 0.18169, 0.26388, 0.26386,
+                BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+    }
+
+    private void setupBluetoothEnergyInfo(long reportedEnergyUc, long consumedEnergyUc) {
+        final BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(1000,
+                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 7000, 5000, 0,
+                reportedEnergyUc);
+        info.setUidTraffic(new UidTraffic[]{
+                new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000),
+                new UidTraffic(APP_UID, 3000, 4000)
+        });
+        mStatsRule.getBatteryStats().updateBluetoothStateLocked(info,
+                consumedEnergyUc, 1000, 1000);
+    }
+
+    private void assertCalculatedPower(double bluetoothUidPowerMah, double appPowerMah,
+            double devicePowerMah, double allAppsPowerMah, int powerModelPowerProfile) {
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
+                bluetoothUidPowerMah, 3583, powerModelPowerProfile);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getUidBatteryConsumer(APP_UID),
+                appPowerMah, 8416, powerModelPowerProfile);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getDeviceBatteryConsumer(),
+                devicePowerMah, 12000, powerModelPowerProfile);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getAppsBatteryConsumer(),
+                allAppsPowerMah, 11999, powerModelPowerProfile);
     }
 
     private void assertBluetoothPowerAndDuration(@Nullable BatteryConsumer batteryConsumer,
@@ -162,7 +141,6 @@
 
         long usageDurationMillis = batteryConsumer.getUsageDurationMillis(
                 BatteryConsumer.POWER_COMPONENT_BLUETOOTH);
-
         assertThat(usageDurationMillis).isEqualTo(durationMs);
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java
index f8c2bc6..5334f07 100644
--- a/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java
@@ -38,7 +38,8 @@
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
 
     @Rule
-    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+            .initMeasuredEnergyStatsLocked(new String[]{"CUSTOM_COMPONENT1", "CUSTOM_COMPONENT2"});
 
     @Test
     public void testMeasuredEnergyCopiedIntoBatteryConsumers() {
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 7a7d9f5..80def71 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -48,13 +48,6 @@
         setExternalStatsSyncLocked(new DummyExternalStatsSync());
         informThatAllExternalStatsAreFlushed();
 
-        final boolean[] supportedStandardBuckets =
-                new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
-        Arrays.fill(supportedStandardBuckets, true);
-        final String[] customBucketNames = {"FOO", "BAR"};
-        mGlobalMeasuredEnergyStats =
-                new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
-
         // A no-op handler.
         mHandler = new Handler(Looper.getMainLooper()) {
         };
@@ -64,6 +57,15 @@
         this(new MockClocks());
     }
 
+    public void initMeasuredEnergyStats() {
+        final boolean[] supportedStandardBuckets =
+                new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
+        Arrays.fill(supportedStandardBuckets, true);
+        final String[] customBucketNames = {"FOO", "BAR"};
+        mGlobalMeasuredEnergyStats =
+                new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
+    }
+
     public TimeBase getOnBatteryTimeBase() {
         return mOnBatteryTimeBase;
     }
diff --git a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
index 4c29c20..c695fc9 100644
--- a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
@@ -47,6 +47,7 @@
 
     @Test
     public void testMeasuredEnergyBasedModel() {
+        mStatsRule.initMeasuredEnergyStatsLocked();
         BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
         batteryStats.noteScreenStateLocked(Display.STATE_ON, 0, 0, 0);
diff --git a/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
index 82830f2..a7f4fb3 100644
--- a/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
@@ -79,8 +79,8 @@
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK))
                 .isWithin(PRECISION).of(0.6);
 
-        BatteryConsumer appConsumer = mStatsRule.getDeviceBatteryConsumer();
+        BatteryConsumer appConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK))
-                .isWithin(PRECISION).of(0.6);
+                .isWithin(PRECISION).of(0.1);
     }
 }
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index e222570..51bf6d53 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -255,6 +255,12 @@
             | FILTER_BITMAP_FLAG;
 
     /**
+     * These flags are always set on a reset paint or a new paint instantiated using
+     * {@link #Paint()}.
+     */
+    private static final int DEFAULT_PAINT_FLAGS = ANTI_ALIAS_FLAG | DITHER_FLAG;
+
+    /**
      * Font hinter option that disables font hinting.
      *
      * @see #setHinting(int)
@@ -570,10 +576,13 @@
      * accelerated drawing always acts as if {@link #FILTER_BITMAP_FLAG} is set.
      * On devices running {@link Build.VERSION_CODES#Q} and above,
      * {@code FILTER_BITMAP_FLAG} is set by this constructor, and it can be
-     * cleared with {@link #setFlags} or {@link #setFilterBitmap}.</p>
+     * cleared with {@link #setFlags} or {@link #setFilterBitmap}.
+     * On devices running {@link Build.VERSION_CODES#S} and above, {@code ANTI_ALIAS_FLAG} and
+     * {@code DITHER_FLAG} are set by this constructor, and they can be cleared with
+     * {@link #setFlags} or {@link #setAntiAlias} and {@link #setDither}, respectively.</p>
      */
     public Paint() {
-        this(0);
+        this(DEFAULT_PAINT_FLAGS);
     }
 
     /**
@@ -618,7 +627,7 @@
     /** Restores the paint to its default settings. */
     public void reset() {
         nReset(mNativePaint);
-        setFlags(HIDDEN_DEFAULT_PAINT_FLAGS);
+        setFlags(HIDDEN_DEFAULT_PAINT_FLAGS | DEFAULT_PAINT_FLAGS);
 
         // TODO: Turning off hinting has undesirable side effects, we need to
         //       revisit hinting once we add support for subpixel positioning
diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java
index d00492c..6c0981a 100644
--- a/graphics/java/android/graphics/drawable/RippleShader.java
+++ b/graphics/java/android/graphics/drawable/RippleShader.java
@@ -43,10 +43,10 @@
             + "uniform shader in_shader;\n";
     private static final String SHADER_LIB =
             "float triangleNoise(vec2 n) {\n"
-            + "    n  = fract(n * vec2(5.3987, 5.4421));\n"
-            + "    n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));\n"
-            + "    float xy = n.x * n.y;\n"
-            + "    return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;\n"
+            + "  n  = fract(n * vec2(5.3987, 5.4421));\n"
+            + "  n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));\n"
+            + "  float xy = n.x * n.y;\n"
+            + "  return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;\n"
             + "}"
             + "const float PI = 3.1415926535897932384626;\n"
             + "\n"
@@ -110,14 +110,16 @@
             + "    vec2 uv = p * in_resolutionScale;\n"
             + "    vec2 densityUv = uv - mod(uv, in_noiseScale);\n"
             + "    float turbulence = turbulence(uv, in_turbulencePhase);\n"
-            + "    float sparkle = sparkles(densityUv, in_noisePhase) * ring * alpha "
+            + "    float sparkleAlpha = sparkles(densityUv, in_noisePhase) * ring * alpha "
             + "* turbulence;\n"
             + "    float fade = min(fadeIn, 1. - fadeOutRipple);\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 waveAlpha = softCircle(p, center, in_maxRadius * scaleIn, 0.2) * fade "
+            + "* in_color.a;\n"
+            + "    vec4 waveColor = vec4(in_color.rgb * waveAlpha, waveAlpha);\n"
+            + "    vec4 sparkleColor = vec4(in_sparkleColor.rgb * in_sparkleColor.a, "
+            + "in_sparkleColor.a);\n"
             + "    float mask = in_hasMask == 1. ? sample(in_shader, p).a > 0. ? 1. : 0. : 1.;\n"
-            + "    float a = (in_color.a * circleAlpha + in_sparkleColor.a * sparkle) * mask;\n"
-            + "    return vec4(color * a, a);\n"
+            + "    return mix(waveColor, sparkleColor, sparkleAlpha) * mask;\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/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index ca05ff4..17e0d1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -208,6 +208,24 @@
     }
 
     /**
+     * A handler class that could register itself to apply the transaction instead of the
+     * animation controller doing it. For example, the menu controller can be one such handler.
+     */
+    public static class PipTransactionHandler {
+
+        /**
+         * Called when the animation controller is about to apply a transaction. Allow a registered
+         * handler to apply the transaction instead.
+         *
+         * @return true if handled by the handler, false otherwise.
+         */
+        public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
+                Rect destinationBounds) {
+            return false;
+        }
+    }
+
+    /**
      * Animator for PiP transition animation which supports both alpha and bounds animation.
      * @param <T> Type of property to animate, either alpha (float) or bounds (Rect)
      */
@@ -225,6 +243,7 @@
         private T mEndValue;
         private float mStartingAngle;
         private PipAnimationCallback mPipAnimationCallback;
+        private PipTransactionHandler mPipTransactionHandler;
         private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
                 mSurfaceControlTransactionFactory;
         private PipSurfaceTransactionHelper mSurfaceTransactionHelper;
@@ -293,6 +312,20 @@
             mPipAnimationCallback = callback;
             return this;
         }
+
+        PipTransitionAnimator<T> setPipTransactionHandler(PipTransactionHandler handler) {
+            mPipTransactionHandler = handler;
+            return this;
+        }
+
+        boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
+                Rect destinationBounds) {
+            if (mPipTransactionHandler != null) {
+                return mPipTransactionHandler.handlePipTransaction(leash, tx, destinationBounds);
+            }
+            return false;
+        }
+
         @VisibleForTesting
         @TransitionDirection public int getTransitionDirection() {
             return mTransitionDirection;
@@ -499,7 +532,9 @@
                         getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
                                 initialSourceValue, bounds, insets);
                     }
-                    tx.apply();
+                    if (!handlePipTransaction(leash, tx, bounds)) {
+                        tx.apply();
+                    }
                 }
 
                 private void applyRotation(SurfaceControl.Transaction tx, SurfaceControl leash,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index 319d57a..48a15d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -20,7 +20,6 @@
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.os.SystemProperties;
 import android.view.SurfaceControl;
 
 import com.android.wm.shell.R;
@@ -44,10 +43,7 @@
      * @param context the current context
      */
     public void onDensityOrFontScaleChanged(Context context) {
-        final boolean enableCornerRadius =
-                SystemProperties.getBoolean("debug.sf.enable_hole_punch_pip", false);
-        mCornerRadius = enableCornerRadius
-                ? context.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius) : 0;
+        mCornerRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 4ce6c9e..0633330 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -200,6 +200,19 @@
         }
     };
 
+    private final PipAnimationController.PipTransactionHandler mPipTransactionHandler =
+            new PipAnimationController.PipTransactionHandler() {
+                @Override
+                public boolean handlePipTransaction(SurfaceControl leash,
+                        SurfaceControl.Transaction tx, Rect destinationBounds) {
+                    if (mPipMenuController.isMenuVisible()) {
+                        mPipMenuController.movePipMenu(leash, tx, destinationBounds);
+                        return true;
+                    }
+                    return false;
+                }
+            };
+
     private ActivityManager.RunningTaskInfo mTaskInfo;
     // To handle the edge case that onTaskInfoChanged callback is received during the entering
     // PiP transition, where we do not want to intercept the transition but still want to apply the
@@ -433,8 +446,10 @@
 
         // removePipImmediately is expected when the following animation finishes.
         ValueAnimator animator = mPipAnimationController
-                .getAnimator(mTaskInfo, mLeash, mPipBoundsState.getBounds(), 1f, 0f)
+                .getAnimator(mTaskInfo, mLeash, mPipBoundsState.getBounds(),
+                        1f /* alphaStart */, 0f /* alphaEnd */)
                 .setTransitionDirection(TRANSITION_DIRECTION_REMOVE_STACK)
+                .setPipTransactionHandler(mPipTransactionHandler)
                 .setPipAnimationCallback(mPipAnimationCallback);
         animator.setDuration(mExitAnimationDuration);
         animator.setInterpolator(Interpolators.ALPHA_OUT);
@@ -573,6 +588,7 @@
                     .getAnimator(mTaskInfo, mLeash, destinationBounds, 0f, 1f)
                     .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
                     .setPipAnimationCallback(mPipAnimationCallback)
+                    .setPipTransactionHandler(mPipTransactionHandler)
                     .setDuration(durationMs)
                     .start();
             // mState is set right after the animation is kicked off to block any resize
@@ -749,6 +765,7 @@
         mPipAnimationController
                 .getAnimator(mTaskInfo, mLeash, mPipBoundsState.getBounds(), alphaStart, alphaEnd)
                 .setTransitionDirection(TRANSITION_DIRECTION_SAME)
+                .setPipTransactionHandler(mPipTransactionHandler)
                 .setDuration(show ? mEnterAnimationDuration : mExitAnimationDuration)
                 .start();
         mHasFadeOut = !show;
@@ -1226,6 +1243,7 @@
                         sourceHintRect, direction, startingAngle, rotationDelta);
         animator.setTransitionDirection(direction)
                 .setPipAnimationCallback(mPipAnimationCallback)
+                .setPipTransactionHandler(mPipTransactionHandler)
                 .setDuration(durationMs)
                 .start();
         if (rotationDelta != Surface.ROTATION_0 && direction == TRANSITION_DIRECTION_TO_PIP) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 9b6909b..4759550 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -111,14 +111,14 @@
             final Rect sourceHintRect =
                     PipBoundsAlgorithm.getValidSourceHintRect(
                             taskInfo.pictureInPictureParams, currentBounds);
-            animator = mPipAnimationController.getAnimator(taskInfo, leash,
-                    currentBounds, currentBounds, destinationBounds, sourceHintRect,
-                    TRANSITION_DIRECTION_TO_PIP, 0 /* startingAngle */, Surface.ROTATION_0);
+            animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
+                    currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
+                    0 /* startingAngle */, Surface.ROTATION_0);
         } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
             t.setAlpha(leash, 0f);
             t.apply();
-            animator = mPipAnimationController.getAnimator(taskInfo, leash,
-                    destinationBounds, 0f, 1f);
+            animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
+                    0f, 1f);
             mOneShotAnimationType = ANIM_TYPE_BOUNDS;
         } else {
             throw new RuntimeException("Unrecognized animation type: "
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 9cf0b72..052653e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -70,12 +70,19 @@
      */
     public interface Listener {
         /**
-         * Called when the PIP menu visibility changes.
+         * Called when the PIP menu visibility change has started.
          *
-         * @param menuState the current state of the menu
+         * @param menuState the new, about-to-change state of the menu
          * @param resize whether or not to resize the PiP with the state change
          */
-        void onPipMenuStateChanged(int menuState, boolean resize, Runnable callback);
+        void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback);
+
+        /**
+         * Called when the PIP menu state has finished changing/animating.
+         *
+         * @param menuState the new state of the menu.
+         */
+        void onPipMenuStateChangeFinish(int menuState);
 
         /**
          * Called when the PIP requested to be expanded.
@@ -485,15 +492,15 @@
     /**
      * Handles changes in menu visibility.
      */
-    void onMenuStateChanged(int menuState, boolean resize, Runnable callback) {
+    void onMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
         if (DEBUG) {
-            Log.d(TAG, "onMenuStateChanged() mMenuState=" + mMenuState
+            Log.d(TAG, "onMenuStateChangeStart() mMenuState=" + mMenuState
                     + " menuState=" + menuState + " resize=" + resize
                     + " callers=\n" + Debug.getCallers(5, "    "));
         }
 
         if (menuState != mMenuState) {
-            mListeners.forEach(l -> l.onPipMenuStateChanged(menuState, resize, callback));
+            mListeners.forEach(l -> l.onPipMenuStateChangeStart(menuState, resize, callback));
             if (menuState == MENU_STATE_FULL) {
                 // Once visible, start listening for media action changes. This call will trigger
                 // the menu actions to be updated again.
@@ -511,6 +518,12 @@
                 Log.e(TAG, "Unable to update focus as menu appears/disappears", e);
             }
         }
+    }
+
+    void onMenuStateChangeFinish(int menuState) {
+        if (menuState != mMenuState) {
+            mListeners.forEach(l -> l.onPipMenuStateChangeFinish(menuState));
+        }
         mMenuState = menuState;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 29a483b..91e3887 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -535,10 +535,8 @@
 
     private void onPipCornerRadiusChanged() {
         if (mPinnedStackAnimationRecentsCallback != null) {
-            final boolean enableCornerRadius =
-                    SystemProperties.getBoolean("debug.sf.enable_hole_punch_pip", false);
-            final int cornerRadius = enableCornerRadius
-                    ? mContext.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius) : 0;
+            final int cornerRadius =
+                    mContext.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
             try {
                 mPinnedStackAnimationRecentsCallback.onPipCornerRadiusChanged(cornerRadius);
             } catch (RemoteException e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index ecbf0f1..7b17fe4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -45,7 +45,6 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Pair;
@@ -152,11 +151,7 @@
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         inflate(context, R.layout.pip_menu, this);
 
-        final boolean enableCornerRadius =
-                SystemProperties.getBoolean("debug.sf.enable_hole_punch_pip", false);
-        mBackgroundDrawable = enableCornerRadius
-                ? mContext.getDrawable(R.drawable.pip_menu_background)
-                : new ColorDrawable(Color.BLACK);
+        mBackgroundDrawable = mContext.getDrawable(R.drawable.pip_menu_background);
         mBackgroundDrawable.setAlpha(0);
         mViewRoot = findViewById(R.id.background);
         mViewRoot.setBackground(mBackgroundDrawable);
@@ -281,17 +276,18 @@
             }
             mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
             mMenuContainerAnimator.setDuration(ANIMATION_HIDE_DURATION_MS);
-            if (allowMenuTimeout) {
-                mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
+            mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    notifyMenuStateChangeFinish(menuState);
+                    if (allowMenuTimeout) {
                         repostDelayedHide(INITIAL_DISMISS_DELAY);
                     }
-                });
-            }
+                }
+            });
             if (withDelay) {
                 // starts the menu container animation after window expansion is completed
-                notifyMenuStateChange(menuState, resizeMenuOnShow, () -> {
+                notifyMenuStateChangeStart(menuState, resizeMenuOnShow, () -> {
                     if (mMenuContainerAnimator == null) {
                         return;
                     }
@@ -300,11 +296,11 @@
                     mMenuContainerAnimator.start();
                 });
             } else {
-                notifyMenuStateChange(menuState, resizeMenuOnShow, null);
+                notifyMenuStateChangeStart(menuState, resizeMenuOnShow, null);
                 setVisibility(VISIBLE);
                 mMenuContainerAnimator.start();
             }
-            updateActionViews(stackBounds);
+            updateActionViews(menuState, stackBounds);
         } else {
             // If we are already visible, then just start the delayed dismiss and unregister any
             // existing input consumers from the previous drag
@@ -357,7 +353,7 @@
         if (mMenuState != MENU_STATE_NONE) {
             cancelDelayedHide();
             if (notifyMenuVisibility) {
-                notifyMenuStateChange(MENU_STATE_NONE, resize, null);
+                notifyMenuStateChangeStart(MENU_STATE_NONE, resize, null);
             }
             mMenuContainerAnimator = new AnimatorSet();
             ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
@@ -376,6 +372,9 @@
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     setVisibility(GONE);
+                    if (notifyMenuVisibility) {
+                        notifyMenuStateChangeFinish(MENU_STATE_NONE);
+                    }
                     if (animationFinishedRunnable != null) {
                         animationFinishedRunnable.run();
                     }
@@ -404,11 +403,11 @@
         mActions.clear();
         mActions.addAll(actions);
         if (mMenuState == MENU_STATE_FULL) {
-            updateActionViews(stackBounds);
+            updateActionViews(mMenuState, stackBounds);
         }
     }
 
-    private void updateActionViews(Rect stackBounds) {
+    private void updateActionViews(int menuState, Rect stackBounds) {
         ViewGroup expandContainer = findViewById(R.id.expand_container);
         ViewGroup actionsContainer = findViewById(R.id.actions_container);
         actionsContainer.setOnTouchListener((v, ev) -> {
@@ -417,13 +416,13 @@
         });
 
         // Update the expand button only if it should show with the menu
-        expandContainer.setVisibility(mMenuState == MENU_STATE_FULL
+        expandContainer.setVisibility(menuState == MENU_STATE_FULL
                 ? View.VISIBLE
                 : View.INVISIBLE);
 
         FrameLayout.LayoutParams expandedLp =
                 (FrameLayout.LayoutParams) expandContainer.getLayoutParams();
-        if (mActions.isEmpty() || mMenuState == MENU_STATE_CLOSE || mMenuState == MENU_STATE_NONE) {
+        if (mActions.isEmpty() || menuState == MENU_STATE_CLOSE || menuState == MENU_STATE_NONE) {
             actionsContainer.setVisibility(View.INVISIBLE);
 
             // Update the expand container margin to adjust the center of the expand button to
@@ -493,9 +492,13 @@
         expandContainer.requestLayout();
     }
 
-    private void notifyMenuStateChange(int menuState, boolean resize, Runnable callback) {
+    private void notifyMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
+        mController.onMenuStateChangeStart(menuState, resize, callback);
+    }
+
+    private void notifyMenuStateChangeFinish(int menuState) {
         mMenuState = menuState;
-        mController.onMenuStateChanged(menuState, resize, callback);
+        mController.onMenuStateChangeFinish(menuState);
     }
 
     private void expandPip() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 8726ee7..0878f54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -419,13 +419,13 @@
                 // Reset the down to begin resizing from this point
                 mDownPoint.set(mLastPoint);
                 mDownSecondPoint.set(mLastSecondPoint);
-            }
 
-            if (mThresholdCrossed) {
                 if (mPhonePipMenuController.isMenuVisible()) {
                     mPhonePipMenuController.hideMenu();
                 }
+            }
 
+            if (mThresholdCrossed) {
                 mAngle = mPinchResizingAlgorithm.calculateBoundsAndAngle(mDownPoint,
                         mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize,
                         mDownBounds, mLastResizeBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 0a0798e..8f9dcef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -129,8 +129,13 @@
      */
     private class PipMenuListener implements PhonePipMenuController.Listener {
         @Override
-        public void onPipMenuStateChanged(int menuState, boolean resize, Runnable callback) {
-            setMenuState(menuState, resize, callback);
+        public void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
+            PipTouchHandler.this.onPipMenuStateChangeStart(menuState, resize, callback);
+        }
+
+        @Override
+        public void onPipMenuStateChangeFinish(int menuState) {
+            setMenuState(menuState);
         }
 
         @Override
@@ -614,7 +619,7 @@
             }
         }
 
-        shouldDeliverToMenu |= !mPipBoundsState.isStashed();
+        shouldDeliverToMenu &= !mPipBoundsState.isStashed();
 
         // Deliver the event to PipMenuActivity to handle button click if the menu has shown.
         if (shouldDeliverToMenu) {
@@ -646,9 +651,9 @@
     }
 
     /**
-     * Sets the menu visibility.
+     * Called when the PiP menu state is in the process of animating/changing from one to another.
      */
-    private void setMenuState(int menuState, boolean resize, Runnable callback) {
+    private void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
         if (mMenuState == menuState && !resize) {
             return;
         }
@@ -686,6 +691,9 @@
                 mSavedSnapFraction = -1f;
             }
         }
+    }
+
+    private void setMenuState(int menuState) {
         mMenuState = menuState;
         updateMovementBounds();
         // If pip menu has dismissed, we should register the A11y ActionReplacingConnection for pip
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index fd6f0ad9..1f098c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -66,7 +66,7 @@
     // For example, an icon with the foreground 108*108 opaque pixels and it's background
     // also 108*108 pixels, then do not enlarge this icon if only need to show foreground icon.
     private static final float ENLARGE_FOREGROUND_ICON_THRESHOLD = (72f * 72f) / (108f * 108f);
-    private static final float NO_BACKGROUND_SCALE = 1.3f;
+    private static final float NO_BACKGROUND_SCALE = 192f / 160;
     private final Context mContext;
     private final IconProvider mIconProvider;
 
@@ -283,7 +283,8 @@
             } else {
                 final float iconScale = (float) mIconSize / (float) mDefaultIconSize;
                 final int densityDpi = mContext.getResources().getDisplayMetrics().densityDpi;
-                final int scaledIconDpi = (int) (0.5f + iconScale * densityDpi);
+                final int scaledIconDpi =
+                        (int) (0.5f + iconScale * densityDpi * NO_BACKGROUND_SCALE);
                 iconDrawable = mIconProvider.getIcon(mActivityInfo, scaledIconDpi);
                 if (iconDrawable == null) {
                     iconDrawable = mContext.getPackageManager().getDefaultActivityIcon();
@@ -356,7 +357,7 @@
                     Slog.d(TAG, "makeSplashScreenContentView: choose fg icon");
                 }
                 // Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
-                // should enlarge the size 108/72 if we only draw adaptiveIcon's foreground.
+                // scale by 192/160 if we only draw adaptiveIcon's foreground.
                 final float noBgScale =
                         foreIconTester.nonTransparentRatio() < ENLARGE_FOREGROUND_ICON_THRESHOLD
                                 ? NO_BACKGROUND_SCALE : 1f;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index a1e6832..26e8753 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -103,7 +103,7 @@
     private Choreographer mChoreographer;
 
     private static final boolean DEBUG_ENABLE_REVEAL_ANIMATION =
-            SystemProperties.getBoolean("persist.debug.enable_reveal_animation", false);
+            SystemProperties.getBoolean("persist.debug.enable_reveal_animation", true);
     /**
      * @param splashScreenExecutor The thread used to control add and remove starting window.
      */
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
index 1f9ff4ab..b5198bb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
@@ -18,7 +18,6 @@
 
 import android.os.SystemClock
 import android.platform.test.annotations.Presubmit
-import android.provider.Settings
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -27,6 +26,8 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.appPairsDividerIsInvisible
 import com.android.wm.shell.flicker.helpers.AppPairsHelper
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
 import org.junit.After
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -49,7 +50,6 @@
 class AppPairsTestCannotPairNonResizeableApps(
     testSpec: FlickerTestParameter
 ) : AppPairsTransition(testSpec) {
-    var prevSupportNonResizableInMultiWindow = 0
 
     override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
         get() = {
@@ -64,21 +64,15 @@
         }
 
     @Before
-    fun setup() {
-        prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver,
-            Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
-        if (prevSupportNonResizableInMultiWindow == 1) {
-            // Not support non-resizable in multi window
-            Settings.Global.putInt(context.contentResolver,
-                    Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0)
-        }
+    override fun setup() {
+        super.setup()
+        setSupportsNonResizableMultiWindow(instrumentation, -1)
     }
 
     @After
-    fun teardown() {
-        Settings.Global.putInt(context.contentResolver,
-                Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
-                prevSupportNonResizableInMultiWindow)
+    override fun teardown() {
+        super.teardown()
+        resetMultiWindowConfig(instrumentation)
     }
 
     @FlakyTest
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
index 1e3595c..f2a375b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
@@ -18,7 +18,6 @@
 
 import android.os.SystemClock
 import android.platform.test.annotations.Presubmit
-import android.provider.Settings
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -27,6 +26,8 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.appPairsDividerIsVisible
 import com.android.wm.shell.flicker.helpers.AppPairsHelper
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
 import org.junit.After
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -49,7 +50,6 @@
 class AppPairsTestSupportPairNonResizeableApps(
     testSpec: FlickerTestParameter
 ) : AppPairsTransition(testSpec) {
-    var prevSupportNonResizableInMultiWindow = 0
 
     override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
         get() = {
@@ -64,21 +64,15 @@
         }
 
     @Before
-    fun setup() {
-        prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver,
-                Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
-        if (prevSupportNonResizableInMultiWindow == 0) {
-            // Support non-resizable in multi window
-            Settings.Global.putInt(context.contentResolver,
-                    Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1)
-        }
+    override fun setup() {
+        super.setup()
+        setSupportsNonResizableMultiWindow(instrumentation, 1)
     }
 
     @After
-    fun teardown() {
-        Settings.Global.putInt(context.contentResolver,
-                Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
-                prevSupportNonResizableInMultiWindow)
+    override fun teardown() {
+        super.teardown()
+        resetMultiWindowConfig(instrumentation)
     }
 
     @FlakyTest
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
index 741773e..1935bb9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
@@ -20,11 +20,9 @@
 import android.content.Context
 import android.platform.test.annotations.Presubmit
 import android.system.helpers.ActivityHelper
-import android.util.Log
 import android.view.Surface
 import androidx.test.filters.FlakyTest
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.compatibility.common.util.SystemUtil
 import com.android.server.wm.flicker.FlickerBuilderProvider
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.dsl.FlickerBuilder
@@ -41,10 +39,14 @@
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import com.android.wm.shell.flicker.helpers.AppPairsHelper
+import com.android.wm.shell.flicker.helpers.BaseAppHelper
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.getDevEnableNonResizableMultiWindow
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setDevEnableNonResizableMultiWindow
 import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.testapp.Components
+import org.junit.After
+import org.junit.Before
 import org.junit.Test
-import java.io.IOException
 
 abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) {
     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
@@ -62,6 +64,21 @@
     protected var primaryTaskId = ""
     protected var secondaryTaskId = ""
     protected var nonResizeableTaskId = ""
+    private var prevDevEnableNonResizableMultiWindow = 0
+
+    @Before
+    open fun setup() {
+        prevDevEnableNonResizableMultiWindow = getDevEnableNonResizableMultiWindow(context)
+        if (prevDevEnableNonResizableMultiWindow != 0) {
+            // Turn off the development option
+            setDevEnableNonResizableMultiWindow(context, 0)
+        }
+    }
+
+    @After
+    open fun teardown() {
+        setDevEnableNonResizableMultiWindow(context, prevDevEnableNonResizableMultiWindow)
+    }
 
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
@@ -117,11 +134,7 @@
     }
 
     internal fun executeShellCommand(cmd: String) {
-        try {
-            SystemUtil.runShellCommand(instrumentation, cmd)
-        } catch (e: IOException) {
-            Log.d("AppPairsTest", "executeShellCommand error! $e")
-        }
+        BaseAppHelper.executeShellCommand(instrumentation, cmd)
     }
 
     internal fun composePairsCommand(
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
index 006b569..4fe69ad 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
@@ -21,11 +21,14 @@
 import android.content.pm.PackageManager.FEATURE_LEANBACK
 import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY
 import android.support.test.launcherhelper.LauncherStrategyFactory
+import android.util.Log
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.UiObject2
 import androidx.test.uiautomator.Until
+import com.android.compatibility.common.util.SystemUtil
 import com.android.server.wm.flicker.helpers.StandardAppHelper
 import com.android.server.wm.traces.parser.toWindowName
+import java.io.IOException
 
 abstract class BaseAppHelper(
     instrumentation: Instrumentation,
@@ -56,5 +59,13 @@
 
     companion object {
         private const val APP_CLOSE_WAIT_TIME_MS = 3_000L
+
+        fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
+            try {
+                SystemUtil.runShellCommand(instrumentation, cmd)
+            } catch (e: IOException) {
+                Log.e("BaseAppHelper", "executeShellCommand error! $e")
+            }
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
new file mode 100644
index 0000000..7f99e62
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.helpers
+
+import android.app.Instrumentation
+import android.content.ComponentName
+import android.content.Context
+import android.provider.Settings
+
+class MultiWindowHelper(
+    instrumentation: Instrumentation,
+    activityLabel: String,
+    componentsInfo: ComponentName
+) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) {
+
+    companion object {
+        fun getDevEnableNonResizableMultiWindow(context: Context): Int =
+                Settings.Global.getInt(context.contentResolver,
+                        Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
+
+        fun setDevEnableNonResizableMultiWindow(context: Context, configValue: Int) =
+                Settings.Global.putInt(context.contentResolver,
+                        Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, configValue)
+
+        fun setSupportsNonResizableMultiWindow(instrumentation: Instrumentation, configValue: Int) =
+            executeShellCommand(
+                    instrumentation,
+                    createConfigSupportsNonResizableMultiWindowCommand(configValue))
+
+        fun resetMultiWindowConfig(instrumentation: Instrumentation) =
+            executeShellCommand(instrumentation, resetMultiWindowConfigCommand)
+
+        private fun createConfigSupportsNonResizableMultiWindowCommand(configValue: Int): String =
+                "wm set-multi-window-config --supportsNonResizable $configValue"
+
+        private const val resetMultiWindowConfigCommand: String = "wm reset-multi-window-config"
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
index 04f97c8..c18c122 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.legacysplitscreen
 
 import android.platform.test.annotations.Presubmit
-import android.provider.Settings
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -27,6 +26,8 @@
 import com.android.server.wm.flicker.helpers.canSplitScreen
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
 import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import org.junit.After
 import org.junit.Assert
@@ -51,7 +52,6 @@
 class EnterSplitScreenNotSupportNonResizable(
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
-    var prevSupportNonResizableInMultiWindow = 0
 
     override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
         get() = { configuration ->
@@ -76,21 +76,15 @@
             splitScreenApp.defaultWindowName)
 
     @Before
-    fun setup() {
-        prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver,
-                Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
-        if (prevSupportNonResizableInMultiWindow == 1) {
-            // Not support non-resizable in multi window
-            Settings.Global.putInt(context.contentResolver,
-                    Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0)
-        }
+    override fun setup() {
+        super.setup()
+        setSupportsNonResizableMultiWindow(instrumentation, -1)
     }
 
     @After
-    fun teardown() {
-        Settings.Global.putInt(context.contentResolver,
-                Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
-                prevSupportNonResizableInMultiWindow)
+    override fun teardown() {
+        super.teardown()
+        resetMultiWindowConfig(instrumentation)
     }
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
index 2832bb4..d5b9a13 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.legacysplitscreen
 
 import android.platform.test.annotations.Presubmit
-import android.provider.Settings
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -27,6 +26,8 @@
 import com.android.server.wm.flicker.helpers.launchSplitScreen
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
 import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import org.junit.After
 import org.junit.Before
@@ -50,7 +51,6 @@
 class EnterSplitScreenSupportNonResizable(
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
-    var prevSupportNonResizableInMultiWindow = 0
 
     override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
         get() = { configuration ->
@@ -73,21 +73,15 @@
                 splitScreenApp.defaultWindowName)
 
     @Before
-    fun setup() {
-        prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver,
-                Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
-        if (prevSupportNonResizableInMultiWindow != 1) {
-            // Support non-resizable in multi window
-            Settings.Global.putInt(context.contentResolver,
-                    Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1)
-        }
+    override fun setup() {
+        super.setup()
+        setSupportsNonResizableMultiWindow(instrumentation, 1)
     }
 
     @After
-    fun teardown() {
-        Settings.Global.putInt(context.contentResolver,
-                Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
-                prevSupportNonResizableInMultiWindow)
+    override fun teardown() {
+        super.teardown()
+        resetMultiWindowConfig(instrumentation)
     }
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
index 32afd19..612018e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.legacysplitscreen
 
 import android.platform.test.annotations.Presubmit
-import android.provider.Settings
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -32,6 +31,8 @@
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
 import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
 import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import org.junit.After
 import org.junit.Before
@@ -53,7 +54,6 @@
 class LegacySplitScreenFromIntentNotSupportNonResizable(
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
-    var prevSupportNonResizableInMultiWindow = 0
 
     override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
         get() = { configuration ->
@@ -77,21 +77,15 @@
             WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
 
     @Before
-    fun setup() {
-        prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver,
-                Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
-        if (prevSupportNonResizableInMultiWindow == 1) {
-            // Not support non-resizable in multi window
-            Settings.Global.putInt(context.contentResolver,
-                    Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0)
-        }
+    override fun setup() {
+        super.setup()
+        setSupportsNonResizableMultiWindow(instrumentation, -1)
     }
 
     @After
-    fun teardown() {
-        Settings.Global.putInt(context.contentResolver,
-                Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
-                prevSupportNonResizableInMultiWindow)
+    override fun teardown() {
+        super.teardown()
+        resetMultiWindowConfig(instrumentation)
     }
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
index af30758..65062f9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.legacysplitscreen
 
 import android.platform.test.annotations.Presubmit
-import android.provider.Settings
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -30,6 +29,8 @@
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
 import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
 import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import org.junit.After
 import org.junit.Before
@@ -51,7 +52,6 @@
 class LegacySplitScreenFromIntentSupportNonResizable(
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
-    var prevSupportNonResizableInMultiWindow = 0
 
     override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
         get() = { configuration ->
@@ -75,21 +75,15 @@
             WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
 
     @Before
-    fun setup() {
-        prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver,
-                Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
-        if (prevSupportNonResizableInMultiWindow == 0) {
-            // Support non-resizable in multi window
-            Settings.Global.putInt(context.contentResolver,
-                    Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1)
-        }
+    override fun setup() {
+        super.setup()
+        setSupportsNonResizableMultiWindow(instrumentation, 1)
     }
 
     @After
-    fun teardown() {
-        Settings.Global.putInt(context.contentResolver,
-                Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
-                prevSupportNonResizableInMultiWindow)
+    override fun teardown() {
+        super.teardown()
+        resetMultiWindowConfig(instrumentation)
     }
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
index 8c62758..3720787 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.legacysplitscreen
 
 import android.platform.test.annotations.Presubmit
-import android.provider.Settings
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -33,6 +32,8 @@
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
 import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
 import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import org.junit.After
 import org.junit.Before
@@ -54,7 +55,6 @@
 class LegacySplitScreenFromRecentNotSupportNonResizable(
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
-    var prevSupportNonResizableInMultiWindow = 0
 
     override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
         get() = { configuration ->
@@ -78,21 +78,15 @@
                 WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
 
     @Before
-    fun setup() {
-        prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver,
-                Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
-        if (prevSupportNonResizableInMultiWindow == 1) {
-            // Not support non-resizable in multi window
-            Settings.Global.putInt(context.contentResolver,
-                    Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0)
-        }
+    override fun setup() {
+        super.setup()
+        setSupportsNonResizableMultiWindow(instrumentation, -1)
     }
 
     @After
-    fun teardown() {
-        Settings.Global.putInt(context.contentResolver,
-                Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
-                prevSupportNonResizableInMultiWindow)
+    override fun teardown() {
+        super.teardown()
+        resetMultiWindowConfig(instrumentation)
     }
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
index 5b48f8a..61ebcd2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.legacysplitscreen
 
 import android.platform.test.annotations.Presubmit
-import android.provider.Settings
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -31,6 +30,8 @@
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
 import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
 import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import org.junit.After
 import org.junit.Before
@@ -52,7 +53,6 @@
 class LegacySplitScreenFromRecentSupportNonResizable(
     testSpec: FlickerTestParameter
 ) : LegacySplitScreenTransition(testSpec) {
-    var prevSupportNonResizableInMultiWindow = 0
 
     override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
         get() = { configuration ->
@@ -76,21 +76,15 @@
                 WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
 
     @Before
-    fun setup() {
-        prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver,
-                Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
-        if (prevSupportNonResizableInMultiWindow == 0) {
-            // Support non-resizable in multi window
-            Settings.Global.putInt(context.contentResolver,
-                    Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1)
-        }
+    override fun setup() {
+        super.setup()
+        setSupportsNonResizableMultiWindow(instrumentation, 1)
     }
 
     @After
-    fun teardown() {
-        Settings.Global.putInt(context.contentResolver,
-                Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
-                prevSupportNonResizableInMultiWindow)
+    override fun teardown() {
+        super.teardown()
+        resetMultiWindowConfig(instrumentation)
     }
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
index 8684ba5..e8d4d1e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
@@ -32,7 +32,11 @@
 import com.android.server.wm.flicker.repetitions
 import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.getDevEnableNonResizableMultiWindow
+import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setDevEnableNonResizableMultiWindow
 import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.After
+import org.junit.Before
 import org.junit.Test
 
 abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestParameter) {
@@ -44,6 +48,21 @@
     protected val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation)
     protected val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation)
         .launcherStrategy.supportedLauncherPackage
+    private var prevDevEnableNonResizableMultiWindow = 0
+
+    @Before
+    open fun setup() {
+        prevDevEnableNonResizableMultiWindow = getDevEnableNonResizableMultiWindow(context)
+        if (prevDevEnableNonResizableMultiWindow != 0) {
+            // Turn off the development option
+            setDevEnableNonResizableMultiWindow(context, 0)
+        }
+    }
+
+    @After
+    open fun teardown() {
+        setDevEnableNonResizableMultiWindow(context, prevDevEnableNonResizableMultiWindow)
+    }
 
     /**
      * List of windows that are ignored when verifying that visible elements appear on 2
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
index a164233..0adc0f6 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -157,7 +157,7 @@
 static inline void mapRect(const RenderProperties& props, const SkRect& in, SkRect* out) {
     if (in.isEmpty()) return;
     SkRect temp(in);
-    if (Properties::stretchEffectBehavior == StretchEffectBehavior::LinearScale) {
+    if (Properties::stretchEffectBehavior == StretchEffectBehavior::UniformScale) {
         const StretchEffect& stretch = props.layerProperties().getStretchEffect();
         if (!stretch.isEmpty()) {
             applyMatrix(stretch.makeLinearStretch(props.getWidth(), props.getHeight()), &temp);
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 9ed801b..a4614a9 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -138,7 +138,7 @@
     if (targetCpuTimePercentage <= 0 || targetCpuTimePercentage > 100) targetCpuTimePercentage = 70;
 
     int stretchType = base::GetIntProperty(PROPERTY_STRETCH_EFFECT_TYPE, 0);
-    stretchType = std::clamp(stretchType, 0, static_cast<int>(StretchEffectBehavior::LinearScale));
+    stretchType = std::clamp(stretchType, 0, static_cast<int>(StretchEffectBehavior::UniformScale));
     stretchEffectBehavior = static_cast<StretchEffectBehavior>(stretchType);
 
     return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 9d2b617..9964254 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -200,9 +200,9 @@
 enum class RenderPipelineType { SkiaGL, SkiaVulkan, NotInitialized = 128 };
 
 enum class StretchEffectBehavior {
-    ShaderHWUI, // Stretch shader in HWUI only, matrix scale in SF
-    Shader, // Stretch shader in both HWUI and SF
-    LinearScale // Linear stretch everywhere
+    ShaderHWUI,   // Stretch shader in HWUI only, matrix scale in SF
+    Shader,       // Stretch shader in both HWUI and SF
+    UniformScale  // Uniform scale stretch everywhere
 };
 
 /**
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 64abd94..ad2cd8c 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -478,7 +478,7 @@
         }
     }
 
-    if (Properties::stretchEffectBehavior == StretchEffectBehavior::LinearScale) {
+    if (Properties::stretchEffectBehavior == StretchEffectBehavior::UniformScale) {
         const StretchEffect& stretch = properties().layerProperties().getStretchEffect();
         if (!stretch.isEmpty()) {
             matrix.multiply(
diff --git a/libs/hwui/effects/StretchEffect.h b/libs/hwui/effects/StretchEffect.h
index 3b3067d..0e1a654 100644
--- a/libs/hwui/effects/StretchEffect.h
+++ b/libs/hwui/effects/StretchEffect.h
@@ -111,7 +111,7 @@
 
     bool requiresLayer() const {
         return !(isEmpty() ||
-                 Properties::stretchEffectBehavior == StretchEffectBehavior::LinearScale);
+                 Properties::stretchEffectBehavior == StretchEffectBehavior::UniformScale);
     }
 
 private:
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index cd0783f..a096ed0 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -573,8 +573,8 @@
             const RenderProperties& props = node.properties();
 
             uirenderer::Rect bounds(props.getWidth(), props.getHeight());
-            bool useStretchShader = Properties::stretchEffectBehavior !=
-                StretchEffectBehavior::LinearScale;
+            bool useStretchShader =
+                    Properties::stretchEffectBehavior != StretchEffectBehavior::UniformScale;
             if (useStretchShader && info.stretchEffectCount) {
                 handleStretchEffect(info, bounds);
             }
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 0b43f09..1c5515c7d 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -248,7 +248,7 @@
 
             const StretchEffect& stretch = properties.layerProperties().getStretchEffect();
             if (stretch.isEmpty() ||
-                Properties::stretchEffectBehavior == StretchEffectBehavior::LinearScale) {
+                Properties::stretchEffectBehavior == StretchEffectBehavior::UniformScale) {
                 // If we don't have any stretch effects, issue the filtered
                 // canvas draw calls to make sure we still punch a hole
                 // with the same canvas transformation + clip into the target
@@ -327,7 +327,7 @@
             canvas->concat(*properties.getTransformMatrix());
         }
     }
-    if (Properties::stretchEffectBehavior == StretchEffectBehavior::LinearScale) {
+    if (Properties::stretchEffectBehavior == StretchEffectBehavior::UniformScale) {
         const StretchEffect& stretch = properties.layerProperties().getStretchEffect();
         if (!stretch.isEmpty()) {
             canvas->concat(
diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
index 0b995bc..1042703 100644
--- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
@@ -86,12 +86,9 @@
             }
         }
 
-        // if we don't have a resource name then we don't know how to label the
-        // data and should abort.
+        // if we don't have a pretty name then use the dumpName
         if (resourceName == nullptr) {
-            mCurrentElement.clear();
-            mCurrentValues.clear();
-            return;
+            resourceName = mCurrentElement.c_str();
         }
 
         auto result = mResults.find(resourceName);
@@ -157,6 +154,14 @@
     }
 }
 
+size_t SkiaMemoryTracer::total() {
+    processElement();
+    if (!strcmp("bytes", mTotalSize.units)) {
+        return mTotalSize.value;
+    }
+    return 0;
+}
+
 void SkiaMemoryTracer::logTotals(String8& log) {
     TraceValue total = convertUnits(mTotalSize);
     TraceValue purgeable = convertUnits(mPurgeableSize);
diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
index b393b07..cba3b04 100644
--- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
@@ -37,6 +37,7 @@
     bool hasOutput();
     void logOutput(String8& log);
     void logTotals(String8& log);
+    size_t total();
 
     void dumpNumericValue(const char* dumpName, const char* valueName, const char* units,
                           uint64_t value) override;
diff --git a/libs/hwui/pipeline/skia/StretchMask.cpp b/libs/hwui/pipeline/skia/StretchMask.cpp
index 2bbd8a4..1c58c6a 100644
--- a/libs/hwui/pipeline/skia/StretchMask.cpp
+++ b/libs/hwui/pipeline/skia/StretchMask.cpp
@@ -46,9 +46,16 @@
 
     if (mIsDirty) {
         SkCanvas* maskCanvas = mMaskSurface->getCanvas();
+        // Make sure to apply target transformation to the mask canvas
+        // to ensure the replayed drawing commands generate the same result
+        auto previousMatrix = displayList->mParentMatrix;
+        displayList->mParentMatrix = maskCanvas->getTotalMatrix();
+        maskCanvas->save();
         maskCanvas->drawColor(0, SkBlendMode::kClear);
         TransformCanvas transformCanvas(maskCanvas, SkBlendMode::kSrcOver);
         displayList->draw(&transformCanvas);
+        maskCanvas->restore();
+        displayList->mParentMatrix = previousMatrix;
     }
 
     sk_sp<SkImage> maskImage = mMaskSurface->makeImageSnapshot();
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 5047be9..46e8060 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -130,27 +130,43 @@
     mGrContext->purgeResourcesNotUsedInMs(std::chrono::seconds(30));
 }
 
+void CacheManager::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
+    *cpuUsage = 0;
+    *gpuUsage = 0;
+    if (!mGrContext) {
+        return;
+    }
+
+    skiapipeline::SkiaMemoryTracer cpuTracer("category", true);
+    SkGraphics::DumpMemoryStatistics(&cpuTracer);
+    *cpuUsage += cpuTracer.total();
+
+    skiapipeline::SkiaMemoryTracer gpuTracer("category", true);
+    mGrContext->dumpMemoryStatistics(&gpuTracer);
+    *gpuUsage += gpuTracer.total();
+}
+
 void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) {
     if (!mGrContext) {
         log.appendFormat("No valid cache instance.\n");
         return;
     }
 
-    log.appendFormat("Font Cache (CPU):\n");
-    log.appendFormat("  Size: %.2f kB \n", SkGraphics::GetFontCacheUsed() / 1024.0f);
-    log.appendFormat("  Glyph Count: %d \n", SkGraphics::GetFontCacheCountUsed());
-
     std::vector<skiapipeline::ResourcePair> cpuResourceMap = {
             {"skia/sk_resource_cache/bitmap_", "Bitmaps"},
             {"skia/sk_resource_cache/rrect-blur_", "Masks"},
             {"skia/sk_resource_cache/rects-blur_", "Masks"},
             {"skia/sk_resource_cache/tessellated", "Shadows"},
+            {"skia/sk_glyph_cache", "Glyph Cache"},
     };
     skiapipeline::SkiaMemoryTracer cpuTracer(cpuResourceMap, false);
     SkGraphics::DumpMemoryStatistics(&cpuTracer);
     if (cpuTracer.hasOutput()) {
         log.appendFormat("CPU Caches:\n");
         cpuTracer.logOutput(log);
+        log.appendFormat("  Glyph Count: %d \n", SkGraphics::GetFontCacheCountUsed());
+        log.appendFormat("Total CPU memory usage:\n");
+        cpuTracer.logTotals(log);
     }
 
     skiapipeline::SkiaMemoryTracer gpuTracer("category", true);
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index 0a6b8dc..713ea99 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -47,6 +47,7 @@
     void trimMemory(TrimMemoryMode mode);
     void trimStaleResources();
     void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr);
+    void getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage);
 
     size_t getCacheSize() const { return mMaxResourceBytes; }
     size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; }
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index ad325cf..95aa29d 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -195,6 +195,17 @@
     }
 }
 
+void RenderProxy::purgeCaches() {
+    if (RenderThread::hasInstance()) {
+        RenderThread& thread = RenderThread::getInstance();
+        thread.queue().post([&thread]() {
+            if (thread.getGrContext()) {
+                thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
+            }
+        });
+    }
+}
+
 void RenderProxy::overrideProperty(const char* name, const char* value) {
     // expensive, but block here since name/value pointers owned by caller
     RenderThread::getInstance().queue().runSync(
@@ -256,6 +267,13 @@
     }
 }
 
+void RenderProxy::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
+    if (RenderThread::hasInstance()) {
+        auto& thread = RenderThread::getInstance();
+        thread.queue().runSync([&]() { thread.getMemoryUsage(cpuUsage, gpuUsage); });
+    }
+}
+
 void RenderProxy::setProcessStatsBuffer(int fd) {
     auto& rt = RenderThread::getInstance();
     rt.queue().post([&rt, fd = dup(fd)]() {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 662b445..0681dc5 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -98,6 +98,7 @@
 
     void destroyHardwareResources();
     static void trimMemory(int level);
+    static void purgeCaches();
     static void overrideProperty(const char* name, const char* value);
 
     void fence();
@@ -110,6 +111,7 @@
     void resetProfileInfo();
     uint32_t frameTimePercentile(int p);
     static void dumpGraphicsMemory(int fd, bool includeProfileData = true);
+    static void getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage);
 
     static void rotateProcessStatsBuffer();
     static void setProcessStatsBuffer(int fd);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 308352d..0268bfd7 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -323,6 +323,10 @@
     dprintf(fd, "\nPipeline=%s\n%s\n", pipelineToString(), cachesOutput.string());
 }
 
+void RenderThread::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
+    mCacheManager->getMemoryUsage(cpuUsage, gpuUsage);
+}
+
 Readback& RenderThread::readback() {
     if (!mReadback) {
         mReadback = new Readback(*this);
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index afd5750..5021085 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -151,6 +151,7 @@
 
     sk_sp<Bitmap> allocateHardwareBitmap(SkBitmap& skBitmap);
     void dumpGraphicsMemory(int fd, bool includeProfileData);
+    void getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage);
 
     void requireGlContext();
     void requireVkContext();
diff --git a/libs/hwui/tests/common/TestScene.h b/libs/hwui/tests/common/TestScene.h
index 781884a..6b0be53 100644
--- a/libs/hwui/tests/common/TestScene.h
+++ b/libs/hwui/tests/common/TestScene.h
@@ -40,6 +40,7 @@
         int reportFrametimeWeight = 0;
         bool renderOffscreen = true;
         bool reportGpuMemoryUsage = false;
+        bool reportGpuMemoryUsageVerbose = false;
     };
 
     template <class T>
diff --git a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
index 964b8bf..c451112 100644
--- a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
@@ -22,8 +22,10 @@
 
 class StretchyListViewAnimation;
 class StretchyListViewHolePunch;
-class StretchyLinearListView;
-class StretchyLinearListViewHolePunch;
+class StretchyUniformListView;
+class StretchyUniformListViewHolePunch;
+class StretchyUniformLayerListView;
+class StretchyUniformLayerListViewHolePunch;
 
 static TestScene::Registrar _StretchyListViewAnimation(TestScene::Info{
         "stretchylistview",
@@ -36,21 +38,34 @@
         "A mock ListView of scrolling content that's stretching. Includes a hole punch",
         TestScene::simpleCreateScene<StretchyListViewHolePunch>});
 
-static TestScene::Registrar _StretchyLinearListView(TestScene::Info{
-        "stretchylistview_linear",
-        "A mock ListView of scrolling content that's stretching using a linear stretch effect.",
-        TestScene::simpleCreateScene<StretchyLinearListView>});
+static TestScene::Registrar _StretchyUniformListView(TestScene::Info{
+        "stretchylistview_uniform",
+        "A mock ListView of scrolling content that's stretching using a uniform stretch effect.",
+        TestScene::simpleCreateScene<StretchyUniformListView>});
 
-static TestScene::Registrar _StretchyLinearListViewHolePunch(TestScene::Info{
-        "stretchylistview_linear_holepunch",
-        "A mock ListView of scrolling content that's stretching using a linear stretch effect. "
+static TestScene::Registrar _StretchyUniformListViewHolePunch(TestScene::Info{
+        "stretchylistview_uniform_holepunch",
+        "A mock ListView of scrolling content that's stretching using a uniform stretch effect. "
         "Includes a hole punch",
-        TestScene::simpleCreateScene<StretchyLinearListViewHolePunch>});
+        TestScene::simpleCreateScene<StretchyUniformListViewHolePunch>});
+
+static TestScene::Registrar _StretchyUniformLayerListView(TestScene::Info{
+        "stretchylistview_uniform_layer",
+        "A mock ListView of scrolling content that's stretching using a uniform stretch effect. "
+        "Uses a layer",
+        TestScene::simpleCreateScene<StretchyUniformLayerListView>});
+
+static TestScene::Registrar _StretchyUniformLayerListViewHolePunch(TestScene::Info{
+        "stretchylistview_uniform_layer_holepunch",
+        "A mock ListView of scrolling content that's stretching using a uniform stretch effect. "
+        "Uses a layer & includes a hole punch",
+        TestScene::simpleCreateScene<StretchyUniformLayerListViewHolePunch>});
 
 class StretchyListViewAnimation : public TestScene {
 protected:
     virtual StretchEffectBehavior stretchBehavior() { return StretchEffectBehavior::Shader; }
     virtual bool haveHolePunch() { return false; }
+    virtual bool forceLayer() { return false; }
 
 private:
     int mItemHeight;
@@ -172,6 +187,10 @@
     void doFrame(int frameNr) override {
         if (frameNr == 0) {
             Properties::stretchEffectBehavior = stretchBehavior();
+            if (forceLayer()) {
+                mListView->mutateStagingProperties().mutateLayerProperties().setType(
+                        LayerType::RenderLayer);
+            }
         }
         auto& props = mListView->mutateStagingProperties();
         auto& stretch = props.mutateLayerProperties().mutableStretchEffect();
@@ -190,11 +209,22 @@
     bool haveHolePunch() override { return true; }
 };
 
-class StretchyLinearListView : public StretchyListViewAnimation {
-    StretchEffectBehavior stretchBehavior() override { return StretchEffectBehavior::LinearScale; }
+class StretchyUniformListView : public StretchyListViewAnimation {
+    StretchEffectBehavior stretchBehavior() override { return StretchEffectBehavior::UniformScale; }
 };
 
-class StretchyLinearListViewHolePunch : public StretchyListViewAnimation {
-    StretchEffectBehavior stretchBehavior() override { return StretchEffectBehavior::LinearScale; }
+class StretchyUniformListViewHolePunch : public StretchyListViewAnimation {
+    StretchEffectBehavior stretchBehavior() override { return StretchEffectBehavior::UniformScale; }
     bool haveHolePunch() override { return true; }
+};
+
+class StretchyUniformLayerListView : public StretchyListViewAnimation {
+    StretchEffectBehavior stretchBehavior() override { return StretchEffectBehavior::UniformScale; }
+    bool forceLayer() override { return true; }
+};
+
+class StretchyUniformLayerListViewHolePunch : public StretchyListViewAnimation {
+    StretchEffectBehavior stretchBehavior() override { return StretchEffectBehavior::UniformScale; }
+    bool haveHolePunch() override { return true; }
+    bool forceLayer() override { return true; }
 };
\ No newline at end of file
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index 9d3b732..b640b90 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -28,6 +28,20 @@
 #include <log/log.h>
 #include <ui/PixelFormat.h>
 
+// These are unstable internal APIs in google-benchmark. We should just implement our own variant
+// of these instead, but this was quicker. Disabled-by-default to avoid any breakages when
+// google-benchmark updates if they change anything
+#if 0
+#define USE_SKETCHY_INTERNAL_STATS
+namespace benchmark {
+std::vector<BenchmarkReporter::Run> ComputeStats(
+        const std::vector<BenchmarkReporter::Run> &reports);
+double StatisticsMean(const std::vector<double>& v);
+double StatisticsMedian(const std::vector<double>& v);
+double StatisticsStdDev(const std::vector<double>& v);
+}
+#endif
+
 using namespace android;
 using namespace android::uirenderer;
 using namespace android::uirenderer::renderthread;
@@ -66,6 +80,7 @@
 
 void outputBenchmarkReport(const TestScene::Info& info, const TestScene::Options& opts,
                            double durationInS, int repetationIndex, BenchmarkResults* reports) {
+    using namespace benchmark;
     benchmark::BenchmarkReporter::Run report;
     report.repetitions = opts.repeatCount;
     report.repetition_index = repetationIndex;
@@ -73,12 +88,22 @@
     report.iterations = static_cast<int64_t>(opts.frameCount);
     report.real_accumulated_time = durationInS;
     report.cpu_accumulated_time = durationInS;
-    report.counters["items_per_second"] = opts.frameCount / durationInS;
+    report.counters["FPS"] = opts.frameCount / durationInS;
+    if (opts.reportGpuMemoryUsage) {
+        size_t cpuUsage, gpuUsage;
+        RenderProxy::getMemoryUsage(&cpuUsage, &gpuUsage);
+        report.counters["Rendering RAM"] = Counter{static_cast<double>(cpuUsage + gpuUsage),
+                                                   Counter::kDefaults, Counter::kIs1024};
+    }
     reports->push_back(report);
 }
 
 static void doRun(const TestScene::Info& info, const TestScene::Options& opts, int repetitionIndex,
                   BenchmarkResults* reports) {
+    if (opts.reportGpuMemoryUsage) {
+        // If we're reporting GPU memory usage we need to first start with a clean slate
+        RenderProxy::purgeCaches();
+    }
     Properties::forceDrawFrame = true;
     TestContext testContext;
     testContext.setRenderOffscreen(opts.renderOffscreen);
@@ -162,11 +187,6 @@
 
 void run(const TestScene::Info& info, const TestScene::Options& opts,
          benchmark::BenchmarkReporter* reporter) {
-    if (opts.reportGpuMemoryUsage) {
-        // If we're reporting GPU memory usage we need to first start with a clean slate
-        // All repetitions of the same test will share a single memory usage report
-        RenderProxy::trimMemory(100);
-    }
     BenchmarkResults results;
     for (int i = 0; i < opts.repeatCount; i++) {
         doRun(info, opts, i, reporter ? &results : nullptr);
@@ -174,10 +194,21 @@
     if (reporter) {
         reporter->ReportRuns(results);
         if (results.size() > 1) {
-            // TODO: Report summary
+#ifdef USE_SKETCHY_INTERNAL_STATS
+            std::vector<benchmark::internal::Statistics> stats;
+            stats.reserve(3);
+            stats.emplace_back("mean", benchmark::StatisticsMean);
+            stats.emplace_back("median", benchmark::StatisticsMedian);
+            stats.emplace_back("stddev", benchmark::StatisticsStdDev);
+            for (auto& it : results) {
+                it.statistics = &stats;
+            }
+            auto summary = benchmark::ComputeStats(results);
+            reporter->ReportRuns(summary);
+#endif
         }
     }
-    if (opts.reportGpuMemoryUsage) {
+    if (opts.reportGpuMemoryUsageVerbose) {
         RenderProxy::dumpGraphicsMemory(STDOUT_FILENO, false);
     }
 }
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index e9e962a..f3f32eb 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -71,7 +71,7 @@
   --benchmark_format   Set output format. Possible values are tabular, json, csv
   --renderer=TYPE      Sets the render pipeline to use. May be skiagl or skiavk
   --skip-leak-check    Skips the memory leak check
-  --report-gpu-memory  Dumps the GPU memory usage after each test run
+  --report-gpu-memory[=verbose]  Dumps the GPU memory usage after each test run
 )");
 }
 
@@ -142,7 +142,7 @@
     } else if (!strcmp(format, "json")) {
         gBenchmarkReporter.reset(new benchmark::JSONReporter());
     } else {
-        fprintf(stderr, "Unknown format '%s'", format);
+        fprintf(stderr, "Unknown format '%s'\n", format);
         return false;
     }
     return true;
@@ -154,7 +154,7 @@
     } else if (!strcmp(renderer, "skiavk")) {
         Properties::overrideRenderPipelineType(RenderPipelineType::SkiaVulkan);
     } else {
-        fprintf(stderr, "Unknown format '%s'", renderer);
+        fprintf(stderr, "Unknown format '%s'\n", renderer);
         return false;
     }
     return true;
@@ -191,7 +191,7 @@
         {"offscreen", no_argument, nullptr, LongOpts::Offscreen},
         {"renderer", required_argument, nullptr, LongOpts::Renderer},
         {"skip-leak-check", no_argument, nullptr, LongOpts::SkipLeakCheck},
-        {"report-gpu-memory", no_argument, nullptr, LongOpts::ReportGpuMemory},
+        {"report-gpu-memory", optional_argument, nullptr, LongOpts::ReportGpuMemory},
         {0, 0, 0, 0}};
 
 static const char* SHORT_OPTIONS = "c:r:h";
@@ -296,6 +296,14 @@
 
             case LongOpts::ReportGpuMemory:
                 gOpts.reportGpuMemoryUsage = true;
+                if (optarg) {
+                    if (!strcmp("verbose", optarg)) {
+                        gOpts.reportGpuMemoryUsageVerbose = true;
+                    } else {
+                        fprintf(stderr, "Invalid report gpu memory option '%s'\n", optarg);
+                        error = true;
+                    }
+                }
                 break;
 
             case 'h':
@@ -313,7 +321,7 @@
     }
 
     if (error) {
-        fprintf(stderr, "Try 'hwuitest --help' for more information.\n");
+        fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]);
         exit(EXIT_FAILURE);
     }
 
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 3d6cc68..2e4d8f8 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -139,19 +139,13 @@
      */
     private String mRequestorPackageName;
 
-    /**
-     * Indicates what fields should be redacted from this instance.
-     */
-    private final @RedactionType long mRedactions;
-
     public NetworkCapabilities() {
-        mRedactions = REDACT_ALL;
         clearAll();
         mNetworkCapabilities = DEFAULT_CAPABILITIES;
     }
 
     public NetworkCapabilities(NetworkCapabilities nc) {
-        this(nc, REDACT_ALL);
+        this(nc, REDACT_NONE);
     }
 
     /**
@@ -163,10 +157,12 @@
      * @hide
      */
     public NetworkCapabilities(@Nullable NetworkCapabilities nc, @RedactionType long redactions) {
-        mRedactions = redactions;
         if (nc != null) {
             set(nc);
         }
+        if (mTransportInfo != null) {
+            mTransportInfo = nc.mTransportInfo.makeCopy(redactions);
+        }
     }
 
     /**
@@ -175,14 +171,6 @@
      * @hide
      */
     public void clearAll() {
-        // Ensures that the internal copies maintained by the connectivity stack does not set it to
-        // anything other than |REDACT_ALL|.
-        if (mRedactions != REDACT_ALL) {
-            // This is needed because the current redaction mechanism relies on redaction while
-            // parceling.
-            throw new UnsupportedOperationException(
-                    "Cannot clear NetworkCapabilities when mRedactions is set");
-        }
         mNetworkCapabilities = mTransportTypes = mForbiddenNetworkCapabilities = 0;
         mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
         mNetworkSpecifier = null;
@@ -211,7 +199,7 @@
         mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps;
         mNetworkSpecifier = nc.mNetworkSpecifier;
         if (nc.getTransportInfo() != null) {
-            setTransportInfo(nc.getTransportInfo().makeCopy(mRedactions));
+            setTransportInfo(nc.getTransportInfo());
         } else {
             setTransportInfo(null);
         }
@@ -839,8 +827,17 @@
         final int[] originalAdministratorUids = getAdministratorUids();
         final TransportInfo originalTransportInfo = getTransportInfo();
         clearAll();
-        mTransportTypes = (originalTransportTypes & TEST_NETWORKS_ALLOWED_TRANSPORTS)
-                | (1 << TRANSPORT_TEST);
+        if (0 != (originalCapabilities & NET_CAPABILITY_NOT_RESTRICTED)) {
+            // If the test network is not restricted, then it is only allowed to declare some
+            // specific transports. This is to minimize impact on running apps in case an app
+            // run from the shell creates a test a network.
+            mTransportTypes =
+                    (originalTransportTypes & UNRESTRICTED_TEST_NETWORKS_ALLOWED_TRANSPORTS)
+                            | (1 << TRANSPORT_TEST);
+        } else {
+            // If the test transport is restricted, then it may declare any transport.
+            mTransportTypes = (originalTransportTypes | (1 << TRANSPORT_TEST));
+        }
         mNetworkCapabilities = originalCapabilities & TEST_NETWORKS_ALLOWED_CAPABILITIES;
         mNetworkSpecifier = originalSpecifier;
         mSignalStrength = originalSignalStrength;
@@ -951,9 +948,10 @@
     };
 
     /**
-     * Allowed transports on a test network, in addition to TRANSPORT_TEST.
+     * Allowed transports on an unrestricted test network (in addition to TRANSPORT_TEST).
      */
-    private static final int TEST_NETWORKS_ALLOWED_TRANSPORTS = 1 << TRANSPORT_TEST
+    private static final int UNRESTRICTED_TEST_NETWORKS_ALLOWED_TRANSPORTS =
+            1 << TRANSPORT_TEST
             // Test ethernet networks can be created with EthernetManager#setIncludeTestInterfaces
             | 1 << TRANSPORT_ETHERNET
             // Test VPN networks can be created but their UID ranges must be empty.
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 16a946d..f8cb5d3 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -561,7 +561,20 @@
                 break;
         }
 
-        Log.d(TAG, "status=" + statusString + ", cause=" + causeString + ", detail=" + detail);
+        StringBuilder msg = new StringBuilder();
+        msg.append("status: " + statusString + ", cause: " + causeString);
+        if (status == STATUS_IN_PROGRESS) {
+            msg.append(
+                    String.format(
+                            ", partition name: %s, progress: %d/%d",
+                            mCurrentPartitionName,
+                            mCurrentPartitionInstalledSize,
+                            mCurrentPartitionSize));
+        }
+        if (detail != null) {
+            msg.append(", detail: " + detail);
+        }
+        Log.d(TAG, msg.toString());
 
         if (notifyOnNotificationBar) {
             mNM.notify(NOTIFICATION_ID, buildNotification(status, cause, detail));
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index 59ea9f0..f18d426 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -320,20 +320,21 @@
         }
     }
 
-    private void installScratch() throws IOException {
-        final long scratchSize = mDynSystem.suggestScratchSize();
+    private void installWritablePartition(final String partitionName, final long partitionSize)
+            throws IOException {
+        Log.d(TAG, "Creating writable partition: " + partitionName + ", size: " + partitionSize);
+
         Thread thread = new Thread() {
             @Override
             public void run() {
                 mInstallationSession =
-                        mDynSystem.createPartition("scratch", scratchSize, /* readOnly= */ false);
+                        mDynSystem.createPartition(
+                                partitionName, partitionSize, /* readOnly= */ false);
             }
         };
 
-        Log.d(TAG, "Creating partition: scratch, size = " + scratchSize);
         thread.start();
-
-        Progress progress = new Progress("scratch", scratchSize, mNumInstalledPartitions++);
+        Progress progress = new Progress(partitionName, partitionSize, mNumInstalledPartitions++);
 
         while (thread.isAlive()) {
             if (isCancelled()) {
@@ -356,53 +357,22 @@
 
         if (mInstallationSession == null) {
             throw new IOException(
-                    "Failed to start installation with requested size: " + scratchSize);
+                    "Failed to start installation with requested size: " + partitionSize);
         }
+
         // Reset installation session and verify that installation completes successfully.
         mInstallationSession = null;
         if (!mDynSystem.closePartition()) {
-            throw new IOException("Failed to complete partition installation: scratch");
+            throw new IOException("Failed to complete partition installation: " + partitionName);
         }
     }
 
+    private void installScratch() throws IOException {
+        installWritablePartition("scratch", mDynSystem.suggestScratchSize());
+    }
+
     private void installUserdata() throws IOException {
-        Thread thread = new Thread(() -> {
-            mInstallationSession = mDynSystem.createPartition("userdata", mUserdataSize, false);
-        });
-
-        Log.d(TAG, "Creating partition: userdata, size = " + mUserdataSize);
-        thread.start();
-
-        Progress progress = new Progress("userdata", mUserdataSize, mNumInstalledPartitions++);
-
-        while (thread.isAlive()) {
-            if (isCancelled()) {
-                return;
-            }
-
-            final long installedSize = mDynSystem.getInstallationProgress().bytes_processed;
-
-            if (installedSize > progress.installedSize + MIN_PROGRESS_TO_PUBLISH) {
-                progress.installedSize = installedSize;
-                publishProgress(progress);
-            }
-
-            try {
-                Thread.sleep(100);
-            } catch (InterruptedException e) {
-                // Ignore the error.
-            }
-        }
-
-        if (mInstallationSession == null) {
-            throw new IOException(
-                    "Failed to start installation with requested size: " + mUserdataSize);
-        }
-        // Reset installation session and verify that installation completes successfully.
-        mInstallationSession = null;
-        if (!mDynSystem.closePartition()) {
-            throw new IOException("Failed to complete partition installation: userdata");
-        }
+        installWritablePartition("userdata", mUserdataSize);
     }
 
     private void installImages() throws IOException, ImageValidationException {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm b/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm
index 44d5a0c..1614188 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm
@@ -171,15 +171,15 @@
 }
 
 key LEFT_BRACKET {
-    label:                              '['
+    label:                              ']'
     base, capslock:                     '\u062c'
-    shift:                              '<'
+    shift:                              '>'
 }
 
 key RIGHT_BRACKET {
-    label:                              ']'
+    label:                              '['
     base, capslock:                     '\u062f'
-    shift:                              '>'
+    shift:                              '<'
 }
 
 key BACKSLASH {
diff --git a/packages/SettingsLib/FooterPreference/Android.bp b/packages/SettingsLib/FooterPreference/Android.bp
index 11f39e7..0929706 100644
--- a/packages/SettingsLib/FooterPreference/Android.bp
+++ b/packages/SettingsLib/FooterPreference/Android.bp
@@ -20,4 +20,8 @@
     ],
     sdk_version: "system_current",
     min_sdk_version: "21",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.permission",
+    ],
 }
diff --git a/packages/SettingsLib/RadioButtonPreference/Android.bp b/packages/SettingsLib/RadioButtonPreference/Android.bp
index b309c01..28ff71f 100644
--- a/packages/SettingsLib/RadioButtonPreference/Android.bp
+++ b/packages/SettingsLib/RadioButtonPreference/Android.bp
@@ -20,4 +20,8 @@
 
     sdk_version: "system_current",
     min_sdk_version: "21",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.permission",
+    ],
 }
diff --git a/packages/SettingsLib/TwoTargetPreference/Android.bp b/packages/SettingsLib/TwoTargetPreference/Android.bp
index 078e8c3..b32d5b4 100644
--- a/packages/SettingsLib/TwoTargetPreference/Android.bp
+++ b/packages/SettingsLib/TwoTargetPreference/Android.bp
@@ -19,4 +19,8 @@
     ],
     sdk_version: "system_current",
     min_sdk_version: "21",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.permission",
+    ],
 }
diff --git a/packages/SettingsLib/res/drawable/ic_no_internet_wifi_signal_0.xml b/packages/SettingsLib/res/drawable/ic_no_internet_wifi_signal_0.xml
new file mode 100644
index 0000000..efae569
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_no_internet_wifi_signal_0.xml
@@ -0,0 +1,30 @@
+<!--
+    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">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M20.66,7C18.19,5.07 15.14,4 12,4C8.58,4 5.27,5.27 2.7,7.53L12,18.85l6,-7.3v3.15L12,22L0,7.39C2.97,4.08 7.25,2 12,2c4.56,0 8.69,1.92 11.64,5H20.66z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,10h-2v8h2V10z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,20h-2v2h2V20z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_no_internet_wifi_signal_1.xml b/packages/SettingsLib/res/drawable/ic_no_internet_wifi_signal_1.xml
new file mode 100644
index 0000000..d50e734
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_no_internet_wifi_signal_1.xml
@@ -0,0 +1,30 @@
+<!--
+    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">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M23.62,7C20.65,3.93 16.6,2 12,2C7.2,2 3,4.1 0,7.4L12,22l6,-7.3v-3.19l-2.13,2.59C14.74,13.4 13.39,13 12,13s-2.74,0.4 -3.87,1.1L2.7,7.5C5.3,5.3 8.6,4 12,4c3.13,0 6.18,1.1 8.68,3H23.62z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,10h-2v8h2V10z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,20h-2v2h2V20z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_no_internet_wifi_signal_2.xml b/packages/SettingsLib/res/drawable/ic_no_internet_wifi_signal_2.xml
new file mode 100644
index 0000000..1be297e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_no_internet_wifi_signal_2.xml
@@ -0,0 +1,30 @@
+<!--
+    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">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M23.62,7C20.65,3.93 16.6,2 12,2C7.2,2 3,4.1 0,7.4L12,22l6,-7.3v-3.19l-0.27,0.33C16.12,10.67 14.09,10 12,10c-2.09,0 -4.12,0.67 -5.73,1.84L2.7,7.5C5.3,5.3 8.6,4 12,4c3.13,0 6.18,1.1 8.68,3H23.62z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,10h-2v8h2V10z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,20h-2v2h2V20z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_no_internet_wifi_signal_3.xml b/packages/SettingsLib/res/drawable/ic_no_internet_wifi_signal_3.xml
new file mode 100644
index 0000000..738bd5a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_no_internet_wifi_signal_3.xml
@@ -0,0 +1,30 @@
+<!--
+    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">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M0,7.4L12,22l6,-7.3V8.57C16.21,7.56 14.14,7 12,7C9.2,7 6.53,7.96 4.45,9.62L2.7,7.5C5.3,5.3 8.6,4 12,4c3.13,0 6.18,1.1 8.68,3h2.95C20.65,3.93 16.6,2 12,2C7.2,2 3,4.1 0,7.4z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,10h-2v8h2V10z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,20h-2v2h2V20z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_no_internet_wifi_signal_4.xml b/packages/SettingsLib/res/drawable/ic_no_internet_wifi_signal_4.xml
new file mode 100644
index 0000000..14d020b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_no_internet_wifi_signal_4.xml
@@ -0,0 +1,30 @@
+<!--
+    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">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M0,7.4C3,4.1 7.2,2 12,2c4.6,0 8.65,1.93 11.62,5H18v7.7L12,22L0,7.4z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,10h-2v8h2V10z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,20h-2v2h2V20z"/>
+</vector>
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
index 9889419..4dd3ff1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
@@ -187,8 +187,9 @@
     }
 
     protected int getIconColorAttr() {
-        return (mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED)
-                ? android.R.attr.colorAccent : android.R.attr.colorControlNormal;
+        final boolean accent = (mWifiEntry.hasInternetAccess()
+                && mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED);
+        return accent ? android.R.attr.colorAccent : android.R.attr.colorControlNormal;
     }
 
     private void updateIcon(boolean showX, int level) {
@@ -267,7 +268,7 @@
         }
 
         public Drawable getIcon(boolean showX, int level) {
-            return mContext.getDrawable(Utils.getWifiIconResource(showX, level));
+            return mContext.getDrawable(WifiUtils.getInternetIconResource(level, showX));
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
index 15b146d..6100615 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -36,6 +36,22 @@
 
     private static final int INVALID_RSSI = -127;
 
+    static final int[] WIFI_PIE = {
+            com.android.internal.R.drawable.ic_wifi_signal_0,
+            com.android.internal.R.drawable.ic_wifi_signal_1,
+            com.android.internal.R.drawable.ic_wifi_signal_2,
+            com.android.internal.R.drawable.ic_wifi_signal_3,
+            com.android.internal.R.drawable.ic_wifi_signal_4
+    };
+
+    static final int[] NO_INTERNET_WIFI_PIE = {
+            R.drawable.ic_no_internet_wifi_signal_0,
+            R.drawable.ic_no_internet_wifi_signal_1,
+            R.drawable.ic_no_internet_wifi_signal_2,
+            R.drawable.ic_no_internet_wifi_signal_3,
+            R.drawable.ic_no_internet_wifi_signal_4
+    };
+
     public static String buildLoggingSummary(AccessPoint accessPoint, WifiConfiguration config) {
         final StringBuilder summary = new StringBuilder();
         final WifiInfo info = accessPoint.getInfo();
@@ -245,6 +261,20 @@
         return context.getString(R.string.wifi_unmetered_label);
     }
 
+    /**
+     * Returns the Internet icon resource for a given RSSI level.
+     *
+     * @param level The number of bars to show (0-4)
+     * @param noInternet True if a connected Wi-Fi network cannot access the Internet
+     * @throws IllegalArgumentException if an invalid RSSI level is given.
+     */
+    public static int getInternetIconResource(int level, boolean noInternet) {
+        if (level < 0 || level >= WIFI_PIE.length) {
+            throw new IllegalArgumentException("No Wifi icon found for level: " + level);
+        }
+        return noInternet ? NO_INTERNET_WIFI_PIE[level] : WIFI_PIE[level];
+    }
+
     public static boolean isMeteredOverridden(WifiConfiguration config) {
         return config.meteredOverride != WifiConfiguration.METERED_OVERRIDE_NONE;
     }
diff --git a/packages/SystemUI/res/drawable/ic_conversation_icon.xml b/packages/SystemUI/res/drawable/ic_conversation_icon.xml
new file mode 100644
index 0000000..0e3533b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_conversation_icon.xml
@@ -0,0 +1,47 @@
+<?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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+    <path
+        android:pathData="M24,24m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"
+        android:fillColor="#81C995"/>
+    <path
+        android:pathData="M27,34C23.134,34 20,30.866 20,27C20,23.134 23.134,20 27,20C30.866,20 34,23.134 34,27C34,28.4872 33.5362,29.8662 32.7453,31"
+        android:strokeWidth="2"
+        android:fillColor="#00000000"
+        android:strokeColor="#3C4043"/>
+    <path
+        android:pathData="M35,33l-8,0l-0,2l8,0z"
+        android:fillColor="#3C4043"/>
+    <path
+        android:pathData="M21,21m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"
+        android:fillColor="#81C995"/>
+    <path
+        android:pathData="M16,25h5v2h-5z"
+        android:fillColor="#81C995"/>
+    <path
+        android:pathData="M21,28C24.866,28 28,24.866 28,21C28,17.134 24.866,14 21,14C17.134,14 14,17.134 14,21C14,22.4872 14.4638,23.8662 15.2547,25"
+        android:strokeWidth="2"
+        android:fillColor="#00000000"
+        android:strokeColor="#ffffff"/>
+    <path
+        android:pathData="M13,27h8v2h-8z"
+        android:fillColor="#ffffff"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_corp_badge_off.xml b/packages/SystemUI/res/drawable/ic_corp_badge_off.xml
new file mode 100644
index 0000000..a441fb2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_corp_badge_off.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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="20dp"
+    android:height="20dp"
+    android:viewportWidth="20.0"
+    android:viewportHeight="20.0">
+    <path
+        android:pathData="M10,10m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
+        android:fillColor="#607D8B"/>
+    <path
+        android:pathData="M16.42,15.68l-0.85,-0.85L7.21,6.47L4.9,4.16L4.16,4.9l1.57,1.57H5.36c-0.65,0 -1.16,0.52 -1.16,1.17L4.2,14.05c0,0.65 0.52,1.17 1.17,1.17h9.12l1.2,1.2L16.42,15.68zM15.83,7.64c0.03,-0.65 -0.49,-1.17 -1.14,-1.14h-2.33V5.3c0,-0.65 -0.52,-1.17 -1.17,-1.14H8.86C8.22,4.14 7.7,4.66 7.7,5.3v0.19l8.14,8.17V7.64zM11.2,6.5H8.83V5.3h2.36V6.5z"
+        android:fillColor="#FFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/people_tile_empty_background.xml b/packages/SystemUI/res/drawable/people_tile_empty_background.xml
new file mode 100644
index 0000000..2dac740
--- /dev/null
+++ b/packages/SystemUI/res/drawable/people_tile_empty_background.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <solid android:color="?androidprv:attr/colorSurface" />
+    <corners android:radius="@dimen/people_space_widget_radius" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/people_tile_status_scrim.xml b/packages/SystemUI/res/drawable/people_tile_status_scrim.xml
new file mode 100644
index 0000000..cf16f1c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/people_tile_status_scrim.xml
@@ -0,0 +1,23 @@
+<?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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <gradient
+        android:type="linear"
+        android:angle="90"
+        android:endColor="@android:color/transparent"
+        android:startColor="?androidprv:attr/colorSurfaceHeader" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/people_tile_suppressed_background.xml b/packages/SystemUI/res/drawable/people_tile_suppressed_background.xml
new file mode 100644
index 0000000..e0c111d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/people_tile_suppressed_background.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <solid android:color="?androidprv:attr/colorSurfaceVariant" />
+    <corners android:radius="@dimen/people_space_widget_radius" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/ongoing_call_chip.xml b/packages/SystemUI/res/layout/ongoing_call_chip.xml
index a146547..90214b7 100644
--- a/packages/SystemUI/res/layout/ongoing_call_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_call_chip.xml
@@ -23,6 +23,7 @@
     android:background="@drawable/ongoing_call_chip_bg"
     android:paddingStart="@dimen/ongoing_call_chip_side_padding"
     android:paddingEnd="@dimen/ongoing_call_chip_side_padding"
+    android:contentDescription="@string/ongoing_phone_call_content_description"
 >
 
     <ImageView
diff --git a/packages/SystemUI/res/layout/people_status_scrim_layout.xml b/packages/SystemUI/res/layout/people_status_scrim_layout.xml
new file mode 100644
index 0000000..9808f74
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_status_scrim_layout.xml
@@ -0,0 +1,58 @@
+<?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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/scrim_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView
+        android:id="@+id/status_icon"
+        android:scaleType="centerCrop"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:weightSum="1"
+        android:orientation="vertical">
+        <ImageView
+            android:layout_weight=".2"
+            android:layout_width="match_parent"
+            android:layout_height="0dp" />
+        <ImageView
+            android:background="@drawable/people_tile_status_scrim"
+            android:layout_weight=".8"
+            android:scaleType="centerCrop"
+            android:layout_width="match_parent"
+            android:layout_height="0dp" />
+    </LinearLayout>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:weightSum="1"
+        android:orientation="vertical">
+        <ImageView
+            android:layout_weight=".66"
+            android:layout_width="match_parent"
+            android:layout_height="0dp" />
+        <ImageView
+            android:background="@drawable/people_tile_status_scrim"
+            android:layout_weight=".33"
+            android:layout_width="match_parent"
+            android:layout_height="0dp" />
+    </LinearLayout>
+</RelativeLayout>
diff --git a/packages/SystemUI/res/layout/people_tile_empty_layout.xml b/packages/SystemUI/res/layout/people_tile_empty_layout.xml
index 7d3b919..8e9ebc6 100644
--- a/packages/SystemUI/res/layout/people_tile_empty_layout.xml
+++ b/packages/SystemUI/res/layout/people_tile_empty_layout.xml
@@ -14,16 +14,17 @@
   ~ limitations under the License.
   -->
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/item"
     android:theme="@android:style/Theme.DeviceDefault.DayNight"
-    android:background="@drawable/people_space_tile_view_card"
+    android:background="@drawable/people_tile_empty_background"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
     <ImageView
-        android:id="@+id/item"
+        android:id="@+id/icon"
+        android:src="@drawable/ic_conversation_icon"
         android:gravity="center"
         android:layout_gravity="center"
-        android:padding="8dp"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"/>
+        android:layout_width="48dp"
+        android:layout_height="48dp"/>
 </FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_tile_large_with_content.xml b/packages/SystemUI/res/layout/people_tile_large_with_content.xml
index 6f8de3b..b77670e 100644
--- a/packages/SystemUI/res/layout/people_tile_large_with_content.xml
+++ b/packages/SystemUI/res/layout/people_tile_large_with_content.xml
@@ -1,145 +1,159 @@
 <?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.
- -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:theme="@android:style/Theme.DeviceDefault.DayNight"
-    android:id="@+id/item"
+~ 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:background="@drawable/people_space_tile_view_card"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:id="@+id/item"
+    android:clipToOutline="true"
+    android:theme="@android:style/Theme.DeviceDefault.DayNight"
     android:layout_gravity="center"
-    android:padding="16dp"
-    android:orientation="vertical">
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
 
-    <RelativeLayout
+    <include layout="@layout/people_status_scrim_layout" />
+
+    <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="start|top">
+        android:layout_height="match_parent"
+        android:layout_gravity="center"
+        android:padding="16dp"
+        android:orientation="vertical">
 
-        <LinearLayout
-            android:layout_width="wrap_content"
+        <RelativeLayout
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_alignParentStart="true"
-            android:gravity="start|top"
-            android:orientation="horizontal">
+            android:gravity="start|top">
 
-            <ImageView
-                android:id="@+id/person_icon"
-                android:layout_marginStart="-2dp"
-                android:layout_marginTop="-2dp"
+            <LinearLayout
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_weight="1" />
+                android:layout_alignParentStart="true"
+                android:gravity="start|top"
+                android:orientation="horizontal">
 
-            <ImageView
-                android:id="@+id/availability"
-                android:layout_marginStart="-2dp"
-                android:layout_width="10dp"
-                android:layout_height="10dp"
-                android:background="@drawable/circle_green_10dp" />
-        </LinearLayout>
+                <ImageView
+                    android:id="@+id/person_icon"
+                    android:layout_marginStart="-2dp"
+                    android:layout_marginTop="-2dp"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1" />
 
-        <TextView
-            android:id="@+id/messages_count"
-            android:layout_alignParentEnd="true"
-            android:paddingStart="8dp"
-            android:paddingEnd="8dp"
-            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-            android:textColor="?android:attr/textColorPrimary"
-            android:background="@drawable/people_space_messages_count_background"
-            android:textSize="14sp"
-            android:maxLines="1"
-            android:ellipsize="end"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:visibility="gone"
-            />
-    </RelativeLayout>
-    <RelativeLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-        <include layout="@layout/people_tile_punctuation_background_large" />
-        <include layout="@layout/people_tile_emoji_background_large" />
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:orientation="vertical">
-        <TextView
-            android:layout_gravity="center"
-            android:id="@+id/name"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingBottom="12dp"
-            android:gravity="start"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:text="@string/empty_user_name"
-            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-            android:textColor="?android:attr/textColorPrimary"
-            android:textSize="14sp" />
-
-        <LinearLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:paddingBottom="4dp"
-            android:gravity="center_vertical"
-            android:orientation="horizontal">
-
-            <ImageView
-                android:id="@+id/predefined_icon"
-                android:tint="?android:attr/colorAccent"
-                android:gravity="start|center_vertical"
-                android:paddingEnd="6dp"
-                android:layout_width="24dp"
-                android:layout_height="18dp" />
+                <ImageView
+                    android:id="@+id/availability"
+                    android:layout_marginStart="-2dp"
+                    android:layout_width="10dp"
+                    android:layout_height="10dp"
+                    android:background="@drawable/circle_green_10dp" />
+            </LinearLayout>
 
             <TextView
-                android:layout_gravity="center"
-                android:id="@+id/subtext"
-                android:gravity="center_vertical"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:ellipsize="end"
-                android:singleLine="true"
-                android:text="@string/empty_user_name"
+                android:id="@+id/messages_count"
+                android:layout_alignParentEnd="true"
+                android:paddingStart="8dp"
+                android:paddingEnd="8dp"
                 android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                android:textColor="?android:attr/textColorSecondary"
-                android:textSize="12sp" />
-        </LinearLayout>
+                android:textColor="?android:attr/textColorPrimary"
+                android:background="@drawable/people_space_messages_count_background"
+                android:textSize="14sp"
+                android:maxLines="1"
+                android:ellipsize="end"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:visibility="gone" />
+        </RelativeLayout>
 
-        <ImageView
-            android:id="@+id/image"
+        <RelativeLayout
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:background="@drawable/people_space_content_background"
-            android:gravity="center"
-            android:scaleType="centerCrop" />
+            android:layout_height="match_parent">
 
-        <TextView
-            android:layout_gravity="center"
-            android:id="@+id/text_content"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:ellipsize="end"
-            android:maxLines="2"
-            android:singleLine="false"
-            android:text="@string/empty_status"
-            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-            android:textColor="?android:attr/textColorPrimary"
-            android:textSize="12sp" />
-        </LinearLayout>
-    </RelativeLayout>
-</LinearLayout>
+            <include layout="@layout/people_tile_punctuation_background_large" />
+
+            <include layout="@layout/people_tile_emoji_background_large" />
+
+            <LinearLayout
+                android:id="@+id/content"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="vertical">
+
+                <TextView
+                    android:layout_gravity="center"
+                    android:id="@+id/name"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingBottom="12dp"
+                    android:gravity="start"
+                    android:singleLine="true"
+                    android:ellipsize="end"
+                    android:text="@string/empty_user_name"
+                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                    android:textColor="?android:attr/textColorPrimary"
+                    android:textSize="14sp" />
+
+                <LinearLayout
+                    android:id="@+id/status_icon_and_label"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:paddingBottom="4dp"
+                    android:gravity="center_vertical"
+                    android:orientation="horizontal">
+
+                    <ImageView
+                        android:id="@+id/predefined_icon"
+                        android:tint="?android:attr/colorAccent"
+                        android:gravity="start|center_vertical"
+                        android:paddingEnd="6dp"
+                        android:layout_width="24dp"
+                        android:layout_height="18dp" />
+
+                    <TextView
+                        android:layout_gravity="center"
+                        android:id="@+id/subtext"
+                        android:gravity="center_vertical"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:ellipsize="end"
+                        android:singleLine="true"
+                        android:text="@string/empty_user_name"
+                        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                        android:textColor="?android:attr/textColorSecondary"
+                        android:textSize="12sp" />
+                </LinearLayout>
+
+                <ImageView
+                    android:id="@+id/image"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:background="@drawable/people_space_content_background"
+                    android:gravity="center"
+                    android:scaleType="centerCrop" />
+
+                <TextView
+                    android:layout_gravity="center"
+                    android:id="@+id/text_content"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:ellipsize="end"
+                    android:maxLines="2"
+                    android:singleLine="false"
+                    android:text="@string/empty_status"
+                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                    android:textColor="?android:attr/textColorPrimary"
+                    android:textSize="@dimen/content_text_size_for_large" />
+            </LinearLayout>
+        </RelativeLayout>
+    </LinearLayout>
+</RelativeLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
index a8c15ab..8df0bf8 100644
--- a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
+++ b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
@@ -17,18 +17,20 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:theme="@android:style/Theme.DeviceDefault.DayNight"
+    android:id="@+id/item"
+    android:background="@drawable/people_space_tile_view_card"
     android:layout_gravity="center"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
     <RelativeLayout
-        android:background="@drawable/people_space_tile_view_card"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
         <include layout="@layout/people_tile_punctuation_background_medium" />
-        <include layout="@layout/people_tile_emoji_background_medium" />
+        <include layout="@layout/people_tile_punctuation_background_medium" />
+        <include layout="@layout/people_status_scrim_layout" />
         <LinearLayout
-            android:id="@+id/item"
+            android:id="@+id/content"
             android:orientation="vertical"
             android:layout_gravity="center"
             android:padding="8dp"
@@ -89,7 +91,7 @@
                         android:text="@string/empty_status"
                         android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
                         android:textColor="?android:attr/textColorPrimary"
-                        android:textSize="12sp"
+                        android:textSize="@dimen/content_text_size_for_medium"
                         android:layout_width="match_parent"
                         android:layout_height="wrap_content"
                         android:maxLines="2"
diff --git a/packages/SystemUI/res/layout/people_tile_suppressed_layout.xml b/packages/SystemUI/res/layout/people_tile_suppressed_layout.xml
new file mode 100644
index 0000000..b151c60
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_tile_suppressed_layout.xml
@@ -0,0 +1,29 @@
+<!--
+  ~ 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.
+  -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/item"
+    android:theme="@android:style/Theme.DeviceDefault.DayNight"
+    android:background="@drawable/people_tile_suppressed_background"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:gravity="center"
+        android:layout_gravity="center"
+        android:layout_width="48dp"
+        android:layout_height="48dp"/>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_tile_work_profile_quiet_layout.xml b/packages/SystemUI/res/layout/people_tile_work_profile_quiet_layout.xml
new file mode 100644
index 0000000..25ab5a6
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_tile_work_profile_quiet_layout.xml
@@ -0,0 +1,39 @@
+<!--
+  ~ 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.
+  -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:id="@+id/item"
+    android:theme="@android:style/Theme.DeviceDefault.DayNight"
+    android:background="@drawable/people_tile_suppressed_background"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:gravity="center"
+        android:layout_gravity="center"
+        android:layout_width="48dp"
+        android:layout_height="48dp"/>
+
+    <ImageView android:id="@+id/work_widget_badge_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|right"
+        android:layout_marginBottom="12dp"
+        android:layout_marginRight="12dp"
+        android:src="@drawable/ic_corp_badge_off"
+        android:clickable="false" />
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
index f056402..59e1a75 100644
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ b/packages/SystemUI/res/layout/qs_detail.xml
@@ -23,7 +23,6 @@
     android:clickable="true"
     android:orientation="vertical"
     android:layout_marginTop="@*android:dimen/quick_qs_offset_height"
-    android:layout_marginBottom="@dimen/qs_container_bottom_padding"
     android:paddingBottom="8dp"
     android:visibility="invisible"
     android:elevation="4dp"
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 4607e5f..4c6418a 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -17,7 +17,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/quick_settings_container"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
+    android:layout_height="match_parent"
     android:clipToPadding="false"
     android:clipChildren="false" >
 
@@ -25,7 +25,6 @@
         android:id="@+id/expanded_qs_scroll_view"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:paddingBottom="@dimen/qs_container_bottom_padding"
         android:elevation="4dp"
         android:importantForAccessibility="no"
         android:scrollbars="none"
diff --git a/packages/SystemUI/res/layout/qs_tile_side_icon.xml b/packages/SystemUI/res/layout/qs_tile_side_icon.xml
index 9f9af9d..1ae0a1c 100644
--- a/packages/SystemUI/res/layout/qs_tile_side_icon.xml
+++ b/packages/SystemUI/res/layout/qs_tile_side_icon.xml
@@ -35,6 +35,7 @@
         android:layout_width="@dimen/qs_icon_size"
         android:layout_height="@dimen/qs_icon_size"
         android:src="@*android:drawable/ic_chevron_end"
+        android:autoMirrored="true"
         android:visibility="gone"
         android:importantForAccessibility="no"
     />
diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
index a62310b..c88703d 100644
--- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml
+++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
@@ -28,7 +28,7 @@
 
     <com.android.systemui.statusbar.policy.Clock
         android:id="@+id/clock"
-        android:layout_width="0dp"
+        android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:minWidth="48dp"
         android:minHeight="48dp"
@@ -61,9 +61,9 @@
 
     <LinearLayout
         android:id="@+id/rightLayout"
-        android:layout_width="0dp"
+        android:layout_width="wrap_content"
         android:layout_height="match_parent"
-        android:layout_weight="1"
+        android:gravity="center_vertical|end"
         >
         <com.android.systemui.statusbar.phone.StatusIconContainer
             android:id="@+id/statusIcons"
diff --git a/packages/SystemUI/res/layout/quick_settings_security_footer.xml b/packages/SystemUI/res/layout/quick_settings_security_footer.xml
index ce7f827..08bd71c 100644
--- a/packages/SystemUI/res/layout/quick_settings_security_footer.xml
+++ b/packages/SystemUI/res/layout/quick_settings_security_footer.xml
@@ -55,6 +55,7 @@
         android:layout_marginStart="8dp"
         android:contentDescription="@null"
         android:src="@*android:drawable/ic_chevron_end"
+        android:autoMirrored="true"
         android:tint="?android:attr/textColorSecondary" />
 
 </com.android.systemui.util.DualHeightHorizontalLinearLayout>
diff --git a/packages/SystemUI/res/layout/wallet_fullscreen.xml b/packages/SystemUI/res/layout/wallet_fullscreen.xml
index c4939c6..d365aa3 100644
--- a/packages/SystemUI/res/layout/wallet_fullscreen.xml
+++ b/packages/SystemUI/res/layout/wallet_fullscreen.xml
@@ -36,8 +36,8 @@
         android:orientation="vertical">
         <ImageView
             android:id="@+id/icon"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_width="@dimen/wallet_screen_header_view_size"
+            android:layout_height="@dimen/wallet_screen_header_view_size"
             android:layout_gravity="center_horizontal"
             android:layout_marginVertical="10dp"
             android:scaleType="center"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7e56063..fa4771d 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -623,8 +623,6 @@
 
     <dimen name="qs_notif_collapsed_space">64dp</dimen>
 
-    <dimen name="qs_container_bottom_padding">24dp</dimen>
-
     <!-- Desired qs icon overlay size. -->
     <dimen name="qs_detail_icon_overlay_size">24dp</dimen>
 
@@ -1492,7 +1490,8 @@
 
     <!-- Wallet activity screen specs -->
     <dimen name="wallet_icon_size">36sp</dimen>
-    <dimen name="wallet_view_header_icon_size">56dp</dimen>
+    <dimen name="wallet_screen_header_icon_size">56dp</dimen>
+    <dimen name="wallet_screen_header_view_size">80dp</dimen>
     <dimen name="card_margin">16dp</dimen>
     <dimen name="card_carousel_dot_offset">24dp</dimen>
     <dimen name="card_carousel_dot_unselected_radius">2dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 150fc73..4c9618f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2968,4 +2968,6 @@
     <!-- Message shown to suggest authentication using [CHAR LIMIT=60]-->
     <string name="keyguard_try_fingerprint">Use fingerprint to open</string>
 
+    <!-- Content description for a chip in the status bar showing that the user is currently on a phone call. [CHAR LIMIT=NONE] -->
+    <string name="ongoing_phone_call_content_description">Ongoing phone call</string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 4b71a3a..baf3458 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -19,42 +19,22 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
-import android.app.PendingIntent;
 import android.app.WallpaperManager;
-import android.app.smartspace.SmartspaceConfig;
-import android.app.smartspace.SmartspaceManager;
-import android.app.smartspace.SmartspaceSession;
-import android.app.smartspace.SmartspaceTarget;
-import android.content.Intent;
-import android.content.pm.UserInfo;
 import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.view.View;
 import android.widget.FrameLayout;
 import android.widget.RelativeLayout;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.keyguard.clock.ClockManager;
-import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.BcSmartspaceDataPlugin;
-import com.android.systemui.plugins.BcSmartspaceDataPlugin.IntentStarter;
 import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -62,14 +42,10 @@
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
 import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.ViewController;
-import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.Locale;
-import java.util.Optional;
 import java.util.TimeZone;
-import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -85,9 +61,8 @@
     private final KeyguardSliceViewController mKeyguardSliceViewController;
     private final NotificationIconAreaController mNotificationIconAreaController;
     private final BroadcastDispatcher mBroadcastDispatcher;
-    private final Executor mUiExecutor;
     private final BatteryController mBatteryController;
-    private final FeatureFlags mFeatureFlags;
+    private final LockscreenSmartspaceController mSmartspaceController;
 
     /**
      * Clock for both small and large sizes
@@ -97,20 +72,8 @@
     private AnimatableClockController mLargeClockViewController;
     private FrameLayout mLargeClockFrame;
 
-    private SmartspaceSession mSmartspaceSession;
-    private SmartspaceSession.OnTargetsAvailableListener mSmartspaceCallback;
-    private ConfigurationController mConfigurationController;
-    private ActivityStarter mActivityStarter;
-    private FalsingManager mFalsingManager;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final KeyguardBypassController mBypassController;
-    private Handler mHandler;
-    private UserTracker mUserTracker;
-    private SecureSettings mSecureSettings;
-    private ContentObserver mSettingsObserver;
-    private boolean mShowSensitiveContentForCurrentUser;
-    private boolean mShowSensitiveContentForManagedUser;
-    private UserHandle mManagedUserHandle;
 
     /**
      * Listener for changes to the color palette.
@@ -118,59 +81,30 @@
      * The color palette changes when the wallpaper is changed.
      */
     private final ColorExtractor.OnColorsChangedListener mColorsListener =
-            new ColorExtractor.OnColorsChangedListener() {
-        @Override
-        public void onColorsChanged(ColorExtractor extractor, int which) {
-            if ((which & WallpaperManager.FLAG_LOCK) != 0) {
-                mView.updateColors(getGradientColors());
-            }
-        }
-    };
-
-    private final ConfigurationController.ConfigurationListener mConfigurationListener =
-            new ConfigurationController.ConfigurationListener() {
-        @Override
-        public void onThemeChanged() {
-            updateWallpaperColor();
-        }
-    };
-
-    private ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
-
-    private final StatusBarStateController.StateListener mStatusBarStateListener =
-            new StatusBarStateController.StateListener() {
-                @Override
-                public void onDozeAmountChanged(float linear, float eased) {
-                    if (mSmartspaceView != null) {
-                        mSmartspaceView.setDozeAmount(eased);
-                    }
+            (extractor, which) -> {
+                if ((which & WallpaperManager.FLAG_LOCK) != 0) {
+                    mView.updateColors(getGradientColors());
                 }
             };
 
+    private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
+
     // If set, will replace keyguard_status_area
-    private BcSmartspaceDataPlugin.SmartspaceView mSmartspaceView;
-    private Optional<BcSmartspaceDataPlugin> mSmartspacePlugin;
+    private View mSmartspaceView;
 
     @Inject
     public KeyguardClockSwitchController(
             KeyguardClockSwitch keyguardClockSwitch,
             StatusBarStateController statusBarStateController,
-            SysuiColorExtractor colorExtractor, ClockManager clockManager,
+            SysuiColorExtractor colorExtractor,
+            ClockManager clockManager,
             KeyguardSliceViewController keyguardSliceViewController,
             NotificationIconAreaController notificationIconAreaController,
             BroadcastDispatcher broadcastDispatcher,
-            FeatureFlags featureFlags,
-            @Main Executor uiExecutor,
             BatteryController batteryController,
-            ConfigurationController configurationController,
-            ActivityStarter activityStarter,
-            FalsingManager falsingManager,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             KeyguardBypassController bypassController,
-            @Main Handler handler,
-            UserTracker userTracker,
-            SecureSettings secureSettings,
-            Optional<BcSmartspaceDataPlugin> smartspacePlugin) {
+            LockscreenSmartspaceController smartspaceController) {
         super(keyguardClockSwitch);
         mStatusBarStateController = statusBarStateController;
         mColorExtractor = colorExtractor;
@@ -178,18 +112,10 @@
         mKeyguardSliceViewController = keyguardSliceViewController;
         mNotificationIconAreaController = notificationIconAreaController;
         mBroadcastDispatcher = broadcastDispatcher;
-        mFeatureFlags = featureFlags;
-        mUiExecutor = uiExecutor;
         mBatteryController = batteryController;
-        mConfigurationController = configurationController;
-        mActivityStarter = activityStarter;
-        mFalsingManager = falsingManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mBypassController = bypassController;
-        mHandler = handler;
-        mUserTracker = userTracker;
-        mSecureSettings = secureSettings;
-        mSmartspacePlugin = smartspacePlugin;
+        mSmartspaceController = smartspaceController;
     }
 
     /**
@@ -232,119 +158,33 @@
                         mBypassController);
         mLargeClockViewController.init();
 
-        mStatusBarStateController.addCallback(mStatusBarStateListener);
-        mConfigurationController.addCallback(mConfigurationListener);
+        if (mSmartspaceController.isEnabled()) {
+            mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
 
-        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);
 
-            mSmartspaceView = smartspaceDataPlugin.getView(mView);
-            mSmartspaceView.registerDataProvider(smartspaceDataPlugin);
-            mSmartspaceView.setIntentStarter(new IntentStarter() {
-                public void startIntent(View v, Intent i) {
-                    mActivityStarter.startActivity(i, true /* dismissShade */);
-                }
-
-                public void startPendingIntent(PendingIntent pi) {
-                    mActivityStarter.startPendingIntentDismissingKeyguard(pi);
-                }
-            });
-            mSmartspaceView.setFalsingManager(mFalsingManager);
-            updateWallpaperColor();
-            View asView = (View) mSmartspaceView;
-
             // Place smartspace view below normal clock...
             RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
                     MATCH_PARENT, WRAP_CONTENT);
             lp.addRule(RelativeLayout.BELOW, R.id.lockscreen_clock_view);
 
-            mView.addView(asView, ksaIndex, lp);
+            mView.addView(mSmartspaceView, ksaIndex, lp);
             int padding = getContext().getResources()
                     .getDimensionPixelSize(R.dimen.below_clock_padding_start);
-            asView.setPadding(padding, 0, padding, 0);
+            mSmartspaceView.setPadding(padding, 0, padding, 0);
 
             // ... but above the large clock
             lp = new RelativeLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT);
-            lp.addRule(RelativeLayout.BELOW, asView.getId());
+            lp.addRule(RelativeLayout.BELOW, mSmartspaceView.getId());
             mLargeClockFrame.setLayoutParams(lp);
 
             View nic = mView.findViewById(
                     R.id.left_aligned_notification_icon_container);
             lp = (RelativeLayout.LayoutParams) nic.getLayoutParams();
-            lp.addRule(RelativeLayout.BELOW, asView.getId());
+            lp.addRule(RelativeLayout.BELOW, mSmartspaceView.getId());
             nic.setLayoutParams(lp);
-
-            mSmartspaceSession = getContext().getSystemService(SmartspaceManager.class)
-                    .createSmartspaceSession(
-                            new SmartspaceConfig.Builder(getContext(), "lockscreen").build());
-            mSmartspaceCallback = targets -> {
-                targets.removeIf(this::filterSmartspaceTarget);
-                smartspaceDataPlugin.onTargetsAvailable(targets);
-            };
-            mSmartspaceSession.addOnTargetsAvailableListener(mUiExecutor, mSmartspaceCallback);
-            mSettingsObserver = new ContentObserver(mHandler) {
-                @Override
-                public void onChange(boolean selfChange, Uri uri) {
-                    reloadSmartspace();
-                }
-            };
-
-            getContext().getContentResolver().registerContentObserver(
-                    Settings.Secure.getUriFor(
-                            Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
-                    true, mSettingsObserver, UserHandle.USER_ALL);
-            reloadSmartspace();
-        }
-
-        float dozeAmount = mStatusBarStateController.getDozeAmount();
-        mStatusBarStateListener.onDozeAmountChanged(dozeAmount, dozeAmount);
-    }
-
-    @VisibleForTesting
-    boolean filterSmartspaceTarget(SmartspaceTarget t) {
-        if (!t.isSensitive()) return false;
-
-        if (t.getUserHandle().equals(mUserTracker.getUserHandle())) {
-            return !mShowSensitiveContentForCurrentUser;
-        }
-        if (t.getUserHandle().equals(mManagedUserHandle)) {
-            return !mShowSensitiveContentForManagedUser;
-        }
-
-        return false;
-    }
-
-    private void reloadSmartspace() {
-        mManagedUserHandle = getWorkProfileUser();
-        final String setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
-
-        mShowSensitiveContentForCurrentUser =
-                mSecureSettings.getIntForUser(setting, 0, mUserTracker.getUserId()) == 1;
-        if (mManagedUserHandle != null) {
-            int id = mManagedUserHandle.getIdentifier();
-            mShowSensitiveContentForManagedUser =
-                    mSecureSettings.getIntForUser(setting, 0, id) == 1;
-        }
-
-        mSmartspaceSession.requestSmartspaceUpdate();
-    }
-
-    private UserHandle getWorkProfileUser() {
-        for (UserInfo userInfo : mUserTracker.getUserProfiles()) {
-            if (userInfo.isManagedProfile()) {
-                return userInfo.getUserHandle();
-            }
-        }
-        return null;
-    }
-
-    private void updateWallpaperColor() {
-        if (mSmartspaceView != null) {
-            int color = Utils.getColorAttrDefaultColor(getContext(), R.attr.wallpaperTextColor);
-            mSmartspaceView.setPrimaryTextColor(color);
         }
     }
 
@@ -356,16 +196,16 @@
         mColorExtractor.removeOnColorsChangedListener(mColorsListener);
         mView.setClockPlugin(null, mStatusBarStateController.getState());
 
-        if (mSmartspaceSession != null) {
-            mSmartspaceSession.removeOnTargetsAvailableListener(mSmartspaceCallback);
-            mSmartspaceSession.close();
-            mSmartspaceSession = null;
-        }
-        mStatusBarStateController.removeCallback(mStatusBarStateListener);
-        mConfigurationController.removeCallback(mConfigurationListener);
+        mSmartspaceController.disconnect();
 
-        if (mSettingsObserver != null) {
-            getContext().getContentResolver().unregisterContentObserver(mSettingsObserver);
+        // TODO: This is an unfortunate necessity since smartspace plugin retains a single instance
+        // of the smartspace view -- if we don't remove the view, it can't be reused by a later
+        // instance of this class. In order to fix this, we need to modify the plugin so that
+        // (a) we get a new view each time and (b) we can properly clean up an old view by making
+        // it unregister itself as a plugin listener.
+        if (mSmartspaceView != null) {
+            mView.removeView(mSmartspaceView);
+            mSmartspaceView = null;
         }
     }
 
@@ -436,7 +276,7 @@
                 scale, props, animate);
 
         if (mSmartspaceView != null) {
-            PropertyAnimator.setProperty((View) mSmartspaceView, AnimatableProperty.TRANSLATION_X,
+            PropertyAnimator.setProperty(mSmartspaceView, AnimatableProperty.TRANSLATION_X,
                     x, props, animate);
         }
 
@@ -510,14 +350,4 @@
     private int getCurrentLayoutDirection() {
         return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
     }
-
-    @VisibleForTesting
-    ConfigurationController.ConfigurationListener getConfigurationListener() {
-        return mConfigurationListener;
-    }
-
-    @VisibleForTesting
-    ContentObserver getSettingsObserver() {
-        return mSettingsObserver;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index be50eb1..afda2a4 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -55,8 +55,6 @@
 import android.graphics.drawable.Drawable;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.HandlerThread;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings.Secure;
@@ -91,11 +89,13 @@
 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.DelayableExecutor;
 import com.android.systemui.util.concurrency.ThreadFactory;
 import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -122,7 +122,7 @@
     @VisibleForTesting
     protected boolean mIsRegistered;
     private final BroadcastDispatcher mBroadcastDispatcher;
-    private final Handler mMainHandler;
+    private final Executor mMainExecutor;
     private final TunerService mTunerService;
     private final SecureSettings mSecureSettings;
     private DisplayManager.DisplayListener mDisplayListener;
@@ -153,6 +153,7 @@
     private WindowManager mWindowManager;
     private int mRotation;
     private SecureSetting mColorInversionSetting;
+    private DelayableExecutor mExecutor;
     private Handler mHandler;
     private boolean mPendingRotationChange;
     private boolean mIsRoundedCornerMultipleRadius;
@@ -212,7 +213,7 @@
 
     @Inject
     public ScreenDecorations(Context context,
-            @Main Handler handler,
+            @Main Executor mainExecutor,
             SecureSettings secureSettings,
             BroadcastDispatcher broadcastDispatcher,
             TunerService tunerService,
@@ -220,7 +221,7 @@
             PrivacyDotViewController dotViewController,
             ThreadFactory threadFactory) {
         super(context);
-        mMainHandler = handler;
+        mMainExecutor = mainExecutor;
         mSecureSettings = secureSettings;
         mBroadcastDispatcher = broadcastDispatcher;
         mTunerService = tunerService;
@@ -235,17 +236,10 @@
             Log.i(TAG, "ScreenDecorations is disabled");
             return;
         }
-        mHandler = startHandlerThread();
-        mHandler.post(this::startOnScreenDecorationsThread);
-        mDotViewController.setUiExecutor(
-                mThreadFactory.buildDelayableExecutorOnLooper(mHandler.getLooper()));
-    }
-
-    @VisibleForTesting
-    Handler startHandlerThread() {
-        HandlerThread thread = new HandlerThread("ScreenDecorations");
-        thread.start();
-        return thread.getThreadHandler();
+        mHandler = mThreadFactory.builderHandlerOnNewThread("ScreenDecorations");
+        mExecutor = mThreadFactory.buildDelayableExecutorOnHandler(mHandler);
+        mExecutor.execute(this::startOnScreenDecorationsThread);
+        mDotViewController.setUiExecutor(mExecutor);
     }
 
     private void startOnScreenDecorationsThread() {
@@ -332,7 +326,7 @@
             mDisplayManager.getDisplay(DEFAULT_DISPLAY).getMetrics(metrics);
             mDensity = metrics.density;
 
-            mMainHandler.post(() -> mTunerService.addTunable(this, SIZE));
+            mExecutor.execute(() -> mTunerService.addTunable(this, SIZE));
 
             // Watch color inversion and invert the overlay as needed.
             if (mColorInversionSetting == null) {
@@ -351,10 +345,10 @@
             IntentFilter filter = new IntentFilter();
             filter.addAction(Intent.ACTION_USER_SWITCHED);
             mBroadcastDispatcher.registerReceiver(mUserSwitchIntentReceiver, filter,
-                    new HandlerExecutor(mHandler), UserHandle.ALL);
+                    mExecutor, UserHandle.ALL);
             mIsRegistered = true;
         } else {
-            mMainHandler.post(() -> mTunerService.removeTunable(this));
+            mMainExecutor.execute(() -> mTunerService.removeTunable(this));
 
             if (mColorInversionSetting != null) {
                 mColorInversionSetting.setListening(false);
@@ -567,7 +561,7 @@
         Resources res = mContext.getResources();
         boolean enabled = res.getBoolean(R.bool.config_enableDisplayCutoutProtection);
         if (enabled) {
-            mCameraListener = CameraAvailabilityListener.Factory.build(mContext, mHandler::post);
+            mCameraListener = CameraAvailabilityListener.Factory.build(mContext, mExecutor);
             mCameraListener.addTransitionCallback(mCameraTransitionCallback);
             mCameraListener.startListening();
         }
@@ -624,7 +618,7 @@
             Log.i(TAG, "ScreenDecorations is disabled");
             return;
         }
-        mHandler.post(() -> {
+        mExecutor.execute(() -> {
             int oldRotation = mRotation;
             mPendingRotationChange = false;
             updateOrientation();
@@ -825,7 +819,7 @@
             Log.i(TAG, "ScreenDecorations is disabled");
             return;
         }
-        mHandler.post(() -> {
+        mExecutor.execute(() -> {
             if (mOverlays == null) return;
             if (SIZE.equals(key)) {
                 Point size = mRoundedDefault;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
index cd5abd7..2c48d0a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
@@ -36,7 +36,7 @@
 public class UdfpsEnrollDrawable extends UdfpsDrawable {
     private static final String TAG = "UdfpsAnimationEnroll";
 
-    static final float PROGRESS_BAR_RADIUS = 180.f;
+    static final float PROGRESS_BAR_RADIUS = 360.f;
 
     @NonNull private final Drawable mMovingTargetFpIcon;
     @NonNull private final Paint mSensorOutlinePaint;
@@ -96,11 +96,6 @@
             return;
         }
 
-        if (mSensorRect != null) {
-            canvas.drawOval(mSensorRect, mSensorOutlinePaint);
-        }
-        mFingerprintDrawable.draw(canvas);
-
         // Draw moving target
         if (mEnrollHelper.isCenterEnrollmentComplete()) {
             mFingerprintDrawable.setAlpha(mAlpha == 255 ? 64 : mAlpha);
@@ -117,6 +112,10 @@
             mMovingTargetFpIcon.draw(canvas);
             canvas.restore();
         } else {
+            if (mSensorRect != null) {
+                canvas.drawOval(mSensorRect, mSensorOutlinePaint);
+            }
+            mFingerprintDrawable.draw(canvas);
             mFingerprintDrawable.setAlpha(mAlpha);
             mSensorOutlinePaint.setAlpha(mAlpha);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index a904cef..26be987 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -120,7 +120,7 @@
     private val onSeedingComplete = Consumer<Boolean> {
         accepted ->
             if (accepted) {
-                selectedStructure = controlsController.get().getFavorites().maxByOrNull {
+                selectedStructure = controlsController.get().getFavorites().maxBy {
                     it.controls.size
                 } ?: EMPTY_STRUCTURE
                 updatePreferences(selectedStructure)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index fd80d50..26db33d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -29,6 +29,7 @@
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.role.RoleManager;
+import android.app.smartspace.SmartspaceManager;
 import android.app.trust.TrustManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -400,4 +401,10 @@
     static PermissionManager providePermissionManager(Context context) {
         return context.getSystemService(PermissionManager.class);
     }
+
+    @Provides
+    @Singleton
+    static SmartspaceManager provideSmartspaceManager(Context context) {
+        return context.getSystemService(SmartspaceManager.class);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 816cf61..ef53233 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -535,12 +535,20 @@
             this.desiredLocation = desiredLocation
             this.desiredHostState = it
             currentlyExpanded = it.expansion > 0
+
+            val shouldCloseGuts = !currentlyExpanded && !mediaManager.hasActiveMedia() &&
+                    desiredHostState.showsOnlyActiveMedia
+
             for (mediaPlayer in MediaPlayerData.players()) {
                 if (animate) {
                     mediaPlayer.mediaViewController.animatePendingStateChange(
                             duration = duration,
                             delay = startDelay)
                 }
+                if (shouldCloseGuts && mediaPlayer.mediaViewController.isGutsVisible) {
+                    mediaPlayer.closeGuts(!animate)
+                }
+
                 mediaPlayer.mediaViewController.onLocationPreChange(desiredLocation)
             }
             mediaCarouselScrollHandler.showsSettingsButton = !it.showsOnlyActiveMedia
@@ -556,9 +564,9 @@
         }
     }
 
-    fun closeGuts() {
+    fun closeGuts(immediate: Boolean = true) {
         MediaPlayerData.players().forEach {
-            it.closeGuts(true)
+            it.closeGuts(immediate)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
index e5a6271..c806bcf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
@@ -59,7 +59,7 @@
     private val mainExecutor: DelayableExecutor,
     private val dismissCallback: () -> Unit,
     private var translationChangedListener: () -> Unit,
-    private val closeGuts: () -> Unit,
+    private val closeGuts: (immediate: Boolean) -> Unit,
     private val falsingCollector: FalsingCollector,
     private val falsingManager: FalsingManager,
     private val logSmartspaceImpression: () -> Unit
@@ -473,7 +473,7 @@
             if (oldIndex != visibleMediaIndex && visibleToUser) {
                 logSmartspaceImpression()
             }
-            closeGuts()
+            closeGuts(false)
             updatePlayerVisibilities()
         }
         val relativeLocation = visibleMediaIndex.toFloat() + if (playerWidthPlusPadding > 0)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index bc11fee..3e9559b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -213,7 +213,8 @@
                 mMediaViewController.openGuts();
                 return true;
             } else {
-                return false;
+                closeGuts();
+                return true;
             }
         });
         mPlayerViewHolder.getCancel().setOnClickListener(v -> {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 3c28f6e..60e832a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -273,6 +273,7 @@
                 } else {
                     updateDesiredLocation()
                     qsExpanded = false
+                    closeGuts()
                 }
                 mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
             }
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 29685a4..7b5ab0d 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -416,9 +416,8 @@
                 && birthdayString == null;
         boolean addBirthdayStatus = !hasBirthdayStatus(storedTile, context)
                 && birthdayString != null;
-        boolean shouldUpdate =
-                storedTile.getContactAffinity() != affinity || outdatedBirthdayStatus
-                        || addBirthdayStatus;
+        boolean shouldUpdate = storedTile.getContactAffinity() != affinity || outdatedBirthdayStatus
+                || addBirthdayStatus;
         if (shouldUpdate) {
             if (DEBUG) Log.d(TAG, "Update " + storedTile.getUserName() + " from contacts");
             manager.updateAppWidgetOptionsAndView(appWidgetId,
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index 06f8a60..9fc9cad 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -41,8 +41,6 @@
 import android.app.people.PeopleSpaceTile;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
@@ -57,13 +55,13 @@
 import android.util.IconDrawableFactory;
 import android.util.Log;
 import android.util.Pair;
+import android.view.Gravity;
 import android.view.View;
 import android.widget.RemoteViews;
 import android.widget.TextView;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.launcher3.icons.FastBitmapDrawable;
-import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.people.widget.LaunchConversationActivity;
 import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
@@ -195,16 +193,10 @@
      */
     private RemoteViews getViewForTile() {
         if (DEBUG) Log.d(TAG, "Creating view for tile key: " + mKey.toString());
-        if (mTile == null || mTile.isPackageSuspended() || mTile.isUserQuieted()) {
-            if (DEBUG) Log.d(TAG, "Create empty view: " + mTile);
-            return createEmptyView();
-        }
-
-        boolean dndBlockingTileData = isDndBlockingTileData(mTile);
-        if (dndBlockingTileData) {
-            if (DEBUG) Log.d(TAG, "Create DND view: " + mTile.getNotificationPolicyState());
-            // TODO: Create DND view.
-            return createEmptyView();
+        if (mTile == null || mTile.isPackageSuspended() || mTile.isUserQuieted()
+                || isDndBlockingTileData(mTile)) {
+            if (DEBUG) Log.d(TAG, "Create suppressed view: " + mTile);
+            return createSuppressedView();
         }
 
         if (Objects.equals(mTile.getNotificationCategory(), CATEGORY_MISSED_CALL)) {
@@ -265,34 +257,27 @@
         return !tile.canBypassDnd();
     }
 
-    private RemoteViews createEmptyView() {
-        RemoteViews views = new RemoteViews(mContext.getPackageName(),
-                R.layout.people_tile_empty_layout);
-        Drawable appIcon = getAppBadge(mKey.getPackageName(), mKey.getUserId());
+    private RemoteViews createSuppressedView() {
+        RemoteViews views;
+        if (mTile.isUserQuieted()) {
+            views = new RemoteViews(mContext.getPackageName(),
+                    R.layout.people_tile_work_profile_quiet_layout);
+        } else {
+            views = new RemoteViews(mContext.getPackageName(),
+                    R.layout.people_tile_suppressed_layout);
+        }
+        Drawable appIcon = mContext.getDrawable(R.drawable.ic_conversation_icon);
         Bitmap appIconAsBitmap = convertDrawableToBitmap(appIcon);
-        FastBitmapDrawable drawable = new FastBitmapDrawable(
-                appIconAsBitmap);
+        FastBitmapDrawable drawable = new FastBitmapDrawable(appIconAsBitmap);
         drawable.setIsDisabled(true);
         Bitmap convertedBitmap = convertDrawableToBitmap(drawable);
-        views.setImageViewBitmap(R.id.item, convertedBitmap);
+        views.setImageViewBitmap(R.id.icon, convertedBitmap);
         return views;
     }
 
-    private Drawable getAppBadge(String packageName, int userId) {
-        Drawable badge = null;
-        try {
-            final ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfoAsUser(
-                    packageName, PackageManager.GET_META_DATA, userId);
-            badge = Utils.getBadgedIcon(mContext, appInfo);
-        } catch (PackageManager.NameNotFoundException e) {
-            badge = mContext.getPackageManager().getDefaultActivityIcon();
-        }
-        return badge;
-    }
-
     private void setMaxLines(RemoteViews views, boolean showSender) {
         int textSize = mLayoutSize == LAYOUT_LARGE ? getSizeInDp(
-                R.dimen.content_text_size_for_medium)
+                R.dimen.content_text_size_for_large)
                 : getSizeInDp(R.dimen.content_text_size_for_medium);
         int lineHeight = getLineHeight(textSize);
         int notificationContentHeight = getContentHeightForLayout(lineHeight);
@@ -422,9 +407,6 @@
                 views.setViewVisibility(R.id.availability, View.GONE);
             }
 
-            if (mTile.getUserName() != null) {
-                views.setTextViewText(R.id.name, mTile.getUserName().toString());
-            }
             views.setBoolean(R.id.image, "setClipToOutline", true);
             views.setImageViewBitmap(R.id.person_icon,
                     getPersonIconBitmap(mContext, mTile, maxAvatarSize));
@@ -537,25 +519,31 @@
             statusText = getStatusTextByType(status.getActivity());
         }
         views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
-        views.setViewVisibility(R.id.messages_count, View.GONE);
-        setMaxLines(views, false);
-        // Secondary text color for statuses.
-        views.setColorAttr(R.id.text_content, "setTextColor", android.R.attr.textColorSecondary);
         views.setTextViewText(R.id.text_content, statusText);
+        if (mLayoutSize == LAYOUT_LARGE) {
+            views.setInt(R.id.content, "setGravity", Gravity.BOTTOM);
+        }
 
         Icon statusIcon = status.getIcon();
         if (statusIcon != null) {
-            // No multi-line text with status images on medium layout.
-            views.setViewVisibility(R.id.text_content, View.GONE);
+            // No text content styled text on medium or large.
+            views.setViewVisibility(R.id.scrim_layout, View.VISIBLE);
+            views.setImageViewIcon(R.id.status_icon, statusIcon);
             // Show 1-line subtext on large layout with status images.
             if (mLayoutSize == LAYOUT_LARGE) {
-                views.setViewVisibility(R.id.subtext, View.VISIBLE);
-                views.setTextViewText(R.id.subtext, statusText);
+                if (DEBUG) Log.d(TAG, "Remove name for large");
+                views.setViewVisibility(R.id.name, View.GONE);
+                views.setColorAttr(R.id.text_content, "setTextColor",
+                        android.R.attr.textColorPrimary);
+            } else if (mLayoutSize == LAYOUT_MEDIUM) {
+                views.setViewVisibility(R.id.text_content, View.GONE);
+                views.setTextViewText(R.id.name, statusText);
             }
-            views.setViewVisibility(R.id.image, View.VISIBLE);
-            views.setImageViewIcon(R.id.image, statusIcon);
         } else {
-            views.setViewVisibility(R.id.image, View.GONE);
+            // Secondary text color for statuses without icons.
+            views.setColorAttr(R.id.text_content, "setTextColor",
+                    android.R.attr.textColorSecondary);
+            setMaxLines(views, false);
         }
         // TODO: Set status pre-defined icons
         views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_person);
@@ -741,16 +729,23 @@
             views.setViewVisibility(R.id.name, View.VISIBLE);
             views.setViewVisibility(R.id.text_content, View.VISIBLE);
             views.setViewVisibility(R.id.subtext, View.GONE);
+            views.setViewVisibility(R.id.image, View.GONE);
+            views.setViewVisibility(R.id.scrim_layout, View.GONE);
         }
 
         if (mLayoutSize == LAYOUT_MEDIUM) {
             if (DEBUG) Log.d(TAG, "Set vertical padding: " + mMediumVerticalPadding);
             int horizontalPadding = (int) Math.floor(MAX_MEDIUM_PADDING * mDensity);
             int verticalPadding = (int) Math.floor(mMediumVerticalPadding * mDensity);
-            views.setViewPadding(R.id.item, horizontalPadding, verticalPadding, horizontalPadding,
+            views.setViewPadding(R.id.content, horizontalPadding, verticalPadding,
+                    horizontalPadding,
                     verticalPadding);
         }
         views.setViewVisibility(R.id.messages_count, View.GONE);
+        if (mTile.getUserName() != null) {
+            views.setTextViewText(R.id.name, mTile.getUserName());
+        }
+
         return views;
     }
 
@@ -761,6 +756,9 @@
             views.setViewVisibility(R.id.predefined_icon, View.GONE);
             views.setViewVisibility(R.id.messages_count, View.GONE);
         }
+        if (mTile.getUserName() != null) {
+            views.setTextViewText(R.id.name, mTile.getUserName());
+        }
         String status = getLastInteractionString(mContext,
                 mTile.getLastInteractionTimestamp());
         if (status != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt
index eec69f98..1d2e747 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt
@@ -28,7 +28,7 @@
         appsAndTypes = itemsList.groupBy({ it.application }, { it.privacyType })
                 .toList()
                 .sortedWith(compareBy({ -it.second.size }, // Sort by number of AppOps
-                        { it.second.minOrNull() })) // Sort by "smallest" AppOpp (Location is largest)
+                        { it.second.min() })) // Sort by "smallest" AppOpp (Location is largest)
         types = itemsList.map { it.privacyType }.distinct().sorted()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index c459963..3a3f3f1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -129,6 +129,12 @@
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         mNavBarInset = insets.getInsets(WindowInsets.Type.navigationBars()).bottom;
+        mQSPanelContainer.setPaddingRelative(
+                mQSPanelContainer.getPaddingStart(),
+                mQSPanelContainer.getPaddingTop(),
+                mQSPanelContainer.getPaddingEnd(),
+                mNavBarInset
+        );
         return super.onApplyWindowInsets(insets);
     }
 
@@ -138,8 +144,7 @@
         // bottom and footer are inside the screen.
         MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanelContainer.getLayoutParams();
 
-        int availableScreenHeight = getDisplayHeight() - mNavBarInset;
-        int maxQs = availableScreenHeight - layoutParams.topMargin - layoutParams.bottomMargin
+        int maxQs = getDisplayHeight() - layoutParams.topMargin - layoutParams.bottomMargin
                 - getPaddingBottom();
         int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin
                 + layoutParams.rightMargin;
@@ -148,10 +153,8 @@
         mQSPanelContainer.measure(qsPanelWidthSpec,
                 MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST));
         int width = mQSPanelContainer.getMeasuredWidth() + padding;
-        int height = layoutParams.topMargin + layoutParams.bottomMargin
-                + mQSPanelContainer.getMeasuredHeight() + getPaddingBottom();
         super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec(availableScreenHeight, MeasureSpec.EXACTLY));
+                MeasureSpec.makeMeasureSpec(getDisplayHeight(), MeasureSpec.EXACTLY));
         // QSCustomizer will always be the height of the screen, but do this after
         // other measuring to avoid changing the height of the QS.
         mQSCustomizer.measure(widthMeasureSpec,
@@ -196,13 +199,10 @@
 
     void updateResources(QSPanelController qsPanelController,
             QuickStatusBarHeaderController quickStatusBarHeaderController) {
-        mQSPanelContainer.setPaddingRelative(
-                mQSPanelContainer.getPaddingStart(),
-                mContext.getResources().getDimensionPixelSize(
-                        com.android.internal.R.dimen.quick_qs_offset_height),
-                mQSPanelContainer.getPaddingEnd(),
-                mContext.getResources().getDimensionPixelSize(R.dimen.qs_container_bottom_padding)
-        );
+        LayoutParams layoutParams = (LayoutParams) mQSPanelContainer.getLayoutParams();
+        layoutParams.topMargin = mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.quick_qs_offset_height);
+        mQSPanelContainer.setLayoutParams(layoutParams);
 
         int sideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
         int padding = getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 05197e4..0335319 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -30,6 +30,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
+import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -153,11 +154,18 @@
         MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
         lp.topMargin = mContext.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.quick_qs_offset_height);
-        lp.bottomMargin = mContext.getResources().getDimensionPixelSize(
-                R.dimen.qs_container_bottom_padding);
         setLayoutParams(lp);
     }
 
+    @Override
+    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        int bottomNavBar = insets.getInsets(WindowInsets.Type.navigationBars()).bottom;
+        MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
+        lp.bottomMargin = bottomNavBar;
+        setLayoutParams(lp);
+        return super.onApplyWindowInsets(insets);
+    }
+
     public boolean isClosingDetail() {
         return mClosingDetail;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 26332f4..81b5318 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -23,6 +23,7 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.util.FeatureFlagUtils;
 import android.util.Pair;
 import android.view.DisplayCutout;
 import android.view.View;
@@ -87,13 +88,21 @@
     private int mTopViewMeasureHeight;
 
     private final String mMobileSlotName;
+    private final String mNoCallingSlotName;
     private final String mCallStrengthSlotName;
+    private final boolean mProviderModel;
 
     public QuickStatusBarHeader(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mMobileSlotName = context.getString(com.android.internal.R.string.status_bar_no_calling);
+        mMobileSlotName = context.getString(com.android.internal.R.string.status_bar_mobile);
+        mNoCallingSlotName = context.getString(com.android.internal.R.string.status_bar_no_calling);
         mCallStrengthSlotName =
                 context.getString(com.android.internal.R.string.status_bar_call_strength);
+        if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
+            mProviderModel = true;
+        } else {
+            mProviderModel = false;
+        }
     }
 
     /**
@@ -242,14 +251,24 @@
                 .setListener(new TouchAnimator.ListenerAdapter() {
                     @Override
                     public void onAnimationAtEnd() {
-                        mIconContainer.addIgnoredSlot(mMobileSlotName);
-                        mIconContainer.addIgnoredSlot(mCallStrengthSlotName);
+                        // TODO(b/185580157): Remove the mProviderModel if the mobile slot can be
+                        // hidden in Provider model.
+                        if (mProviderModel) {
+                            mIconContainer.addIgnoredSlot(mNoCallingSlotName);
+                            mIconContainer.addIgnoredSlot(mCallStrengthSlotName);
+                        } else {
+                            mIconContainer.addIgnoredSlot(mMobileSlotName);
+                        }
                     }
 
                     @Override
                     public void onAnimationStarted() {
-                        mIconContainer.addIgnoredSlot(mMobileSlotName);
-                        mIconContainer.addIgnoredSlot(mCallStrengthSlotName);
+                        if (mProviderModel) {
+                            mIconContainer.addIgnoredSlot(mNoCallingSlotName);
+                            mIconContainer.addIgnoredSlot(mCallStrengthSlotName);
+                        } else {
+                            mIconContainer.addIgnoredSlot(mMobileSlotName);
+                        }
 
                         setSeparatorVisibility(false);
                     }
@@ -257,8 +276,12 @@
                     @Override
                     public void onAnimationAtStart() {
                         super.onAnimationAtStart();
-                        mIconContainer.removeIgnoredSlot(mMobileSlotName);
-                        mIconContainer.removeIgnoredSlot(mCallStrengthSlotName);
+                        if (mProviderModel) {
+                            mIconContainer.removeIgnoredSlot(mNoCallingSlotName);
+                            mIconContainer.removeIgnoredSlot(mCallStrengthSlotName);
+                        } else {
+                            mIconContainer.removeIgnoredSlot(mMobileSlotName);
+                        }
 
                         setSeparatorVisibility(mShowClockIconsSeparator);
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/IgnorableChildLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/IgnorableChildLinearLayout.kt
index 7055760..2bac298 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/IgnorableChildLinearLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/IgnorableChildLinearLayout.kt
@@ -45,13 +45,15 @@
         super.onMeasure(widthMeasureSpec, heightMeasureSpec)
         if (ignoreLastView && childCount > 0) {
             val lastView = getChildAt(childCount - 1)
-            val lp = lastView.layoutParams as MarginLayoutParams
-            if (orientation == VERTICAL) {
-                val height = lastView.measuredHeight + lp.bottomMargin + lp.topMargin
-                setMeasuredDimension(measuredWidth, measuredHeight - height)
-            } else {
-                val width = lastView.measuredWidth + lp.leftMargin + lp.rightMargin
-                setMeasuredDimension(measuredWidth - width, measuredHeight)
+            if (lastView.visibility != GONE) {
+                val lp = lastView.layoutParams as MarginLayoutParams
+                if (orientation == VERTICAL) {
+                    val height = lastView.measuredHeight + lp.bottomMargin + lp.topMargin
+                    setMeasuredDimension(measuredWidth, measuredHeight - height)
+                } else {
+                    val width = lastView.measuredWidth + lp.leftMargin + lp.rightMargin
+                    setMeasuredDimension(measuredWidth - width, measuredHeight)
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index 1c5df41..5b69497 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -430,7 +430,6 @@
     private static class LeftIconApplicator implements ResultApplicator {
 
         public static final int[] MARGIN_ADJUSTED_VIEWS = {
-                R.id.notification_headerless_view_column,
                 R.id.text,
                 R.id.big_text,
                 R.id.title,
@@ -438,22 +437,31 @@
                 R.id.notification_header};
 
         @Override
-        public void apply(View parent, View child, boolean apply, boolean reset) {
-            ImageView rightIcon = child.findViewById(com.android.internal.R.id.right_icon);
+        public void apply(View parent, View child, boolean showLeftIcon, boolean reset) {
             ImageView leftIcon = child.findViewById(com.android.internal.R.id.left_icon);
-            if (rightIcon == null || leftIcon == null) {
+            if (leftIcon == null) {
                 return;
             }
-            Drawable iconDrawable = rightIcon.getDrawable();
-            if (iconDrawable == null) {
-                return;
+            ImageView rightIcon = child.findViewById(com.android.internal.R.id.right_icon);
+            boolean keepRightIcon = rightIcon != null && Integer.valueOf(1).equals(
+                    rightIcon.getTag(R.id.tag_keep_when_showing_left_icon));
+            boolean leftIconUsesRightIconDrawable = Integer.valueOf(1).equals(
+                    leftIcon.getTag(R.id.tag_uses_right_icon_drawable));
+            if (leftIconUsesRightIconDrawable) {
+                // Use the right drawable when showing the left, unless the right is being kept
+                Drawable rightDrawable = rightIcon == null ? null : rightIcon.getDrawable();
+                leftIcon.setImageDrawable(showLeftIcon && !keepRightIcon ? rightDrawable : null);
             }
-            rightIcon.setVisibility(apply ? View.GONE : View.VISIBLE);
-            leftIcon.setVisibility(apply ? View.VISIBLE : View.GONE);
-            leftIcon.setImageDrawable(apply ? iconDrawable : null);
+            leftIcon.setVisibility(showLeftIcon ? View.VISIBLE : View.GONE);
 
-            for (int viewId : MARGIN_ADJUSTED_VIEWS) {
-                adjustMargins(!apply, child.findViewById(viewId));
+            // update the right icon as well
+            if (rightIcon != null) {
+                boolean showRightIcon = (keepRightIcon || !showLeftIcon)
+                        && rightIcon.getDrawable() != null;
+                rightIcon.setVisibility(showRightIcon ? View.VISIBLE : View.GONE);
+                for (int viewId : MARGIN_ADJUSTED_VIEWS) {
+                    adjustMargins(showRightIcon, child.findViewById(viewId));
+                }
             }
         }
 
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 eb7854e..4919593 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -61,6 +61,7 @@
 import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
 import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
+import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -243,11 +244,12 @@
             SystemClock systemClock,
             ActivityStarter activityStarter,
             @Main Executor mainExecutor,
-            IActivityManager iActivityManager) {
+            IActivityManager iActivityManager,
+            OngoingCallLogger logger) {
         OngoingCallController ongoingCallController =
                 new OngoingCallController(
                         notifCollection, featureFlags, systemClock, activityStarter, mainExecutor,
-                        iActivityManager);
+                        iActivityManager, logger);
         ongoingCallController.init();
         return ongoingCallController;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
new file mode 100644
index 0000000..ce60c85
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -0,0 +1,277 @@
+/*
+ * 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.statusbar.lockscreen
+
+import android.app.PendingIntent
+import android.app.smartspace.SmartspaceConfig
+import android.app.smartspace.SmartspaceManager
+import android.app.smartspace.SmartspaceSession
+import android.app.smartspace.SmartspaceTarget
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
+import android.view.View
+import android.view.ViewGroup
+import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.BcSmartspaceDataPlugin
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.Execution
+import com.android.systemui.util.settings.SecureSettings
+import java.lang.RuntimeException
+import java.util.Optional
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Controller for managing the smartspace view on the lockscreen
+ */
+@SysUISingleton
+class LockscreenSmartspaceController @Inject constructor(
+    private val context: Context,
+    private val featureFlags: FeatureFlags,
+    private val smartspaceManager: SmartspaceManager,
+    private val activityStarter: ActivityStarter,
+    private val falsingManager: FalsingManager,
+    private val secureSettings: SecureSettings,
+    private val userTracker: UserTracker,
+    private val contentResolver: ContentResolver,
+    private val configurationController: ConfigurationController,
+    private val statusBarStateController: StatusBarStateController,
+    private val execution: Execution,
+    @Main private val uiExecutor: Executor,
+    @Main private val handler: Handler,
+    optionalPlugin: Optional<BcSmartspaceDataPlugin>
+) {
+    private var session: SmartspaceSession? = null
+    private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
+    private lateinit var smartspaceView: SmartspaceView
+
+    lateinit var view: View
+        private set
+
+    private var showSensitiveContentForCurrentUser = false
+    private var showSensitiveContentForManagedUser = false
+    private var managedUserHandle: UserHandle? = null
+
+    fun isEnabled(): Boolean {
+        execution.assertIsMainThread()
+
+        return featureFlags.isSmartspaceEnabled && plugin != null
+    }
+
+    /**
+     * Constructs the smartspace view and connects it to the smartspace service. Subsequent calls
+     * are idempotent until [disconnect] is called.
+     */
+    fun buildAndConnectView(parent: ViewGroup): View {
+        execution.assertIsMainThread()
+
+        if (!isEnabled()) {
+            throw RuntimeException("Cannot build view when not enabled")
+        }
+
+        buildView(parent)
+        connectSession()
+
+        return view
+    }
+
+    private fun buildView(parent: ViewGroup) {
+        if (plugin == null || this::view.isInitialized) {
+            return
+        }
+
+        val ssView = plugin.getView(parent)
+        ssView.registerDataProvider(plugin)
+        ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
+            override fun startIntent(v: View?, i: Intent?) {
+                activityStarter.startActivity(i, true /* dismissShade */)
+            }
+
+            override fun startPendingIntent(pi: PendingIntent?) {
+                activityStarter.startPendingIntentDismissingKeyguard(pi)
+            }
+        })
+        ssView.setFalsingManager(falsingManager)
+
+        this.smartspaceView = ssView
+        this.view = ssView as View
+
+        updateTextColorFromWallpaper()
+        statusBarStateListener.onDozeAmountChanged(0f, statusBarStateController.dozeAmount)
+    }
+
+    private fun connectSession() {
+        if (plugin == null || session != null) {
+            return
+        }
+        val session = smartspaceManager.createSmartspaceSession(
+                SmartspaceConfig.Builder(context, "lockscreen").build())
+        session.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+
+        userTracker.addCallback(userTrackerCallback, uiExecutor)
+        contentResolver.registerContentObserver(
+                secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
+                true,
+                settingsObserver,
+                UserHandle.USER_ALL
+        )
+        configurationController.addCallback(configChangeListener)
+        statusBarStateController.addCallback(statusBarStateListener)
+
+        this.session = session
+
+        reloadSmartspace()
+    }
+
+    /**
+     * Disconnects the smartspace view from the smartspace service and cleans up any resources.
+     * Calling [buildAndConnectView] again will cause the same view to be reconnected to the
+     * service.
+     */
+    fun disconnect() {
+        execution.assertIsMainThread()
+
+        if (session == null) {
+            return
+        }
+
+        session?.let {
+            it.removeOnTargetsAvailableListener(sessionListener)
+            it.close()
+        }
+        userTracker.removeCallback(userTrackerCallback)
+        contentResolver.unregisterContentObserver(settingsObserver)
+        configurationController.removeCallback(configChangeListener)
+        statusBarStateController.removeCallback(statusBarStateListener)
+        session = null
+
+        plugin?.onTargetsAvailable(emptyList())
+    }
+
+    fun addListener(listener: SmartspaceTargetListener) {
+        execution.assertIsMainThread()
+        plugin?.registerListener(listener)
+    }
+
+    fun removeListener(listener: SmartspaceTargetListener) {
+        execution.assertIsMainThread()
+        plugin?.unregisterListener(listener)
+    }
+
+    private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
+        execution.assertIsMainThread()
+        val filteredTargets = targets.filter(::filterSmartspaceTarget)
+        plugin?.onTargetsAvailable(filteredTargets)
+    }
+
+    private val userTrackerCallback = object : UserTracker.Callback {
+        override fun onUserChanged(newUser: Int, userContext: Context) {
+            execution.assertIsMainThread()
+            reloadSmartspace()
+        }
+
+        override fun onProfilesChanged(profiles: List<UserInfo>) {
+        }
+    }
+
+    private val settingsObserver = object : ContentObserver(handler) {
+        override fun onChange(selfChange: Boolean, uri: Uri?) {
+            execution.assertIsMainThread()
+            reloadSmartspace()
+        }
+    }
+
+    private val configChangeListener = object : ConfigurationController.ConfigurationListener {
+        override fun onThemeChanged() {
+            execution.assertIsMainThread()
+            updateTextColorFromWallpaper()
+        }
+    }
+
+    private val statusBarStateListener = object : StatusBarStateController.StateListener {
+        override fun onDozeAmountChanged(linear: Float, eased: Float) {
+            execution.assertIsMainThread()
+            smartspaceView.setDozeAmount(eased)
+        }
+    }
+
+    private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
+        return when (t.userHandle) {
+            userTracker.userHandle -> {
+                !t.isSensitive || showSensitiveContentForCurrentUser
+            }
+            managedUserHandle -> {
+                // Really, this should be "if this managed profile is associated with the current
+                // active user", but we don't have a good way to check that, so instead we cheat:
+                // Only the primary user can have an associated managed profile, so only show
+                // content for the managed profile if the primary user is active
+                userTracker.userHandle.identifier == UserHandle.USER_SYSTEM &&
+                        (!t.isSensitive || showSensitiveContentForManagedUser)
+            }
+            else -> {
+                false
+            }
+        }
+    }
+
+    private fun updateTextColorFromWallpaper() {
+        val wallpaperTextColor = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor)
+        smartspaceView.setPrimaryTextColor(wallpaperTextColor)
+    }
+
+    private fun reloadSmartspace() {
+        val setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
+
+        showSensitiveContentForCurrentUser =
+                secureSettings.getIntForUser(setting, 0, userTracker.userId) == 1
+
+        managedUserHandle = getWorkProfileUser()
+        val managedId = managedUserHandle?.identifier
+        if (managedId != null) {
+            showSensitiveContentForManagedUser =
+                    secureSettings.getIntForUser(setting, 0, managedId) == 1
+        }
+
+        session?.requestSmartspaceUpdate()
+    }
+
+    private fun getWorkProfileUser(): UserHandle? {
+        for (userInfo in userTracker.userProfiles) {
+            if (userInfo.isManagedProfile) {
+                return userInfo.userHandle
+            }
+        }
+        return null
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
index 151840a..79f99b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
@@ -42,12 +42,17 @@
         updateImageTag(row.getEntry().getSbn());
     }
 
-    private void updateImageTag(StatusBarNotification notification) {
-        final Bundle extras = notification.getNotification().extras;
-        Icon overriddenIcon = extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG);
-        if (overriddenIcon != null) {
-            mRightIcon.setTag(ImageTransformState.ICON_TAG, overriddenIcon);
-            mLeftIcon.setTag(ImageTransformState.ICON_TAG, overriddenIcon);
+    private void updateImageTag(StatusBarNotification sbn) {
+        final Bundle extras = sbn.getNotification().extras;
+        boolean bigLargeIconSet = extras.containsKey(Notification.EXTRA_LARGE_ICON_BIG);
+        if (bigLargeIconSet) {
+            Icon bigLargeIcon = extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG);
+            mRightIcon.setTag(ImageTransformState.ICON_TAG, bigLargeIcon);
+            mLeftIcon.setTag(ImageTransformState.ICON_TAG, bigLargeIcon);
+        } else {
+            // Overwrite in case the superclass populated this tag with the promoted picture,
+            // which won't be right since this is the expanded state.
+            mRightIcon.setTag(ImageTransformState.ICON_TAG, getLargeIcon(sbn.getNotification()));
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
index 0548611..a4f1172 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
@@ -20,10 +20,12 @@
 
 import static com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.DEFAULT_HEADER_VISIBLE_AMOUNT;
 
+import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
+import android.graphics.drawable.Icon;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
 import android.view.View;
@@ -32,6 +34,8 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.util.ContrastColorUtil;
 import com.android.internal.widget.NotificationActionListLayout;
 import com.android.systemui.Dependency;
@@ -143,16 +147,14 @@
                 com.android.internal.R.dimen.notification_content_margin_top);
     }
 
-    private void resolveTemplateViews(StatusBarNotification notification) {
+    private void resolveTemplateViews(StatusBarNotification sbn) {
         mRightIcon = mView.findViewById(com.android.internal.R.id.right_icon);
         if (mRightIcon != null) {
-            mRightIcon.setTag(ImageTransformState.ICON_TAG,
-                    notification.getNotification().getLargeIcon());
+            mRightIcon.setTag(ImageTransformState.ICON_TAG, getRightIcon(sbn.getNotification()));
         }
         mLeftIcon = mView.findViewById(com.android.internal.R.id.left_icon);
         if (mLeftIcon != null) {
-            mLeftIcon.setTag(ImageTransformState.ICON_TAG,
-                    notification.getNotification().getLargeIcon());
+            mLeftIcon.setTag(ImageTransformState.ICON_TAG, getLargeIcon(sbn.getNotification()));
         }
         mTitle = mView.findViewById(com.android.internal.R.id.title);
         mText = mView.findViewById(com.android.internal.R.id.text);
@@ -171,6 +173,27 @@
         updatePendingIntentCancellations();
     }
 
+    @Nullable
+    protected final Icon getLargeIcon(Notification n) {
+        Icon modernLargeIcon = n.getLargeIcon();
+        if (modernLargeIcon == null && n.largeIcon != null) {
+            return Icon.createWithBitmap(n.largeIcon);
+        }
+        return modernLargeIcon;
+    }
+
+    @Nullable
+    protected final Icon getRightIcon(Notification n) {
+        if (n.extras.getBoolean(Notification.EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED)
+                && n.getNotificationStyle() == Notification.BigPictureStyle.class) {
+            Icon pictureIcon = Notification.BigPictureStyle.getPictureIcon(n.extras);
+            if (pictureIcon != null) {
+                return pictureIcon;
+            }
+        }
+        return getLargeIcon(n);
+    }
+
     private void updatePendingIntentCancellations() {
         if (mActions != null) {
             int numActions = mActions.getChildCount();
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 120f973..786fe2c 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
@@ -664,7 +664,7 @@
             mDebugPaint.setColor(Color.CYAN);
             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
 
-            y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight());
+            y = (int) (mAmbientState.getStackY() + mSidePaddings + mAmbientState.getStackHeight());
             mDebugPaint.setColor(Color.BLUE);
             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
         }
@@ -1148,12 +1148,15 @@
         if (mOnStackYChanged != null) {
             mOnStackYChanged.run();
         }
-
-        final float stackEndHeight = getHeight() - getEmptyBottomMargin() - mTopPadding;
-        mAmbientState.setStackEndHeight(stackEndHeight);
-        mAmbientState.setStackHeight(
-                MathUtils.lerp(stackEndHeight * StackScrollAlgorithm.START_FRACTION,
-                        stackEndHeight, fraction));
+        if (mQsExpansionFraction <= 0) {
+            final float scrimTopPadding = mAmbientState.isOnKeyguard() ? 0 : mSidePaddings;
+            final float stackEndHeight = Math.max(0f,
+                    getHeight() - getEmptyBottomMargin() - stackY - scrimTopPadding);
+            mAmbientState.setStackEndHeight(stackEndHeight);
+            mAmbientState.setStackHeight(
+                    MathUtils.lerp(stackEndHeight * StackScrollAlgorithm.START_FRACTION,
+                            stackEndHeight, fraction));
+        }
     }
 
     void setOnStackYChanged(Runnable onStackYChanged) {
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 d94d030..b2d39a9 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
@@ -58,6 +58,7 @@
     private int mStatusBarHeight;
     private float mHeadsUpInset;
     private int mPinnedZTranslationExtra;
+    private float mNotificationScrimPadding;
 
     public StackScrollAlgorithm(
             Context context,
@@ -82,6 +83,7 @@
         mPinnedZTranslationExtra = res.getDimensionPixelSize(
                 R.dimen.heads_up_pinned_elevation);
         mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
+        mNotificationScrimPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
     }
 
     /**
@@ -258,6 +260,9 @@
         // expanded. Consider updating these states in updateContentView instead so that we don't
         // have to recalculate in every frame.
         float currentY = -scrollY;
+        if (!ambientState.isOnKeyguard()) {
+            currentY += mNotificationScrimPadding;
+        }
         float previousY = 0;
         state.firstViewInShelf = null;
         state.viewHeightBeforeShelf = -1;
@@ -318,6 +323,9 @@
             AmbientState ambientState) {
         // The y coordinate of the current child.
         float currentYPosition = -algorithmState.scrollY;
+        if (!ambientState.isOnKeyguard()) {
+            currentYPosition += mNotificationScrimPadding;
+        }
         int childCount = algorithmState.visibleChildren.size();
         for (int i = 0; i < childCount; i++) {
             currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition);
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 b9fe9c4a..c5a155e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -326,11 +326,13 @@
         // Show the ongoing call chip only if there is an ongoing call *and* notification icons
         // are allowed. (The ongoing call chip occupies the same area as the notification icons,
         // so if the icons are disabled then the call chip should be, too.)
-        if (hasOngoingCall && !disableNotifications) {
+        boolean showOngoingCallChip = hasOngoingCall && !disableNotifications;
+        if (showOngoingCallChip) {
             showOngoingCallChip(animate);
         } else {
             hideOngoingCallChip(animate);
         }
+        mOngoingCallController.notifyChipVisibilityChanged(showOngoingCallChip);
     }
 
     private boolean shouldHideNotificationIcons() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 71ba091..c64b893 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -929,7 +929,7 @@
         }
         GetWalletCardsRequest request =
                 new GetWalletCardsRequest(1 /* cardWidth */, 1 /* cardHeight */,
-                        1 /* iconSizePx */, 2 /* maxCards */);
+                        1 /* iconSizePx */, 1 /* maxCards */);
         mQuickAccessWalletClient.getWalletCards(mUiExecutor, request, mCardRetriever);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 16abd12..075a0c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -2072,14 +2072,13 @@
         final int qsPanelBottomY = calculateQsBottomPosition(getQsExpansionFraction());
         final boolean visible = (getQsExpansionFraction() > 0 || qsPanelBottomY > 0)
                 && !mShouldUseSplitNotificationShade;
-        final float notificationTop = mAmbientState.getStackY()
-                - mNotificationScrimPadding
-                - mAmbientState.getScrollY();
+        final float notificationTop = mAmbientState.getStackY() - mAmbientState.getScrollY();
         setQsExpansionEnabled(mAmbientState.getScrollY() == 0);
 
         int radius = mScrimCornerRadius;
         if (!mShouldUseSplitNotificationShade) {
-            top = (int) Math.min(qsPanelBottomY, notificationTop);
+            top = (int) (isOnKeyguard() ? Math.min(qsPanelBottomY, notificationTop)
+                    : notificationTop);
             bottom = getView().getBottom();
             left = getView().getLeft();
             right = getView().getRight();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt
index 1fe77fd..6e27cae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt
@@ -56,6 +56,7 @@
         // call starts.
         minimumTextWidth = 0
         shouldHideText = false
+        visibility = VISIBLE
         super.setBase(base)
     }
 
@@ -76,6 +77,9 @@
 
         if (desiredTextWidth > enforcedTextWidth) {
             shouldHideText = true
+            // Changing visibility ensures that the content description is not read aloud when the
+            // time isn't displayed.
+            visibility = GONE
             setMeasuredDimension(0, 0)
         } else {
             // It's possible that the current text could fit in a smaller width, but we don't want
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 6d1df5b..e9d256c 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
@@ -49,7 +49,8 @@
     private val systemClock: SystemClock,
     private val activityStarter: ActivityStarter,
     @Main private val mainExecutor: Executor,
-    private val iActivityManager: IActivityManager
+    private val iActivityManager: IActivityManager,
+    private val logger: OngoingCallLogger
 ) : CallbackController<OngoingCallListener> {
 
     /** Null if there's no ongoing call. */
@@ -104,7 +105,7 @@
     /**
      * Sets the chip view that will contain ongoing call information.
      *
-     * Should only be called from [CollapedStatusBarFragment].
+     * Should only be called from [CollapsedStatusBarFragment].
      */
     fun setChipView(chipView: ViewGroup) {
         this.chipView = chipView
@@ -113,6 +114,16 @@
         }
     }
 
+
+    /**
+     * Called when the chip's visibility may have changed.
+     *
+     * Should only be called from [CollapsedStatusBarFragment].
+     */
+    fun notifyChipVisibilityChanged(chipIsVisible: Boolean) {
+        logger.logChipVisibilityChanged(chipIsVisible)
+    }
+
     /**
      * Returns true if there's an active ongoing call that should be displayed in a status bar chip.
      */
@@ -150,6 +161,7 @@
             timeView.start()
 
             currentChipView.setOnClickListener {
+                logger.logChipClicked()
                 activityStarter.postStartActivityDismissingKeyguard(
                         currentOngoingCallInfo.intent, 0,
                         ActivityLaunchAnimator.Controller.fromView(it))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt
new file mode 100644
index 0000000..177f215
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.ongoingcall
+
+import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** A class to log events for the ongoing call chip. */
+@SysUISingleton
+class OngoingCallLogger @Inject constructor(private val logger: UiEventLogger) {
+
+    private var chipIsVisible: Boolean = false
+
+    /** Logs that the ongoing call chip was clicked. */
+    fun logChipClicked() {
+        logger.log(OngoingCallEvents.ONGOING_CALL_CLICKED)
+    }
+
+    /**
+     * If needed, logs that the ongoing call chip's visibility has changed.
+     *
+     * For now, only logs when the chip changes from not visible to visible.
+     */
+    fun logChipVisibilityChanged(chipIsVisible: Boolean) {
+        if (chipIsVisible && chipIsVisible != this.chipIsVisible) {
+            logger.log(OngoingCallEvents.ONGOING_CALL_VISIBLE)
+        }
+        this.chipIsVisible = chipIsVisible
+    }
+
+    @VisibleForTesting
+    enum class OngoingCallEvents(val metricId: Int) : UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "The ongoing call chip became visible")
+        ONGOING_CALL_VISIBLE(813),
+
+        @UiEvent(doc = "The ongoing call chip was clicked")
+        ONGOING_CALL_CLICKED(814);
+
+        override fun getId() = metricId
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 82ad00a..2e75395 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -30,6 +30,8 @@
 import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.AlertingNotificationManager;
@@ -60,9 +62,28 @@
     private final ArrayMap<String, Long> mSnoozedPackages;
     private final AccessibilityManagerWrapper mAccessibilityMgr;
 
+    private final UiEventLogger mUiEventLogger;
+
+    /**
+     * Enum entry for notification peek logged from this class.
+     */
+    enum NotificationPeekEvent implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "Heads-up notification peeked on screen.")
+        NOTIFICATION_PEEK(801);
+
+        private final int mId;
+        NotificationPeekEvent(int id) {
+            mId = id;
+        }
+        @Override public int getId() {
+            return mId;
+        }
+    }
+
     public HeadsUpManager(@NonNull final Context context) {
         mContext = context;
         mAccessibilityMgr = Dependency.get(AccessibilityManagerWrapper.class);
+        mUiEventLogger = Dependency.get(UiEventLogger.class);
         Resources resources = context.getResources();
         mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
         mAutoDismissNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
@@ -130,6 +151,11 @@
         if (entry.isRowPinned() != isPinned) {
             entry.setRowPinned(isPinned);
             updatePinnedMode();
+            if (isPinned && entry.getSbn() != null) {
+                mUiEventLogger.logWithInstanceId(
+                        NotificationPeekEvent.NOTIFICATION_PEEK, entry.getSbn().getUid(),
+                        entry.getSbn().getPackageName(), entry.getSbn().getInstanceId());
+            }
             for (OnHeadsUpChangedListener listener : mListeners) {
                 if (isPinned) {
                     listener.onHeadsUpPinned(entry);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index 3c1e123..865aa23f 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -66,6 +66,13 @@
             "android.theme.customization.accent_color";
     static final String OVERLAY_CATEGORY_SYSTEM_PALETTE =
             "android.theme.customization.system_palette";
+
+    static final String OVERLAY_COLOR_SOURCE = "android.theme.customization.color_source";
+
+    static final String COLOR_SOURCE_PRESET = "preset";
+
+    static final String TIMESTAMP_FIELD = "_applied_timestamp";
+
     @VisibleForTesting
     static final String OVERLAY_CATEGORY_FONT = "android.theme.customization.font";
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 9bdd8c0..195114f 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -15,8 +15,11 @@
  */
 package com.android.systemui.theme;
 
+import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_SOURCE;
+import static com.android.systemui.theme.ThemeOverlayApplier.TIMESTAMP_FIELD;
 
 import android.annotation.Nullable;
 import android.app.WallpaperColors;
@@ -90,12 +93,12 @@
     private final UserManager mUserManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final Executor mBgExecutor;
-    private final SecureSettings mSecureSettings;
+    private SecureSettings mSecureSettings;
     private final Executor mMainExecutor;
     private final Handler mBgHandler;
     private final WallpaperManager mWallpaperManager;
     private final boolean mIsMonetEnabled;
-    private final UserTracker mUserTracker;
+    private UserTracker mUserTracker;
     private DeviceProvisionedController mDeviceProvisionedController;
     private WallpaperColors mSystemColors;
     // If fabricated overlays were already created for the current theme.
@@ -112,6 +115,8 @@
     private boolean mAcceptColorEvents = true;
     // Defers changing themes until Setup Wizard is done.
     private boolean mDeferredThemeEvaluation;
+    // Determines if we should ignore THEME_CUSTOMIZATION_OVERLAY_PACKAGES setting changes.
+    private boolean mSkipSettingChange;
 
     private final DeviceProvisionedListener mDeviceProvisionedListener =
             new DeviceProvisionedListener() {
@@ -162,6 +167,35 @@
                 }
             }
         }
+        // Check if we need to reset to default colors (if a color override was set that is sourced
+        // from the wallpaper)
+        int currentUser = mUserTracker.getUserId();
+        String overlayPackageJson = mSecureSettings.getStringForUser(
+                Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+                currentUser);
+        if (!TextUtils.isEmpty(overlayPackageJson)) {
+            try {
+                JSONObject jsonObject = new JSONObject(overlayPackageJson);
+                if ((jsonObject.has(OVERLAY_CATEGORY_ACCENT_COLOR)
+                        || jsonObject.has(OVERLAY_CATEGORY_SYSTEM_PALETTE))
+                        && !COLOR_SOURCE_PRESET.equals(
+                        jsonObject.optString(OVERLAY_COLOR_SOURCE))) {
+                    mSkipSettingChange = true;
+                    jsonObject.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
+                    jsonObject.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
+                    jsonObject.remove(OVERLAY_COLOR_SOURCE);
+                    jsonObject.put(TIMESTAMP_FIELD, System.currentTimeMillis());
+                    if (DEBUG) {
+                        Log.d(TAG, "Updating theme setting from "
+                                + overlayPackageJson + " to " + jsonObject.toString());
+                    }
+                    mSecureSettings.putString(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+                            jsonObject.toString());
+                }
+            } catch (JSONException e) {
+                Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e);
+            }
+        }
         reevaluateSystemTheme(false /* forceReload */);
     };
 
@@ -232,6 +266,11 @@
                             mDeferredThemeEvaluation = true;
                             return;
                         }
+                        if (mSkipSettingChange) {
+                            if (DEBUG) Log.d(TAG, "Skipping setting change");
+                            mSkipSettingChange = false;
+                            return;
+                        }
                         reevaluateSystemTheme(true /* forceReload */);
                     }
                 },
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 2270f96..7a5ceb5 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactory.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.util.concurrency;
 
+import android.os.Handler;
 import android.os.Looper;
 
 import java.util.concurrent.Executor;
@@ -29,6 +30,14 @@
  */
 public interface ThreadFactory {
     /**
+     * Returns a {@link Handler} running on a named thread.
+     *
+     * The thread is implicitly started and may be left running indefinitely, depending on the
+     * implementation. Assume this is the case and use responsibly.
+     */
+    Handler builderHandlerOnNewThread(String threadName);
+
+    /**
      * Return an {@link java.util.concurrent.Executor} running on a named thread.
      *
      * The thread is implicitly started and may be left running indefinitely, depending on the
@@ -45,6 +54,11 @@
     DelayableExecutor buildDelayableExecutorOnNewThread(String threadName);
 
     /**
+     * Return an {@link DelayableExecutor} running on the given HandlerThread.
+     **/
+    DelayableExecutor buildDelayableExecutorOnHandler(Handler handler);
+
+    /**
      * 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 2d9f2b4..184b831 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ThreadFactoryImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.util.concurrency;
 
+import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
 
@@ -27,16 +28,31 @@
     @Inject
     ThreadFactoryImpl() {}
 
+    @Override
+    public Handler builderHandlerOnNewThread(String threadName) {
+        HandlerThread handlerThread = new HandlerThread(threadName);
+        handlerThread.start();
+        return new Handler(handlerThread.getLooper());
+    }
+
+    @Override
     public Executor buildExecutorOnNewThread(String threadName) {
         return buildDelayableExecutorOnNewThread(threadName);
     }
 
+    @Override
     public DelayableExecutor buildDelayableExecutorOnNewThread(String threadName) {
         HandlerThread handlerThread = new HandlerThread(threadName);
         handlerThread.start();
-        return new ExecutorImpl(handlerThread.getLooper());
+        return buildDelayableExecutorOnLooper(handlerThread.getLooper());
     }
 
+    @Override
+    public DelayableExecutor buildDelayableExecutorOnHandler(Handler handler) {
+        return buildDelayableExecutorOnLooper(handler.getLooper());
+    }
+
+    @Override
     public DelayableExecutor buildDelayableExecutorOnLooper(Looper looper) {
         return new ExecutorImpl(looper);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
index ec62981..b57d937 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
@@ -234,7 +234,9 @@
         mWalletView.show();
         mWalletView.hideErrorMessage();
         int iconSizePx =
-                mContext.getResources().getDimensionPixelSize(R.dimen.wallet_view_header_icon_size);
+                mContext
+                        .getResources()
+                        .getDimensionPixelSize(R.dimen.wallet_screen_header_icon_size);
         GetWalletCardsRequest request =
                 new GetWalletCardsRequest(cardWidthPx, cardHeightPx, iconSizePx, MAX_CARDS);
         mWalletClient.getWalletCards(mExecutor, request, this);
@@ -340,7 +342,11 @@
 
         @Override
         public CharSequence getLabel() {
-            return mWalletCard.getCardLabel();
+            CharSequence label = mWalletCard.getCardLabel();
+            if (label == null) {
+                return "";
+            }
+            return label;
         }
 
         @Override
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 dbb99d3..e42ce6a 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
@@ -134,7 +134,8 @@
         mCardCarouselContainer.setVisibility(VISIBLE);
         mErrorView.setVisibility(GONE);
         mEmptyStateView.setVisibility(GONE);
-        renderHeaderIconAndActionButton(data.get(selectedIndex), isDeviceLocked);
+        mIcon.setImageDrawable(getHeaderIcon(mContext, data.get(selectedIndex)));
+        renderActionButton(data.get(selectedIndex), isDeviceLocked);
         if (shouldAnimate) {
             animateViewsShown(mIcon, mCardLabel, mActionButton);
         }
@@ -221,17 +222,6 @@
         return mCardLabel;
     }
 
-    private void renderHeaderIconAndActionButton(WalletCardViewInfo walletCard, boolean isLocked) {
-        Drawable icon = getHeaderIcon(mContext, walletCard);
-        if (icon != null) {
-            mIcon.setImageDrawable(walletCard.getIcon());
-            mIcon.setVisibility(VISIBLE);
-        } else {
-            mIcon.setVisibility(INVISIBLE);
-        }
-        renderActionButton(walletCard, isLocked);
-    }
-
     @Nullable
     private static Drawable getHeaderIcon(Context context, WalletCardViewInfo walletCard) {
         Drawable icon = walletCard.getIcon();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 8c5f74d..98467d4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -19,30 +19,20 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.smartspace.SmartspaceTarget;
-import android.content.Context;
-import android.content.pm.UserInfo;
 import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
-import android.util.AttributeSet;
 import android.view.View;
 import android.widget.FrameLayout;
 import android.widget.RelativeLayout;
 
-import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.keyguard.clock.ClockManager;
@@ -50,21 +40,14 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.BcSmartspaceDataPlugin;
-import com.android.systemui.plugins.BcSmartspaceDataPlugin.IntentStarter;
 import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
 import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -74,78 +57,54 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.verification.VerificationMode;
 
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
 
     @Mock
+    private KeyguardClockSwitch mView;
+    @Mock
     private StatusBarStateController mStatusBarStateController;
     @Mock
     private SysuiColorExtractor mColorExtractor;
     @Mock
     private ClockManager mClockManager;
     @Mock
-    private KeyguardClockSwitch mView;
-    @Mock
-    private NotificationIconContainer mNotificationIcons;
-    @Mock
-    private ClockPlugin mClockPlugin;
-    @Mock
-    ColorExtractor.GradientColors mGradientColors;
-    @Mock
     KeyguardSliceViewController mKeyguardSliceViewController;
     @Mock
-    Resources mResources;
-    @Mock
     NotificationIconAreaController mNotificationIconAreaController;
     @Mock
     BroadcastDispatcher mBroadcastDispatcher;
     @Mock
-    private FeatureFlags mFeatureFlags;
+    BatteryController mBatteryController;
     @Mock
-    private Executor mExecutor;
+    KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock
+    KeyguardBypassController mBypassController;
+    @Mock
+    LockscreenSmartspaceController mSmartspaceController;
+
+    @Mock
+    Resources mResources;
+    @Mock
+    private ClockPlugin mClockPlugin;
+    @Mock
+    ColorExtractor.GradientColors mGradientColors;
+
+    @Mock
+    private NotificationIconContainer mNotificationIcons;
     @Mock
     private AnimatableClockView mClockView;
     @Mock
     private AnimatableClockView mLargeClockView;
     @Mock
     private FrameLayout mLargeClockFrame;
-    @Mock
-    BatteryController mBatteryController;
-    @Mock
-    ConfigurationController mConfigurationController;
-    @Mock
-    Optional<BcSmartspaceDataPlugin> mOptionalSmartspaceDataProvider;
-    @Mock
-    BcSmartspaceDataPlugin mSmartspaceDataProvider;
-    @Mock
-    SmartspaceView mSmartspaceView;
-    @Mock
-    ActivityStarter mActivityStarter;
-    @Mock
-    FalsingManager mFalsingManager;
-    @Mock
-    KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
-    KeyguardBypassController mBypassController;
-    @Mock
-    Handler mHandler;
-    @Mock
-    UserTracker mUserTracker;
-    @Mock
-    SecureSettings mSecureSettings;
+
+    private final View mFakeSmartspaceView = new View(mContext);
 
     private KeyguardClockSwitchController mController;
     private View mStatusArea;
 
-    private static final int USER_ID = 5;
-    private static final int MANAGED_USER_ID = 15;
-
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -162,9 +121,9 @@
         when(mClockView.getContext()).thenReturn(getContext());
         when(mLargeClockView.getContext()).thenReturn(getContext());
 
-        when(mFeatureFlags.isSmartspaceEnabled()).thenReturn(true);
         when(mView.isAttachedToWindow()).thenReturn(true);
         when(mResources.getString(anyInt())).thenReturn("h:mm");
+        when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
         mController = new KeyguardClockSwitchController(
                 mView,
                 mStatusBarStateController,
@@ -173,28 +132,16 @@
                 mKeyguardSliceViewController,
                 mNotificationIconAreaController,
                 mBroadcastDispatcher,
-                mFeatureFlags,
-                mExecutor,
                 mBatteryController,
-                mConfigurationController,
-                mActivityStarter,
-                mFalsingManager,
                 mKeyguardUpdateMonitor,
                 mBypassController,
-                mHandler,
-                mUserTracker,
-                mSecureSettings,
-                mOptionalSmartspaceDataProvider
-        );
+                mSmartspaceController);
 
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
         when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
 
         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);
     }
 
     @Test
@@ -255,119 +202,34 @@
 
     @Test
     public void testSmartspaceEnabledRemovesKeyguardStatusArea() {
-        when(mFeatureFlags.isSmartspaceEnabled()).thenReturn(true);
+        when(mSmartspaceController.isEnabled()).thenReturn(true);
+        when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
         mController.init();
 
         assertEquals(View.GONE, mStatusArea.getVisibility());
     }
 
     @Test
-    public void testSmartspaceEnabledNoDataProviderShowsKeyguardStatusArea() {
-        when(mFeatureFlags.isSmartspaceEnabled()).thenReturn(true);
-        when(mOptionalSmartspaceDataProvider.isPresent()).thenReturn(false);
-        mController.init();
-
-        assertEquals(View.VISIBLE, mStatusArea.getVisibility());
-    }
-
-    @Test
     public void testSmartspaceDisabledShowsKeyguardStatusArea() {
-        when(mFeatureFlags.isSmartspaceEnabled()).thenReturn(false);
+        when(mSmartspaceController.isEnabled()).thenReturn(false);
         mController.init();
 
         assertEquals(View.VISIBLE, mStatusArea.getVisibility());
     }
 
     @Test
-    public void testThemeChangeNotifiesSmartspace() {
+    public void testDetachRemovesSmartspaceView() {
+        when(mSmartspaceController.isEnabled()).thenReturn(true);
+        when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
         mController.init();
-        verify(mSmartspaceView).setPrimaryTextColor(anyInt());
+        verify(mView).addView(eq(mFakeSmartspaceView), anyInt(), any());
 
-        mController.getConfigurationListener().onThemeChanged();
-        verify(mSmartspaceView, times(2)).setPrimaryTextColor(anyInt());
-    }
+        ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
+                ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+        verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
 
-    @Test
-    public void doNotFilterRegularTarget() {
-        setupPrimaryAndManagedUser();
-        mController.init();
-
-        when(mSecureSettings.getIntForUser(anyString(), anyInt(), eq(USER_ID))).thenReturn(0);
-        when(mSecureSettings.getIntForUser(anyString(), anyInt(), eq(MANAGED_USER_ID)))
-                .thenReturn(0);
-
-        mController.getSettingsObserver().onChange(true, null);
-
-        SmartspaceTarget t = mock(SmartspaceTarget.class);
-        when(t.isSensitive()).thenReturn(false);
-        when(t.getUserHandle()).thenReturn(new UserHandle(USER_ID));
-        assertEquals(false, mController.filterSmartspaceTarget(t));
-
-        reset(t);
-        when(t.isSensitive()).thenReturn(false);
-        when(t.getUserHandle()).thenReturn(new UserHandle(MANAGED_USER_ID));
-        assertEquals(false, mController.filterSmartspaceTarget(t));
-    }
-
-    @Test
-    public void filterAllSensitiveTargetsAllUsers() {
-        setupPrimaryAndManagedUser();
-        mController.init();
-
-        when(mSecureSettings.getIntForUser(anyString(), anyInt(), eq(USER_ID))).thenReturn(0);
-        when(mSecureSettings.getIntForUser(anyString(), anyInt(), eq(MANAGED_USER_ID)))
-                .thenReturn(0);
-
-        mController.getSettingsObserver().onChange(true, null);
-
-        SmartspaceTarget t = mock(SmartspaceTarget.class);
-        when(t.isSensitive()).thenReturn(true);
-        when(t.getUserHandle()).thenReturn(new UserHandle(USER_ID));
-        assertEquals(true, mController.filterSmartspaceTarget(t));
-
-        reset(t);
-        when(t.isSensitive()).thenReturn(true);
-        when(t.getUserHandle()).thenReturn(new UserHandle(MANAGED_USER_ID));
-        assertEquals(true, mController.filterSmartspaceTarget(t));
-    }
-
-    @Test
-    public void filterSensitiveManagedUserTargets() {
-        setupPrimaryAndManagedUser();
-        mController.init();
-
-        when(mSecureSettings.getIntForUser(anyString(), anyInt(), eq(USER_ID))).thenReturn(1);
-        when(mSecureSettings.getIntForUser(anyString(), anyInt(), eq(MANAGED_USER_ID)))
-                .thenReturn(0);
-
-        mController.getSettingsObserver().onChange(true, null);
-
-        SmartspaceTarget t = mock(SmartspaceTarget.class);
-        when(t.isSensitive()).thenReturn(true);
-        when(t.getUserHandle()).thenReturn(new UserHandle(USER_ID));
-        assertEquals(false, mController.filterSmartspaceTarget(t));
-
-        reset(t);
-        when(t.isSensitive()).thenReturn(true);
-        when(t.getUserHandle()).thenReturn(new UserHandle(MANAGED_USER_ID));
-        assertEquals(true, mController.filterSmartspaceTarget(t));
-    }
-
-    private void setupPrimaryAndManagedUser() {
-        UserInfo userInfo = mock(UserInfo.class);
-        when(userInfo.isManagedProfile()).thenReturn(true);
-        when(userInfo.getUserHandle()).thenReturn(new UserHandle(MANAGED_USER_ID));
-        when(mUserTracker.getUserProfiles()).thenReturn(List.of(userInfo));
-
-        when(mUserTracker.getUserId()).thenReturn(USER_ID);
-        when(mUserTracker.getUserHandle()).thenReturn(new UserHandle(USER_ID));
-    }
-
-    private void setupPrimaryAndNoManagedUser() {
-        when(mUserTracker.getUserProfiles()).thenReturn(Collections.emptyList());
-
-        when(mUserTracker.getUserId()).thenReturn(USER_ID);
-        when(mUserTracker.getUserHandle()).thenReturn(new UserHandle(USER_ID));
+        listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
+        verify(mView).removeView(mFakeSmartspaceView);
     }
 
     private void verifyAttachment(VerificationMode times) {
@@ -377,25 +239,4 @@
                 any(ColorExtractor.OnColorsChangedListener.class));
         verify(mView, times).updateColors(mGradientColors);
     }
-
-    private static class SmartspaceView extends View
-            implements BcSmartspaceDataPlugin.SmartspaceView {
-        SmartspaceView(Context context, AttributeSet attrs) {
-            super(context, attrs);
-        }
-
-        public void registerDataProvider(BcSmartspaceDataPlugin plugin) { }
-
-        public void setPrimaryTextColor(int color) { }
-
-        public void setDozeAmount(float amount) { }
-
-        public void setIntentStarter(IntentStarter intentStarter) { }
-
-        public void setFalsingManager(FalsingManager falsingManager) { }
-
-        public void setDnd(@Nullable Drawable dndIcon, @Nullable String description) { }
-
-        public void setNextAlarm(@Nullable Drawable 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 b9ce203..ed5cbe2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -67,7 +67,6 @@
 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;
@@ -87,13 +86,12 @@
 
     private static final Rect ZERO_RECT = new Rect();
 
-    private TestableLooper mTestableLooper;
     private ScreenDecorations mScreenDecorations;
     private WindowManager mWindowManager;
     private DisplayManager mDisplayManager;
     private SecureSettings mSecureSettings;
-    private Handler mMainHandler;
-    private ThreadFactory mThreadFactory;
+    private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+    private FakeThreadFactory mThreadFactory;
     @Mock
     private TunerService mTunerService;
     @Mock
@@ -107,10 +105,10 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        mTestableLooper = TestableLooper.get(this);
-        mMainHandler = new Handler(mTestableLooper.getLooper());
+        Handler mainHandler = new Handler(TestableLooper.get(this).getLooper());
         mSecureSettings = new FakeSettings();
-        mThreadFactory = new FakeThreadFactory(new FakeExecutor(new FakeSystemClock()));
+        mThreadFactory = new FakeThreadFactory(mExecutor);
+        mThreadFactory.setHandler(mainHandler);
 
         mWindowManager = mock(WindowManager.class);
         WindowMetrics metrics = mContext.getSystemService(WindowManager.class)
@@ -124,30 +122,25 @@
         when(mDisplayManager.getDisplay(anyInt())).thenReturn(display);
         mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
 
-        mScreenDecorations = spy(new ScreenDecorations(mContext, mMainHandler, mSecureSettings,
+        mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
                 mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController,
                 mThreadFactory) {
             @Override
             public void start() {
                 super.start();
-                mTestableLooper.processAllMessages();
-            }
-
-            @Override
-            Handler startHandlerThread() {
-                return new Handler(mTestableLooper.getLooper());
+                mExecutor.runAllReady();
             }
 
             @Override
             protected void onConfigurationChanged(Configuration newConfig) {
                 super.onConfigurationChanged(newConfig);
-                mTestableLooper.processAllMessages();
+                mExecutor.runAllReady();
             }
 
             @Override
             public void onTuningChanged(String key, String newValue) {
                 super.onTuningChanged(key, newValue);
-                mTestableLooper.processAllMessages();
+                mExecutor.runAllReady();
             }
         });
         reset(mTunerService);
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 dd3a192..e62fa91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -289,6 +289,7 @@
 
         captor.value.onLongClick(holder.player)
         verify(mediaViewController, never()).openGuts()
+        verify(mediaViewController).closeGuts(false)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index 406f40c..a974421 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -73,11 +74,15 @@
     @Mock
     private lateinit var mediaCarouselController: MediaCarouselController
     @Mock
+    private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
+    @Mock
     private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
     @Mock
     private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
     @Captor
     private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
+    @Captor
+    private lateinit var statusBarCallback: ArgumentCaptor<(StatusBarStateController.StateListener)>
     @JvmField
     @Rule
     val mockito = MockitoJUnit.rule()
@@ -96,10 +101,13 @@
                 wakefulnessLifecycle,
                 statusBarKeyguardViewManager)
         verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
+        verify(statusBarStateController).addCallback(statusBarCallback.capture())
         setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN)
         setupHost(qsHost, MediaHierarchyManager.LOCATION_QS)
         setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS)
         `when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+        `when`(mediaCarouselController.mediaCarouselScrollHandler)
+                .thenReturn(mediaCarouselScrollHandler)
         // We'll use the viewmanager to verify a few calls below, let's reset this.
         clearInvocations(mediaCarouselController)
     }
@@ -153,4 +161,11 @@
 
         verify(mediaCarouselController).closeGuts()
     }
+
+    @Test
+    fun testCloseGutsWhenDoze() {
+        statusBarCallback.value.onDozingChanged(true)
+
+        verify(mediaCarouselController).closeGuts()
+    }
 }
\ No newline at end of file
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 5c70a4ef2..7172307 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.people;
 
 import static android.app.Notification.CATEGORY_MISSED_CALL;
+import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
 import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
 import static android.app.people.ConversationStatus.ACTIVITY_GAME;
 import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
@@ -25,6 +26,7 @@
 import static android.app.people.PeopleSpaceTile.SHOW_CONTACTS;
 import static android.app.people.PeopleSpaceTile.SHOW_IMPORTANT_CONVERSATIONS;
 import static android.app.people.PeopleSpaceTile.SHOW_STARRED_CONTACTS;
+import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT;
 import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
 
 import static com.android.systemui.people.PeopleSpaceUtils.STARRED_CONTACT;
@@ -127,6 +129,9 @@
                     .build();
 
     @Mock
+    private Icon mIcon;
+
+    @Mock
     private Context mMockContext;
     @Mock
     private PackageManager mPackageManager;
@@ -169,7 +174,7 @@
 
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_large));
-        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+        mOptions.putInt(OPTION_APPWIDGET_MAX_HEIGHT,
                 getSizeInDp(R.dimen.required_height_for_large));
         RemoteViews largeView = getPeopleTileViewHelper(
                 PERSON_TILE_WITHOUT_NOTIFICATION, mOptions).getViews();
@@ -218,7 +223,7 @@
 
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_large));
-        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+        mOptions.putInt(OPTION_APPWIDGET_MAX_HEIGHT,
                 getSizeInDp(R.dimen.required_height_for_large));
         RemoteViews largeView = getPeopleTileViewHelper(
                 tileWithLastInteraction, mOptions).getViews();
@@ -278,7 +283,7 @@
 
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_large));
-        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+        mOptions.putInt(OPTION_APPWIDGET_MAX_HEIGHT,
                 getSizeInDp(R.dimen.required_height_for_large));
         RemoteViews largeView = getPeopleTileViewHelper(
                 tileWithAvailabilityAndNewStory, mOptions).getViews();
@@ -340,7 +345,7 @@
 
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_large));
-        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+        mOptions.putInt(OPTION_APPWIDGET_MAX_HEIGHT,
                 getSizeInDp(R.dimen.required_height_for_large));
         RemoteViews largeView = getPeopleTileViewHelper(
                 tileWithStatusTemplate, mOptions).getViews();
@@ -376,6 +381,8 @@
         assertEquals(name.getText(), NAME);
         assertEquals(View.GONE, result.findViewById(R.id.subtext).getVisibility());
         assertEquals(View.VISIBLE, result.findViewById(R.id.predefined_icon).getVisibility());
+        assertEquals(View.GONE, result.findViewById(R.id.scrim_layout).getVisibility());
+        assertEquals(View.GONE, result.findViewById(R.id.image).getVisibility());
         // Has availability.
         assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
         // Has person icon.
@@ -403,7 +410,7 @@
 
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_large));
-        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+        mOptions.putInt(OPTION_APPWIDGET_MAX_HEIGHT,
                 getSizeInDp(R.dimen.required_height_for_large));
         RemoteViews largeView = getPeopleTileViewHelper(
                 tileWithStatusTemplate, mOptions).getViews();
@@ -413,6 +420,8 @@
         assertEquals(name.getText(), NAME);
         assertEquals(View.GONE, largeResult.findViewById(R.id.subtext).getVisibility());
         assertEquals(View.VISIBLE, largeResult.findViewById(R.id.predefined_icon).getVisibility());
+        assertEquals(View.GONE, largeResult.findViewById(R.id.scrim_layout).getVisibility());
+        assertEquals(View.GONE, largeResult.findViewById(R.id.image).getVisibility());
         // Has availability.
         assertEquals(View.VISIBLE, largeResult.findViewById(R.id.availability).getVisibility());
         // Has person icon.
@@ -426,6 +435,55 @@
     }
 
     @Test
+    public void testCreateRemoteViewsWithStatusTemplateWithImageOnMediumAndLarge() {
+        PeopleSpaceTile tileWithIconInStatusTemplate =
+                PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+                        Arrays.asList(new ConversationStatus.Builder(PERSON_TILE.getId(),
+                                ACTIVITY_ANNIVERSARY).setDescription("Anniversary").setAvailability(
+                                AVAILABILITY_AVAILABLE).setIcon(mIcon).build())).build();
+        RemoteViews views = getPeopleTileViewHelper(
+                tileWithIconInStatusTemplate, mOptions).getViews();
+        View result = views.apply(mContext, null);
+
+        assertEquals(View.GONE, result.findViewById(R.id.subtext).getVisibility());
+        assertEquals(View.VISIBLE, result.findViewById(R.id.predefined_icon).getVisibility());
+        assertEquals(View.VISIBLE, result.findViewById(R.id.scrim_layout).getVisibility());
+        assertEquals(View.GONE, result.findViewById(R.id.image).getVisibility());
+        // Has availability.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
+        // Has person icon.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+        // Has status.
+        TextView statusContent = (TextView) result.findViewById(R.id.name);
+        assertEquals(statusContent.getText(), "Anniversary");
+        assertThat(statusContent.getMaxLines()).isEqualTo(1);
+
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_width_for_large));
+        mOptions.putInt(OPTION_APPWIDGET_MAX_HEIGHT,
+                getSizeInDp(R.dimen.required_height_for_large));
+        RemoteViews largeView = getPeopleTileViewHelper(
+                tileWithIconInStatusTemplate, mOptions).getViews();
+        View largeResult = largeView.apply(mContext, null);
+
+        assertEquals(View.GONE, largeResult.findViewById(R.id.subtext).getVisibility());
+        assertEquals(View.GONE, largeResult.findViewById(R.id.name).getVisibility());
+        assertEquals(View.VISIBLE, largeResult.findViewById(R.id.predefined_icon).getVisibility());
+        assertEquals(View.VISIBLE, largeResult.findViewById(R.id.scrim_layout).getVisibility());
+        assertEquals(View.GONE, largeResult.findViewById(R.id.image).getVisibility());
+        // Has availability.
+        assertEquals(View.VISIBLE, largeResult.findViewById(R.id.availability).getVisibility());
+        // Has person icon.
+        View personIcon = largeResult.findViewById(R.id.person_icon);
+        assertEquals(View.VISIBLE, personIcon.getVisibility());
+        // Has status content.
+        statusContent = (TextView) largeResult.findViewById(R.id.text_content);
+        assertEquals(View.VISIBLE, statusContent.getVisibility());
+        assertEquals(statusContent.getText(), "Anniversary");
+        assertThat(statusContent.getMaxLines()).isEqualTo(2);
+    }
+
+    @Test
     public void testCreateRemoteViewsWithPackageSuspended() {
         PeopleSpaceTile tile = PERSON_TILE.toBuilder()
                 .setIsPackageSuspended(true)
@@ -434,7 +492,7 @@
                 tile, mOptions).getViews();
         View result = views.apply(mContext, null);
 
-        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_suppressed_layout);
     }
 
     @Test
@@ -446,7 +504,7 @@
                 tile, mOptions).getViews();
         View result = views.apply(mContext, null);
 
-        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_work_profile_quiet_layout);
     }
 
     @Test
@@ -458,7 +516,7 @@
                 tileWithDndBlocking, mOptions).getViews();
         View result = views.apply(mContext, null);
 
-        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_suppressed_layout);
 
         tileWithDndBlocking = PERSON_TILE.toBuilder()
                 .setNotificationPolicyState(BLOCK_CONVERSATIONS)
@@ -468,7 +526,7 @@
                 tileWithDndBlocking, mOptions).getViews();
         result = views.apply(mContext, null);
 
-        assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+        assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_suppressed_layout);
 
         tileWithDndBlocking = PERSON_TILE.toBuilder()
                 .setNotificationPolicyState(SHOW_IMPORTANT_CONVERSATIONS)
@@ -477,7 +535,7 @@
                 tileWithDndBlocking, mOptions).getViews();
         result = views.apply(mContext, null);
 
-        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_suppressed_layout);
 
         tileWithDndBlocking = PERSON_TILE.toBuilder()
                 .setNotificationPolicyState(SHOW_IMPORTANT_CONVERSATIONS)
@@ -487,7 +545,7 @@
                 tileWithDndBlocking, mOptions).getViews();
         result = views.apply(mContext, null);
 
-        assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+        assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_suppressed_layout);
 
         tileWithDndBlocking = PERSON_TILE.toBuilder()
                 .setNotificationPolicyState(SHOW_STARRED_CONTACTS)
@@ -497,7 +555,7 @@
                 tileWithDndBlocking, mOptions).getViews();
         result = views.apply(mContext, null);
 
-        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_suppressed_layout);
 
         tileWithDndBlocking = PERSON_TILE.toBuilder()
                 .setNotificationPolicyState(SHOW_STARRED_CONTACTS)
@@ -507,7 +565,7 @@
                 tileWithDndBlocking, mOptions).getViews();
         result = views.apply(mContext, null);
 
-        assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+        assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_suppressed_layout);
 
         tileWithDndBlocking = PERSON_TILE.toBuilder()
                 .setNotificationPolicyState(SHOW_CONTACTS)
@@ -517,7 +575,7 @@
                 tileWithDndBlocking, mOptions).getViews();
         result = views.apply(mContext, null);
 
-        assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+        assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_suppressed_layout);
 
         tileWithDndBlocking = PERSON_TILE.toBuilder()
                 .setNotificationPolicyState(SHOW_CONTACTS)
@@ -527,7 +585,7 @@
                 tileWithDndBlocking, mOptions).getViews();
         result = views.apply(mContext, null);
 
-        assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+        assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_suppressed_layout);
 
         tileWithDndBlocking = PERSON_TILE.toBuilder()
                 .setNotificationPolicyState(SHOW_CONTACTS)
@@ -536,7 +594,7 @@
                 tileWithDndBlocking, mOptions).getViews();
         result = views.apply(mContext, null);
 
-        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_suppressed_layout);
     }
 
     @Test
@@ -581,7 +639,7 @@
 
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_large));
-        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+        mOptions.putInt(OPTION_APPWIDGET_MAX_HEIGHT,
                 getSizeInDp(R.dimen.required_height_for_large));
         RemoteViews largeView = getPeopleTileViewHelper(
                 tileWithMissedCallNotification, mOptions).getViews();
@@ -617,6 +675,7 @@
         assertEquals(name.getText(), NAME);
         assertEquals(View.GONE, result.findViewById(R.id.subtext).getVisibility());
         assertEquals(View.GONE, result.findViewById(R.id.predefined_icon).getVisibility());
+        assertEquals(View.GONE, result.findViewById(R.id.scrim_layout).getVisibility());
         // Has availability.
         assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
         // Has person icon.
@@ -649,7 +708,7 @@
 
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_large));
-        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+        mOptions.putInt(OPTION_APPWIDGET_MAX_HEIGHT,
                 getSizeInDp(R.dimen.required_height_for_large));
         RemoteViews largeView = getPeopleTileViewHelper(
                 tileWithStatusAndNotification, mOptions).getViews();
@@ -659,6 +718,7 @@
         assertEquals(name.getText(), NAME);
         assertEquals(View.GONE, largeResult.findViewById(R.id.subtext).getVisibility());
         assertEquals(View.GONE, largeResult.findViewById(R.id.predefined_icon).getVisibility());
+        assertEquals(View.GONE, largeResult.findViewById(R.id.scrim_layout).getVisibility());
         // Has availability.
         assertEquals(View.VISIBLE, largeResult.findViewById(R.id.availability).getVisibility());
         // Has person icon.
@@ -725,7 +785,7 @@
 
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_large));
-        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+        mOptions.putInt(OPTION_APPWIDGET_MAX_HEIGHT,
                 getSizeInDp(R.dimen.required_height_for_large));
         RemoteViews largeView = getPeopleTileViewHelper(
                 tileWithStatusAndNotification, mOptions).getViews();
@@ -802,7 +862,7 @@
 
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_large));
-        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+        mOptions.putInt(OPTION_APPWIDGET_MAX_HEIGHT,
                 getSizeInDp(R.dimen.required_height_for_large));
         RemoteViews largeView = getPeopleTileViewHelper(
                 tileWithStatusAndNotification, mOptions).getViews();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
index c050b62..ba2b37c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
@@ -30,6 +30,7 @@
 import android.testing.ViewUtils;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.FrameLayout;
 
 import androidx.test.filters.SmallTest;
 
@@ -58,24 +59,26 @@
     private DetailAdapter mMockDetailAdapter;
     private TestableLooper mTestableLooper;
     private UiEventLoggerFake mUiEventLogger;
+    private FrameLayout mParent;
 
     @Before
     public void setup() throws Exception {
         mTestableLooper = TestableLooper.get(this);
         mUiEventLogger = QSEvents.INSTANCE.setLoggerForTesting();
 
-        mTestableLooper.runWithLooper(() -> {
-            mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
-            mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
-            mQsDetail = (QSDetail) LayoutInflater.from(mContext).inflate(R.layout.qs_detail, null);
-            mQsPanelController = mock(QSPanelController.class);
-            mQuickHeader = mock(QuickStatusBarHeader.class);
-            mQsDetail.setQsPanel(mQsPanelController, mQuickHeader, mock(QSFooter.class));
+        mParent = new FrameLayout(mContext);
+        mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
+        mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
+        LayoutInflater.from(mContext).inflate(R.layout.qs_detail, mParent);
+        mQsDetail = (QSDetail) mParent.getChildAt(0);
 
-            mMockDetailAdapter = mock(DetailAdapter.class);
-            when(mMockDetailAdapter.createDetailView(any(), any(), any()))
-                    .thenReturn(mock(View.class));
-        });
+        mQsPanelController = mock(QSPanelController.class);
+        mQuickHeader = mock(QuickStatusBarHeader.class);
+        mQsDetail.setQsPanel(mQsPanelController, mQuickHeader, mock(QSFooter.class));
+
+        mMockDetailAdapter = mock(DetailAdapter.class);
+        when(mMockDetailAdapter.createDetailView(any(), any(), any()))
+                .thenReturn(new View(mContext));
 
         // Only detail in use is the user detail
         when(mMockDetailAdapter.openDetailEvent())
@@ -84,16 +87,18 @@
                 .thenReturn(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE);
         when(mMockDetailAdapter.moreSettingsEvent())
                 .thenReturn(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS);
+        ViewUtils.attachView(mParent);
     }
 
     @After
     public void tearDown() {
         QSEvents.INSTANCE.resetLogger();
+        mTestableLooper.processAllMessages();
+        ViewUtils.detachView(mParent);
     }
 
     @Test
     public void testShowDetail_Metrics() {
-        ViewUtils.attachView(mQsDetail);
         mTestableLooper.processAllMessages();
 
         mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
@@ -107,14 +112,10 @@
 
         assertEquals(1, mUiEventLogger.numLogs());
         assertEquals(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE.getId(), mUiEventLogger.eventId(0));
-
-        ViewUtils.detachView(mQsDetail);
-        mTestableLooper.processAllMessages();
     }
 
     @Test
     public void testMoreSettingsButton() {
-        ViewUtils.attachView(mQsDetail);
         mTestableLooper.processAllMessages();
 
         mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
@@ -127,9 +128,6 @@
         assertEquals(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS.getId(), mUiEventLogger.eventId(0));
 
         verify(mActivityStarter).postStartActivityDismissingKeyguard(any(), anyInt());
-
-        ViewUtils.detachView(mQsDetail);
-        mTestableLooper.processAllMessages();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
new file mode 100644
index 0000000..5366858
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -0,0 +1,518 @@
+/*
+ * 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.statusbar.lockscreen
+
+
+import android.app.smartspace.SmartspaceManager
+import android.app.smartspace.SmartspaceSession
+import android.app.smartspace.SmartspaceSession.OnTargetsAvailableListener
+import android.app.smartspace.SmartspaceTarget
+import android.content.ComponentName
+import android.content.ContentResolver
+import android.content.pm.UserInfo
+import android.database.ContentObserver
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.BcSmartspaceDataPlugin
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.android.systemui.util.concurrency.FakeExecution
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.Optional
+
+@SmallTest
+class LockscreenSmartspaceControllerTest : SysuiTestCase() {
+    @Mock
+    private lateinit var featureFlags: FeatureFlags
+    @Mock
+    private lateinit var smartspaceManager: SmartspaceManager
+    @Mock
+    private lateinit var smartspaceSession: SmartspaceSession
+    @Mock
+    private lateinit var activityStarter: ActivityStarter
+    @Mock
+    private lateinit var falsingManager: FalsingManager
+    @Mock
+    private lateinit var secureSettings: SecureSettings
+    @Mock
+    private lateinit var userTracker: UserTracker
+    @Mock
+    private lateinit var contentResolver: ContentResolver
+    @Mock
+    private lateinit var configurationController: ConfigurationController
+    @Mock
+    private lateinit var statusBarStateController: StatusBarStateController
+    @Mock
+    private lateinit var handler: Handler
+
+    @Mock
+    private lateinit var plugin: BcSmartspaceDataPlugin
+    @Mock
+    private lateinit var controllerListener: SmartspaceTargetListener
+
+    @Captor
+    private lateinit var sessionListenerCaptor: ArgumentCaptor<OnTargetsAvailableListener>
+    @Captor
+    private lateinit var userTrackerCaptor: ArgumentCaptor<UserTracker.Callback>
+    @Captor
+    private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
+    @Captor
+    private lateinit var configChangeListenerCaptor: ArgumentCaptor<ConfigurationListener>
+    @Captor
+    private lateinit var statusBarStateListenerCaptor: ArgumentCaptor<StateListener>
+
+    private lateinit var sessionListener: OnTargetsAvailableListener
+    private lateinit var userListener: UserTracker.Callback
+    private lateinit var settingsObserver: ContentObserver
+    private lateinit var configChangeListener: ConfigurationListener
+    private lateinit var statusBarStateListener: StateListener
+
+    private val clock = FakeSystemClock()
+    private val executor = FakeExecutor(clock)
+    private val execution = FakeExecution()
+    private val fakeParent = FrameLayout(context)
+    private val fakePrivateLockscreenSettingUri = Uri.Builder().appendPath("test").build()
+
+    private val userHandlePrimary: UserHandle = UserHandle(0)
+    private val userHandleManaged: UserHandle = UserHandle(2)
+    private val userHandleSecondary: UserHandle = UserHandle(3)
+
+    private val userList = listOf(
+            mockUserInfo(userHandlePrimary, isManagedProfile = false),
+            mockUserInfo(userHandleManaged, isManagedProfile = true),
+            mockUserInfo(userHandleSecondary, isManagedProfile = false)
+    )
+
+    private lateinit var controller: LockscreenSmartspaceController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        `when`(featureFlags.isSmartspaceEnabled).thenReturn(true)
+
+        `when`(secureSettings.getUriFor(PRIVATE_LOCKSCREEN_SETTING))
+                .thenReturn(fakePrivateLockscreenSettingUri)
+        `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(smartspaceSession)
+        `when`(plugin.getView(any())).thenReturn(fakeSmartspaceView)
+        `when`(userTracker.userProfiles).thenReturn(userList)
+        `when`(statusBarStateController.dozeAmount).thenReturn(0.5f)
+
+        setActiveUser(userHandlePrimary)
+        setAllowPrivateNotifications(userHandlePrimary, true)
+        setAllowPrivateNotifications(userHandleManaged, true)
+        setAllowPrivateNotifications(userHandleSecondary, true)
+
+        controller = LockscreenSmartspaceController(
+                context,
+                featureFlags,
+                smartspaceManager,
+                activityStarter,
+                falsingManager,
+                secureSettings,
+                userTracker,
+                contentResolver,
+                configurationController,
+                statusBarStateController,
+                execution,
+                executor,
+                handler,
+                Optional.of(plugin)
+                )
+    }
+
+    @Test(expected = RuntimeException::class)
+    fun testThrowsIfFlagIsDisabled() {
+        // GIVEN the feature flag is disabled
+        `when`(featureFlags.isSmartspaceEnabled).thenReturn(false)
+
+        // WHEN we try to build the view
+        controller.buildAndConnectView(fakeParent)
+
+        // THEN an exception is thrown
+    }
+
+    @Test
+    fun testListenersAreRegistered() {
+        // GIVEN a listener is added after a session is created
+        connectSession()
+
+        // WHEN a listener is registered
+        controller.addListener(controllerListener)
+
+        // THEN the listener is registered to the underlying plugin
+        verify(plugin).registerListener(controllerListener)
+    }
+
+    @Test
+    fun testEarlyRegisteredListenersAreAttachedAfterConnected() {
+        // GIVEN a listener that is registered before the session is created
+        controller.addListener(controllerListener)
+
+        // WHEN the session is created
+        connectSession()
+
+        // THEN the listener is subsequently registered
+        verify(plugin).registerListener(controllerListener)
+    }
+
+    @Test
+    fun testEmptyListIsEmittedAfterDisconnect() {
+        // GIVEN a registered listener on an active session
+        connectSession()
+        clearInvocations(plugin)
+
+        // WHEN the session is closed
+        controller.disconnect()
+
+        // THEN the listener receives an empty list of targets
+        verify(plugin).onTargetsAvailable(emptyList())
+    }
+
+    @Test
+    fun testUserChangeReloadsSmartspace() {
+        // GIVEN a connected smartspace session
+        connectSession()
+
+        // WHEN the active user changes
+        userListener.onUserChanged(-1, context)
+
+        // THEN we request a new smartspace update
+        verify(smartspaceSession).requestSmartspaceUpdate()
+    }
+
+    @Test
+    fun testSettingsChangeReloadsSmartspace() {
+        // GIVEN a connected smartspace session
+        connectSession()
+
+        // WHEN the lockscreen privacy setting changes
+        settingsObserver.onChange(true, null)
+
+        // THEN we request a new smartspace update
+        verify(smartspaceSession).requestSmartspaceUpdate()
+    }
+
+    @Test
+    fun testThemeChangeUpdatesTextColor() {
+        // GIVEN a connected smartspace session
+        connectSession()
+
+        // WHEN the theme changes
+        configChangeListener.onThemeChanged()
+
+        // We update the new text color to match the wallpaper color
+        verify(fakeSmartspaceView).setPrimaryTextColor(anyInt())
+    }
+
+    @Test
+    fun testDozeAmountChangeUpdatesView() {
+        // GIVEN a connected smartspace session
+        connectSession()
+
+        // WHEN the doze amount changes
+        statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f)
+
+        // We pass that along to the view
+        verify(fakeSmartspaceView).setDozeAmount(0.7f)
+    }
+
+    @Test
+    fun testSensitiveTargetsAreNotFilteredIfAllowed() {
+        // GIVEN the active and managed users allow sensitive content
+        connectSession()
+
+        // WHEN we receive a list of targets
+        val targets = listOf(
+                makeTarget(1, userHandlePrimary, isSensitive = true),
+                makeTarget(2, userHandleManaged, isSensitive = true),
+                makeTarget(3, userHandlePrimary, isSensitive = true)
+        )
+        sessionListener.onTargetsAvailable(targets)
+
+        // THEN all sensitive content is still shown
+        verify(plugin).onTargetsAvailable(eq(targets))
+    }
+
+    @Test
+    fun testNonSensitiveTargetsAreNeverFiltered() {
+        // GIVEN the active user doesn't allow sensitive lockscreen content
+        setAllowPrivateNotifications(userHandlePrimary, false)
+        connectSession()
+
+        // WHEN we receive a list of targets
+        val targets = listOf(
+                makeTarget(1, userHandlePrimary),
+                makeTarget(2, userHandlePrimary),
+                makeTarget(3, userHandlePrimary)
+        )
+        sessionListener.onTargetsAvailable(targets)
+
+        // THEN all non-sensitive content is still shown
+        verify(plugin).onTargetsAvailable(eq(targets))
+    }
+
+    @Test
+    fun testSensitiveTargetsAreFilteredOutForAppropriateUsers() {
+        // GIVEN the active and managed users don't allow sensitive lockscreen content
+        setAllowPrivateNotifications(userHandlePrimary, false)
+        setAllowPrivateNotifications(userHandleManaged, false)
+        connectSession()
+
+        // WHEN we receive a list of targets
+        val targets = listOf(
+                makeTarget(0, userHandlePrimary),
+                makeTarget(1, userHandlePrimary, isSensitive = true),
+                makeTarget(2, userHandleManaged, isSensitive = true),
+                makeTarget(3, userHandleManaged),
+                makeTarget(4, userHandlePrimary, isSensitive = true),
+                makeTarget(5, userHandlePrimary),
+                makeTarget(6, userHandleSecondary, isSensitive = true)
+        )
+        sessionListener.onTargetsAvailable(targets)
+
+        // THEN only non-sensitive content from those accounts is shown
+        verify(plugin).onTargetsAvailable(eq(listOf(
+                targets[0],
+                targets[3],
+                targets[5]
+        )))
+    }
+
+    @Test
+    fun testSettingsAreReloaded() {
+        // GIVEN a connected session where the privacy settings later flip to false
+        connectSession()
+        setAllowPrivateNotifications(userHandlePrimary, false)
+        setAllowPrivateNotifications(userHandleManaged, false)
+        settingsObserver.onChange(true, fakePrivateLockscreenSettingUri)
+
+        // WHEN we receive a new list of targets
+        val targets = listOf(
+                makeTarget(1, userHandlePrimary, isSensitive = true),
+                makeTarget(2, userHandleManaged, isSensitive = true),
+                makeTarget(4, userHandlePrimary, isSensitive = true)
+        )
+        sessionListener.onTargetsAvailable(targets)
+
+        // THEN we filter based on the new settings values
+        verify(plugin).onTargetsAvailable(emptyList())
+    }
+
+    @Test
+    fun testRecognizeSwitchToSecondaryUser() {
+        // GIVEN an inactive secondary user that doesn't allow sensitive content
+        setAllowPrivateNotifications(userHandleSecondary, false)
+        connectSession()
+
+        // WHEN the secondary user becomes the active user
+        setActiveUser(userHandleSecondary)
+        userListener.onUserChanged(userHandleSecondary.identifier, context)
+
+        // WHEN we receive a new list of targets
+        val targets = listOf(
+                makeTarget(0, userHandlePrimary),
+                makeTarget(1, userHandleSecondary),
+                makeTarget(2, userHandleSecondary, isSensitive = true),
+                makeTarget(3, userHandleManaged),
+                makeTarget(4, userHandleSecondary),
+                makeTarget(5, userHandleManaged),
+                makeTarget(6, userHandlePrimary)
+        )
+        sessionListener.onTargetsAvailable(targets)
+
+        // THEN only non-sensitive content from the secondary user is shown
+        verify(plugin).onTargetsAvailable(listOf(
+                targets[1],
+                targets[4]
+        ))
+    }
+
+    @Test
+    fun testUnregisterListenersOnCleanup() {
+        // GIVEN a connected session
+        connectSession()
+
+        // WHEN we are told to cleanup
+        controller.disconnect()
+
+        // THEN we disconnect from the session and unregister any listeners
+        verify(smartspaceSession).removeOnTargetsAvailableListener(sessionListener)
+        verify(smartspaceSession).close()
+        verify(userTracker).removeCallback(userListener)
+        verify(contentResolver).unregisterContentObserver(settingsObserver)
+        verify(configurationController).removeCallback(configChangeListener)
+        verify(statusBarStateController).removeCallback(statusBarStateListener)
+    }
+
+    @Test
+    fun testBuildViewIsIdempotent() {
+        // GIVEN a connected session
+        connectSession()
+        clearInvocations(plugin)
+
+        // WHEN we disconnect and then reconnect
+        controller.disconnect()
+        controller.buildAndConnectView(fakeParent)
+
+        // THEN the view is not rebuilt
+        verify(plugin, never()).getView(any())
+        assertEquals(fakeSmartspaceView, controller.view)
+    }
+
+    @Test
+    fun testDoubleConnectIsIgnored() {
+        // GIVEN a connected session
+        connectSession()
+        clearInvocations(smartspaceManager)
+        clearInvocations(plugin)
+
+        // WHEN we're asked to connect a second time
+        controller.buildAndConnectView(fakeParent)
+
+        // THEN the existing view and session are reused
+        verify(smartspaceManager, never()).createSmartspaceSession(any())
+        verify(plugin, never()).getView(any())
+        assertEquals(fakeSmartspaceView, controller.view)
+    }
+
+    private fun connectSession(): View {
+        val view = controller.buildAndConnectView(fakeParent)
+
+        verify(smartspaceSession)
+                .addOnTargetsAvailableListener(any(), capture(sessionListenerCaptor))
+        sessionListener = sessionListenerCaptor.value
+
+        verify(userTracker).addCallback(capture(userTrackerCaptor), any())
+        userListener = userTrackerCaptor.value
+
+        verify(contentResolver).registerContentObserver(
+                eq(fakePrivateLockscreenSettingUri),
+                eq(true),
+                capture(settingsObserverCaptor),
+                eq(UserHandle.USER_ALL))
+        settingsObserver = settingsObserverCaptor.value
+
+        verify(configurationController).addCallback(configChangeListenerCaptor.capture())
+        configChangeListener = configChangeListenerCaptor.value
+
+        verify(statusBarStateController).addCallback(statusBarStateListenerCaptor.capture())
+        statusBarStateListener = statusBarStateListenerCaptor.value
+
+        verify(smartspaceSession).requestSmartspaceUpdate()
+        clearInvocations(smartspaceSession)
+
+        verify(fakeSmartspaceView).setPrimaryTextColor(anyInt())
+        verify(fakeSmartspaceView).setDozeAmount(0.5f)
+        clearInvocations(fakeSmartspaceView)
+
+        return view
+    }
+
+    private fun setActiveUser(userHandle: UserHandle) {
+        `when`(userTracker.userId).thenReturn(userHandle.identifier)
+        `when`(userTracker.userHandle).thenReturn(userHandle)
+    }
+
+    private fun mockUserInfo(userHandle: UserHandle, isManagedProfile: Boolean): UserInfo {
+        val userInfo = mock(UserInfo::class.java)
+        `when`(userInfo.userHandle).thenReturn(userHandle)
+        `when`(userInfo.isManagedProfile).thenReturn(isManagedProfile)
+        return userInfo
+    }
+
+    fun makeTarget(
+        id: Int,
+        userHandle: UserHandle,
+        isSensitive: Boolean = false
+    ): SmartspaceTarget {
+        return SmartspaceTarget.Builder(
+                "target$id",
+                ComponentName("testpackage", "testclass$id"),
+                userHandle)
+                .setSensitive(isSensitive)
+                .build()
+    }
+
+    private fun setAllowPrivateNotifications(user: UserHandle, value: Boolean) {
+        `when`(secureSettings.getIntForUser(
+                eq(PRIVATE_LOCKSCREEN_SETTING),
+                anyInt(),
+                eq(user.identifier))
+        ).thenReturn(if (value) 1 else 0)
+    }
+
+    private val fakeSmartspaceView = spy(object : View(context), SmartspaceView {
+        override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
+        }
+
+        override fun setPrimaryTextColor(color: Int) {
+        }
+
+        override fun setDozeAmount(amount: Float) {
+        }
+
+        override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {
+        }
+
+        override fun setFalsingManager(falsingManager: FalsingManager?) {
+        }
+
+        override fun setDnd(image: Drawable?, description: String?) {
+        }
+
+        override fun setNextAlarm(image: Drawable?, description: String?) {
+        }
+    })
+}
+
+private const val PRIVATE_LOCKSCREEN_SETTING =
+        Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
\ No newline at end of file
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 896e330..9a7ab28 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
@@ -29,6 +29,7 @@
 import android.view.LayoutInflater
 import android.widget.LinearLayout
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.ActivityStarter
@@ -70,6 +71,7 @@
 
     private val clock = FakeSystemClock()
     private val mainExecutor = FakeExecutor(clock)
+    private val uiEventLoggerFake = UiEventLoggerFake()
 
     private lateinit var controller: OngoingCallController
     private lateinit var notifCollectionListener: NotifCollectionListener
@@ -99,7 +101,8 @@
                 clock,
                 mockActivityStarter,
                 mainExecutor,
-                mockIActivityManager)
+                mockIActivityManager,
+                OngoingCallLogger(uiEventLoggerFake))
         controller.init()
         controller.addCallback(mockOngoingCallListener)
         controller.setChipView(chipView)
@@ -256,6 +259,28 @@
                 .onOngoingCallStateChanged(anyBoolean())
     }
 
+    @Test
+    fun chipClicked_clickEventLogged() {
+        notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+        chipView.performClick()
+
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+        assertThat(uiEventLoggerFake.eventId(0))
+                .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_CLICKED.id)
+    }
+
+    @Test
+    fun notifyChipVisibilityChanged_visibleEventLogged() {
+        controller.notifyChipVisibilityChanged(true)
+
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+        assertThat(uiEventLoggerFake.eventId(0))
+                .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_VISIBLE.id)
+    }
+    // Other tests for notifyChipVisibilityChanged are in [OngoingCallLogger], since
+    // [OngoingCallController.notifyChipVisibilityChanged] just delegates to that class.
+
     private fun createOngoingCallNotifEntry(): NotificationEntry {
         val notificationEntryBuilder = NotificationEntryBuilder()
         notificationEntryBuilder.modifyNotification(context).style = ongoingCallStyle
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt
new file mode 100644
index 0000000..ecec124
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.statusbar.phone.ongoingcall
+
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class OngoingCallLoggerTest : SysuiTestCase() {
+    private val uiEventLoggerFake = UiEventLoggerFake()
+    private val ongoingCallLogger = OngoingCallLogger(uiEventLoggerFake)
+
+    @Test
+    fun logChipClicked_clickEventLogged() {
+        ongoingCallLogger.logChipClicked()
+
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+        assertThat(uiEventLoggerFake.eventId(0))
+                .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_CLICKED.id)
+    }
+
+    @Test
+    fun logChipVisibilityChanged_changeFromInvisibleToVisible_visibleEventLogged() {
+        ongoingCallLogger.logChipVisibilityChanged(false)
+        ongoingCallLogger.logChipVisibilityChanged(true)
+
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+        assertThat(uiEventLoggerFake.eventId(0))
+                .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_VISIBLE.id)
+    }
+
+    @Test
+    fun logChipVisibilityChanged_changeFromVisibleToInvisible_eventNotLogged() {
+        // Setting the chip to visible here will trigger a log
+        ongoingCallLogger.logChipVisibilityChanged(true)
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+
+        ongoingCallLogger.logChipVisibilityChanged(false)
+
+        // Expect that there were no new logs
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+    }
+
+    @Test
+    fun logChipVisibilityChanged_visibleThenVisibleAgain_eventNotLogged() {
+        // Setting the chip to visible here will trigger a log
+        ongoingCallLogger.logChipVisibilityChanged(true)
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+
+        ongoingCallLogger.logChipVisibilityChanged(true)
+
+        // Expect that there were no new logs
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index 0e4b053..d72f432 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
@@ -25,12 +26,16 @@
 import static org.mockito.Mockito.doReturn;
 
 import android.app.Notification;
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -49,6 +54,7 @@
     private AccessibilityManagerWrapper mAccessibilityMgr;
     private HeadsUpManager mHeadsUpManager;
     private boolean mLivesPastNormalTime;
+    private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
 
     private final class TestableHeadsUpManager extends HeadsUpManager {
         TestableHeadsUpManager(Context context) {
@@ -65,6 +71,7 @@
     @Before
     public void setUp() {
         mAccessibilityMgr = mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
+        mDependency.injectTestDependency(UiEventLogger.class, mUiEventLoggerFake);
 
         mHeadsUpManager = new TestableHeadsUpManager(mContext);
         super.setUp();
@@ -108,5 +115,28 @@
         assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0);
         assertThat(activeRemoteInput.compareTo(ongoingCall)).isGreaterThan(0);
     }
-}
 
+    @Test
+    public void testPinEntry_logsPeek() {
+        // Needs full screen intent in order to be pinned
+        final PendingIntent fullScreenIntent = PendingIntent.getActivity(mContext, 0,
+                new Intent(), PendingIntent.FLAG_MUTABLE);
+
+        HeadsUpManager.HeadsUpEntry entryToPin = mHeadsUpManager.new HeadsUpEntry();
+        entryToPin.setEntry(new NotificationEntryBuilder()
+                .setSbn(createNewSbn(0,
+                        new Notification.Builder(mContext, "")
+                                .setFullScreenIntent(fullScreenIntent, true)))
+                .build());
+        // Note: the standard way to show a notification would be calling showNotification rather
+        // than onAlertEntryAdded. However, in practice showNotification in effect adds
+        // the notification and then updates it; in order to not log twice, the entry needs
+        // to have a functional ExpandableNotificationRow that can keep track of whether it's
+        // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
+        mHeadsUpManager.onAlertEntryAdded(entryToPin);
+
+        assertEquals(1, mUiEventLoggerFake.numLogs());
+        assertEquals(HeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
+                mUiEventLoggerFake.eventId(0));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 2d9d715..9f1dad8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -185,7 +185,8 @@
                 Color.valueOf(Color.BLUE), null);
 
         String jsonString =
-                "{\"android.theme.customization.system_palette\":\"override.package.name\"}";
+                "{\"android.theme.customization.system_palette\":\"override.package.name\","
+                        + "\"android.theme.customization.color_source\":\"preset\"}";
         when(mSecureSettings.getStringForUser(
                 eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
                 .thenReturn(jsonString);
@@ -203,6 +204,32 @@
     }
 
     @Test
+    public void onWallpaperColorsChanged_resetThemeIfNotPreset() {
+        // Should ask for a new theme when wallpaper colors change
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+
+        String jsonString =
+                "{\"android.theme.customization.system_palette\":\"override.package.name\","
+                        + "\"android.theme.customization.color_source\":\"home_wallpaper\"}";
+        when(mSecureSettings.getStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+                .thenReturn(jsonString);
+
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+
+        ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+        verify(mSecureSettings).putString(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
+
+        assertThat(updatedSetting.getValue().contains("android.theme.customization.system_palette"))
+                .isFalse();
+
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+    }
+
+    @Test
     public void onProfileAdded_setsTheme() {
         mBroadcastReceiver.getValue().onReceive(null,
                 new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED));
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 ce71ac8..570e1d8 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,7 @@
 
 package com.android.systemui.util.concurrency;
 
+import android.os.Handler;
 import android.os.Looper;
 
 import java.util.concurrent.Executor;
@@ -25,11 +26,21 @@
  */
 public class FakeThreadFactory implements ThreadFactory {
     private final FakeExecutor mFakeExecutor;
+    private Handler mHandler;
 
     public FakeThreadFactory(FakeExecutor fakeExecutor) {
         mFakeExecutor = fakeExecutor;
     }
 
+    public void setHandler(Handler handler) {
+        mHandler = handler;
+    }
+
+    @Override
+    public Handler builderHandlerOnNewThread(String threadName) {
+        return mHandler;
+    }
+
     @Override
     public Executor buildExecutorOnNewThread(String threadName) {
         return mFakeExecutor;
@@ -41,6 +52,11 @@
     }
 
     @Override
+    public DelayableExecutor buildDelayableExecutorOnHandler(Handler handler) {
+        return mFakeExecutor;
+    }
+
+    @Override
     public DelayableExecutor buildDelayableExecutorOnLooper(Looper looper) {
         return mFakeExecutor;
     }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index ae14e37..e4f2203 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -9134,7 +9134,8 @@
     }
 
     private NetworkCapabilities getNetworkCapabilitiesWithoutUids(@NonNull NetworkCapabilities nc) {
-        final NetworkCapabilities sanitized = new NetworkCapabilities(nc);
+        final NetworkCapabilities sanitized = new NetworkCapabilities(nc,
+                NetworkCapabilities.REDACT_ALL);
         sanitized.setUids(null);
         sanitized.setAdministratorUids(new int[0]);
         sanitized.setOwnerUid(Process.INVALID_UID);
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index db36e62..c3543e7 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -361,10 +361,13 @@
         try {
             executeRescueLevelInternal(context, level, failedPackage);
             EventLogTags.writeRescueSuccess(level);
-            logCriticalInfo(Log.DEBUG,
-                    "Finished rescue level " + levelToString(level));
+            String successMsg = "Finished rescue level " + levelToString(level);
+            if (!TextUtils.isEmpty(failedPackage)) {
+                successMsg += " for package " + failedPackage;
+            }
+            logCriticalInfo(Log.DEBUG, successMsg);
         } catch (Throwable t) {
-            logRescueException(level, t);
+            logRescueException(level, failedPackage, t);
         }
     }
 
@@ -427,7 +430,7 @@
                             pm.reboot(TAG);
                         }
                     } catch (Throwable t) {
-                        logRescueException(level, t);
+                        logRescueException(level, failedPackage, t);
                     }
                 };
                 thread = new Thread(runnable);
@@ -441,7 +444,7 @@
                         try {
                             RecoverySystem.rebootPromptAndWipeUserData(context, TAG);
                         } catch (Throwable t) {
-                            logRescueException(level, t);
+                            logRescueException(level, failedPackage, t);
                         }
                     }
                 };
@@ -455,11 +458,15 @@
         }
     }
 
-    private static void logRescueException(int level, Throwable t) {
+    private static void logRescueException(int level, @Nullable String failedPackageName,
+            Throwable t) {
         final String msg = ExceptionUtils.getCompleteMessage(t);
         EventLogTags.writeRescueFailure(level, msg);
-        logCriticalInfo(Log.ERROR,
-                "Failed rescue level " + levelToString(level) + ": " + msg);
+        String failureMsg = "Failed rescue level " + levelToString(level);
+        if (!TextUtils.isEmpty(failedPackageName)) {
+            failureMsg += " for package " + failedPackageName;
+        }
+        logCriticalInfo(Log.ERROR, failureMsg + ": " + msg);
     }
 
     private static int mapRescueLevelToUserImpact(int rescueLevel) {
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index a05eb68..ca59ce3 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -20,12 +20,17 @@
 import static android.app.ActivityManager.RunningServiceInfo;
 import static android.app.ActivityManager.RunningTaskInfo;
 import static android.app.ActivityManager.getCurrentUser;
+import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA;
+import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
 import static android.content.Intent.EXTRA_PACKAGE_NAME;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS;
 import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR;
 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
 import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
@@ -60,6 +65,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -141,6 +147,7 @@
     private static final int VER0_INDIVIDUAL_ENABLED = 1;
     private static final int VER1_ENABLED = 0;
     private static final int VER1_INDIVIDUAL_ENABLED = 1;
+    public static final int REMINDER_DIALOG_DELAY_MILLIS = 500;
 
     private final Context mContext;
     private final SensorPrivacyServiceImpl mSensorPrivacyServiceImpl;
@@ -206,6 +213,36 @@
         private ArrayMap<Pair<String, UserHandle>, ArrayList<IBinder>> mSuppressReminders =
                 new ArrayMap<>();
 
+        private final ArrayMap<SensorUseReminderDialogInfo, ArraySet<Integer>>
+                mQueuedSensorUseReminderDialogs = new ArrayMap<>();
+
+        private class SensorUseReminderDialogInfo {
+            private int mTaskId;
+            private UserHandle mUser;
+            private String mPackageName;
+
+            SensorUseReminderDialogInfo(int taskId, UserHandle user, String packageName) {
+                mTaskId = taskId;
+                mUser = user;
+                mPackageName = packageName;
+            }
+
+            @Override
+            public boolean equals(Object o) {
+                if (this == o) return true;
+                if (o == null || !(o instanceof SensorUseReminderDialogInfo)) return false;
+                SensorUseReminderDialogInfo that = (SensorUseReminderDialogInfo) o;
+                return mTaskId == that.mTaskId
+                        && Objects.equals(mUser, that.mUser)
+                        && Objects.equals(mPackageName, that.mPackageName);
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(mTaskId, mUser, mPackageName);
+            }
+        }
+
         SensorPrivacyServiceImpl() {
             mHandler = new SensorPrivacyHandler(FgThread.get().getLooper(), mContext);
             File sensorPrivacyFile = new File(Environment.getDataSystemDirectory(),
@@ -228,7 +265,8 @@
                 }
             }
 
-            int[] micAndCameraOps = new int[]{OP_RECORD_AUDIO, OP_CAMERA};
+            int[] micAndCameraOps = new int[]{OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE,
+                    OP_CAMERA, OP_PHONE_CALL_CAMERA};
             mAppOpsManager.startWatchingNoted(micAndCameraOps, this);
             mAppOpsManager.startWatchingStarted(micAndCameraOps, this);
 
@@ -254,15 +292,29 @@
         public void onOpNoted(int code, int uid, String packageName,
                 String attributionTag, @AppOpsManager.OpFlags int flags,
                 @AppOpsManager.Mode int result) {
-            if (result != MODE_IGNORED || (flags & AppOpsManager.OP_FLAGS_ALL_TRUSTED) == 0) {
+            if ((flags & AppOpsManager.OP_FLAGS_ALL_TRUSTED) == 0) {
                 return;
             }
 
             int sensor;
-            if (code == OP_RECORD_AUDIO) {
-                sensor = MICROPHONE;
+            if (result == MODE_IGNORED) {
+                if (code == OP_RECORD_AUDIO) {
+                    sensor = MICROPHONE;
+                } else if (code == OP_CAMERA) {
+                    sensor = CAMERA;
+                } else {
+                    return;
+                }
+            } else if (result == MODE_ALLOWED) {
+                if (code == OP_PHONE_CALL_MICROPHONE) {
+                    sensor = MICROPHONE;
+                } else if (code == OP_PHONE_CALL_CAMERA) {
+                    sensor = CAMERA;
+                } else {
+                    return;
+                }
             } else {
-                sensor = CAMERA;
+                return;
             }
 
             long token = Binder.clearCallingIdentity();
@@ -294,6 +346,11 @@
                 }
             }
 
+            if (uid == Process.SYSTEM_UID) {
+                enqueueSensorUseReminderDialogAsync(-1, user, packageName, sensor);
+                return;
+            }
+
             // TODO: Handle reminders with multiple sensors
 
             // - If we have a likely activity that triggered the sensor use overlay a dialog over
@@ -312,7 +369,7 @@
                 if (task.isVisible && task.topActivity.getPackageName().equals(packageName)) {
                     if (task.isFocused) {
                         // There is the one focused activity
-                        showSensorUseReminderDialog(task.taskId, user, packageName, sensor);
+                        enqueueSensorUseReminderDialogAsync(task.taskId, user, packageName, sensor);
                         return;
                     }
 
@@ -323,7 +380,7 @@
             // TODO: Test this case
             // There is one or more non-focused activity
             if (tasksOfPackageUsingSensor.size() == 1) {
-                showSensorUseReminderDialog(tasksOfPackageUsingSensor.get(0).taskId, user,
+                enqueueSensorUseReminderDialogAsync(tasksOfPackageUsingSensor.get(0).taskId, user,
                         packageName, sensor);
                 return;
             } else if (tasksOfPackageUsingSensor.size() > 1) {
@@ -360,21 +417,60 @@
          * @param packageName The name of the package using the sensor.
          * @param sensor The sensor that is being used.
          */
-        private void showSensorUseReminderDialog(int taskId, @NonNull UserHandle user,
+        private void enqueueSensorUseReminderDialogAsync(int taskId, @NonNull UserHandle user,
                 @NonNull String packageName, int sensor) {
+            mHandler.sendMessage(PooledLambda.obtainMessage(
+                    this:: enqueueSensorUseReminderDialog, taskId, user, packageName, sensor));
+        }
+
+        private void enqueueSensorUseReminderDialog(int taskId, @NonNull UserHandle user,
+                @NonNull String packageName, int sensor) {
+            SensorUseReminderDialogInfo info =
+                    new SensorUseReminderDialogInfo(taskId, user, packageName);
+            if (!mQueuedSensorUseReminderDialogs.containsKey(info)) {
+                ArraySet<Integer> sensors = new ArraySet<Integer>();
+                sensors.add(sensor);
+                mQueuedSensorUseReminderDialogs.put(info, sensors);
+                mHandler.sendMessageDelayed(
+                        PooledLambda.obtainMessage(this::showSensorUserReminderDialog, info),
+                        REMINDER_DIALOG_DELAY_MILLIS);
+                return;
+            }
+            ArraySet<Integer> sensors = mQueuedSensorUseReminderDialogs.get(info);
+            sensors.add(sensor);
+        }
+
+        private void showSensorUserReminderDialog(@NonNull SensorUseReminderDialogInfo info) {
+            ArraySet<Integer> sensors = mQueuedSensorUseReminderDialogs.get(info);
+            mQueuedSensorUseReminderDialogs.remove(info);
+            if (sensors == null) {
+                Log.e(TAG, "Unable to show sensor use dialog because sensor set is null."
+                        + " Was the dialog queue modified from outside the handler thread?");
+                return;
+            }
             Intent dialogIntent = new Intent();
             dialogIntent.setComponent(ComponentName.unflattenFromString(
                     mContext.getResources().getString(
                             R.string.config_sensorUseStartedActivity)));
 
             ActivityOptions options = ActivityOptions.makeBasic();
-            options.setLaunchTaskId(taskId);
+            options.setLaunchTaskId(info.mTaskId);
             options.setTaskOverlay(true, true);
 
-            dialogIntent.putExtra(EXTRA_PACKAGE_NAME, packageName);
-            dialogIntent.putExtra(EXTRA_SENSOR, sensor);
+            dialogIntent.addFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
 
-            mContext.startActivityAsUser(dialogIntent, options.toBundle(), user);
+            dialogIntent.putExtra(EXTRA_PACKAGE_NAME, info.mPackageName);
+            if (sensors.size() == 1) {
+                dialogIntent.putExtra(EXTRA_SENSOR, sensors.valueAt(0));
+            } else if (sensors.size() == 2) {
+                dialogIntent.putExtra(EXTRA_ALL_SENSORS, true);
+            } else {
+                // Currently the only cases can be 1 or two
+                Log.e(TAG, "Attempted to show sensor use dialog for " + sensors.size()
+                        + " sensors");
+                return;
+            }
+            mContext.startActivityAsUser(dialogIntent, options.toBundle(), info.mUser);
         }
 
         /**
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 2bb9084..e7e3ce9 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -167,7 +167,6 @@
     @NonNull private final VcnNetworkProvider mNetworkProvider;
     @NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb;
     @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker;
-    @NonNull private final VcnContext mVcnContext;
     @NonNull private final BroadcastReceiver mPkgChangeReceiver;
 
     @NonNull
@@ -212,7 +211,6 @@
                 mContext, mLooper, mTelephonySubscriptionTrackerCb);
 
         mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE);
-        mVcnContext = mDeps.newVcnContext(mContext, mLooper, mNetworkProvider);
 
         mPkgChangeReceiver = new BroadcastReceiver() {
             @Override
@@ -336,8 +334,9 @@
         public VcnContext newVcnContext(
                 @NonNull Context context,
                 @NonNull Looper looper,
-                @NonNull VcnNetworkProvider vcnNetworkProvider) {
-            return new VcnContext(context, looper, vcnNetworkProvider);
+                @NonNull VcnNetworkProvider vcnNetworkProvider,
+                boolean isInTestMode) {
+            return new VcnContext(context, looper, vcnNetworkProvider, isInTestMode);
         }
 
         /** Creates a new Vcn instance using the provided configuration */
@@ -419,6 +418,14 @@
                 "Carrier privilege required for subscription group to set VCN Config");
     }
 
+    private void enforceManageTestNetworksForTestMode(@NonNull VcnConfig vcnConfig) {
+        if (vcnConfig.isTestModeProfile()) {
+            mContext.enforceCallingPermission(
+                    android.Manifest.permission.MANAGE_TEST_NETWORKS,
+                    "Test-mode require the MANAGE_TEST_NETWORKS permission");
+        }
+    }
+
     private class VcnSubscriptionTrackerCallback implements TelephonySubscriptionTrackerCallback {
         /**
          * Handles subscription group changes, as notified by {@link TelephonySubscriptionTracker}
@@ -542,8 +549,11 @@
 
         final VcnCallbackImpl vcnCallback = new VcnCallbackImpl(subscriptionGroup);
 
+        final VcnContext vcnContext =
+                mDeps.newVcnContext(
+                        mContext, mLooper, mNetworkProvider, config.isTestModeProfile());
         final Vcn newInstance =
-                mDeps.newVcn(mVcnContext, subscriptionGroup, config, mLastSnapshot, vcnCallback);
+                mDeps.newVcn(vcnContext, subscriptionGroup, config, mLastSnapshot, vcnCallback);
         mVcns.put(subscriptionGroup, newInstance);
 
         // Now that a new VCN has started, notify all registered listeners to refresh their
@@ -587,6 +597,7 @@
 
         mContext.getSystemService(AppOpsManager.class)
                 .checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName());
+        enforceManageTestNetworksForTestMode(config);
         enforceCallingUserAndCarrierPrivilege(subscriptionGroup, opPkgName);
 
         Binder.withCleanCallingIdentity(() -> {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 83cff15..6661f88 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -14621,7 +14621,9 @@
             String reason, @TempAllowListType int type, int callingUid) {
         synchronized (mProcLock) {
             // The temp allowlist type could change according to the reasonCode.
-            type = mLocalDeviceIdleController.getTempAllowListType(reasonCode, type);
+            if (mLocalDeviceIdleController != null) {
+                type = mLocalDeviceIdleController.getTempAllowListType(reasonCode, type);
+            }
             if (type == TEMPORARY_ALLOW_LIST_TYPE_NONE) {
                 return;
             }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 50515882..a23b5eb 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -3294,7 +3294,7 @@
             boolean shouldCollectMessage) {
         RestrictionBypass bypass;
         try {
-            bypass = verifyAndGetBypass(uid, packageName, attributionTag);
+            bypass = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
         } catch (SecurityException e) {
             Slog.e(TAG, "noteOperation", e);
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
@@ -3786,7 +3786,7 @@
             boolean shouldCollectMessage, boolean dryRun) {
         RestrictionBypass bypass;
         try {
-            bypass = verifyAndGetBypass(uid, packageName, attributionTag);
+            bypass = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
         } catch (SecurityException e) {
             Slog.e(TAG, "startOperation", e);
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
@@ -4318,17 +4318,26 @@
     }
 
     /**
+     * @see verifyAndGetBypass(int, String, String, String)
+     */
+    private @Nullable RestrictionBypass verifyAndGetBypass(int uid, String packageName,
+            @Nullable String attributionTag) {
+        return verifyAndGetBypass(uid, packageName, attributionTag, null);
+    }
+
+    /**
      * Verify that package belongs to uid and return the {@link RestrictionBypass bypass
      * description} for the package.
      *
      * @param uid The uid the package belongs to
      * @param packageName The package the might belong to the uid
      * @param attributionTag attribution tag or {@code null} if no need to verify
+     * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled
      *
      * @return {@code true} iff the package is privileged
      */
     private @Nullable RestrictionBypass verifyAndGetBypass(int uid, String packageName,
-            @Nullable String attributionTag) {
+            @Nullable String attributionTag, @Nullable String proxyPackageName) {
         if (uid == Process.ROOT_UID) {
             // For backwards compatibility, don't check package name for root UID.
             return null;
@@ -4366,34 +4375,36 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             boolean isAttributionTagValid = false;
-            AndroidPackage pkg = LocalServices.getService(PackageManagerInternal.class)
-                    .getPackage(packageName);
+            PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+            AndroidPackage pkg = pmInt.getPackage(packageName);
             if (pkg != null) {
-                if (attributionTag == null) {
-                    isAttributionTagValid = true;
-                } else {
-                    if (pkg.getAttributions() != null) {
-                        int numAttributions = pkg.getAttributions().size();
-                        for (int i = 0; i < numAttributions; i++) {
-                            if (pkg.getAttributions().get(i).tag.equals(attributionTag)) {
-                                isAttributionTagValid = true;
-                            }
-                        }
-                    }
-                }
+                isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
 
                 pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
                 bypass = getBypassforPackage(pkg);
             }
             if (!isAttributionTagValid) {
-                String msg = "attributionTag " + attributionTag + " not declared in"
-                        + " manifest of " + packageName;
+                AndroidPackage proxyPkg = proxyPackageName != null
+                        ? pmInt.getPackage(proxyPackageName) : null;
+                boolean foundInProxy = isAttributionInPackage(proxyPkg, attributionTag);
+                String msg;
+                if (pkg != null && foundInProxy) {
+                    msg = "attributionTag " + attributionTag + " declared in manifest of the proxy"
+                            + " package " + proxyPackageName + ", this is not advised";
+                } else if (pkg != null) {
+                    msg = "attributionTag " + attributionTag + " not declared in manifest of "
+                            + packageName;
+                } else {
+                    msg = "package " + packageName + " not found, can't check for "
+                            + "attributionTag " + attributionTag;
+                }
+
                 try {
                     if (mPlatformCompat.isChangeEnabledByPackageName(
                             SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
                             userId) && mPlatformCompat.isChangeEnabledByUid(
                                     SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE,
-                            callingUid)) {
+                            callingUid) && !foundInProxy) {
                         throw new SecurityException(msg);
                     } else {
                         Slog.e(TAG, msg);
@@ -4413,6 +4424,25 @@
         return bypass;
     }
 
+    private boolean isAttributionInPackage(@Nullable AndroidPackage pkg,
+            @Nullable String attributionTag) {
+        if (pkg == null) {
+            return false;
+        } else if (attributionTag == null) {
+            return true;
+        }
+        if (pkg.getAttributions() != null) {
+            int numAttributions = pkg.getAttributions().size();
+            for (int i = 0; i < numAttributions; i++) {
+                if (pkg.getAttributions().get(i).tag.equals(attributionTag)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
     /**
      * Get (and potentially create) ops.
      *
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 96bb73f..8961a5a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1034,6 +1034,11 @@
         }
     }
 
+    /*package*/ void clearAvrcpAbsoluteVolumeSupported() {
+        setAvrcpAbsoluteVolumeSupported(false);
+        mAudioService.setAvrcpAbsoluteVolumeSupported(false);
+    }
+
     /*package*/ boolean getBluetoothA2dpEnabled() {
         synchronized (mDeviceStateLock) {
             return mBluetoothA2dpEnabled;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 18d04e9..5944a63 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1015,7 +1015,7 @@
         }
 
         // device to remove was visible by APM, update APM
-        mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false);
+        mDeviceBroker.clearAvrcpAbsoluteVolumeSupported();
         final int res = mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
                 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 098ce7c..a3c5904 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -6535,6 +6535,10 @@
                     if (index == -1) {
                         continue;
                     }
+                    if (mPublicStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED
+                            && mCameraSoundForced) {
+                        index = mIndexMax;
+                    }
                     if (DEBUG_VOL) {
                         Log.v(TAG, "readSettings: found stored index " + getValidIndex(index)
                                  + " for group " + mAudioVolumeGroup.name() + ", device: " + name
@@ -7643,8 +7647,12 @@
         // address is not used for now, but may be used when multiple a2dp devices are supported
         sVolumeLogger.log(new AudioEventLogger.StringEvent("avrcpSupportsAbsoluteVolume addr="
                 + address + " support=" + support));
-        mAvrcpAbsVolSupported = support;
         mDeviceBroker.setAvrcpAbsoluteVolumeSupported(support);
+        setAvrcpAbsoluteVolumeSupported(support);
+    }
+
+    /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean support) {
+        mAvrcpAbsVolSupported = support;
         sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
                     AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0,
                     mStreamStates[AudioSystem.STREAM_MUSIC], 0);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index dd3057e7..6a7d201 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -149,7 +149,7 @@
                     prop.supportsDetectInteraction, prop.halControlsPreview,
                     false /* resetLockoutRequiresChallenge */);
             final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
-                    internalProp);
+                    internalProp, lockoutResetDispatcher);
 
             mSensors.put(sensorId, sensor);
             Slog.d(getTag(), "Added: " + internalProp);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index f551930..1e1b532 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -80,11 +80,26 @@
     }
 
     void onLockoutCleared() {
-        mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_NONE);
-        mLockoutResetDispatcher.notifyLockoutResetCallbacks(getSensorId());
+        resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutCache,
+                mLockoutResetDispatcher);
         mCallback.onClientFinished(this, true /* success */);
     }
 
+    /**
+     * Reset the local lockout state and notify any listeners.
+     *
+     * This should only be called when the HAL sends a reset request directly to the
+     * framework (i.e. time based reset, etc.). When the HAL is responding to a
+     * resetLockout request from an instance of this client {@link #onLockoutCleared()} should
+     * be used instead.
+     */
+    static void resetLocalLockoutStateToNone(int sensorId, int userId,
+            @NonNull LockoutCache lockoutTracker,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+        lockoutTracker.setLockoutModeForUser(userId, LockoutTracker.LOCKOUT_NONE);
+        lockoutResetDispatcher.notifyLockoutResetCallbacks(sensorId);
+    }
+
     @Override
     public int getProtoEnum() {
         return BiometricsProto.CM_RESET_LOCKOUT;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 724531e..0e6a0f7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -57,6 +57,7 @@
 import com.android.server.biometrics.sensors.Interruptable;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.RemovalConsumer;
 import com.android.server.biometrics.sensors.StartUserClient;
 import com.android.server.biometrics.sensors.StopUserClient;
@@ -124,10 +125,16 @@
         private final int mSensorId;
         private final int mUserId;
         @NonNull
+        private final LockoutCache mLockoutCache;
+        @NonNull
+        private final LockoutResetDispatcher mLockoutResetDispatcher;
+        @NonNull
         private final Callback mCallback;
 
         HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
                 @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
+                @NonNull LockoutCache lockoutTracker,
+                @NonNull LockoutResetDispatcher lockoutResetDispatcher,
                 @NonNull Callback callback) {
             mContext = context;
             mHandler = handler;
@@ -135,6 +142,8 @@
             mScheduler = scheduler;
             mSensorId = sensorId;
             mUserId = userId;
+            mLockoutCache = lockoutTracker;
+            mLockoutResetDispatcher = lockoutResetDispatcher;
             mCallback = callback;
         }
 
@@ -327,13 +336,15 @@
             mHandler.post(() -> {
                 final BaseClientMonitor client = mScheduler.getCurrentClient();
                 if (!(client instanceof FaceResetLockoutClient)) {
-                    Slog.e(mTag, "onLockoutCleared for non-resetLockout client: "
-                            + Utils.getClientName(client));
-                    return;
+                    Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL");
+                    FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
+                            mLockoutCache, mLockoutResetDispatcher);
+                } else {
+                    Slog.d(mTag, "onLockoutCleared after resetLockout");
+                    final FaceResetLockoutClient resetLockoutClient =
+                            (FaceResetLockoutClient) client;
+                    resetLockoutClient.onLockoutCleared();
                 }
-
-                final FaceResetLockoutClient resetLockoutClient = (FaceResetLockoutClient) client;
-                resetLockoutClient.onLockoutCleared();
             });
         }
 
@@ -465,7 +476,8 @@
     }
 
     Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
-            @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties) {
+            @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
         mTag = tag;
         mProvider = provider;
         mContext = context;
@@ -493,7 +505,8 @@
                         final int sensorId = mSensorProperties.sensorId;
 
                         final HalSessionCallback resultController = new HalSessionCallback(mContext,
-                                mHandler, mTag, mScheduler, sensorId, newUserId, callback);
+                                mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache,
+                                lockoutResetDispatcher, callback);
 
                         final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
                                 (userIdStarted, newSession) -> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 20d6ee2..e5fafcd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -159,7 +159,7 @@
                             prop.sensorLocations[0].sensorLocationY,
                             prop.sensorLocations[0].sensorRadius);
             final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
-                    internalProp, gestureAvailabilityDispatcher);
+                    internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher);
 
             mSensors.put(sensorId, sensor);
             Slog.d(getTag(), "Added: " + internalProp);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index bab9506..878ef46 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -80,11 +80,26 @@
     }
 
     void onLockoutCleared() {
-        mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_NONE);
-        mLockoutResetDispatcher.notifyLockoutResetCallbacks(getSensorId());
+        resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutCache,
+                mLockoutResetDispatcher);
         mCallback.onClientFinished(this, true /* success */);
     }
 
+    /**
+     * Reset the local lockout state and notify any listeners.
+     *
+     * This should only be called when the HAL sends a reset request directly to the
+     * framework (i.e. time based reset, etc.). When the HAL is responding to a
+     * resetLockout request from an instance of this client {@link #onLockoutCleared()} should
+     * be used instead.
+     */
+    static void resetLocalLockoutStateToNone(int sensorId, int userId,
+            @NonNull LockoutCache lockoutTracker,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+        lockoutTracker.setLockoutModeForUser(userId, LockoutTracker.LOCKOUT_NONE);
+        lockoutResetDispatcher.notifyLockoutResetCallbacks(sensorId);
+    }
+
     @Override
     public int getProtoEnum() {
         return BiometricsProto.CM_RESET_LOCKOUT;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index cf915ad..9884a78 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -54,6 +54,7 @@
 import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.RemovalConsumer;
 import com.android.server.biometrics.sensors.StartUserClient;
 import com.android.server.biometrics.sensors.StopUserClient;
@@ -116,16 +117,27 @@
             void onHardwareUnavailable();
         }
 
-        @NonNull private final Context mContext;
-        @NonNull private final Handler mHandler;
-        @NonNull private final String mTag;
-        @NonNull private final UserAwareBiometricScheduler mScheduler;
+        @NonNull
+        private final Context mContext;
+        @NonNull
+        private final Handler mHandler;
+        @NonNull
+        private final String mTag;
+        @NonNull
+        private final UserAwareBiometricScheduler mScheduler;
         private final int mSensorId;
         private final int mUserId;
-        @NonNull private final Callback mCallback;
+        @NonNull
+        private final LockoutCache mLockoutCache;
+        @NonNull
+        private final LockoutResetDispatcher mLockoutResetDispatcher;
+        @NonNull
+        private final Callback mCallback;
 
         HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
                 @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
+                @NonNull LockoutCache lockoutTracker,
+                @NonNull LockoutResetDispatcher lockoutResetDispatcher,
                 @NonNull Callback callback) {
             mContext = context;
             mHandler = handler;
@@ -133,6 +145,8 @@
             mScheduler = scheduler;
             mSensorId = sensorId;
             mUserId = userId;
+            mLockoutCache = lockoutTracker;
+            mLockoutResetDispatcher = lockoutResetDispatcher;
             mCallback = callback;
         }
 
@@ -303,14 +317,15 @@
             mHandler.post(() -> {
                 final BaseClientMonitor client = mScheduler.getCurrentClient();
                 if (!(client instanceof FingerprintResetLockoutClient)) {
-                    Slog.e(mTag, "onLockoutCleared for non-resetLockout client: "
-                            + Utils.getClientName(client));
-                    return;
+                    Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL");
+                    FingerprintResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
+                            mLockoutCache, mLockoutResetDispatcher);
+                } else {
+                    Slog.d(mTag, "onLockoutCleared after resetLockout");
+                    final FingerprintResetLockoutClient resetLockoutClient =
+                            (FingerprintResetLockoutClient) client;
+                    resetLockoutClient.onLockoutCleared();
                 }
-
-                final FingerprintResetLockoutClient resetLockoutClient =
-                        (FingerprintResetLockoutClient) client;
-                resetLockoutClient.onLockoutCleared();
             });
         }
 
@@ -415,6 +430,7 @@
 
     Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
         mTag = tag;
         mProvider = provider;
@@ -422,6 +438,7 @@
         mToken = new Binder();
         mHandler = handler;
         mSensorProperties = sensorProperties;
+        mLockoutCache = new LockoutCache();
         mScheduler = new UserAwareBiometricScheduler(tag, gestureAvailabilityDispatcher,
                 () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
                 new UserAwareBiometricScheduler.UserSwitchCallback() {
@@ -443,7 +460,8 @@
                         final int sensorId = mSensorProperties.sensorId;
 
                         final HalSessionCallback resultController = new HalSessionCallback(mContext,
-                                mHandler, mTag, mScheduler, sensorId, newUserId, callback);
+                                mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache,
+                                lockoutResetDispatcher, callback);
 
                         final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
                                 (userIdStarted, newSession) -> {
@@ -466,7 +484,6 @@
                                 resultController, userStartedCallback);
                     }
                 });
-        mLockoutCache = new LockoutCache();
         mAuthenticatorIds = new HashMap<>();
         mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
     }
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index 28f208b..5886b1a 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -338,7 +338,8 @@
             return currentPermission;
         }
         try {
-            final PackageInfo app = mPackageManager.getPackageInfo(name, GET_PERMISSIONS);
+            final PackageInfo app = mPackageManager.getPackageInfo(name,
+                    GET_PERMISSIONS | MATCH_ANY_USER);
             final boolean isNetwork = hasNetworkPermission(app);
             final boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app);
             if (isNetwork || hasRestrictedPermission) {
@@ -664,6 +665,7 @@
                     break;
                 case INetd.PERMISSION_UNINSTALLED:
                     uninstalledAppIds.add(netdPermissionsAppIds.keyAt(i));
+                    break;
                 default:
                     Log.e(TAG, "unknown permission type: " + permissions + "for uid: "
                             + netdPermissionsAppIds.keyAt(i));
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 0f9e604..314955b 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -689,6 +689,8 @@
         if (sensorDetails != null) {
             mAmbientLightSensor.type = sensorDetails.getType();
             mAmbientLightSensor.name = sensorDetails.getName();
+        } else {
+            loadAmbientLightSensorFromConfigXml();
         }
     }
 
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 609bd8b..0949ddd 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -20,6 +20,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Typeface;
 import android.graphics.fonts.Font;
 import android.graphics.fonts.FontFamily;
@@ -32,6 +34,9 @@
 import android.os.ShellCallback;
 import android.system.ErrnoException;
 import android.text.FontConfig;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.text.TextPaint;
 import android.text.TextUtils;
 import android.util.AndroidException;
 import android.util.IndentingPrintWriter;
@@ -196,10 +201,32 @@
         }
 
         @Override
-        public void tryToCreateTypeface(File file) throws IOException {
+        public void tryToCreateTypeface(File file) throws Throwable {
             Font font = new Font.Builder(file).build();
             FontFamily family = new FontFamily.Builder(font).build();
-            new Typeface.CustomFallbackBuilder(family).build();
+            Typeface typeface = new Typeface.CustomFallbackBuilder(family).build();
+
+            TextPaint p = new TextPaint();
+            p.setTextSize(24f);
+            p.setTypeface(typeface);
+
+            // Test string to try with the passed font.
+            // TODO: Good to extract from font file.
+            String testTextToDraw = "abcXYZ@- "
+                    + "\uD83E\uDED6" // Emoji E13.0
+                    + "\uD83C\uDDFA\uD83C\uDDF8" // Emoji Flags
+                    + "\uD83D\uDC8F\uD83C\uDFFB" // Emoji Skin tone Sequence
+                    // ZWJ Sequence
+                    + "\uD83D\uDC68\uD83C\uDFFC\u200D\u2764\uFE0F\u200D\uD83D\uDC8B\u200D"
+                    + "\uD83D\uDC68\uD83C\uDFFF";
+
+            int width = (int) Math.ceil(Layout.getDesiredWidth(testTextToDraw, p));
+            StaticLayout layout = StaticLayout.Builder.obtain(
+                    testTextToDraw, 0, testTextToDraw.length(), p, width).build();
+            Bitmap bmp = Bitmap.createBitmap(
+                    layout.getWidth(), layout.getHeight(), Bitmap.Config.ALPHA_8);
+            Canvas canvas = new Canvas(bmp);
+            layout.draw(canvas);
         }
 
         private static ByteBuffer mmap(File file) throws IOException {
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index e74dac5..981cc838 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -69,7 +69,7 @@
 
         long getRevision(File file) throws IOException;
 
-        void tryToCreateTypeface(File file) throws IOException;
+        void tryToCreateTypeface(File file) throws Throwable;
     }
 
     /** Interface to mock fs-verity in tests. */
@@ -378,10 +378,10 @@
             // Try to create Typeface and treat as failure something goes wrong.
             try {
                 mParser.tryToCreateTypeface(fontFileInfo.getFile());
-            } catch (IOException e) {
+            } catch (Throwable t) {
                 throw new SystemFontException(
                         FontManager.RESULT_ERROR_INVALID_FONT_FILE,
-                        "Failed to create Typeface from file", e);
+                        "Failed to create Typeface from file", t);
             }
 
             FontConfig fontConfig = getSystemFontConfig();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 10f6948f..dcd0eb8 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -136,18 +136,19 @@
         if (!mService.isControlEnabled()) {
             return;
         }
-        if (isActiveSource()) {
-            mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(
-                    mAddress, mService.getPhysicalAddress()));
-        }
         boolean wasActiveSource = isActiveSource();
-        // Invalidate the internal active source record when goes to standby
+        // Invalidate the internal active source record when going to standby
         mService.setActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS,
                 "HdmiCecLocalDevicePlayback#onStandby()");
         boolean mTvSendStandbyOnSleep = mService.getHdmiCecConfig().getIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP)
                     == HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED;
-        if (initiatedByCec || !mTvSendStandbyOnSleep || !wasActiveSource) {
+        if (!wasActiveSource) {
+            return;
+        }
+        if (initiatedByCec || !mTvSendStandbyOnSleep) {
+            mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(mAddress,
+                            mService.getPhysicalAddress()));
             return;
         }
         switch (standbyAction) {
@@ -167,6 +168,9 @@
                                         Constants.ADDR_BROADCAST));
                         break;
                     case HdmiControlManager.POWER_CONTROL_MODE_NONE:
+                        mService.sendCecCommand(
+                                HdmiCecMessageBuilder.buildInactiveSource(mAddress,
+                                        mService.getPhysicalAddress()));
                         break;
                 }
                 break;
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 1c27c65..61107b2 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -313,6 +313,7 @@
     private static native void nativeSetFocusedDisplay(long ptr, int displayId);
     private static native boolean nativeTransferTouchFocus(long ptr,
             IBinder fromChannelToken, IBinder toChannelToken, boolean isDragDrop);
+    private static native boolean nativeTransferTouch(long ptr, IBinder destChannelToken);
     private static native void nativeSetPointerSpeed(long ptr, int speed);
     private static native void nativeSetShowTouches(long ptr, boolean enabled);
     private static native void nativeSetInteractive(long ptr, boolean interactive);
@@ -676,6 +677,19 @@
     }
 
     /**
+     * Transfer the current touch gesture to the provided window.
+     *
+     * @param destChannelToken The token of the window or input channel that should receive the
+     * gesture
+     * @return True if the transfer succeeded, false if there was no active touch gesture happening
+     */
+    public boolean transferTouch(IBinder destChannelToken) {
+        // TODO(b/162194035): Replace this with a SPY window
+        Objects.requireNonNull(destChannelToken, "destChannelToken must not be null.");
+        return nativeTransferTouch(mPtr, destChannelToken);
+    }
+
+    /**
      * Creates an input channel that will receive all input from the input dispatcher.
      * @param inputChannelName The input channel name.
      * @param displayId Target display id.
diff --git a/services/core/java/com/android/server/pm/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java
index ec79483..ed00609 100644
--- a/services/core/java/com/android/server/pm/DumpState.java
+++ b/services/core/java/com/android/server/pm/DumpState.java
@@ -58,6 +58,7 @@
     private boolean mTitlePrinted;
     private boolean mFullPreferred;
     private boolean mCheckIn;
+    private boolean mBrief;
 
     private String mTargetPackageName;
 
@@ -128,4 +129,12 @@
     public void setCheckIn(boolean checkIn) {
         mCheckIn = checkIn;
     }
+
+    public boolean isBrief() {
+        return mBrief;
+    }
+
+    public void setBrief(boolean brief) {
+        mBrief = brief;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index 2015c78..34caaf5 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -30,6 +30,7 @@
 import android.util.TypedXmlSerializer;
 
 import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.utils.WatchedArrayMap;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -65,7 +66,7 @@
 
     protected final LongSparseArray<ArraySet<Long>> mKeySetMapping;
 
-    private final ArrayMap<String, PackageSetting> mPackages;
+    private final WatchedArrayMap<String, PackageSetting> mPackages;
 
     private long lastIssuedKeySetId = 0;
 
@@ -114,7 +115,7 @@
         }
     }
 
-    public KeySetManagerService(ArrayMap<String, PackageSetting> packages) {
+    public KeySetManagerService(WatchedArrayMap<String, PackageSetting> packages) {
         mKeySets = new LongSparseArray<KeySetHandle>();
         mPublicKeys = new LongSparseArray<PublicKeyHandle>();
         mKeySetMapping = new LongSparseArray<ArraySet<Long>>();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 679042f..427bb2d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -404,6 +404,7 @@
 import com.android.server.rollback.RollbackManagerInternal;
 import com.android.server.storage.DeviceStorageMonitorInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
+import com.android.server.utils.SnapshotCache;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.utils.Watchable;
 import com.android.server.utils.Watched;
@@ -871,12 +872,17 @@
     @Watched
     @GuardedBy("mLock")
     final WatchedArrayMap<String, AndroidPackage> mPackages = new WatchedArrayMap<>();
+    private final SnapshotCache<WatchedArrayMap<String, AndroidPackage>> mPackagesSnapshot =
+            new SnapshotCache.Auto(mPackages, mPackages, "PackageManagerService.mPackages");
 
     // Keys are isolated uids and values are the uid of the application
     // that created the isolated process.
     @Watched
     @GuardedBy("mLock")
     final WatchedSparseIntArray mIsolatedOwners = new WatchedSparseIntArray();
+    private final SnapshotCache<WatchedSparseIntArray> mIsolatedOwnersSnapshot =
+            new SnapshotCache.Auto(mIsolatedOwners, mIsolatedOwners,
+                                   "PackageManagerService.mIsolatedOwners");
 
     /**
      * Tracks new system packages [received in an OTA] that we expect to
@@ -1309,14 +1315,17 @@
             // Avoid invalidation-thrashing by preventing cache invalidations from causing property
             // writes if the cache isn't enabled yet.  We re-enable writes later when we're
             // done initializing.
-            sSnapshotCorked = true;
+            sSnapshotCorked.incrementAndGet();
             PackageManager.corkPackageInfoCache();
         }
 
         @Override
         public void enablePackageCaches() {
             // Uncork cache invalidations and allow clients to cache package information.
-            sSnapshotCorked = false;
+            int corking = sSnapshotCorked.decrementAndGet();
+            if (TRACE_SNAPSHOTS && corking == 0) {
+                Log.i(TAG, "snapshot: corking returns to 0");
+            }
             PackageManager.uncorkPackageInfoCache();
         }
     }
@@ -1395,14 +1404,27 @@
     @Watched
     final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
             mSharedLibraries = new WatchedArrayMap<>();
+    private final SnapshotCache<WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>>
+            mSharedLibrariesSnapshot =
+            new SnapshotCache.Auto<>(mSharedLibraries, mSharedLibraries,
+                                     "PackageManagerService.mSharedLibraries");
     @Watched
     final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
             mStaticLibsByDeclaringPackage = new WatchedArrayMap<>();
+    private final SnapshotCache<WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>>
+            mStaticLibsByDeclaringPackageSnapshot =
+            new SnapshotCache.Auto<>(mSharedLibraries, mSharedLibraries,
+                                     "PackageManagerService.mSharedLibraries");
 
     // Mapping from instrumentation class names to info about them.
     @Watched
     final WatchedArrayMap<ComponentName, ParsedInstrumentation> mInstrumentation =
             new WatchedArrayMap<>();
+    private final SnapshotCache<WatchedArrayMap<ComponentName, ParsedInstrumentation>>
+            mInstrumentationSnapshot =
+            new SnapshotCache.Auto<>(mInstrumentation, mInstrumentation,
+                                     "PackageManagerService.mInstrumentation");
+
 
     // Packages whose data we have transfered into another package, thus
     // should no longer exist.
@@ -1588,6 +1610,7 @@
     static final int INTEGRITY_VERIFICATION_COMPLETE = 25;
     static final int CHECK_PENDING_INTEGRITY_VERIFICATION = 26;
     static final int DOMAIN_VERIFICATION = 27;
+    static final int SNAPSHOT_UNCORK = 28;
 
     static final int DEFERRED_NO_KILL_POST_DELETE_DELAY_MS = 3 * 1000;
     static final int DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS = 500;
@@ -1834,11 +1857,11 @@
         Snapshot(int type) {
             if (type == Snapshot.SNAPPED) {
                 settings = mSettings.snapshot();
-                isolatedOwners = mIsolatedOwners.snapshot();
-                packages = mPackages.snapshot();
-                sharedLibs = mSharedLibraries.snapshot();
-                staticLibs = mStaticLibsByDeclaringPackage.snapshot();
-                instrumentation = mInstrumentation.snapshot();
+                isolatedOwners = mIsolatedOwnersSnapshot.snapshot();
+                packages = mPackagesSnapshot.snapshot();
+                sharedLibs = mSharedLibrariesSnapshot.snapshot();
+                staticLibs = mStaticLibsByDeclaringPackageSnapshot.snapshot();
+                instrumentation = mInstrumentationSnapshot.snapshot();
                 resolveComponentName = mResolveComponentName.clone();
                 resolveActivity = new ActivityInfo(mResolveActivity);
                 instantAppInstallerActivity =
@@ -4874,12 +4897,16 @@
     // A lock-free cache for frequently called functions.
     private volatile Computer mSnapshotComputer;
     // If true, the snapshot is invalid (stale).  The attribute is static since it may be
-    // set from outside classes.
-    private static volatile boolean sSnapshotInvalid = true;
+    // set from outside classes.  The attribute may be set to true anywhere, although it
+    // should only be set true while holding mLock.  However, the attribute id guaranteed
+    // to be set false only while mLock and mSnapshotLock are both held.
+    private static AtomicBoolean sSnapshotInvalid = new AtomicBoolean(true);
+    // The package manager that is using snapshots.
+    private static PackageManagerService sSnapshotConsumer = null;
     // If true, the snapshot is corked.  Do not create a new snapshot but use the live
     // computer.  This throttles snapshot creation during periods of churn in Package
     // Manager.
-    private static volatile boolean sSnapshotCorked = false;
+    private static AtomicInteger sSnapshotCorked = new AtomicInteger(0);
 
     /**
      * This lock is used to make reads from {@link #sSnapshotInvalid} and
@@ -4897,7 +4924,10 @@
 
     // The snapshot disable/enable switch.  An image with the flag set true uses snapshots
     // and an image with the flag set false does not use snapshots.
-    private static final boolean SNAPSHOT_ENABLED = false;
+    private static final boolean SNAPSHOT_ENABLED = true;
+
+    // The default auto-cork delay for snapshots.  This is 1s.
+    private static final long SNAPSHOT_AUTOCORK_DELAY_MS = TimeUnit.SECONDS.toMillis(1);
 
     // The per-instance snapshot disable/enable flag.  This is generally set to false in
     // test instances and set to SNAPSHOT_ENABLED in operational instances.
@@ -4922,15 +4952,16 @@
             // If the current thread holds mLock then it may have modified state but not
             // yet invalidated the snapshot.  Always give the thread the live computer.
             return mLiveComputer;
+        } else if (sSnapshotCorked.get() > 0) {
+            // Snapshots are corked, which means new ones should not be built right now.
+            mSnapshotStatistics.corked();
+            return mLiveComputer;
         }
         synchronized (mSnapshotLock) {
+            // This synchronization block serializes access to the snapshot computer and
+            // to the code that samples mSnapshotInvalid.
             Computer c = mSnapshotComputer;
-            if (sSnapshotCorked && (c != null)) {
-                // Snapshots are corked, which means new ones should not be built right now.
-                c.use();
-                return c;
-            }
-            if (sSnapshotInvalid || (c == null)) {
+            if (sSnapshotInvalid.getAndSet(false) || (c == null)) {
                 // The snapshot is invalid if it is marked as invalid or if it is null.  If it
                 // is null, then it is currently being rebuilt by rebuildSnapshot().
                 synchronized (mLock) {
@@ -4938,9 +4969,7 @@
                     // invalidated as it is rebuilt.  However, the snapshot is still
                     // self-consistent (the lock is being held) and is current as of the time
                     // this function is entered.
-                    if (sSnapshotInvalid) {
-                        rebuildSnapshot();
-                    }
+                    rebuildSnapshot();
 
                     // Guaranteed to be non-null.  mSnapshotComputer is only be set to null
                     // temporarily in rebuildSnapshot(), which is guarded by mLock().  Since
@@ -4958,12 +4987,11 @@
      * Rebuild the cached computer.  mSnapshotComputer is temporarily set to null to block other
      * threads from using the invalid computer until it is rebuilt.
      */
-    @GuardedBy("mLock")
+    @GuardedBy({ "mLock", "mSnapshotLock"})
     private void rebuildSnapshot() {
         final long now = SystemClock.currentTimeMicro();
         final int hits = mSnapshotComputer == null ? -1 : mSnapshotComputer.getUsed();
         mSnapshotComputer = null;
-        sSnapshotInvalid = false;
         final Snapshot args = new Snapshot(Snapshot.SNAPPED);
         mSnapshotComputer = new ComputerEngine(args);
         final long done = SystemClock.currentTimeMicro();
@@ -4972,6 +5000,30 @@
     }
 
     /**
+     * Create a new snapshot.  Used for testing only.  This does collect statistics or
+     * update the snapshot used by other actors.  It does not alter the invalidation
+     * flag.  This method takes the mLock internally.
+     */
+    private Computer createNewSnapshot() {
+        synchronized (mLock) {
+            final Snapshot args = new Snapshot(Snapshot.SNAPPED);
+            return new ComputerEngine(args);
+        }
+    }
+
+    /**
+     * Cork snapshots.  This times out after the programmed delay.
+     */
+    private void corkSnapshots(int multiplier) {
+        int corking = sSnapshotCorked.getAndIncrement();
+        if (TRACE_SNAPSHOTS && corking == 0) {
+            Log.i(TAG, "snapshot: corking goes positive");
+        }
+        Message message = mHandler.obtainMessage(SNAPSHOT_UNCORK);
+        mHandler.sendMessageDelayed(message, SNAPSHOT_AUTOCORK_DELAY_MS * multiplier);
+    }
+
+    /**
      * Create a live computer
      */
     private ComputerLocked createLiveComputer() {
@@ -4986,9 +5038,9 @@
      */
     public static void onChange(@Nullable Watchable what) {
         if (TRACE_SNAPSHOTS) {
-            Log.e(TAG, "snapshot: onChange(" + what + ")");
+            Log.i(TAG, "snapshot: onChange(" + what + ")");
         }
-        sSnapshotInvalid = true;
+        sSnapshotInvalid.set(true);
     }
 
     /**
@@ -5367,6 +5419,13 @@
                     mDomainVerificationManager.runMessage(messageCode, object);
                     break;
                 }
+                case SNAPSHOT_UNCORK: {
+                    int corking = sSnapshotCorked.decrementAndGet();
+                    if (TRACE_SNAPSHOTS && corking == 0) {
+                        Log.e(TAG, "snapshot: corking goes to zero in message handler");
+                    }
+                    break;
+                }
             }
         }
     }
@@ -6383,12 +6442,13 @@
             // constructor, at which time the invalidation method updates it.  The cache is
             // corked initially to ensure a cached computer is not built until the end of the
             // constructor.
-            mSnapshotEnabled = SNAPSHOT_ENABLED;
-            sSnapshotCorked = true;
-            sSnapshotInvalid = true;
             mSnapshotStatistics = new SnapshotStatistics();
+            sSnapshotConsumer = this;
+            sSnapshotCorked.set(1);
+            sSnapshotInvalid.set(true);
             mLiveComputer = createLiveComputer();
             mSnapshotComputer = null;
+            mSnapshotEnabled = SNAPSHOT_ENABLED;
             registerObserver();
         }
 
@@ -18521,7 +18581,7 @@
         }
     }
 
-    @GuardedBy({"mInstallLock", "mLock"})
+    @GuardedBy("mInstallLock")
     private void installPackagesTracedLI(List<InstallRequest> requests) {
         try {
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");
@@ -24018,6 +24078,15 @@
                 dumpState.setDump(DumpState.DUMP_PER_UID_READ_TIMEOUTS);
             } else if ("snapshot".equals(cmd)) {
                 dumpState.setDump(DumpState.DUMP_SNAPSHOT_STATISTICS);
+                if (opti < args.length) {
+                    if ("--full".equals(args[opti])) {
+                        dumpState.setBrief(false);
+                        opti++;
+                    } else if ("--brief".equals(args[opti])) {
+                        dumpState.setBrief(true);
+                        opti++;
+                    }
+                }
             } else if ("write".equals(cmd)) {
                 synchronized (mLock) {
                     writeSettingsLPrTEMP();
@@ -24353,13 +24422,14 @@
                 pw.println("  Snapshots disabled");
             } else {
                 int hits = 0;
+                int level = sSnapshotCorked.get();
                 synchronized (mSnapshotLock) {
                     if (mSnapshotComputer != null) {
                         hits = mSnapshotComputer.getUsed();
                     }
                 }
                 final long now = SystemClock.currentTimeMicro();
-                mSnapshotStatistics.dump(pw, "  ", now, hits, true);
+                mSnapshotStatistics.dump(pw, "  ", now, hits, level, dumpState.isBrief());
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 1b8eee3..f5a13d5 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -351,6 +351,7 @@
 
     private final PackageManagerTracedLock mLock;
 
+    @Watched(manual = true)
     private final RuntimePermissionPersistence mRuntimePermissionsPersistence;
 
     private final File mSettingsFilename;
@@ -364,19 +365,21 @@
     /** Map from package name to settings */
     @Watched
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    final WatchedArrayMap<String, PackageSetting> mPackages = new WatchedArrayMap<>();
+    final WatchedArrayMap<String, PackageSetting> mPackages;
+    private final SnapshotCache<WatchedArrayMap<String, PackageSetting>> mPackagesSnapshot;
 
     /**
      * List of packages that were involved in installing other packages, i.e. are listed
      * in at least one app's InstallSource.
      */
     @Watched
-    private final WatchedArraySet<String> mInstallerPackages = new WatchedArraySet<>();
+    private final WatchedArraySet<String> mInstallerPackages;
+    private final SnapshotCache<WatchedArraySet<String>> mInstallerPackagesSnapshot;
 
     /** Map from package name to appId and excluded userids */
     @Watched
-    private final WatchedArrayMap<String, KernelPackageState> mKernelMapping =
-            new WatchedArrayMap<>();
+    private final WatchedArrayMap<String, KernelPackageState> mKernelMapping;
+    private final SnapshotCache<WatchedArrayMap<String, KernelPackageState>> mKernelMappingSnapshot;
 
     // List of replaced system applications
     @Watched
@@ -397,7 +400,7 @@
 
     /** Map from volume UUID to {@link VersionInfo} */
     @Watched
-    private WatchedArrayMap<String, VersionInfo> mVersion = new WatchedArrayMap<>();
+    private final WatchedArrayMap<String, VersionInfo> mVersion = new WatchedArrayMap<>();
 
     /**
      * Version details for a storage volume that may hold apps.
@@ -435,6 +438,7 @@
     }
 
     /** Device identity for the purpose of package verification. */
+    @Watched(manual = true)
     private VerifierDeviceIdentity mVerifierDeviceIdentity;
 
     // The user's preferred activities associated with particular intent
@@ -462,10 +466,12 @@
     private final WatchedSparseArray<SettingBase> mOtherAppIds;
 
     // For reading/writing settings file.
-    private final ArrayList<Signature> mPastSignatures =
-            new ArrayList<Signature>();
-    private final ArrayMap<Long, Integer> mKeySetRefs =
-            new ArrayMap<Long, Integer>();
+    @Watched
+    private final WatchedArrayList<Signature> mPastSignatures =
+            new WatchedArrayList<Signature>();
+    @Watched
+    private final WatchedArrayMap<Long, Integer> mKeySetRefs =
+            new WatchedArrayMap<Long, Integer>();
 
     // Packages that have been renamed since they were first installed.
     // Keys are the new names of the packages, values are the original
@@ -495,18 +501,21 @@
      * TODO: make this just a local variable that is passed in during package
      * scanning to make it less confusing.
      */
-    private final ArrayList<PackageSetting> mPendingPackages = new ArrayList<>();
+    @Watched
+    private final WatchedArrayList<PackageSetting> mPendingPackages = new WatchedArrayList<>();
 
     private final File mSystemDir;
 
-    public final KeySetManagerService mKeySetManagerService =
-            new KeySetManagerService(mPackages.untrackedStorage());
+    private final KeySetManagerService mKeySetManagerService;
 
     /** Settings and other information about permissions */
+    @Watched(manual = true)
     final LegacyPermissionSettings mPermissions;
 
+    @Watched(manual = true)
     private final LegacyPermissionDataProvider mPermissionDataProvider;
 
+    @Watched(manual = true)
     private final DomainVerificationManagerInternal mDomainVerificationManager;
 
     /**
@@ -532,23 +541,7 @@
             }};
     }
 
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    public Settings(Map<String, PackageSetting> pkgSettings) {
-        mLock = new PackageManagerTracedLock();
-        mPackages.putAll(pkgSettings);
-        mAppIds = new WatchedArrayList<>();
-        mOtherAppIds = new WatchedSparseArray<>();
-        mSystemDir = null;
-        mPermissions = null;
-        mRuntimePermissionsPersistence = null;
-        mPermissionDataProvider = null;
-        mSettingsFilename = null;
-        mBackupSettingsFilename = null;
-        mPackageListFilename = null;
-        mStoppedPackagesFilename = null;
-        mBackupStoppedPackagesFilename = null;
-        mKernelMappingFilename = null;
-        mDomainVerificationManager = null;
+    private void registerObservers() {
         mPackages.registerObserver(mObserver);
         mInstallerPackages.registerObserver(mObserver);
         mKernelMapping.registerObserver(mObserver);
@@ -564,7 +557,43 @@
         mRenamedPackages.registerObserver(mObserver);
         mNextAppLinkGeneration.registerObserver(mObserver);
         mDefaultBrowserApp.registerObserver(mObserver);
+        mPendingPackages.registerObserver(mObserver);
+        mPastSignatures.registerObserver(mObserver);
+        mKeySetRefs.registerObserver(mObserver);
+    }
 
+    // CONSTRUCTOR
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public Settings(Map<String, PackageSetting> pkgSettings) {
+        mPackages = new WatchedArrayMap<>();
+        mPackagesSnapshot =
+                new SnapshotCache.Auto<>(mPackages, mPackages, "Settings.mPackages");
+        mKernelMapping = new WatchedArrayMap<>();
+        mKernelMappingSnapshot =
+                new SnapshotCache.Auto<>(mKernelMapping, mKernelMapping, "Settings.mKernelMapping");
+        mInstallerPackages = new WatchedArraySet<>();
+        mInstallerPackagesSnapshot =
+                new SnapshotCache.Auto<>(mInstallerPackages, mInstallerPackages,
+                                         "Settings.mInstallerPackages");
+        mKeySetManagerService = new KeySetManagerService(mPackages);
+
+        mLock = new PackageManagerTracedLock();
+        mPackages.putAll(pkgSettings);
+        mAppIds = new WatchedArrayList<>();
+        mOtherAppIds = new WatchedSparseArray<>();
+        mSystemDir = null;
+        mPermissions = null;
+        mRuntimePermissionsPersistence = null;
+        mPermissionDataProvider = null;
+        mSettingsFilename = null;
+        mBackupSettingsFilename = null;
+        mPackageListFilename = null;
+        mStoppedPackagesFilename = null;
+        mBackupStoppedPackagesFilename = null;
+        mKernelMappingFilename = null;
+        mDomainVerificationManager = null;
+
+        registerObservers();
         Watchable.verifyWatchedAttributes(this, mObserver);
 
         mSnapshot = makeCache();
@@ -574,6 +603,18 @@
             LegacyPermissionDataProvider permissionDataProvider,
             @NonNull DomainVerificationManagerInternal domainVerificationManager,
             @NonNull PackageManagerTracedLock lock)  {
+        mPackages = new WatchedArrayMap<>();
+        mPackagesSnapshot  =
+                new SnapshotCache.Auto<>(mPackages, mPackages, "Settings.mPackages");
+        mKernelMapping = new WatchedArrayMap<>();
+        mKernelMappingSnapshot =
+                new SnapshotCache.Auto<>(mKernelMapping, mKernelMapping, "Settings.mKernelMapping");
+        mInstallerPackages = new WatchedArraySet<>();
+        mInstallerPackagesSnapshot =
+                new SnapshotCache.Auto<>(mInstallerPackages, mInstallerPackages,
+                                         "Settings.mInstallerPackages");
+        mKeySetManagerService = new KeySetManagerService(mPackages);
+
         mLock = lock;
         mAppIds = new WatchedArrayList<>();
         mOtherAppIds = new WatchedSparseArray<>();
@@ -602,22 +643,7 @@
 
         mDomainVerificationManager = domainVerificationManager;
 
-        mPackages.registerObserver(mObserver);
-        mInstallerPackages.registerObserver(mObserver);
-        mKernelMapping.registerObserver(mObserver);
-        mDisabledSysPackages.registerObserver(mObserver);
-        mBlockUninstallPackages.registerObserver(mObserver);
-        mVersion.registerObserver(mObserver);
-        mPreferredActivities.registerObserver(mObserver);
-        mPersistentPreferredActivities.registerObserver(mObserver);
-        mCrossProfileIntentResolvers.registerObserver(mObserver);
-        mSharedUsers.registerObserver(mObserver);
-        mAppIds.registerObserver(mObserver);
-        mOtherAppIds.registerObserver(mObserver);
-        mRenamedPackages.registerObserver(mObserver);
-        mNextAppLinkGeneration.registerObserver(mObserver);
-        mDefaultBrowserApp.registerObserver(mObserver);
-
+        registerObservers();
         Watchable.verifyWatchedAttributes(this, mObserver);
 
         mSnapshot = makeCache();
@@ -629,8 +655,13 @@
      * are changed by PackageManagerService APIs are deep-copied
      */
     private Settings(Settings r) {
-        final int mPackagesSize = r.mPackages.size();
-        mPackages.putAll(r.mPackages);
+        mPackages = r.mPackagesSnapshot.snapshot();
+        mPackagesSnapshot  = new SnapshotCache.Sealed<>();
+        mKernelMapping = r.mKernelMappingSnapshot.snapshot();
+        mKernelMappingSnapshot = new SnapshotCache.Sealed<>();
+        mInstallerPackages = r.mInstallerPackagesSnapshot.snapshot();
+        mInstallerPackagesSnapshot = new SnapshotCache.Sealed<>();
+        mKeySetManagerService = new KeySetManagerService(mPackages);
 
         // The following assignments satisfy Java requirements but are not
         // needed by the read-only methods.  Note especially that the lock
@@ -647,9 +678,7 @@
 
         mDomainVerificationManager = r.mDomainVerificationManager;
 
-        mInstallerPackages.addAll(r.mInstallerPackages);
-        mKernelMapping.putAll(r.mKernelMapping);
-        mDisabledSysPackages.putAll(r.mDisabledSysPackages);
+        mDisabledSysPackages.snapshot(r.mDisabledSysPackages);
         mBlockUninstallPackages.snapshot(r.mBlockUninstallPackages);
         mVersion.putAll(r.mVersion);
         mVerifierDeviceIdentity = r.mVerifierDeviceIdentity;
@@ -659,23 +688,26 @@
                 mPersistentPreferredActivities, r.mPersistentPreferredActivities);
         WatchedSparseArray.snapshot(
                 mCrossProfileIntentResolvers, r.mCrossProfileIntentResolvers);
-        mSharedUsers.putAll(r.mSharedUsers);
+        mSharedUsers.snapshot(r.mSharedUsers);
         mAppIds = r.mAppIds.snapshot();
         mOtherAppIds = r.mOtherAppIds.snapshot();
-        mPastSignatures.addAll(r.mPastSignatures);
-        mKeySetRefs.putAll(r.mKeySetRefs);
+        WatchedArrayList.snapshot(
+                mPastSignatures, r.mPastSignatures);
+        WatchedArrayMap.snapshot(
+                mKeySetRefs, r.mKeySetRefs);
         mRenamedPackages.snapshot(r.mRenamedPackages);
         mNextAppLinkGeneration.snapshot(r.mNextAppLinkGeneration);
         mDefaultBrowserApp.snapshot(r.mDefaultBrowserApp);
         // mReadMessages
-        mPendingPackages.addAll(r.mPendingPackages);
+        WatchedArrayList.snapshot(
+                mPendingPackages, r.mPendingPackages);
         mSystemDir = null;
         // mKeySetManagerService;
         mPermissions = r.mPermissions;
         mPermissionDataProvider = r.mPermissionDataProvider;
 
         // Do not register any Watchables and do not create a snapshot cache.
-        mSnapshot = null;
+        mSnapshot = new SnapshotCache.Sealed();
     }
 
     /**
@@ -2326,7 +2358,7 @@
                 serializer.startTag(null, "shared-user");
                 serializer.attribute(null, ATTR_NAME, usr.name);
                 serializer.attributeInt(null, "userId", usr.userId);
-                usr.signatures.writeXml(serializer, "sigs", mPastSignatures);
+                usr.signatures.writeXml(serializer, "sigs", mPastSignatures.untrackedStorage());
                 serializer.endTag(null, "shared-user");
             }
 
@@ -2736,11 +2768,11 @@
 
         writeUsesStaticLibLPw(serializer, pkg.usesStaticLibraries, pkg.usesStaticLibrariesVersions);
 
-        pkg.signatures.writeXml(serializer, "sigs", mPastSignatures);
+        pkg.signatures.writeXml(serializer, "sigs", mPastSignatures.untrackedStorage());
 
         if (installSource.initiatingPackageSignatures != null) {
             installSource.initiatingPackageSignatures.writeXml(
-                    serializer, "install-initiator-sigs", mPastSignatures);
+                    serializer, "install-initiator-sigs", mPastSignatures.untrackedStorage());
         }
 
         writeSigningKeySetLPr(serializer, pkg.keySetData);
@@ -2909,7 +2941,7 @@
                 } else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
                     // No longer used.
                 } else if (tagName.equals("keyset-settings")) {
-                    mKeySetManagerService.readKeySetsLPw(parser, mKeySetRefs);
+                    mKeySetManagerService.readKeySetsLPw(parser, mKeySetRefs.untrackedStorage());
                 } else if (TAG_VERSION.equals(tagName)) {
                     final String volumeUuid = XmlUtils.readStringAttribute(parser,
                             ATTR_VOLUME_UUID);
@@ -3697,7 +3729,7 @@
                 } else if (tagName.equals(TAG_ENABLED_COMPONENTS)) {
                     readEnabledComponentsLPw(packageSetting, parser, 0);
                 } else if (tagName.equals("sigs")) {
-                    packageSetting.signatures.readXml(parser, mPastSignatures);
+                    packageSetting.signatures.readXml(parser, mPastSignatures.untrackedStorage());
                 } else if (tagName.equals(TAG_PERMISSIONS)) {
                     readInstallPermissionsLPr(parser,
                             packageSetting.getLegacyPermissionState(), users);
@@ -3728,7 +3760,7 @@
                     packageSetting.keySetData.addDefinedKeySet(id, alias);
                 } else if (tagName.equals("install-initiator-sigs")) {
                     final PackageSignatures signatures = new PackageSignatures();
-                    signatures.readXml(parser, mPastSignatures);
+                    signatures.readXml(parser, mPastSignatures.untrackedStorage());
                     packageSetting.installSource =
                             packageSetting.installSource.setInitiatingPackageSignatures(signatures);
                 } else if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
@@ -3923,7 +3955,7 @@
 
                 String tagName = parser.getName();
                 if (tagName.equals("sigs")) {
-                    su.signatures.readXml(parser, mPastSignatures);
+                    su.signatures.readXml(parser, mPastSignatures.untrackedStorage());
                 } else if (tagName.equals("perms")) {
                     readInstallPermissionsLPr(parser, su.getLegacyPermissionState(), users);
                 } else {
diff --git a/services/core/java/com/android/server/pm/SnapshotStatistics.java b/services/core/java/com/android/server/pm/SnapshotStatistics.java
index c425bad5..7bf00603 100644
--- a/services/core/java/com/android/server/pm/SnapshotStatistics.java
+++ b/services/core/java/com/android/server/pm/SnapshotStatistics.java
@@ -23,6 +23,7 @@
 import android.os.SystemClock;
 import android.text.TextUtils;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.EventLogTags;
 
 import java.io.PrintWriter;
@@ -239,6 +240,11 @@
         public int mTotalUsed = 0;
 
         /**
+         * The total number of times a snapshot was bypassed because corking was in effect.
+         */
+        public int mTotalCorked = 0;
+
+        /**
          * The total number of builds that count as big, which means they took longer than
          * SNAPSHOT_BIG_BUILD_TIME_NS.
          */
@@ -291,6 +297,13 @@
             }
         }
 
+        /**
+         * Record a cork.
+         */
+        private void corked() {
+            mTotalCorked++;
+        }
+
         private Stats(long now) {
             mStartTimeUs = now;
             mTimes = new int[mTimeBins.count()];
@@ -308,6 +321,7 @@
             mUsed = Arrays.copyOf(orig.mUsed, orig.mUsed.length);
             mTotalBuilds = orig.mTotalBuilds;
             mTotalUsed = orig.mTotalUsed;
+            mTotalCorked = orig.mTotalCorked;
             mBigBuilds = orig.mBigBuilds;
             mShortLived = orig.mShortLived;
             mTotalTimeUs = orig.mTotalTimeUs;
@@ -365,6 +379,7 @@
          * Dump the summary statistics record.  Choose the header or the data.
          *    number of builds
          *    number of uses
+         *    number of corks
          *    number of big builds
          *    number of short lifetimes
          *    cumulative build time, in seconds
@@ -373,13 +388,13 @@
         private void dumpStats(PrintWriter pw, String indent, long now, boolean header) {
             dumpPrefix(pw, indent, now, header, "Summary stats");
             if (header) {
-                pw.format(Locale.US, "  %10s  %10s  %10s  %10s  %10s  %10s",
-                          "TotBlds", "TotUsed", "BigBlds", "ShortLvd",
+                pw.format(Locale.US, "  %10s  %10s  %10s  %10s  %10s  %10s  %10s",
+                          "TotBlds", "TotUsed", "TotCork", "BigBlds", "ShortLvd",
                           "TotTime", "MaxTime");
             } else {
                 pw.format(Locale.US,
-                        "  %10d  %10d  %10d  %10d  %10d  %10d",
-                        mTotalBuilds, mTotalUsed, mBigBuilds, mShortLived,
+                        "  %10d  %10d  %10d  %10d  %10d  %10d  %10d",
+                        mTotalBuilds, mTotalUsed, mTotalCorked, mBigBuilds, mShortLived,
                         mTotalTimeUs / 1000, mMaxBuildTimeUs / 1000);
             }
             pw.println();
@@ -516,7 +531,7 @@
      * @param done The time at which the snapshot rebuild completed, in ns.
      * @param hits The number of times the previous snapshot was used.
      */
-    public void rebuild(long now, long done, int hits) {
+    public final void rebuild(long now, long done, int hits) {
         // The duration has a span of about 2000s
         final int duration = (int) (done - now);
         boolean reportEvent = false;
@@ -544,9 +559,20 @@
     }
 
     /**
+     * Record a corked snapshot request.
+     */
+    public final void corked() {
+        synchronized (mLock) {
+            mShort[0].corked();
+            mLong[0].corked();
+        }
+    }
+
+    /**
      * Roll a stats array.  Shift the elements up an index and create a new element at
      * index zero.  The old element zero is completed with the specified time.
      */
+    @GuardedBy("mLock")
     private void shift(Stats[] s, long now) {
         s[0].complete(now);
         for (int i = s.length - 1; i > 0; i--) {
@@ -598,7 +624,8 @@
      * Dump the statistics.  The format is compatible with the PackageManager dumpsys
      * output.
      */
-    public void dump(PrintWriter pw, String indent, long now, int unrecorded, boolean full) {
+    public void dump(PrintWriter pw, String indent, long now, int unrecorded,
+                     int corkLevel, boolean full) {
         // Grab the raw statistics under lock, but print them outside of the lock.
         Stats[] l;
         Stats[] s;
@@ -608,7 +635,8 @@
             s = Arrays.copyOf(mShort, mShort.length);
             s[0] = new Stats(s[0]);
         }
-        pw.format(Locale.US, "%s Unrecorded hits %d", indent, unrecorded);
+        pw.format(Locale.US, "%s Unrecorded-hits: %d  Cork-level: %d", indent,
+                  unrecorded, corkLevel);
         pw.println();
         dump(pw, indent, now, l, s, "stats");
         if (!full) {
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 90a3c58..b7a069e 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -151,6 +151,9 @@
           "include-filter": "com.android.server.pm.parsing.SystemPartitionParseTest"
         }
       ]
+    },
+    {
+      "name": "CtsPackageManagerBootTestCases"
     }
   ],
   "imports": [
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 50f958f..a7bac20 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -37,6 +37,7 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.media.RingtoneManager;
+import android.media.midi.MidiManager;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Environment;
@@ -866,6 +867,11 @@
             grantPermissionsToSystemPackage(pm, systemCaptionsServicePackageName, userId,
                     MICROPHONE_PERMISSIONS);
         }
+
+        // Bluetooth MIDI Service
+        grantSystemFixedPermissionsToSystemPackage(pm,
+                MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE, userId,
+                NEARBY_DEVICES_PERMISSIONS);
     }
 
     private String getDefaultSystemHandlerActivityPackageForCategory(PackageManagerWrapper pm,
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 68e7bdb..09f8941 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
@@ -179,7 +179,7 @@
             true, /* enableQuickDoze */
             true, /* forceAllAppsStandby */
             true, /* forceBackgroundCheck */
-            PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF, /* locationMode */
+            PowerManager.LOCATION_MODE_FOREGROUND_ONLY, /* locationMode */
             PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY /* soundTriggerMode */
     );
 
diff --git a/services/core/java/com/android/server/utils/SnapshotCache.java b/services/core/java/com/android/server/utils/SnapshotCache.java
index b4b8835..42b9b23 100644
--- a/services/core/java/com/android/server/utils/SnapshotCache.java
+++ b/services/core/java/com/android/server/utils/SnapshotCache.java
@@ -19,6 +19,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
+import java.util.WeakHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
 /**
  * A class that caches snapshots.  Instances are instantiated on a {@link Watchable}; when the
  * {@link Watchable} reports a change, the cache is cleared.  The snapshot() method fetches the
@@ -35,25 +38,65 @@
      */
     private static final boolean ENABLED = true;
 
+    /**
+     * The statistics for a single cache.  The object records the number of times a
+     * snapshot was reused and the number of times a snapshot was rebuilt.
+     */
+    private static class Statistics {
+        final String mName;
+        private final AtomicInteger mReused = new AtomicInteger(0);
+        private final AtomicInteger mRebuilt = new AtomicInteger(0);
+        Statistics(@NonNull String n) {
+            mName = n;
+        }
+    }
+
     // The source object from which snapshots are created.  This may be null if createSnapshot()
     // does not require it.
     protected final T mSource;
 
     // The cached snapshot
-    private T mSnapshot = null;
+    private volatile T mSnapshot = null;
 
     // True if the snapshot is sealed and may not be modified.
-    private boolean mSealed = false;
+    private volatile boolean mSealed = false;
+
+    // The statistics for this cache.  This may be null.
+    private final Statistics mStatistics;
+
+    /**
+     * The global list of caches.
+     */
+    private static final WeakHashMap<SnapshotCache, Void> sCaches = new WeakHashMap<>();
 
     /**
      * Create a cache with a source object for rebuilding snapshots and a
-     * {@link Watchable} that notifies when the cache is invalid.
+     * {@link Watchable} that notifies when the cache is invalid.  If the name is null
+     * then statistics are not collected for this cache.
+     * @param source Source data for rebuilding snapshots.
+     * @param watchable The object that notifies when the cache is invalid.
+     * @param name The name of the cache, for statistics reporting.
+     */
+    public SnapshotCache(@Nullable T source, @NonNull Watchable watchable, @Nullable String name) {
+        mSource = source;
+        watchable.registerObserver(this);
+        if (name != null) {
+            mStatistics = new Statistics(name);
+            sCaches.put(this, null);
+        } else {
+            mStatistics = null;
+        }
+    }
+
+    /**
+     * Create a cache with a source object for rebuilding snapshots and a
+     * {@link Watchable} that notifies when the cache is invalid.  The name is null in
+     * this API.
      * @param source Source data for rebuilding snapshots.
      * @param watchable The object that notifies when the cache is invalid.
      */
     public SnapshotCache(@Nullable T source, @NonNull Watchable watchable) {
-        mSource = source;
-        watchable.registerObserver(this);
+        this(source, watchable, null);
     }
 
     /**
@@ -63,13 +106,14 @@
     public SnapshotCache() {
         mSource = null;
         mSealed = true;
+        mStatistics = null;
     }
 
     /**
      * Notify the object that the source object has changed.  If the local object is sealed then
      * IllegalStateException is thrown.  Otherwise, the cache is cleared.
      */
-    public void onChange(@Nullable Watchable what) {
+    public final void onChange(@Nullable Watchable what) {
         if (mSealed) {
             throw new IllegalStateException("attempt to change a sealed object");
         }
@@ -79,7 +123,7 @@
     /**
      * Seal the cache.  Attempts to modify the cache will generate an exception.
      */
-    public void seal() {
+    public final void seal() {
         mSealed = true;
     }
 
@@ -88,11 +132,14 @@
      * new snapshot and saves it in the cache.
      * @return A snapshot as returned by createSnapshot() and possibly cached.
      */
-    public T snapshot() {
+    public final T snapshot() {
         T s = mSnapshot;
         if (s == null || !ENABLED) {
             s = createSnapshot();
             mSnapshot = s;
+            if (mStatistics != null) mStatistics.mRebuilt.incrementAndGet();
+        } else {
+            if (mStatistics != null) mStatistics.mReused.incrementAndGet();
         }
         return s;
     }
@@ -123,4 +170,25 @@
             throw new UnsupportedOperationException("cannot snapshot a sealed snaphot");
         }
     }
+
+    /**
+     * A snapshot cache suitable for Snappable types.  The key is that Snappable types
+     * have a known implementation of createSnapshot() so that this class is concrete.
+     * @param <T> The class whose snapshot is being cached.
+     */
+    public static class Auto<T extends Snappable<T>> extends SnapshotCache<T> {
+        public Auto(@NonNull T source, @NonNull Watchable watchable, @Nullable String name) {
+            super(source, watchable, name);
+        }
+        public Auto(@NonNull T source, @NonNull Watchable watchable) {
+            this(source, watchable, null);
+        }
+        /**
+         * Concrete createSnapshot() using the snapshot() method of <T>.
+         */
+        public T createSnapshot() {
+            return mSource.snapshot();
+        }
+    }
+
 }
diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
index 8818023..3bdeec0 100644
--- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
+++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
@@ -158,8 +158,15 @@
      * carrier owned networks may be selected, as the request specifies only subIds in the VCN's
      * subscription group, while the VCN networks are excluded by virtue of not having subIds set on
      * the VCN-exposed networks.
+     *
+     * <p>If the VCN that this UnderlyingNetworkTracker belongs to is in test-mode, this will return
+     * a NetworkRequest that only matches Test Networks.
      */
     private NetworkRequest getRouteSelectionRequest() {
+        if (mVcnContext.isInTestMode()) {
+            return getTestNetworkRequest(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup));
+        }
+
         return getBaseNetworkRequestBuilder()
                 .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
                 .build();
@@ -210,6 +217,15 @@
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
     }
 
+    /** Builds and returns a NetworkRequest for the given subIds to match Test Networks. */
+    private NetworkRequest getTestNetworkRequest(@NonNull Set<Integer> subIds) {
+        return new NetworkRequest.Builder()
+                .clearCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+                .setSubscriptionIds(subIds)
+                .build();
+    }
+
     /**
      * Update this UnderlyingNetworkTracker's TelephonySubscriptionSnapshot.
      *
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index 7399e56..d958222 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -31,14 +31,17 @@
     @NonNull private final Context mContext;
     @NonNull private final Looper mLooper;
     @NonNull private final VcnNetworkProvider mVcnNetworkProvider;
+    private final boolean mIsInTestMode;
 
     public VcnContext(
             @NonNull Context context,
             @NonNull Looper looper,
-            @NonNull VcnNetworkProvider vcnNetworkProvider) {
+            @NonNull VcnNetworkProvider vcnNetworkProvider,
+            boolean isInTestMode) {
         mContext = Objects.requireNonNull(context, "Missing context");
         mLooper = Objects.requireNonNull(looper, "Missing looper");
         mVcnNetworkProvider = Objects.requireNonNull(vcnNetworkProvider, "Missing networkProvider");
+        mIsInTestMode = isInTestMode;
     }
 
     @NonNull
@@ -56,6 +59,10 @@
         return mVcnNetworkProvider;
     }
 
+    public boolean isInTestMode() {
+        return mIsInTestMode;
+    }
+
     /**
      * Verifies that the caller is running on the VcnContext Thread.
      *
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9f51d97..8c4b75b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2483,39 +2483,52 @@
     }
 
     /**
-     * @return whether this activity supports split-screen multi-window and can be put in the docked
-     *         root task.
+     * @return whether this activity supports split-screen multi-window and can be put in
+     *         split-screen.
      */
     @Override
     public boolean supportsSplitScreenWindowingMode() {
-        // An activity can not be docked even if it is considered resizeable because it only
-        // supports picture-in-picture mode but has a non-resizeable resizeMode
+        return supportsSplitScreenWindowingModeInDisplayArea(getDisplayArea());
+    }
+
+    /**
+     * @return whether this activity supports split-screen multi-window and can be put in
+     *         split-screen if it is in the given {@link TaskDisplayArea}.
+     */
+    boolean supportsSplitScreenWindowingModeInDisplayArea(@Nullable TaskDisplayArea tda) {
         return super.supportsSplitScreenWindowingMode()
-                && mAtmService.mSupportsSplitScreenMultiWindow && supportsMultiWindow();
+                && mAtmService.mSupportsSplitScreenMultiWindow
+                && supportsMultiWindowInDisplayArea(tda);
+    }
+
+    boolean supportsFreeform() {
+        return supportsFreeformInDisplayArea(getDisplayArea());
     }
 
     /**
      * @return whether this activity supports freeform multi-window and can be put in the freeform
-     *         root task.
+     *         windowing mode if it is in the given {@link TaskDisplayArea}.
      */
-    boolean supportsFreeform() {
-        return mAtmService.mSupportsFreeformWindowManagement && supportsMultiWindow();
+    boolean supportsFreeformInDisplayArea(@Nullable TaskDisplayArea tda) {
+        return mAtmService.mSupportsFreeformWindowManagement
+                && supportsMultiWindowInDisplayArea(tda);
+    }
+
+    boolean supportsMultiWindow() {
+        return supportsMultiWindowInDisplayArea(getDisplayArea());
     }
 
     /**
-     * @return whether this activity supports multi-window.
+     * @return whether this activity supports multi-window if it is in the given
+     *         {@link TaskDisplayArea}.
      */
-    boolean supportsMultiWindow() {
-        return mAtmService.mSupportsMultiWindow && !isActivityTypeHome()
-                && (isResizeable() || mAtmService.mDevEnableNonResizableMultiWindow);
-    }
-
-    // TODO(b/176061101) replace supportsMultiWindow() after fixing tests.
-    boolean supportsMultiWindow2() {
+    boolean supportsMultiWindowInDisplayArea(@Nullable TaskDisplayArea tda) {
+        if (isActivityTypeHome()) {
+            return false;
+        }
         if (!mAtmService.mSupportsMultiWindow) {
             return false;
         }
-        final TaskDisplayArea tda = getDisplayArea();
         if (tda == null) {
             return false;
         }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 8583061..4edcfa9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1684,7 +1684,8 @@
 
         // Leave the task in its current root task or a fullscreen root task if it isn't
         // resizeable and the preferred root task is in multi-window mode.
-        if (inMultiWindowMode && !task.supportsMultiWindow()) {
+        if (inMultiWindowMode
+                && !task.supportsMultiWindowInDisplayArea(rootTask.getDisplayArea())) {
             Slog.w(TAG, "Can not move unresizeable task=" + task + " to multi-window root task="
                     + rootTask + " Moving to a fullscreen root task instead.");
             if (prevRootTask != null) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 30f69dd79..37e15c7 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -92,6 +92,7 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
 import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
@@ -2667,7 +2668,8 @@
         if (oldImmersiveMode != newImmersiveMode) {
             mLastImmersiveMode = newImmersiveMode;
             // The immersive confirmation window should be attached to the immersive window root.
-            final int rootDisplayAreaId = win.getRootDisplayArea().mFeatureId;
+            final RootDisplayArea root = win.getRootDisplayArea();
+            final int rootDisplayAreaId = root == null ? FEATURE_UNDEFINED : root.mFeatureId;
             mImmersiveModeConfirmation.immersiveModeChangedLw(rootDisplayAreaId, newImmersiveMode,
                     mService.mPolicy.isUserSetupComplete(),
                     isNavBarEmpty(disableFlags));
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ab7e65c..0879ddd 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3046,7 +3046,7 @@
         // There is a 1-to-1 relationship between root task and task when not in
         // primary split-windowing mode.
         if (task.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                && r.supportsSplitScreenWindowingMode()
+                && r.supportsSplitScreenWindowingModeInDisplayArea(task.getDisplayArea())
                 && (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                 || windowingMode == WINDOWING_MODE_UNDEFINED)) {
             return true;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index be01173..f3368c1 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -61,6 +61,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.SurfaceControl.METADATA_TASK_ID;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
@@ -1670,6 +1671,14 @@
         return isUidPresent;
     }
 
+    ActivityRecord topActivityContainsStartingWindow() {
+        if (getParent() == null) {
+            return null;
+        }
+        return getActivity((r) -> r.getWindow(window ->
+                window.getBaseType() == TYPE_APPLICATION_STARTING) != null);
+    }
+
     ActivityRecord topActivityWithStartingWindow() {
         if (getParent() == null) {
             return null;
@@ -1977,32 +1986,46 @@
 
     @Override
     public boolean supportsSplitScreenWindowingMode() {
-        final Task topTask = getTopMostTask();
-        return super.supportsSplitScreenWindowingMode()
-                && (topTask == null || topTask.supportsSplitScreenWindowingModeInner());
+        return supportsSplitScreenWindowingModeInDisplayArea(getDisplayArea());
     }
 
-    private boolean supportsSplitScreenWindowingModeInner() {
+    boolean supportsSplitScreenWindowingModeInDisplayArea(@Nullable TaskDisplayArea tda) {
+        final Task topTask = getTopMostTask();
+        return super.supportsSplitScreenWindowingMode()
+                && (topTask == null || topTask.supportsSplitScreenWindowingModeInner(tda));
+    }
+
+    private boolean supportsSplitScreenWindowingModeInner(@Nullable TaskDisplayArea tda) {
         return super.supportsSplitScreenWindowingMode()
                 && mAtmService.mSupportsSplitScreenMultiWindow
-                && supportsMultiWindow();
+                && supportsMultiWindowInDisplayArea(tda);
     }
 
     boolean supportsFreeform() {
-        return mAtmService.mSupportsFreeformWindowManagement && supportsMultiWindow();
+        return supportsFreeformInDisplayArea(getDisplayArea());
+    }
+
+    /**
+     * @return whether this task supports freeform multi-window if it is in the given
+     *         {@link TaskDisplayArea}.
+     */
+    boolean supportsFreeformInDisplayArea(@Nullable TaskDisplayArea tda) {
+        return mAtmService.mSupportsFreeformWindowManagement
+                && supportsMultiWindowInDisplayArea(tda);
     }
 
     boolean supportsMultiWindow() {
-        return mAtmService.mSupportsMultiWindow
-                && (isResizeable() || mAtmService.mDevEnableNonResizableMultiWindow);
+        return supportsMultiWindowInDisplayArea(getDisplayArea());
     }
 
-    // TODO(b/176061101) replace supportsMultiWindow() after fixing tests.
-    boolean supportsMultiWindow2() {
+    /**
+     * @return whether this task supports multi-window if it is in the given
+     *         {@link TaskDisplayArea}.
+     */
+    boolean supportsMultiWindowInDisplayArea(@Nullable TaskDisplayArea tda) {
         if (!mAtmService.mSupportsMultiWindow) {
             return false;
         }
-        final TaskDisplayArea tda = getDisplayArea();
         if (tda == null) {
             return false;
         }
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 368e6dd..ccfdb8c 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1614,18 +1614,18 @@
         boolean supportsPip = mAtmService.mSupportsPictureInPicture;
         if (supportsMultiWindow) {
             if (task != null) {
-                supportsSplitScreen = task.supportsSplitScreenWindowingMode();
-                supportsFreeform = task.supportsFreeform();
-                supportsMultiWindow = task.supportsMultiWindow()
+                supportsSplitScreen = task.supportsSplitScreenWindowingModeInDisplayArea(this);
+                supportsFreeform = task.supportsFreeformInDisplayArea(this);
+                supportsMultiWindow = task.supportsMultiWindowInDisplayArea(this)
                         // When the activity needs to be moved to PIP while the Task is not in PIP,
                         // it can be moved to a new created PIP Task, so WINDOWING_MODE_PINNED is
                         // always valid for Task as long as the device supports it.
                         || (windowingMode == WINDOWING_MODE_PINNED && supportsPip);
             } else if (r != null) {
-                supportsSplitScreen = r.supportsSplitScreenWindowingMode();
-                supportsFreeform = r.supportsFreeform();
+                supportsSplitScreen = r.supportsSplitScreenWindowingModeInDisplayArea(this);
+                supportsFreeform = r.supportsFreeformInDisplayArea(this);
                 supportsPip = r.supportsPictureInPicture();
-                supportsMultiWindow = r.supportsMultiWindow();
+                supportsMultiWindow = r.supportsMultiWindowInDisplayArea(this);
             }
         }
 
@@ -2078,14 +2078,15 @@
                 task.finishAllActivitiesImmediately();
             } else {
                 // Reparent task to corresponding launch root or display area.
-                final WindowContainer launchRoot = task.supportsSplitScreenWindowingMode()
-                        ? toDisplayArea.getLaunchRootTask(
-                                task.getWindowingMode(),
-                                task.getActivityType(),
-                                null /* options */,
-                                null /* sourceTask */,
-                                0 /* launchFlags */)
-                        : null;
+                final WindowContainer launchRoot =
+                        task.supportsSplitScreenWindowingModeInDisplayArea(toDisplayArea)
+                                ? toDisplayArea.getLaunchRootTask(
+                                        task.getWindowingMode(),
+                                        task.getActivityType(),
+                                        null /* options */,
+                                        null /* sourceTask */,
+                                        0 /* launchFlags */)
+                                : null;
                 task.reparent(launchRoot == null ? toDisplayArea : launchRoot, POSITION_TOP);
 
                 // Set the windowing mode to undefined by default to let the root task inherited the
@@ -2101,7 +2102,8 @@
 
         if (lastReparentedRootTask != null) {
             if (toDisplayArea.isSplitScreenModeActivated()
-                    && !lastReparentedRootTask.supportsSplitScreenWindowingMode()) {
+                    && !lastReparentedRootTask.supportsSplitScreenWindowingModeInDisplayArea(
+                            toDisplayArea)) {
                 // Dismiss split screen if the last reparented root task doesn't support split mode.
                 mAtmService.getTaskChangeNotificationController()
                         .notifyActivityDismissingDockedRootTask();
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 0bc7999..cc0471c 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -612,7 +612,7 @@
 
     private boolean shouldLaunchUnresizableAppInFreeform(ActivityRecord activity,
             TaskDisplayArea displayArea) {
-        if (!activity.supportsFreeform() || activity.isResizeable()) {
+        if (!activity.supportsFreeformInDisplayArea(displayArea) || activity.isResizeable()) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index fb481b4..cc8ee60 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -184,7 +184,7 @@
             Rect mainFrame = null;
             final boolean playShiftUpAnimation = !task.inMultiWindowMode();
             if (prepareAnimation && playShiftUpAnimation) {
-                final ActivityRecord topActivity = task.topActivityWithStartingWindow();
+                final ActivityRecord topActivity = task.topActivityContainsStartingWindow();
                 if (topActivity != null) {
                     final WindowState mainWindow =
                             topActivity.findMainWindow(false/* includeStartingApp */);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index c29211f..2ff3683 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -569,14 +569,15 @@
                     if (newParent.asTaskDisplayArea() != null) {
                         // For now, reparenting to displayarea is different from other reparents...
                         as.reparent(newParent.asTaskDisplayArea(), hop.getToTop());
-                    } else {
+                    } else if (newParent.asTask() != null) {
                         if (newParent.inMultiWindowMode() && task.isLeafTask()) {
                             if (newParent.inPinnedWindowingMode()) {
                                 Slog.w(TAG, "Can't support moving a task to another PIP window..."
                                         + " newParent=" + newParent + " task=" + task);
                                 return 0;
                             }
-                            if (!task.supportsMultiWindow()) {
+                            if (!task.supportsMultiWindowInDisplayArea(
+                                    newParent.asTask().getDisplayArea())) {
                                 Slog.w(TAG, "Can't support task that doesn't support multi-window"
                                         + " mode in multi-window mode... newParent=" + newParent
                                         + " task=" + task);
@@ -586,6 +587,9 @@
                         task.reparent((Task) newParent,
                                 hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
                                 false /*moveParents*/, "sanitizeAndApplyHierarchyOp");
+                    } else {
+                        throw new RuntimeException("Can only reparent task to another task or"
+                                + " taskDisplayArea, but not " + newParent);
                     }
                 } else {
                     final Task rootTask = (Task) (
@@ -641,6 +645,9 @@
         }
 
         final boolean newParentInMultiWindow = newParent.inMultiWindowMode();
+        final TaskDisplayArea newParentTda = newParent.asTask() != null
+                ? newParent.asTask().getDisplayArea()
+                : newParent.asTaskDisplayArea();
         final WindowContainer finalCurrentParent = currentParent;
         Slog.i(TAG, "reparentChildrenTasksHierarchyOp"
                 + " currentParent=" + currentParent + " newParent=" + newParent + " hop=" + hop);
@@ -656,7 +663,7 @@
                 // are reparenting from.
                 return;
             }
-            if (newParentInMultiWindow && !task.supportsMultiWindow()) {
+            if (newParentInMultiWindow && !task.supportsMultiWindowInDisplayArea(newParentTda)) {
                 Slog.e(TAG, "reparentChildrenTasksHierarchyOp non-resizeable task to multi window,"
                         + " task=" + task);
                 return;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 540035f..b121bfc 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2412,14 +2412,14 @@
         }
 
         if (startingWindow && StartingSurfaceController.DEBUG_ENABLE_SHELL_DRAWER) {
-            // cancel the remove starting window animation on shell
+            // Cancel the remove starting window animation on shell. The main window might changed
+            // during animating, checking for all windows would be safer.
             if (mActivityRecord != null) {
-                final WindowState mainWindow =
-                        mActivityRecord.findMainWindow(false/* includeStartingApp */);
-                if (mainWindow != null && mainWindow.isSelfAnimating(0 /* flags */,
-                        ANIMATION_TYPE_STARTING_REVEAL)) {
-                    mainWindow.cancelAnimation();
-                }
+                mActivityRecord.forAllWindows(w -> {
+                    if (w.isSelfAnimating(0, ANIMATION_TYPE_STARTING_REVEAL)) {
+                        w.cancelAnimation();
+                    }
+                }, true);
             }
         }
 
@@ -2682,6 +2682,13 @@
         }
     }
 
+    /**
+     * Move the touch gesture from the currently touched window on this display to this window.
+     */
+    public boolean transferTouch() {
+        return mWmService.mInputManager.transferTouch(mInputChannelToken);
+    }
+
     void disposeInputChannel() {
         if (mDeadWindowEventReceiver != null) {
             mDeadWindowEventReceiver.dispose();
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index cca62b9..eb9c8db 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1834,8 +1834,19 @@
     }
 }
 
-static void nativeSetPointerSpeed(JNIEnv* /* env */,
-        jclass /* clazz */, jlong ptr, jint speed) {
+static jboolean nativeTransferTouch(JNIEnv* env, jclass /* clazz */, jlong ptr,
+                                    jobject destChannelTokenObj) {
+    sp<IBinder> destChannelToken = ibinderForJavaObject(env, destChannelTokenObj);
+
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    if (im->getInputManager()->getDispatcher()->transferTouch(destChannelToken)) {
+        return JNI_TRUE;
+    } else {
+        return JNI_FALSE;
+    }
+}
+
+static void nativeSetPointerSpeed(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint speed) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
 
     im->setPointerSpeed(speed);
@@ -2308,6 +2319,7 @@
         {"nativeSetSystemUiLightsOut", "(JZ)V", (void*)nativeSetSystemUiLightsOut},
         {"nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;Z)Z",
          (void*)nativeTransferTouchFocus},
+        {"nativeTransferTouch", "(JLandroid/os/IBinder;)Z", (void*)nativeTransferTouch},
         {"nativeSetPointerSpeed", "(JI)V", (void*)nativeSetPointerSpeed},
         {"nativeSetShowTouches", "(JZ)V", (void*)nativeSetShowTouches},
         {"nativeSetInteractive", "(JZ)V", (void*)nativeSetInteractive},
diff --git a/services/net/TEST_MAPPING b/services/net/TEST_MAPPING
index 7025dd1..7eca1f9 100644
--- a/services/net/TEST_MAPPING
+++ b/services/net/TEST_MAPPING
@@ -2,6 +2,9 @@
   "imports": [
     {
       "path": "frameworks/base/core/java/android/net"
+    },
+    {
+      "path": "packages/modules/Wifi/framework"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
new file mode 100644
index 0000000..b8fbe34
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.face.ISession;
+import android.os.Handler;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.sensors.LockoutCache;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+@Presubmit
+@SmallTest
+public class SensorTest {
+
+    private static final String TAG = "SensorTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+    private static final byte[] HAT = new byte[69];
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private IBiometricService mBiometricService;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
+    @Mock
+    private Sensor.HalSessionCallback.Callback mHalSessionCallback;
+    @Mock
+    private LockoutResetDispatcher mLockoutResetDispatcher;
+
+    private final TestLooper mLooper = new TestLooper();
+    private final LockoutCache mLockoutCache = new LockoutCache();
+
+    private UserAwareBiometricScheduler mScheduler;
+    private Sensor.HalSessionCallback mHalCallback;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
+
+        mScheduler = new UserAwareBiometricScheduler(TAG,
+                null /* gestureAvailabilityDispatcher */,
+                () -> USER_ID,
+                mUserSwitchCallback);
+        mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
+                TAG, mScheduler, SENSOR_ID,
+                USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback);
+    }
+
+    @Test
+    public void halSessionCallback_respondsToResetLockout() throws Exception {
+        doAnswer((Answer<Void>) invocationOnMock -> {
+            mHalCallback.onLockoutCleared();
+            return null;
+        }).when(mSession).resetLockout(any());
+        mLockoutCache.setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_TIMED);
+
+        mScheduler.scheduleClientMonitor(new FaceResetLockoutClient(mContext,
+                () -> mSession, USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache,
+                mLockoutResetDispatcher));
+        mLooper.dispatchAll();
+
+        verifyNotLocked();
+    }
+
+    @Test
+    public void halSessionCallback_respondsToUnprovokedResetLockout() {
+        mLockoutCache.setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_TIMED);
+
+        mHalCallback.onLockoutCleared();
+        mLooper.dispatchAll();
+
+        verifyNotLocked();
+    }
+
+    private void verifyNotLocked() {
+        assertEquals(LockoutTracker.LOCKOUT_NONE, mLockoutCache.getLockoutModeForUser(USER_ID));
+        verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
new file mode 100644
index 0000000..5dfc248
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.os.Handler;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.sensors.LockoutCache;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+@Presubmit
+@SmallTest
+public class SensorTest {
+
+    private static final String TAG = "SensorTest";
+    private static final int USER_ID = 2;
+    private static final int SENSOR_ID = 4;
+    private static final byte[] HAT = new byte[69];
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private IBiometricService mBiometricService;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
+    @Mock
+    private Sensor.HalSessionCallback.Callback mHalSessionCallback;
+    @Mock
+    private LockoutResetDispatcher mLockoutResetDispatcher;
+
+    private final TestLooper mLooper = new TestLooper();
+    private final LockoutCache mLockoutCache = new LockoutCache();
+
+    private UserAwareBiometricScheduler mScheduler;
+    private Sensor.HalSessionCallback mHalCallback;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
+
+        mScheduler = new UserAwareBiometricScheduler(TAG,
+                null /* gestureAvailabilityDispatcher */,
+                () -> USER_ID,
+                mUserSwitchCallback);
+        mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
+                TAG, mScheduler, SENSOR_ID,
+                USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback);
+    }
+
+    @Test
+    public void halSessionCallback_respondsToResetLockout() throws Exception {
+        doAnswer((Answer<Void>) invocationOnMock -> {
+            mHalCallback.onLockoutCleared();
+            return null;
+        }).when(mSession).resetLockout(any());
+        mLockoutCache.setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_TIMED);
+
+        mScheduler.scheduleClientMonitor(new FingerprintResetLockoutClient(mContext,
+                () -> mSession, USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache,
+                mLockoutResetDispatcher));
+        mLooper.dispatchAll();
+
+        verifyNotLocked();
+    }
+
+    @Test
+    public void halSessionCallback_respondsToUnprovokedResetLockout() {
+        mLockoutCache.setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_TIMED);
+
+        mHalCallback.onLockoutCleared();
+        mLooper.dispatchAll();
+
+        verifyNotLocked();
+    }
+
+    private void verifyNotLocked() {
+        assertEquals(LockoutTracker.LOCKOUT_NONE, mLockoutCache.getLockoutModeForUser(USER_ID));
+        verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 5caff3d..7003ef7 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -92,7 +92,7 @@
         }
 
         @Override
-        public void tryToCreateTypeface(File file) throws IOException {
+        public void tryToCreateTypeface(File file) throws Throwable {
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index c08857c..6cc8d471 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
@@ -68,10 +68,12 @@
 
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
-        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
-                mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
-        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
-        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenAnswer(i ->
+                new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mTestLooper.getLooper())));
+        when(mContextSpy.getSystemService(PowerManager.class)).thenAnswer(i ->
+                new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mTestLooper.getLooper())));
         when(mIPowerManagerMock.isInteractive()).thenReturn(true);
 
         mHdmiControlService = new HdmiControlService(mContextSpy) {
@@ -97,7 +99,8 @@
 
             @Override
             protected PowerManager getPowerManager() {
-                return powerManager;
+                return new PowerManager(mContextSpy, mIPowerManagerMock,
+                        mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
             }
 
             @Override
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index ee9de07..97bd066 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -70,10 +70,12 @@
 
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
-        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
-                mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
-        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
-        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenAnswer(i ->
+                new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mTestLooper.getLooper())));
+        when(mContextSpy.getSystemService(PowerManager.class)).thenAnswer(i ->
+                new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mTestLooper.getLooper())));
         when(mIPowerManagerMock.isInteractive()).thenReturn(true);
 
         HdmiControlService hdmiControlService =
@@ -89,7 +91,8 @@
 
                     @Override
                     protected PowerManager getPowerManager() {
-                        return powerManager;
+                        return new PowerManager(mContextSpy, mIPowerManagerMock,
+                                mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
                     }
 
                     @Override
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index d5df071..29c9b40 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -71,10 +71,12 @@
 
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
-        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
-                mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
-        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
-        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenAnswer(i ->
+                new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mTestLooper.getLooper())));
+        when(mContextSpy.getSystemService(PowerManager.class)).thenAnswer(i ->
+                new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mTestLooper.getLooper())));
         when(mIPowerManagerMock.isInteractive()).thenReturn(true);
 
         HdmiControlService hdmiControlService =
@@ -85,7 +87,8 @@
 
                     @Override
                     protected PowerManager getPowerManager() {
-                        return powerManager;
+                        return new PowerManager(mContextSpy, mIPowerManagerMock,
+                                mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
                     }
 
                     @Override
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index 4f97c26..650ffe9 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -80,10 +80,12 @@
 
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
-        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
-                mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
-        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
-        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenAnswer(i ->
+                new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mTestLooper.getLooper())));
+        when(mContextSpy.getSystemService(PowerManager.class)).thenAnswer(i ->
+                new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mTestLooper.getLooper())));
         when(mIPowerManagerMock.isInteractive()).thenReturn(true);
 
         mHdmiControlService = new HdmiControlService(mContextSpy) {
@@ -109,7 +111,8 @@
 
             @Override
             protected PowerManager getPowerManager() {
-                return powerManager;
+                return new PowerManager(mContextSpy, mIPowerManagerMock,
+                        mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
             }
 
             @Override
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionTest.java
index 678f8b2..fa5cb67 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionTest.java
@@ -103,8 +103,6 @@
 
         Context context = InstrumentationRegistry.getTargetContext();
         mMyLooper = mTestLooper.getLooper();
-        PowerManager powerManager = new PowerManager(context, mIPowerManagerMock,
-                mIThermalServiceMock, new Handler(mMyLooper));
 
         mHdmiControlService =
                 new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
@@ -129,7 +127,8 @@
 
                     @Override
                     protected PowerManager getPowerManager() {
-                        return powerManager;
+                        return new PowerManager(context, mIPowerManagerMock,
+                                mIThermalServiceMock, new Handler(mMyLooper));
                     }
                 };
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index 45409c8..29f62b5 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -96,9 +97,10 @@
         mContextSpy = spy(new ContextWrapper(
                 InstrumentationRegistry.getInstrumentation().getTargetContext()));
 
-        PowerManager powerManager = new PowerManager(
-                mContextSpy, mIPowerManagerMock, mIThermalServiceMock, new Handler(mLooper));
-        doReturn(powerManager).when(mContextSpy).getSystemService(Context.POWER_SERVICE);
+
+        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenAnswer(i ->
+                new PowerManager(mContextSpy, mIPowerManagerMock,
+                        mIThermalServiceMock, new Handler(mLooper)));
         doReturn(true).when(mIPowerManagerMock).isInteractive();
 
         mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy));
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 38a44c6..7911c40 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -94,8 +94,6 @@
 
         Context context = InstrumentationRegistry.getTargetContext();
         mMyLooper = mTestLooper.getLooper();
-        PowerManager powerManager = new PowerManager(context, mIPowerManagerMock,
-                mIThermalServiceMock, new Handler(mMyLooper));
 
         mHdmiControlService =
             new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
@@ -183,7 +181,8 @@
 
                 @Override
                 protected PowerManager getPowerManager() {
-                    return powerManager;
+                    return new PowerManager(context, mIPowerManagerMock,
+                            mIThermalServiceMock, new Handler(mMyLooper));
                 }
             };
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 1ac0150..5fbf8de 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -92,8 +92,6 @@
 
         Context context = InstrumentationRegistry.getTargetContext();
         mMyLooper = mTestLooper.getLooper();
-        PowerManager powerManager = new PowerManager(context, mIPowerManagerMock,
-                mIThermalServiceMock, new Handler(mMyLooper));
 
         mHdmiControlService =
                 new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
@@ -140,7 +138,8 @@
 
                     @Override
                     protected PowerManager getPowerManager() {
-                        return powerManager;
+                        return new PowerManager(context, mIPowerManagerMock,
+                                mIThermalServiceMock, new Handler(mMyLooper));
                     }
                 };
 
@@ -168,6 +167,8 @@
         mPlaybackLogicalAddress = mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress();
         mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
         mNativeWrapper.clearResultMessages();
+        mHdmiCecLocalDevicePlayback.mPlaybackDeviceActionOnRoutingControl =
+                HdmiProperties.playback_device_action_on_routing_control_values.NONE;
     }
 
     @Test
@@ -698,9 +699,12 @@
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_TV);
         HdmiCecMessage standbyMessageBroadcast = HdmiCecMessageBuilder.buildStandby(
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_BROADCAST);
+        HdmiCecMessage inactiveSource = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
 
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageToTv);
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageBroadcast);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSource);
     }
 
     @Test
@@ -720,9 +724,12 @@
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_TV);
         HdmiCecMessage standbyMessageBroadcast = HdmiCecMessageBuilder.buildStandby(
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_BROADCAST);
+        HdmiCecMessage inactiveSource = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
 
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageToTv);
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageBroadcast);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSource);
     }
 
     @Test
@@ -742,9 +749,12 @@
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_TV);
         HdmiCecMessage standbyMessageBroadcast = HdmiCecMessageBuilder.buildStandby(
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_BROADCAST);
+        HdmiCecMessage inactiveSource = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
 
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageToTv);
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageBroadcast);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSource);
     }
 
     @Test
@@ -764,9 +774,12 @@
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_TV);
         HdmiCecMessage standbyMessageBroadcast = HdmiCecMessageBuilder.buildStandby(
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_BROADCAST);
+        HdmiCecMessage inactiveSource = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
 
         assertThat(mNativeWrapper.getResultMessages()).contains(standbyMessageToTv);
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageBroadcast);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSource);
     }
 
     @Test
@@ -786,9 +799,12 @@
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_TV);
         HdmiCecMessage standbyMessageBroadcast = HdmiCecMessageBuilder.buildStandby(
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_BROADCAST);
+        HdmiCecMessage inactiveSource = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
 
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageToTv);
         assertThat(mNativeWrapper.getResultMessages()).contains(standbyMessageBroadcast);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSource);
     }
 
     @Test
@@ -808,9 +824,37 @@
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_TV);
         HdmiCecMessage standbyMessageBroadcast = HdmiCecMessageBuilder.buildStandby(
                 mHdmiCecLocalDevicePlayback.mAddress, ADDR_BROADCAST);
+        HdmiCecMessage inactiveSource = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
 
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageToTv);
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageBroadcast);
+        assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSource);
+    }
+
+    @Test
+    public void handleOnStandby_CecMessageReceived() {
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+                HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
+                HdmiControlManager.POWER_CONTROL_MODE_TV);
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED);
+        mHdmiCecLocalDevicePlayback.onStandby(true, HdmiControlService.STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage standbyMessageToTv = HdmiCecMessageBuilder.buildStandby(
+                mHdmiCecLocalDevicePlayback.mAddress, ADDR_TV);
+        HdmiCecMessage standbyMessageBroadcast = HdmiCecMessageBuilder.buildStandby(
+                mHdmiCecLocalDevicePlayback.mAddress, ADDR_BROADCAST);
+        HdmiCecMessage inactiveSource = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackLogicalAddress, mPlaybackPhysicalAddress);
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageToTv);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessageBroadcast);
+        assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSource);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 8ee983f..59711a6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -86,8 +86,6 @@
 
         Context context = InstrumentationRegistry.getTargetContext();
         mMyLooper = mTestLooper.getLooper();
-        PowerManager powerManager = new PowerManager(context, mIPowerManagerMock,
-                mIThermalServiceMock, new Handler(mMyLooper));
 
         mHdmiControlService =
                 new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
@@ -118,7 +116,8 @@
 
                     @Override
                     protected PowerManager getPowerManager() {
-                        return powerManager;
+                        return new PowerManager(context, mIPowerManagerMock,
+                                mIThermalServiceMock, new Handler(mMyLooper));
                     }
 
                     @Override
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
index 1c7ff42..572ffd9 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -75,10 +75,12 @@
 
         Context contextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
         Looper myLooper = mTestLooper.getLooper();
-        PowerManager powerManager = new PowerManager(contextSpy, mIPowerManagerMock,
-                mIThermalServiceMock, new Handler(myLooper));
-        when(contextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
-        when(contextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        when(contextSpy.getSystemService(Context.POWER_SERVICE)).thenAnswer(i ->
+                new PowerManager(contextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(myLooper)));
+        when(contextSpy.getSystemService(PowerManager.class)).thenAnswer(i ->
+                new PowerManager(contextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(myLooper)));
         when(mIPowerManagerMock.isInteractive()).thenReturn(true);
 
         mHdmiControlService = new HdmiControlService(contextSpy) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index b1d77d0..bcf30a2 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -192,10 +192,12 @@
 
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
-        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
-                mIThermalServiceMock, null);
-        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
-        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenAnswer(i ->
+                new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, null));
+        when(mContextSpy.getSystemService(PowerManager.class)).thenAnswer(i ->
+                new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, null));
         when(mIPowerManagerMock.isInteractive()).thenReturn(true);
 
         HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 826438f..4cd17e8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -91,10 +91,12 @@
 
         setHdmiControlEnabled(hdmiControlEnabled);
 
-        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
-                mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
-        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
-        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenAnswer(i ->
+                new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mTestLooper.getLooper())));
+        when(mContextSpy.getSystemService(PowerManager.class)).thenAnswer(i ->
+                new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mTestLooper.getLooper())));
         when(mIPowerManagerMock.isInteractive()).thenReturn(true);
 
         mHdmiControlService = new HdmiControlService(mContextSpy) {
@@ -120,7 +122,8 @@
 
             @Override
             protected PowerManager getPowerManager() {
-                return powerManager;
+                return new PowerManager(mContextSpy, mIPowerManagerMock,
+                        mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
             }
 
             @Override
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
index 53b4b49..a9880c0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -78,10 +78,12 @@
 
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
-        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
-                mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
-        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
-        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenAnswer(i ->
+                new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mTestLooper.getLooper())));
+        when(mContextSpy.getSystemService(PowerManager.class)).thenAnswer(i ->
+                new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mTestLooper.getLooper())));
         when(mIPowerManagerMock.isInteractive()).thenReturn(true);
 
         mHdmiControlService = new HdmiControlService(mContextSpy,
@@ -108,7 +110,8 @@
 
             @Override
             protected PowerManager getPowerManager() {
-                return powerManager;
+                return new PowerManager(mContextSpy, mIPowerManagerMock,
+                        mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
             }
 
             @Override
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
index 865eb7a..2cf4ef1 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
@@ -78,10 +78,12 @@
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
         Looper myLooper = mTestLooper.getLooper();
-        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
-                mIThermalServiceMock, new Handler(myLooper));
-        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
-        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenAnswer(i ->
+                new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(myLooper)));
+        when(mContextSpy.getSystemService(PowerManager.class)).thenAnswer(i ->
+                new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(myLooper)));
         when(mIPowerManagerMock.isInteractive()).thenReturn(true);
 
         mHdmiControlService = new HdmiControlService(mContextSpy) {
@@ -107,7 +109,8 @@
 
             @Override
             protected PowerManager getPowerManager() {
-                return powerManager;
+                return new PowerManager(mContextSpy, mIPowerManagerMock,
+                        mIThermalServiceMock, new Handler(myLooper));
             }
 
             @Override
diff --git a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
index 709b009..1b6bddc 100644
--- a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
@@ -25,6 +25,7 @@
 import android.util.LongSparseArray;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.server.utils.WatchedArrayMap;
 
 import java.io.File;
 import java.io.IOException;
@@ -33,7 +34,7 @@
 
 public class KeySetManagerServiceTest extends AndroidTestCase {
 
-    private ArrayMap<String, PackageSetting> mPackagesMap;
+    private WatchedArrayMap<String, PackageSetting> mPackagesMap;
     private KeySetManagerService mKsms;
 
     public PackageSetting generateFakePackageSetting(String name) {
@@ -46,7 +47,7 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        mPackagesMap = new ArrayMap<String, PackageSetting>();
+        mPackagesMap = new WatchedArrayMap<String, PackageSetting>();
         mKsms = new KeySetManagerService(mPackagesMap);
     }
 
@@ -94,7 +95,8 @@
     }
 
     public void testEncodePublicKey() throws IOException {
-        ArrayMap<String, PackageSetting> packagesMap = new ArrayMap<String, PackageSetting>();
+        WatchedArrayMap<String, PackageSetting> packagesMap =
+                new WatchedArrayMap<String, PackageSetting>();
         KeySetManagerService ksms = new KeySetManagerService(packagesMap);
 
         PublicKey keyA = PackageParser.parsePublicKey(KeySetStrings.ctsKeySetPublicKeyA);
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index a231169..29f4aa9 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -47,7 +47,6 @@
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.UserHandle;
-import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Log;
@@ -64,6 +63,7 @@
 import com.android.server.pm.permission.LegacyPermissionDataProvider;
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
 import com.android.server.utils.WatchableTester;
+import com.android.server.utils.WatchedArrayMap;
 
 import com.google.common.truth.Truth;
 
@@ -1202,9 +1202,8 @@
 
     private void verifyKeySetMetaData(Settings settings)
             throws ReflectiveOperationException, IllegalAccessException {
-        ArrayMap<String, PackageSetting> packages =
-                settings.mPackages.untrackedStorage();
-        KeySetManagerService ksms = settings.mKeySetManagerService;
+        WatchedArrayMap<String, PackageSetting> packages = settings.mPackages;
+        KeySetManagerService ksms = settings.getKeySetManagerService();
 
         /* verify keyset and public key ref counts */
         assertThat(KeySetUtils.getKeySetRefCount(ksms, 1), is(2));
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index 9001b3d..443476c 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
@@ -48,7 +48,7 @@
     private static final float PRECISION = 0.001f;
     private static final int GPS_MODE = 0; // LOCATION_MODE_NO_CHANGE
     private static final int DEFAULT_GPS_MODE =
-            PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
+            PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
     private static final int SOUND_TRIGGER_MODE = 0; // SOUND_TRIGGER_MODE_ALL_ENABLED
     private static final int DEFAULT_SOUND_TRIGGER_MODE =
             PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY;
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/OWNERS b/services/tests/servicestests/src/com/android/server/power/batterysaver/OWNERS
new file mode 100644
index 0000000..08276f5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/power/batterysaver/OWNERS
\ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index ddaf3ab..9a89626 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2034,20 +2034,29 @@
                 .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
                 .build();
 
-        // Non-resizable
+        // Not allow non-resizable
         mAtm.mForceResizableActivities = false;
+        mAtm.mSupportsNonResizableMultiWindow = -1;
         mAtm.mDevEnableNonResizableMultiWindow = false;
         assertFalse(activity.supportsSplitScreenWindowingMode());
 
         // Force resizable
         mAtm.mForceResizableActivities = true;
+        mAtm.mSupportsNonResizableMultiWindow = -1;
         mAtm.mDevEnableNonResizableMultiWindow = false;
         assertTrue(activity.supportsSplitScreenWindowingMode());
 
-        // Allow non-resizable
+        // Use development option to allow non-resizable
         mAtm.mForceResizableActivities = false;
+        mAtm.mSupportsNonResizableMultiWindow = -1;
         mAtm.mDevEnableNonResizableMultiWindow = true;
         assertTrue(activity.supportsSplitScreenWindowingMode());
+
+        // Always allow non-resizable
+        mAtm.mForceResizableActivities = false;
+        mAtm.mSupportsNonResizableMultiWindow = 1;
+        mAtm.mDevEnableNonResizableMultiWindow = false;
+        assertTrue(activity.supportsSplitScreenWindowingMode());
     }
 
     @Test
@@ -2058,20 +2067,29 @@
                 .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
                 .build();
 
-        // Non-resizable
+        // Not allow non-resizable
         mAtm.mForceResizableActivities = false;
+        mAtm.mSupportsNonResizableMultiWindow = -1;
         mAtm.mDevEnableNonResizableMultiWindow = false;
         assertFalse(activity.supportsFreeform());
 
         // Force resizable
         mAtm.mForceResizableActivities = true;
+        mAtm.mSupportsNonResizableMultiWindow = -1;
         mAtm.mDevEnableNonResizableMultiWindow = false;
         assertTrue(activity.supportsFreeform());
 
-        // Allow non-resizable
+        // Use development option to allow non-resizable
         mAtm.mForceResizableActivities = false;
+        mAtm.mSupportsNonResizableMultiWindow = -1;
         mAtm.mDevEnableNonResizableMultiWindow = true;
         assertTrue(activity.supportsFreeform());
+
+        // Always allow non-resizable
+        mAtm.mForceResizableActivities = false;
+        mAtm.mSupportsNonResizableMultiWindow = 1;
+        mAtm.mDevEnableNonResizableMultiWindow = false;
+        assertTrue(activity.supportsFreeform());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 618de21..2558259 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -320,8 +320,8 @@
                 .build();
         final Task task = activity.getTask();
 
-        assertTrue(activity.supportsMultiWindow2());
-        assertTrue(task.supportsMultiWindow2());
+        assertTrue(activity.supportsMultiWindow());
+        assertTrue(task.supportsMultiWindow());
     }
 
     @Test
@@ -336,14 +336,14 @@
         // Device config as not support.
         mAtm.mSupportsNonResizableMultiWindow = -1;
 
-        assertFalse(activity.supportsMultiWindow2());
-        assertFalse(task.supportsMultiWindow2());
+        assertFalse(activity.supportsMultiWindow());
+        assertFalse(task.supportsMultiWindow());
 
         // Device config as always support.
         mAtm.mSupportsNonResizableMultiWindow = 1;
 
-        assertTrue(activity.supportsMultiWindow2());
-        assertTrue(task.supportsMultiWindow2());
+        assertTrue(activity.supportsMultiWindow());
+        assertTrue(task.supportsMultiWindow());
 
         // The default config is relying on the screen size.
         mAtm.mSupportsNonResizableMultiWindow = 0;
@@ -351,14 +351,14 @@
         // Supports on large screen.
         tda.getConfiguration().smallestScreenWidthDp = mAtm.mLargeScreenSmallestScreenWidthDp;
 
-        assertTrue(activity.supportsMultiWindow2());
-        assertTrue(task.supportsMultiWindow2());
+        assertTrue(activity.supportsMultiWindow());
+        assertTrue(task.supportsMultiWindow());
 
         // Not supports on small screen.
         tda.getConfiguration().smallestScreenWidthDp = mAtm.mLargeScreenSmallestScreenWidthDp - 1;
 
-        assertFalse(activity.supportsMultiWindow2());
-        assertFalse(task.supportsMultiWindow2());
+        assertFalse(activity.supportsMultiWindow());
+        assertFalse(task.supportsMultiWindow());
     }
 
     @Test
@@ -381,14 +381,14 @@
         // Ignore the activity min width/height for determine multi window eligibility.
         mAtm.mRespectsActivityMinWidthHeightMultiWindow = -1;
 
-        assertTrue(activity.supportsMultiWindow2());
-        assertTrue(task.supportsMultiWindow2());
+        assertTrue(activity.supportsMultiWindow());
+        assertTrue(task.supportsMultiWindow());
 
         // Always check the activity min width/height.
         mAtm.mRespectsActivityMinWidthHeightMultiWindow = 1;
 
-        assertFalse(activity.supportsMultiWindow2());
-        assertFalse(task.supportsMultiWindow2());
+        assertFalse(activity.supportsMultiWindow());
+        assertFalse(task.supportsMultiWindow());
 
         // The default config is relying on the screen size.
         mAtm.mRespectsActivityMinWidthHeightMultiWindow = 0;
@@ -396,14 +396,14 @@
         // Ignore on large screen.
         tda.getConfiguration().smallestScreenWidthDp = mAtm.mLargeScreenSmallestScreenWidthDp;
 
-        assertTrue(activity.supportsMultiWindow2());
-        assertTrue(task.supportsMultiWindow2());
+        assertTrue(activity.supportsMultiWindow());
+        assertTrue(task.supportsMultiWindow());
 
         // Check on small screen.
         tda.getConfiguration().smallestScreenWidthDp = mAtm.mLargeScreenSmallestScreenWidthDp - 1;
 
-        assertFalse(activity.supportsMultiWindow2());
-        assertFalse(task.supportsMultiWindow2());
+        assertFalse(activity.supportsMultiWindow());
+        assertFalse(task.supportsMultiWindow());
     }
 
     @Test
@@ -429,14 +429,14 @@
         // Always check the activity min width/height.
         mAtm.mSupportsNonResizableMultiWindow = 1;
 
-        assertTrue(activity.supportsMultiWindow2());
-        assertTrue(task.supportsMultiWindow2());
+        assertTrue(activity.supportsMultiWindow());
+        assertTrue(task.supportsMultiWindow());
 
         // The default config is relying on the screen size. Check for small screen
         mAtm.mSupportsNonResizableMultiWindow = 0;
 
-        assertTrue(activity.supportsMultiWindow2());
-        assertTrue(task.supportsMultiWindow2());
+        assertTrue(activity.supportsMultiWindow());
+        assertTrue(task.supportsMultiWindow());
     }
 }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 61b7002..42ef086 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -488,6 +488,7 @@
             mLargeScreenSmallestScreenWidthDp = 600;
             mSupportsNonResizableMultiWindow = 0;
             mRespectsActivityMinWidthHeightMultiWindow = 0;
+            mForceResizableActivities = false;
 
             doReturn(mock(IPackageManager.class)).when(this).getPackageManager();
             // allow background activity starts by default
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 8c87bef..3f1248a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1193,13 +1193,13 @@
                 splitPrimaryRootTask.mRemoteToken.toWindowContainerToken(), true /* onTop */);
 
         // Can't reparent non-resizable to split screen
-        mAtm.mDevEnableNonResizableMultiWindow = false;
+        mAtm.mSupportsNonResizableMultiWindow = -1;
         mAtm.mWindowOrganizerController.applyTransaction(wct);
 
         assertEquals(rootTask, activity.getRootTask());
 
         // Allow reparent non-resizable to split screen
-        mAtm.mDevEnableNonResizableMultiWindow = true;
+        mAtm.mSupportsNonResizableMultiWindow = 1;
         mAtm.mWindowOrganizerController.applyTransaction(wct);
 
         assertEquals(splitPrimaryRootTask, activity.getRootTask());
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index d1bd159..5df3d9c 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -612,7 +612,7 @@
                         options,
                         new IDspHotwordDetectionCallback.Stub() {
                             @Override
-                            public void onRejected(@Nullable HotwordRejectedResult result)
+                            public void onRejected(HotwordRejectedResult result)
                                     throws RemoteException {
                                 bestEffortClose(serviceAudioSink);
                                 bestEffortClose(serviceAudioSource);
@@ -622,7 +622,7 @@
                             }
 
                             @Override
-                            public void onDetected(@Nullable HotwordDetectedResult triggerResult)
+                            public void onDetected(HotwordDetectedResult triggerResult)
                                     throws RemoteException {
                                 bestEffortClose(serviceAudioSink);
                                 bestEffortClose(serviceAudioSource);
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 73f1783..30403f4 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -806,6 +806,15 @@
      */
     public static final String EXTRA_AUDIO_CODEC_BANDWIDTH_KHZ =
             "android.telecom.extra.AUDIO_CODEC_BANDWIDTH_KHZ";
+
+    /**
+     * Boolean connection extra key used to indicate whether device to device communication is
+     * available for the current call.
+     * @hide
+     */
+    public static final String EXTRA_IS_DEVICE_TO_DEVICE_COMMUNICATION_AVAILABLE =
+            "android.telecom.extra.IS_DEVICE_TO_DEVICE_COMMUNICATION_AVAILABLE";
+
     /**
      * Connection event used to inform Telecom that it should play the on hold tone.  This is used
      * to play a tone when the peer puts the current call on hold.  Sent to Telecom via
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index 1c8a1bf..9efdde4 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -341,7 +341,7 @@
 
     private void testParcelSane(NetworkCapabilities cap) {
         if (isAtLeastS()) {
-            assertParcelSane(cap, 17);
+            assertParcelSane(cap, 16);
         } else if (isAtLeastR()) {
             assertParcelSane(cap, 15);
         } else {
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index f277e94..8dbc6e6 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.CHANGE_NETWORK_STATE;
 import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
 import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.LOCAL_MAC_ADDRESS;
 import static android.Manifest.permission.NETWORK_FACTORY;
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
@@ -2777,8 +2778,9 @@
 
     private void grantUsingBackgroundNetworksPermissionForUid(
             final int uid, final String packageName) throws Exception {
-        when(mPackageManager.getPackageInfo(eq(packageName), eq(GET_PERMISSIONS)))
-                .thenReturn(buildPackageInfo(true, uid));
+        when(mPackageManager.getPackageInfo(
+                eq(packageName), eq(GET_PERMISSIONS | MATCH_ANY_USER)))
+                .thenReturn(buildPackageInfo(true /* hasSystemPermission */, uid));
         mService.mPermissionMonitor.onPackageAdded(packageName, uid);
     }
 
@@ -9469,9 +9471,9 @@
         @Override
         public TransportInfo makeCopy(@NetworkCapabilities.RedactionType long redactions) {
             return new TestTransportInfo(
-                    (redactions & REDACT_FOR_ACCESS_FINE_LOCATION) != 0,
-                    (redactions & REDACT_FOR_LOCAL_MAC_ADDRESS) != 0,
-                    (redactions & REDACT_FOR_NETWORK_SETTINGS) != 0
+                    locationRedacted | (redactions & REDACT_FOR_ACCESS_FINE_LOCATION) != 0,
+                    localMacAddressRedacted | (redactions & REDACT_FOR_LOCAL_MAC_ADDRESS) != 0,
+                    settingsRedacted | (redactions & REDACT_FOR_NETWORK_SETTINGS) != 0
             );
         }
 
@@ -9494,8 +9496,26 @@
         public int hashCode() {
             return Objects.hash(locationRedacted, localMacAddressRedacted, settingsRedacted);
         }
+
+        @Override
+        public String toString() {
+            return String.format(
+                    "TestTransportInfo{locationRedacted=%s macRedacted=%s settingsRedacted=%s}",
+                    locationRedacted, localMacAddressRedacted, settingsRedacted);
+        }
     }
 
+    private TestTransportInfo getTestTransportInfo(NetworkCapabilities nc) {
+        return (TestTransportInfo) nc.getTransportInfo();
+    }
+
+    private TestTransportInfo getTestTransportInfo(TestNetworkAgentWrapper n) {
+        final NetworkCapabilities nc = mCm.getNetworkCapabilities(n.getNetwork());
+        assertNotNull(nc);
+        return getTestTransportInfo(nc);
+    }
+
+
     private void verifyNetworkCallbackLocationDataInclusionUsingTransportInfoAndOwnerUidInNetCaps(
             @NonNull TestNetworkCallback wifiNetworkCallback, int actualOwnerUid,
             @NonNull TransportInfo actualTransportInfo, int expectedOwnerUid,
@@ -9524,7 +9544,6 @@
         wifiNetworkCallback.expectCapabilitiesThat(mWiFiNetworkAgent,
                 nc -> Objects.equals(expectedOwnerUid, nc.getOwnerUid())
                         && Objects.equals(expectedTransportInfo, nc.getTransportInfo()));
-
     }
 
     @Test
@@ -9545,6 +9564,40 @@
                 wifiNetworkCallack, ownerUid, transportInfo, INVALID_UID, sanitizedTransportInfo);
     }
 
+    @Test
+    public void testTransportInfoRedactionInSynchronousCalls() throws Exception {
+        final NetworkCapabilities ncTemplate = new NetworkCapabilities()
+                .addTransportType(TRANSPORT_WIFI)
+                .setTransportInfo(new TestTransportInfo());
+
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(),
+                ncTemplate);
+        mWiFiNetworkAgent.connect(true /* validated; waits for callback */);
+
+        // NETWORK_SETTINGS redaction is controlled by the NETWORK_SETTINGS permission
+        assertTrue(getTestTransportInfo(mWiFiNetworkAgent).settingsRedacted);
+        withPermission(NETWORK_SETTINGS, () -> {
+            assertFalse(getTestTransportInfo(mWiFiNetworkAgent).settingsRedacted);
+        });
+        assertTrue(getTestTransportInfo(mWiFiNetworkAgent).settingsRedacted);
+
+        // LOCAL_MAC_ADDRESS redaction is controlled by the LOCAL_MAC_ADDRESS permission
+        assertTrue(getTestTransportInfo(mWiFiNetworkAgent).localMacAddressRedacted);
+        withPermission(LOCAL_MAC_ADDRESS, () -> {
+            assertFalse(getTestTransportInfo(mWiFiNetworkAgent).localMacAddressRedacted);
+        });
+        assertTrue(getTestTransportInfo(mWiFiNetworkAgent).localMacAddressRedacted);
+
+        // Synchronous getNetworkCapabilities calls never return unredacted location-sensitive
+        // information.
+        assertTrue(getTestTransportInfo(mWiFiNetworkAgent).locationRedacted);
+        setupLocationPermissions(Build.VERSION_CODES.S, true, AppOpsManager.OPSTR_FINE_LOCATION,
+                Manifest.permission.ACCESS_FINE_LOCATION);
+        assertTrue(getTestTransportInfo(mWiFiNetworkAgent).locationRedacted);
+        denyAllLocationPrivilegedPermissions();
+        assertTrue(getTestTransportInfo(mWiFiNetworkAgent).locationRedacted);
+    }
+
     private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType)
             throws Exception {
         final Set<UidRange> vpnRange = Collections.singleton(PRIMARY_UIDRANGE);
@@ -9903,12 +9956,27 @@
         // Connect the cell agent verify that it notifies TestNetworkCallback that it is available
         final TestNetworkCallback callback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(callback);
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+
+        final NetworkCapabilities ncTemplate = new NetworkCapabilities()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .setTransportInfo(new TestTransportInfo());
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(),
+                ncTemplate);
         mCellNetworkAgent.connect(true);
         callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         callback.assertNoCallback();
     }
 
+    private boolean areConnDiagCapsRedacted(NetworkCapabilities nc) {
+        TestTransportInfo ti = (TestTransportInfo) nc.getTransportInfo();
+        return nc.getUids() == null
+                && nc.getAdministratorUids().length == 0
+                && nc.getOwnerUid() == Process.INVALID_UID
+                && getTestTransportInfo(nc).locationRedacted
+                && getTestTransportInfo(nc).localMacAddressRedacted
+                && getTestTransportInfo(nc).settingsRedacted;
+    }
+
     @Test
     public void testConnectivityDiagnosticsCallbackOnConnectivityReportAvailable()
             throws Exception {
@@ -9919,12 +9987,7 @@
 
         // Verify onConnectivityReport fired
         verify(mConnectivityDiagnosticsCallback).onConnectivityReportAvailable(
-                argThat(report -> {
-                    final NetworkCapabilities nc = report.getNetworkCapabilities();
-                    return nc.getUids() == null
-                            && nc.getAdministratorUids().length == 0
-                            && nc.getOwnerUid() == Process.INVALID_UID;
-                }));
+                argThat(report -> areConnDiagCapsRedacted(report.getNetworkCapabilities())));
     }
 
     @Test
@@ -9940,12 +10003,7 @@
 
         // Verify onDataStallSuspected fired
         verify(mConnectivityDiagnosticsCallback).onDataStallSuspected(
-                argThat(report -> {
-                    final NetworkCapabilities nc = report.getNetworkCapabilities();
-                    return nc.getUids() == null
-                            && nc.getAdministratorUids().length == 0
-                            && nc.getOwnerUid() == Process.INVALID_UID;
-                }));
+                argThat(report -> areConnDiagCapsRedacted(report.getNetworkCapabilities())));
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
index d7535a9..02a5808 100644
--- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -479,13 +479,14 @@
     public void testUidFilteringDuringVpnConnectDisconnectAndUidUpdates() throws Exception {
         when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
                 Arrays.asList(new PackageInfo[] {
-                        buildPackageInfo(/* SYSTEM */ true, SYSTEM_UID1, MOCK_USER1),
-                        buildPackageInfo(/* SYSTEM */ false, MOCK_UID1, MOCK_USER1),
-                        buildPackageInfo(/* SYSTEM */ false, MOCK_UID2, MOCK_USER1),
-                        buildPackageInfo(/* SYSTEM */ false, VPN_UID, MOCK_USER1)
+                        buildPackageInfo(true /* hasSystemPermission */, SYSTEM_UID1, MOCK_USER1),
+                        buildPackageInfo(false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1),
+                        buildPackageInfo(false /* hasSystemPermission */, MOCK_UID2, MOCK_USER1),
+                        buildPackageInfo(false /* hasSystemPermission */, VPN_UID, MOCK_USER1)
                 }));
-        when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), eq(GET_PERMISSIONS))).thenReturn(
-                buildPackageInfo(false, MOCK_UID1, MOCK_USER1));
+        when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1),
+                eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
+                buildPackageInfo(false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1));
         mPermissionMonitor.startMonitoring();
         // Every app on user 0 except MOCK_UID2 are under VPN.
         final Set<UidRange> vpnRange1 = new HashSet<>(Arrays.asList(new UidRange[] {
@@ -530,11 +531,12 @@
     public void testUidFilteringDuringPackageInstallAndUninstall() throws Exception {
         when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
                 Arrays.asList(new PackageInfo[] {
-                        buildPackageInfo(true, SYSTEM_UID1, MOCK_USER1),
-                        buildPackageInfo(false, VPN_UID, MOCK_USER1)
+                        buildPackageInfo(true /* hasSystemPermission */, SYSTEM_UID1, MOCK_USER1),
+                        buildPackageInfo(false /* hasSystemPermission */, VPN_UID, MOCK_USER1)
                 }));
-        when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), eq(GET_PERMISSIONS))).thenReturn(
-                        buildPackageInfo(false, MOCK_UID1, MOCK_USER1));
+        when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1),
+                eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
+                buildPackageInfo(false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1));
 
         mPermissionMonitor.startMonitoring();
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(MOCK_USER1));
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index 9410886..c59dcf8 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -16,13 +16,17 @@
 
 package android.net.vcn;
 
+import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.net.NetworkCapabilities;
+import android.net.ipsec.ike.IkeSessionParams;
 import android.net.ipsec.ike.IkeTunnelConnectionParams;
+import android.net.vcn.persistablebundleutils.IkeSessionParamsUtilsTest;
 import android.net.vcn.persistablebundleutils.TunnelConnectionParamsUtilsTest;
 
 import androidx.test.filters.SmallTest;
@@ -120,6 +124,21 @@
     }
 
     @Test
+    public void testBuilderRequiresMobikeEnabled() {
+        try {
+            final IkeSessionParams ikeParams =
+                    IkeSessionParamsUtilsTest.createBuilderMinimum()
+                            .removeIkeOption(IKE_OPTION_MOBIKE)
+                            .build();
+            final IkeTunnelConnectionParams tunnelParams =
+                    TunnelConnectionParamsUtilsTest.buildTestParams(ikeParams);
+            new VcnGatewayConnectionConfig.Builder(GATEWAY_CONNECTION_NAME_PREFIX, tunnelParams);
+            fail("Expected exception due to MOBIKE not enabled");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    @Test
     public void testBuilderRequiresNonEmptyExposedCaps() {
         try {
             newBuilder()
diff --git a/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java b/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java
index 582275d..00a0bff 100644
--- a/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java
@@ -16,14 +16,17 @@
 
 package android.net.vcn;
 
-import static android.net.NetworkCapabilities.REDACT_ALL;
+import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
+import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
 import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
+import static android.net.NetworkCapabilities.REDACT_NONE;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 
+import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.os.Parcel;
 
@@ -39,12 +42,6 @@
     private static final VcnTransportInfo WIFI_UNDERLYING_INFO = new VcnTransportInfo(WIFI_INFO);
 
     @Test
-    public void testRedactionDefaults() {
-        assertEquals(REDACT_ALL, CELL_UNDERLYING_INFO.getRedaction());
-        assertEquals(REDACT_ALL, WIFI_UNDERLYING_INFO.getRedaction());
-    }
-
-    @Test
     public void testGetWifiInfo() {
         assertEquals(WIFI_INFO, WIFI_UNDERLYING_INFO.getWifiInfo());
 
@@ -59,15 +56,15 @@
     }
 
     @Test
-    public void testMakeCopySetsRedactions() {
+    public void testMakeCopyRedactForAccessFineLocation() {
         assertEquals(
-                REDACT_FOR_NETWORK_SETTINGS,
-                ((VcnTransportInfo) CELL_UNDERLYING_INFO.makeCopy(REDACT_FOR_NETWORK_SETTINGS))
-                        .getRedaction());
+                SUB_ID,
+                ((VcnTransportInfo) CELL_UNDERLYING_INFO.makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION))
+                        .getSubId());
         assertEquals(
-                REDACT_FOR_NETWORK_SETTINGS,
-                ((VcnTransportInfo) WIFI_UNDERLYING_INFO.makeCopy(REDACT_FOR_NETWORK_SETTINGS))
-                        .getRedaction());
+                WifiConfiguration.INVALID_NETWORK_ID,
+                ((VcnTransportInfo) WIFI_UNDERLYING_INFO.makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION))
+                        .getWifiInfo().getNetworkId());
     }
 
     @Test
@@ -78,35 +75,31 @@
     }
 
     @Test
-    public void testParcelUnparcel() {
-        verifyParcelingIsNull(CELL_UNDERLYING_INFO);
-        verifyParcelingIsNull(WIFI_UNDERLYING_INFO);
-    }
-
-    private void verifyParcelingIsNull(VcnTransportInfo vcnTransportInfo) {
-        // Verify redacted by default
-        Parcel parcel = Parcel.obtain();
-        vcnTransportInfo.writeToParcel(parcel, 0 /* flags */);
-        parcel.setDataPosition(0);
-
-        assertNull(VcnTransportInfo.CREATOR.createFromParcel(parcel));
+    public void testApplicableRedactions() {
+        assertEquals(REDACT_NONE, CELL_UNDERLYING_INFO.getApplicableRedactions());
+        assertEquals(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_LOCAL_MAC_ADDRESS
+                        | REDACT_FOR_NETWORK_SETTINGS,
+                WIFI_UNDERLYING_INFO.getApplicableRedactions());
     }
 
     @Test
-    public void testParcelUnparcelNotRedactedForSysUi() {
-        verifyParcelingForSysUi(CELL_UNDERLYING_INFO);
-        verifyParcelingForSysUi(WIFI_UNDERLYING_INFO);
+    public void testParcelNotRedactedForSysUi() {
+        VcnTransportInfo cellRedacted = parcelForSysUi(CELL_UNDERLYING_INFO);
+        assertEquals(SUB_ID, cellRedacted.getSubId());
+        VcnTransportInfo wifiRedacted = parcelForSysUi(WIFI_UNDERLYING_INFO);
+        assertEquals(NETWORK_ID, wifiRedacted.getWifiInfo().getNetworkId());
     }
 
-    private void verifyParcelingForSysUi(VcnTransportInfo vcnTransportInfo) {
+    private VcnTransportInfo parcelForSysUi(VcnTransportInfo vcnTransportInfo) {
         // Allow fully unredacted; SysUI will have all the relevant permissions.
-        final VcnTransportInfo unRedacted = (VcnTransportInfo) vcnTransportInfo.makeCopy(0);
+        final VcnTransportInfo unRedacted = (VcnTransportInfo) vcnTransportInfo.makeCopy(
+                REDACT_NONE);
         final Parcel parcel = Parcel.obtain();
         unRedacted.writeToParcel(parcel, 0 /* flags */);
         parcel.setDataPosition(0);
 
         final VcnTransportInfo unparceled = VcnTransportInfo.CREATOR.createFromParcel(parcel);
         assertEquals(vcnTransportInfo, unparceled);
-        assertEquals(REDACT_ALL, unparceled.getRedaction());
+        return unparceled;
     }
 }
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
index 393787f..f385113 100644
--- a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
@@ -52,8 +52,8 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class IkeSessionParamsUtilsTest {
-    // Package private for use in EncryptedTunnelParamsUtilsTest
-    static IkeSessionParams.Builder createBuilderMinimum() {
+    // Public for use in VcnGatewayConnectionConfigTest, EncryptedTunnelParamsUtilsTest
+    public static IkeSessionParams.Builder createBuilderMinimum() {
         final InetAddress serverAddress = InetAddresses.parseNumericAddress("192.0.2.100");
 
         // TODO: b/185941731 Make sure all valid IKE_OPTIONS are added and validated.
@@ -63,6 +63,7 @@
                 .setLocalIdentification(new IkeFqdnIdentification("client.test.android.net"))
                 .setRemoteIdentification(new IkeFqdnIdentification("server.test.android.net"))
                 .addIkeOption(IkeSessionParams.IKE_OPTION_FORCE_PORT_4500)
+                .addIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE)
                 .setAuthPsk("psk".getBytes());
     }
 
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtilsTest.java
index 0c8ad32..f9dc9eb 100644
--- a/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtilsTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.net.ipsec.ike.IkeSessionParams;
 import android.net.ipsec.ike.IkeTunnelConnectionParams;
 
 import androidx.test.filters.SmallTest;
@@ -31,9 +32,13 @@
 public class TunnelConnectionParamsUtilsTest {
     // Public for use in VcnGatewayConnectionConfigTest
     public static IkeTunnelConnectionParams buildTestParams() {
+        return buildTestParams(IkeSessionParamsUtilsTest.createBuilderMinimum().build());
+    }
+
+    // Public for use in VcnGatewayConnectionConfigTest
+    public static IkeTunnelConnectionParams buildTestParams(IkeSessionParams params) {
         return new IkeTunnelConnectionParams(
-                IkeSessionParamsUtilsTest.createBuilderMinimum().build(),
-                TunnelModeChildSessionParamsUtilsTest.createBuilderMinimum().build());
+                params, TunnelModeChildSessionParamsUtilsTest.createBuilderMinimum().build());
     }
 
     @Test
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 9ecd82f..3360d40 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -37,6 +37,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.CALLS_REAL_METHODS;
@@ -66,6 +67,7 @@
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
 import android.net.vcn.VcnConfigTest;
+import android.net.vcn.VcnGatewayConnectionConfigTest;
 import android.net.vcn.VcnManager;
 import android.net.vcn.VcnUnderlyingNetworkPolicy;
 import android.os.IBinder;
@@ -197,7 +199,8 @@
                 .newVcnContext(
                         eq(mMockContext),
                         eq(mTestLooper.getLooper()),
-                        any(VcnNetworkProvider.class));
+                        any(VcnNetworkProvider.class),
+                        anyBoolean());
         doReturn(mSubscriptionTracker)
                 .when(mMockDeps)
                 .newTelephonySubscriptionTracker(
@@ -371,6 +374,12 @@
         TelephonySubscriptionSnapshot snapshot =
                 triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
         verify(mMockDeps)
+                .newVcnContext(
+                        eq(mMockContext),
+                        eq(mTestLooper.getLooper()),
+                        any(VcnNetworkProvider.class),
+                        anyBoolean());
+        verify(mMockDeps)
                 .newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG), eq(snapshot), any());
     }
 
@@ -528,6 +537,28 @@
     }
 
     @Test
+    public void testSetVcnConfigTestModeRequiresPermission() throws Exception {
+        doThrow(new SecurityException("Requires MANAGE_TEST_NETWORKS"))
+                .when(mMockContext)
+                .enforceCallingPermission(
+                        eq(android.Manifest.permission.MANAGE_TEST_NETWORKS), any());
+
+        final VcnConfig vcnConfig =
+                new VcnConfig.Builder(mMockContext)
+                        .addGatewayConnectionConfig(
+                                VcnGatewayConnectionConfigTest.buildTestConfig())
+                        .setIsTestModeProfile()
+                        .build();
+
+        try {
+            mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, vcnConfig, TEST_PACKAGE_NAME);
+            fail("Expected exception due to using test-mode without permission");
+        } catch (SecurityException e) {
+            verify(mMockPolicyListener, never()).onPolicyChanged();
+        }
+    }
+
+    @Test
     public void testSetVcnConfigNotifiesStatusCallback() throws Exception {
         triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_2));
 
diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
index 8289e85..0b72cd9 100644
--- a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -112,8 +113,14 @@
         MockitoAnnotations.initMocks(this);
 
         mTestLooper = new TestLooper();
-        mVcnContext = spy(new VcnContext(mContext, mTestLooper.getLooper(), mVcnNetworkProvider));
-        doNothing().when(mVcnContext).ensureRunningOnLooperThread();
+        mVcnContext =
+                spy(
+                        new VcnContext(
+                                mContext,
+                                mTestLooper.getLooper(),
+                                mVcnNetworkProvider,
+                                false /* isInTestMode */));
+        resetVcnContext();
 
         setupSystemService(
                 mContext,
@@ -132,6 +139,11 @@
                         mNetworkTrackerCb);
     }
 
+    private void resetVcnContext() {
+        reset(mVcnContext);
+        doNothing().when(mVcnContext).ensureRunningOnLooperThread();
+    }
+
     private static LinkProperties getLinkPropertiesWithName(String iface) {
         LinkProperties linkProperties = new LinkProperties();
         linkProperties.setInterfaceName(iface);
@@ -149,6 +161,31 @@
         verifyNetworkRequestsRegistered(INITIAL_SUB_IDS);
     }
 
+    @Test
+    public void testNetworkCallbacksRegisteredOnStartupForTestMode() {
+        final VcnContext vcnContext =
+                spy(
+                        new VcnContext(
+                                mContext,
+                                mTestLooper.getLooper(),
+                                mVcnNetworkProvider,
+                                true /* isInTestMode */));
+
+        mUnderlyingNetworkTracker =
+                new UnderlyingNetworkTracker(
+                        vcnContext,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot,
+                        Collections.singleton(NetworkCapabilities.NET_CAPABILITY_INTERNET),
+                        mNetworkTrackerCb);
+
+        verify(mConnectivityManager)
+                .requestBackgroundNetwork(
+                        eq(getTestNetworkRequest(INITIAL_SUB_IDS)),
+                        any(RouteSelectionCallback.class),
+                        any());
+    }
+
     private void verifyNetworkRequestsRegistered(Set<Integer> expectedSubIds) {
         verify(mConnectivityManager)
                 .requestBackgroundNetwork(
@@ -165,7 +202,8 @@
         verify(mConnectivityManager)
                 .requestBackgroundNetwork(
                         eq(getRouteSelectionRequest(expectedSubIds)),
-                        any(RouteSelectionCallback.class), any());
+                        any(RouteSelectionCallback.class),
+                        any());
     }
 
     @Test
@@ -204,6 +242,14 @@
         return getExpectedRequestBase().setSubscriptionIds(netCapsSubIds).build();
     }
 
+    private NetworkRequest getTestNetworkRequest(Set<Integer> netCapsSubIds) {
+        return new NetworkRequest.Builder()
+                .clearCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+                .setSubscriptionIds(netCapsSubIds)
+                .build();
+    }
+
     private NetworkRequest.Builder getExpectedRequestBase() {
         final NetworkRequest.Builder builder =
                 new NetworkRequest.Builder()
diff --git a/tools/codegen/src/com/android/codegen/Utils.kt b/tools/codegen/src/com/android/codegen/Utils.kt
index 9ceb204..7cfa784 100644
--- a/tools/codegen/src/com/android/codegen/Utils.kt
+++ b/tools/codegen/src/com/android/codegen/Utils.kt
@@ -43,8 +43,8 @@
  * cccc dd
  */
 fun Iterable<Pair<String, String>>.columnize(separator: String = " | "): String {
-    val col1w = map { (a, _) -> a.length }.maxOrNull()!!
-    val col2w = map { (_, b) -> b.length }.maxOrNull()!!
+    val col1w = map { (a, _) -> a.length }.max()!!
+    val col2w = map { (_, b) -> b.length }.max()!!
     return map { it.first.padEnd(col1w) + separator + it.second.padEnd(col2w) }.joinToString("\n")
 }