Merge "Use app info to load app data instead of component info" into udc-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index f779b4d..b0b3b1f 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -319,6 +319,8 @@
     private SensorManager mSensorManager;
     private final boolean mUseMotionSensor;
     private Sensor mMotionSensor;
+    private final boolean mIsLocationPrefetchEnabled;
+    @Nullable
     private LocationRequest mLocationRequest;
     private Intent mIdleIntent;
     private Bundle mIdleIntentOptions;
@@ -2460,6 +2462,11 @@
             return null;
         }
 
+        boolean isLocationPrefetchEnabled() {
+            return mContext.getResources().getBoolean(
+                   com.android.internal.R.bool.config_autoPowerModePrefetchLocation);
+        }
+
         boolean useMotionSensor() {
             return mContext.getResources().getBoolean(
                    com.android.internal.R.bool.config_autoPowerModeUseMotionSensor);
@@ -2489,6 +2496,7 @@
         mAppStateTracker = mInjector.getAppStateTracker(context,
                 AppSchedulingModuleThread.get().getLooper());
         LocalServices.addService(AppStateTracker.class, mAppStateTracker);
+        mIsLocationPrefetchEnabled = mInjector.isLocationPrefetchEnabled();
         mUseMotionSensor = mInjector.useMotionSensor();
     }
 
@@ -2602,8 +2610,7 @@
                     mMotionSensor = mInjector.getMotionSensor();
                 }
 
-                if (getContext().getResources().getBoolean(
-                        com.android.internal.R.bool.config_autoPowerModePrefetchLocation)) {
+                if (mIsLocationPrefetchEnabled) {
                     mLocationRequest = new LocationRequest.Builder(/*intervalMillis=*/ 0)
                         .setQuality(LocationRequest.QUALITY_HIGH_ACCURACY)
                         .setMaxUpdates(1)
@@ -3779,34 +3786,40 @@
             case STATE_SENSING:
                 cancelSensingTimeoutAlarmLocked();
                 moveToStateLocked(STATE_LOCATING, reason);
-                scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT);
-                LocationManager locationManager = mInjector.getLocationManager();
-                if (locationManager != null
-                        && locationManager.getProvider(LocationManager.FUSED_PROVIDER) != null) {
-                    locationManager.requestLocationUpdates(LocationManager.FUSED_PROVIDER,
-                            mLocationRequest,
-                            AppSchedulingModuleThread.getExecutor(),
-                            mGenericLocationListener);
-                    mLocating = true;
+                if (mIsLocationPrefetchEnabled) {
+                    scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT);
+                    LocationManager locationManager = mInjector.getLocationManager();
+                    if (locationManager != null
+                            && locationManager.getProvider(LocationManager.FUSED_PROVIDER)
+                                    != null) {
+                        locationManager.requestLocationUpdates(LocationManager.FUSED_PROVIDER,
+                                mLocationRequest,
+                                AppSchedulingModuleThread.getExecutor(),
+                                mGenericLocationListener);
+                        mLocating = true;
+                    } else {
+                        mHasFusedLocation = false;
+                    }
+                    if (locationManager != null
+                            && locationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
+                        mHasGps = true;
+                        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
+                                1000, 5, mGpsLocationListener, mHandler.getLooper());
+                        mLocating = true;
+                    } else {
+                        mHasGps = false;
+                    }
+                    // If we have a location provider, we're all set, the listeners will move state
+                    // forward.
+                    if (mLocating) {
+                        break;
+                    }
+                    // Otherwise, we have to move from locating into idle maintenance.
                 } else {
-                    mHasFusedLocation = false;
-                }
-                if (locationManager != null
-                        && locationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
-                    mHasGps = true;
-                    locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 5,
-                            mGpsLocationListener, mHandler.getLooper());
-                    mLocating = true;
-                } else {
-                    mHasGps = false;
-                }
-                // If we have a location provider, we're all set, the listeners will move state
-                // forward.
-                if (mLocating) {
-                    break;
+                    mLocating = false;
                 }
 
-                // Otherwise, we have to move from locating into idle maintenance.
+                // We're not doing any locating work, so move on to the next state.
             case STATE_LOCATING:
                 cancelAlarmLocked();
                 cancelLocatingLocked();
@@ -5303,15 +5316,19 @@
                 pw.print("  "); pw.print(mStationaryListeners.size());
                 pw.println(" stationary listeners registered");
             }
-            pw.print("  mLocating="); pw.print(mLocating);
-            pw.print(" mHasGps="); pw.print(mHasGps);
-            pw.print(" mHasFused="); pw.print(mHasFusedLocation);
-            pw.print(" mLocated="); pw.println(mLocated);
-            if (mLastGenericLocation != null) {
-                pw.print("  mLastGenericLocation="); pw.println(mLastGenericLocation);
-            }
-            if (mLastGpsLocation != null) {
-                pw.print("  mLastGpsLocation="); pw.println(mLastGpsLocation);
+            if (mIsLocationPrefetchEnabled) {
+                pw.print("  mLocating="); pw.print(mLocating);
+                pw.print(" mHasGps="); pw.print(mHasGps);
+                pw.print(" mHasFused="); pw.print(mHasFusedLocation);
+                pw.print(" mLocated="); pw.println(mLocated);
+                if (mLastGenericLocation != null) {
+                    pw.print("  mLastGenericLocation="); pw.println(mLastGenericLocation);
+                }
+                if (mLastGpsLocation != null) {
+                    pw.print("  mLastGpsLocation="); pw.println(mLastGpsLocation);
+                }
+            } else {
+                pw.println("  Location prefetching disabled");
             }
             pw.print("  mState="); pw.print(stateToString(mState));
             pw.print(" mLightState=");
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 577260e..8a4b464 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1560,7 +1560,8 @@
                     jobStatus.getJob().getMinLatencyMillis(),
                     jobStatus.getEstimatedNetworkDownloadBytes(),
                     jobStatus.getEstimatedNetworkUploadBytes(),
-                    jobStatus.getWorkCount());
+                    jobStatus.getWorkCount(),
+                    ActivityManager.processStateAmToProto(mUidProcStates.get(jobStatus.getUid())));
 
             // If the job is immediately ready to run, then we can just immediately
             // put it in the pending list and try to schedule it.  This is especially
@@ -1935,6 +1936,7 @@
      * {@code incomingJob} is non-null, it replaces {@code cancelled} in the store of
      * currently scheduled jobs.
      */
+    @GuardedBy("mLock")
     private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob,
             @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
         if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
@@ -1986,7 +1988,8 @@
                     cancelled.getJob().getMinLatencyMillis(),
                     cancelled.getEstimatedNetworkDownloadBytes(),
                     cancelled.getEstimatedNetworkUploadBytes(),
-                    cancelled.getWorkCount());
+                    cancelled.getWorkCount(),
+                    ActivityManager.processStateAmToProto(mUidProcStates.get(cancelled.getUid())));
         }
         // If this is a replacement, bring in the new version of the job
         if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index fb36cde..f95df44 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -25,6 +25,7 @@
 import android.annotation.BytesLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.Notification;
 import android.app.compat.CompatChanges;
@@ -476,7 +477,8 @@
                     job.getJob().getMinLatencyMillis(),
                     job.getEstimatedNetworkDownloadBytes(),
                     job.getEstimatedNetworkUploadBytes(),
-                    job.getWorkCount());
+                    job.getWorkCount(),
+                    ActivityManager.processStateAmToProto(mService.getUidProcState(job.getUid())));
             final String sourcePackage = job.getSourcePackageName();
             if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
                 final String componentPackage = job.getServiceComponent().getPackageName();
@@ -1447,7 +1449,9 @@
                 completedJob.getJob().getMinLatencyMillis(),
                 completedJob.getEstimatedNetworkDownloadBytes(),
                 completedJob.getEstimatedNetworkUploadBytes(),
-                completedJob.getWorkCount());
+                completedJob.getWorkCount(),
+                ActivityManager
+                        .processStateAmToProto(mService.getUidProcState(completedJob.getUid())));
         if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
             Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
                     getId());
diff --git a/config/dirty-image-objects b/config/dirty-image-objects
index dfd091c..2584610e 100644
--- a/config/dirty-image-objects
+++ b/config/dirty-image-objects
@@ -28,359 +28,270 @@
 # Then, grep for lines containing "Private dirty object" from the output.
 # This particular file was generated by dumping systemserver and systemui.
 #
-Landroid/accounts/Account;
-Landroid/accounts/OnAccountsUpdateListener;
 Landroid/animation/LayoutTransition;
 Landroid/app/ActivityManager;
-Landroid/app/ActivityManager$OnUidImportanceListener;
 Landroid/app/ActivityTaskManager;
 Landroid/app/ActivityThread;
-Landroid/app/admin/DevicePolicyManager;
 Landroid/app/AlarmManager;
-Landroid/app/Application;
 Landroid/app/AppOpsManager;
-Landroid/app/backup/BackupManager;
 Landroid/app/ContextImpl;
-Landroid/app/INotificationManager;
-Landroid/app/Notification$BigPictureStyle;
-Landroid/app/Notification$BigTextStyle;
-Landroid/app/Notification$InboxStyle;
-Landroid/app/NotificationChannel;
-Landroid/app/NotificationChannelGroup;
+Landroid/app/Notification;
 Landroid/app/NotificationManager;
-Landroid/app/PendingIntent;
-Landroid/app/PendingIntent$OnFinished;
+Landroid/app/PendingIntent$FinishedDispatcher;
+Landroid/app/PropertyInvalidatedCache$NoPreloadHolder;
 Landroid/app/QueuedWork;
 Landroid/app/ResourcesManager;
+Landroid/app/SystemServiceRegistry;
 Landroid/app/WallpaperManager;
-Landroid/app/WindowConfiguration;
-Landroid/bluetooth/BluetoothAdapter;
-Landroid/bluetooth/BluetoothDevice;
-Landroid/bluetooth/BluetoothProfile;
-Landroid/bluetooth/IBluetoothA2dp;
-Landroid/bluetooth/IBluetoothHeadsetPhone;
-Landroid/bluetooth/IBluetoothHidDevice;
-Landroid/bluetooth/IBluetoothHidHost;
-Landroid/bluetooth/IBluetoothMap;
-Landroid/bluetooth/IBluetoothPan;
-Landroid/bluetooth/IBluetoothPbap;
-Landroid/bluetooth/IBluetoothSap;
-Landroid/content/ClipboardManager$OnPrimaryClipChangedListener;
-Landroid/content/ComponentName;
-Landroid/content/ContentProvider$PipeDataWriter;
+Landroid/app/backup/BackupManager;
+Landroid/compat/Compatibility;
+Landroid/content/AsyncQueryHandler;
+Landroid/content/ContentProviderClient;
 Landroid/content/ContentResolver;
 Landroid/content/Context;
-Landroid/content/Intent;
-Landroid/content/pm/PackageManager$OnPermissionsChangedListener;
-Landroid/content/pm/VersionedPackage;
-Landroid/content/res/Configuration;
-Landroid/content/SharedPreferences$OnSharedPreferenceChangeListener;
+Landroid/content/pm/PackageItemInfo;
+Landroid/content/pm/UserPackage;
+Landroid/content/res/ResourceTimer;
 Landroid/database/CursorWindow;
 Landroid/database/sqlite/SQLiteCompatibilityWalFlags;
-Landroid/database/sqlite/SQLiteDatabase$CursorFactory;
+Landroid/database/sqlite/SQLiteDebug$NoPreloadHolder;
 Landroid/database/sqlite/SQLiteGlobal;
-Landroid/database/sqlite/SQLiteTransactionListener;
 Landroid/ddm/DdmHandleAppName;
 Landroid/graphics/Bitmap;
 Landroid/graphics/Canvas;
-Landroid/graphics/drawable/AdaptiveIconDrawable;
-Landroid/graphics/drawable/ColorDrawable;
-Landroid/graphics/drawable/GradientDrawable;
-Landroid/graphics/drawable/Icon;
-Landroid/graphics/drawable/InsetDrawable;
-Landroid/graphics/drawable/RippleDrawable;
-Landroid/graphics/drawable/VectorDrawable$VGroup;
-Landroid/graphics/ImageDecoder;
-Landroid/graphics/Rect;
+Landroid/graphics/Compatibility;
+Landroid/graphics/HardwareRenderer;
 Landroid/graphics/TemporaryBuffer;
-Landroid/hardware/biometrics/BiometricSourceType;
-Landroid/hardware/display/ColorDisplayManager$ColorDisplayManagerInternal;
-Landroid/hardware/display/DisplayManagerGlobal;
-Landroid/hardware/display/NightDisplayListener$Callback;
-Landroid/hardware/input/InputManager;
-Landroid/hardware/input/InputManager$InputDeviceListener;
+Landroid/graphics/Typeface;
+Landroid/graphics/drawable/AdaptiveIconDrawable;
 Landroid/hardware/SensorPrivacyManager;
 Landroid/hardware/SystemSensorManager;
-Landroid/icu/impl/OlsonTimeZone;
-Landroid/icu/text/BreakIterator;
+Landroid/hardware/devicestate/DeviceStateManagerGlobal;
+Landroid/hardware/display/ColorDisplayManager$ColorDisplayManagerInternal;
+Landroid/hardware/display/DisplayManagerGlobal;
+Landroid/hardware/input/InputManagerGlobal;
+Landroid/hardware/location/GeofenceHardwareImpl;
+Landroid/icu/impl/number/range/StandardPluralRanges;
 Landroid/icu/text/Collator;
-Landroid/icu/text/DateFormat$BooleanAttribute;
-Landroid/icu/text/DateTimePatternGenerator$DTPGflags;
-Landroid/icu/text/PluralRules$Operand;
 Landroid/icu/util/TimeZone;
-Landroid/location/GpsStatus$Listener;
-Landroid/location/LocationListener;
+Landroid/location/LocationManager;
 Landroid/media/AudioManager;
+Landroid/media/AudioPlaybackConfiguration;
+Landroid/media/AudioSystem;
+Landroid/media/MediaCodec;
+Landroid/media/MediaCodecList;
+Landroid/media/MediaFrameworkPlatformInitializer;
+Landroid/media/MediaRouter2Manager;
 Landroid/media/MediaRouter;
 Landroid/media/PlayerBase;
-Landroid/media/session/MediaSessionManager;
-Landroid/net/apf/ApfCapabilities;
-Landroid/net/ConnectivityManager;
-Landroid/net/ConnectivityManager$OnNetworkActiveListener;
-Landroid/net/ConnectivityThread$Singleton;
-Landroid/net/IpConfiguration$IpAssignment;
-Landroid/net/IpConfiguration$ProxySettings;
-Landroid/net/IpPrefix;
-Landroid/net/LinkAddress;
-Landroid/net/LinkProperties;
-Landroid/net/Network;
-Landroid/net/NetworkCapabilities;
-Landroid/net/NetworkInfo;
-Landroid/net/NetworkInfo$State;
-Landroid/net/NetworkRequest;
-Landroid/net/NetworkRequest$Type;
-Landroid/net/RouteInfo;
-Landroid/net/StringNetworkSpecifier;
-Landroid/net/TrafficStats;
-Landroid/net/UidRange;
-Landroid/net/Uri$HierarchicalUri;
-Landroid/net/Uri$StringUri;
-Landroid/net/wifi/WifiManager;
-Landroid/net/wifi/WifiManager$SoftApCallback;
-Landroid/os/AsyncResult;
+Landroid/media/audiopolicy/AudioProductStrategy;
+Landroid/media/audiopolicy/AudioVolumeGroup;
+Landroid/nfc/NfcAdapter;
+Landroid/nfc/NfcFrameworkInitializer;
+Landroid/nfc/cardemulation/CardEmulation;
 Landroid/os/AsyncTask;
+Landroid/os/BaseBundle;
+Landroid/os/Binder;
 Landroid/os/BinderProxy;
-Landroid/os/Bundle;
-Landroid/os/DeadObjectException;
 Landroid/os/Environment;
 Landroid/os/FileObserver;
 Landroid/os/Handler;
-Landroid/os/IDeviceIdleController;
 Landroid/os/LocaleList;
 Landroid/os/Looper;
 Landroid/os/Message;
-Landroid/os/ParcelUuid;
+Landroid/os/NullVibrator;
+Landroid/os/Parcel;
 Landroid/os/Process;
-Landroid/os/RecoverySystem;
 Landroid/os/ServiceManager;
-Landroid/os/storage/StorageManager;
 Landroid/os/StrictMode;
-Landroid/os/Trace;
+Landroid/os/UEventObserver;
+Landroid/os/UserManager;
 Landroid/os/WorkSource;
-Landroid/os/WorkSource$WorkChain;
+Landroid/os/storage/StorageManager;
 Landroid/permission/PermissionManager;
+Landroid/provider/DeviceConfigInitializer;
 Landroid/provider/FontsContract;
-Landroid/provider/Settings$SettingNotFoundException;
+Landroid/provider/Settings;
+Landroid/renderscript/RenderScript;
 Landroid/renderscript/RenderScriptCacheDir;
-Landroid/security/IKeyChainService;
-Landroid/security/keystore/AndroidKeyStoreProvider;
+Landroid/security/keystore2/KeyStoreCryptoOperationUtils;
 Landroid/security/net/config/ApplicationConfig;
 Landroid/security/net/config/SystemCertificateSource$NoPreloadHolder;
-Landroid/telecom/PhoneAccountHandle;
+Landroid/security/net/config/UserCertificateSource$NoPreloadHolder;
+Landroid/telecom/Log;
+Landroid/telecom/TelecomManager;
 Landroid/telephony/AnomalyReporter;
-Landroid/telephony/CellSignalStrengthCdma;
-Landroid/telephony/CellSignalStrengthGsm;
-Landroid/telephony/CellSignalStrengthLte;
-Landroid/telephony/CellSignalStrengthNr;
-Landroid/telephony/CellSignalStrengthTdscdma;
-Landroid/telephony/CellSignalStrengthWcdma;
-Landroid/telephony/DataSpecificRegistrationInfo;
-Landroid/telephony/emergency/EmergencyNumber;
-Landroid/telephony/ims/ImsMmTelManager$CapabilityCallback$CapabilityBinder;
-Landroid/telephony/ims/ImsMmTelManager$RegistrationCallback$RegistrationBinder;
-Landroid/telephony/ims/ImsReasonInfo;
-Landroid/telephony/ims/ProvisioningManager$Callback$CallbackBinder;
-Landroid/telephony/ModemActivityInfo;
-Landroid/telephony/ModemInfo;
-Landroid/telephony/NetworkRegistrationInfo;
-Landroid/telephony/NetworkService;
+Landroid/telephony/TelephonyFrameworkInitializer;
+Landroid/telephony/TelephonyLocalConnection;
 Landroid/telephony/TelephonyManager;
-Landroid/telephony/VoiceSpecificRegistrationInfo;
-Landroid/text/format/DateFormat;
-Landroid/text/method/SingleLineTransformationMethod;
-Landroid/text/Selection$MemoryTextWatcher;
-Landroid/text/SpanWatcher;
-Landroid/text/style/AlignmentSpan;
-Landroid/text/style/CharacterStyle;
-Landroid/text/style/LeadingMarginSpan;
-Landroid/text/style/LineBackgroundSpan;
-Landroid/text/style/LineHeightSpan;
-Landroid/text/style/MetricAffectingSpan;
-Landroid/text/style/ReplacementSpan;
-Landroid/text/style/SuggestionSpan;
-Landroid/text/style/TabStopSpan;
+Landroid/telephony/TelephonyRegistryManager;
+Landroid/text/DynamicLayout;
 Landroid/text/TextUtils;
-Landroid/text/TextWatcher;
-Landroid/transition/ChangeClipBounds;
-Landroid/transition/ChangeImageTransform;
-Landroid/transition/ChangeTransform;
+Landroid/text/format/DateFormat;
+Landroid/text/format/DateUtils;
+Landroid/text/method/ArrowKeyMovementMethod;
+Landroid/text/method/LinkMovementMethod;
+Landroid/text/method/SingleLineTransformationMethod;
+Landroid/text/style/ClickableSpan;
+Landroid/timezone/TelephonyLookup;
+Landroid/timezone/TimeZoneFinder;
 Landroid/util/ArrayMap;
 Landroid/util/ArraySet;
-Landroid/util/DisplayMetrics;
 Landroid/util/EventLog;
-Landroid/util/Log;
-Landroid/util/Patterns;
-Landroid/view/AbsSavedState$1;
-Landroid/view/accessibility/AccessibilityManager;
-Landroid/view/accessibility/AccessibilityManager$AccessibilityServicesStateChangeListener;
-Landroid/view/accessibility/AccessibilityManager$TouchExplorationStateChangeListener;
-Landroid/view/accessibility/AccessibilityNodeIdManager;
-Landroid/view/autofill/AutofillManager;
-Landroid/view/autofill/Helper;
+Landroid/util/NtpTrustedTime;
 Landroid/view/Choreographer;
-Landroid/view/inputmethod/InputMethodManager;
-Landroid/view/IWindowManager;
+Landroid/view/CrossWindowBlurListeners;
+Landroid/view/DisplayCutout;
+Landroid/view/KeyEvent;
+Landroid/view/MotionEvent;
 Landroid/view/PointerIcon;
-Landroid/view/RemoteAnimationAdapter;
-Landroid/view/ThreadedRenderer;
+Landroid/view/RoundedCorners;
+Landroid/view/SurfaceControl;
 Landroid/view/View;
-Landroid/view/View$OnHoverListener;
+Landroid/view/ViewGroup$TouchTarget;
 Landroid/view/ViewRootImpl;
-Landroid/view/ViewStub;
-Landroid/view/ViewStub$OnInflateListener;
 Landroid/view/ViewTreeObserver;
-Landroid/view/WindowManager$LayoutParams;
 Landroid/view/WindowManagerGlobal;
-Landroid/widget/ActionMenuPresenter$OverflowMenuButton;
-Landroid/widget/ActionMenuView;
-Landroid/widget/Button;
-Landroid/widget/CheckBox;
-Landroid/widget/FrameLayout;
-Landroid/widget/ImageButton;
+Landroid/view/accessibility/AccessibilityManager;
+Landroid/view/accessibility/AccessibilityNodeIdManager;
+Landroid/view/autofill/Helper;
+Landroid/view/inputmethod/IInputMethodManagerGlobalInvoker;
+Landroid/view/inputmethod/InputMethodManager;
+Landroid/webkit/CookieSyncManager;
+Landroid/webkit/WebView;
+Landroid/webkit/WebViewFactory;
+Landroid/webkit/WebViewZygote;
+Landroid/widget/AbsListView;
 Landroid/widget/ImageView;
 Landroid/widget/LinearLayout;
-Landroid/widget/RelativeLayout;
-Landroid/widget/SeekBar;
-Landroid/widget/Space;
-Landroid/widget/TextView;
-Landroid/widget/Toolbar;
-[B
-Lcom/android/ims/ImsManager;
+Landroid/widget/Toast;
+Landroid/window/SurfaceSyncGroup;
+Lcom/android/i18n/timezone/TelephonyLookup;
+Lcom/android/i18n/timezone/TimeZoneFinder;
+Lcom/android/internal/config/appcloning/AppCloningDeviceConfigHelper;
+Lcom/android/internal/content/om/OverlayConfig;
+Lcom/android/internal/display/BrightnessSynchronizer;
+Lcom/android/internal/infra/AndroidFuture;
+Lcom/android/internal/inputmethod/ImeTracing;
+Lcom/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry;
+Lcom/android/internal/jank/InteractionJankMonitor$InstanceHolder;
+Lcom/android/internal/jank/InteractionJankMonitor;
 Lcom/android/internal/logging/MetricsLogger;
 Lcom/android/internal/os/BackgroundThread;
 Lcom/android/internal/os/BinderInternal;
-Lcom/android/internal/os/BinderInternal$BinderProxyLimitListener;
+Lcom/android/internal/os/KernelCpuBpfTracking;
 Lcom/android/internal/os/RuntimeInit;
 Lcom/android/internal/os/SomeArgs;
-Lcom/android/internal/policy/DecorView;
-Lcom/android/internal/statusbar/IStatusBarService;
-Lcom/android/internal/telephony/AppSmsManager;
-Landroid/telephony/CallerInfoAsyncQuery$OnQueryCompleteListener;
-Lcom/android/internal/telephony/CarrierActionAgent;
-Lcom/android/internal/telephony/cat/CatService;
-Lcom/android/internal/telephony/cat/IconLoader;
-Lcom/android/internal/telephony/cat/RilMessageDecoder;
-Lcom/android/internal/telephony/cdma/CdmaSubscriptionSourceManager;
-Lcom/android/internal/telephony/cdma/EriManager;
-Lcom/android/internal/telephony/CellularNetworkValidator;
-Lcom/android/internal/telephony/CommandException;
-Lcom/android/internal/telephony/dataconnection/DataConnection$DcActivatingState;
-Lcom/android/internal/telephony/dataconnection/DataConnection$DcActiveState;
-Lcom/android/internal/telephony/dataconnection/DataConnection$DcInactiveState;
-Lcom/android/internal/telephony/dataconnection/DataEnabledSettings;
-Lcom/android/internal/telephony/dataconnection/DcTracker;
-Lcom/android/internal/telephony/euicc/EuiccCardController;
-Lcom/android/internal/telephony/euicc/EuiccController;
-Lcom/android/internal/telephony/GsmAlphabet;
-Lcom/android/internal/telephony/GsmCdmaCallTracker;
-Lcom/android/internal/telephony/GsmCdmaPhone;
-Lcom/android/internal/telephony/IccPhoneBookInterfaceManager;
-Lcom/android/internal/telephony/IccSmsInterfaceManager;
-Lcom/android/internal/telephony/ims/ImsResolver;
-Lcom/android/internal/telephony/imsphone/ImsExternalCallTracker;
-Lcom/android/internal/telephony/imsphone/ImsPhone;
-Lcom/android/internal/telephony/imsphone/ImsPhoneCallTracker;
-Lcom/android/internal/telephony/ims/RcsMessageStoreController;
+Lcom/android/internal/os/ZygoteInit;
+Lcom/android/internal/policy/AttributeCache;
+Lcom/android/internal/protolog/BaseProtoLogImpl;
+Lcom/android/internal/protolog/ProtoLogImpl;
+Lcom/android/internal/statusbar/NotificationVisibility;
+Lcom/android/internal/telephony/CellBroadcastServiceManager;
 Lcom/android/internal/telephony/IntentBroadcaster;
-Lcom/android/internal/telephony/ITelephonyRegistry$Stub$Proxy;
-Lcom/android/internal/telephony/metrics/TelephonyMetrics;
+Lcom/android/internal/telephony/MccTable;
 Lcom/android/internal/telephony/MultiSimSettingController;
-Lcom/android/internal/telephony/nano/CarrierIdProto$CarrierAttribute;
-Lcom/android/internal/telephony/nano/CarrierIdProto$CarrierId;
-Lcom/android/internal/telephony/nano/TelephonyProto$RilDataCall;
-Lcom/android/internal/telephony/nano/TelephonyProto$SmsSession$Event;
-Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyCallSession$Event$RilCall;
-Lcom/android/internal/telephony/NitzStateMachine;
+Lcom/android/internal/telephony/PackageChangeReceiver;
 Lcom/android/internal/telephony/PhoneConfigurationManager;
 Lcom/android/internal/telephony/PhoneFactory;
-Lcom/android/internal/telephony/PhoneSwitcher;
 Lcom/android/internal/telephony/ProxyController;
-Lcom/android/internal/telephony/RadioConfig;
-Lcom/android/internal/telephony/RIL;
 Lcom/android/internal/telephony/RILRequest;
-Lcom/android/internal/telephony/RilWakelockInfo;
-Lcom/android/internal/telephony/ServiceStateTracker;
-Lcom/android/internal/telephony/SimActivationTracker;
+Lcom/android/internal/telephony/RadioConfig;
+Lcom/android/internal/telephony/RadioInterfaceCapabilityController;
 Lcom/android/internal/telephony/SmsApplication;
 Lcom/android/internal/telephony/SmsBroadcastUndelivered;
-Lcom/android/internal/telephony/SmsStorageMonitor;
-Lcom/android/internal/telephony/SmsUsageMonitor;
-Lcom/android/internal/telephony/SubscriptionController;
-Lcom/android/internal/telephony/SubscriptionInfoUpdater;
+Lcom/android/internal/telephony/SomeArgs;
 Lcom/android/internal/telephony/TelephonyComponentFactory;
 Lcom/android/internal/telephony/TelephonyDevController;
-Lcom/android/internal/telephony/TelephonyTester;
-Lcom/android/internal/telephony/uicc/AdnRecordCache;
-Lcom/android/internal/telephony/uicc/UiccCardApplication;
+Lcom/android/internal/telephony/cat/CatService;
+Lcom/android/internal/telephony/cdma/CdmaInboundSmsHandler;
+Lcom/android/internal/telephony/cdma/CdmaSubscriptionSourceManager;
+Lcom/android/internal/telephony/euicc/EuiccCardController;
+Lcom/android/internal/telephony/euicc/EuiccController;
+Lcom/android/internal/telephony/ims/ImsResolver;
+Lcom/android/internal/telephony/metrics/TelephonyMetrics;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$CarrierIdMismatch;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$CellularDataServiceSwitch;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$CellularServiceState;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$DataCallSession;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$EmergencyNumbersInfo;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$GbaEvent;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsDedicatedBearerEvent;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsDedicatedBearerListenerEvent;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationFeatureTagStats;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationServiceDescStats;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationStats;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationTermination;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$IncomingSms;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$NetworkRequests;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$NetworkRequestsV2;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$OutgoingShortCodeSms;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$OutgoingSms;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$PresenceNotifyEvent;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$RcsAcsProvisioningStats;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$RcsClientProvisioningStats;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteController;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteIncomingDatagram;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteOutgoingDatagram;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteProvision;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteSession;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteSosMessageRecommender;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SipDelegateStats;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SipMessageResponse;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SipTransportFeatureTagStats;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$SipTransportSession;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$UceEventStats;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$UnmeteredNetworks;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$VoiceCallRatUsage;
+Lcom/android/internal/telephony/nano/PersistAtomsProto$VoiceCallSession;
+Lcom/android/internal/telephony/nano/TelephonyProto$RilDataCall;
+Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyCallSession$Event$RilCall;
+Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyServiceState$NetworkRegistrationInfo;
+Lcom/android/internal/telephony/satellite/PointingAppController;
+Lcom/android/internal/telephony/satellite/SatelliteModemInterface;
 Lcom/android/internal/telephony/uicc/UiccController;
-Lcom/android/internal/telephony/uicc/UiccProfile;
 Lcom/android/internal/telephony/uicc/UiccStateChangedLauncher;
-Lcom/android/internal/telephony/uicc/UsimFileHandler;
-Lcom/android/internal/telephony/uicc/VoiceMailConstants;
-Lcom/android/internal/util/LatencyTracker;
-Lcom/android/internal/util/StateMachine$SmHandler;
-Lcom/android/okhttp/OkHttpClient;
-Lcom/android/okhttp/okio/AsyncTimeout;
-Lcom/android/okhttp/okio/SegmentPool;
+Lcom/android/internal/util/ContrastColorUtil;
+Lcom/android/internal/view/WindowManagerPolicyThread;
+Lcom/android/org/bouncycastle/crypto/CryptoServicesRegistrar;
 Lcom/android/phone/ecc/nano/ProtobufEccData$CountryInfo;
 Lcom/android/phone/ecc/nano/ProtobufEccData$EccInfo;
-Lcom/android/server/sip/SipWakeupTimer;
-Lcom/android/server/SystemConfig;
+Lcom/android/server/AppWidgetBackupBridge;
 Ldalvik/system/BaseDexClassLoader;
 Ldalvik/system/BlockGuard;
 Ldalvik/system/CloseGuard;
 Ldalvik/system/RuntimeHooks;
 Ldalvik/system/SocketTagger;
-Ljava/io/BufferedReader;
-Ljava/lang/AssertionError;
-Ljava/lang/Boolean;
-Ljava/lang/Byte;
-Ljava/lang/Character;
-Ljava/lang/CharSequence;
-Ljava/lang/Class;
-Ljava/lang/IllegalAccessException;
-Ljava/lang/IllegalStateException;
-Ljava/lang/NoSuchMethodException;
-Ljava/lang/NullPointerException;
-Ljava/lang/Object;
-[Ljava/lang/Object;
-Ljava/lang/ref/FinalizerReference;
-Ljava/lang/Runnable;
-Ljava/lang/SecurityException;
-Ljava/lang/Short;
-[Ljava/lang/String;
+Ldalvik/system/VMRuntime;
+Ldalvik/system/ZipPathValidator;
+Ldalvik/system/ZygoteHooks;
 Ljava/lang/System;
 Ljava/lang/Thread;
 Ljava/lang/Throwable;
-Ljava/lang/UnsatisfiedLinkError;
-Ljava/net/Inet6Address;
-Ljava/net/Socket;
-Ljava/net/SocketException;
+Ljava/lang/ref/FinalizerReference;
+Ljava/lang/ref/ReferenceQueue;
+Ljava/net/ResponseCache;
 Ljava/nio/Bits;
 Ljava/nio/charset/Charset;
-Ljava/security/interfaces/RSAPrivateKey;
 Ljava/security/Provider;
 Ljava/util/Collections;
-Ljava/util/concurrent/Executor;
 Ljava/util/GregorianCalendar;
-Ljava/util/Locale;
 Ljava/util/Locale$NoImagePreloadHolder;
+Ljava/util/Locale;
 Ljava/util/Scanner;
-Ljava/util/Set;
 Ljava/util/TimeZone;
+Ljava/util/concurrent/ForkJoinPool;
+Ljava/util/concurrent/ThreadLocalRandom;
+Ljavax/net/ServerSocketFactory;
 Ljavax/net/SocketFactory;
-Ljavax/net/ssl/HttpsURLConnection;
 Ljavax/net/ssl/HttpsURLConnection$NoPreloadHolder;
+Ljavax/net/ssl/HttpsURLConnection;
 Ljavax/net/ssl/SSLContext;
-Ljavax/net/ssl/SSLSessionContext;
+Ljavax/net/ssl/SSLServerSocketFactory;
 Ljavax/net/ssl/SSLSocketFactory;
 Llibcore/io/Libcore;
-Llibcore/io/Memory;
 Llibcore/net/NetworkSecurityPolicy;
-Llibcore/timezone/TimeZoneFinder;
-Lorg/apache/http/params/HttpParams;
 Lsun/misc/Cleaner;
-Lsun/nio/ch/FileChannelImpl;
 Lsun/nio/ch/FileChannelImpl$Unmapper;
-Lsun/nio/fs/UnixChannelFactory;
+Lsun/nio/ch/FileChannelImpl;
 Lsun/security/jca/Providers;
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 332c53c..d97f718 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3632,18 +3632,12 @@
   }
 
   public final class AutofillManager {
-    method public void clearAutofillRequestCallback();
-    method @RequiresPermission(android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS) public void setAutofillRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.autofill.AutofillRequestCallback);
     field public static final String ANY_HINT = "any";
     field public static final int FLAG_SMART_SUGGESTION_OFF = 0; // 0x0
     field public static final int FLAG_SMART_SUGGESTION_SYSTEM = 1; // 0x1
     field public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 120000; // 0x1d4c0
   }
 
-  public interface AutofillRequestCallback {
-    method public void onFillRequest(@Nullable android.view.inputmethod.InlineSuggestionsRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
-  }
-
 }
 
 package android.view.contentcapture {
@@ -3767,11 +3761,6 @@
     method @NonNull public static android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(@NonNull android.widget.inline.InlinePresentationSpec, @NonNull String, @Nullable String[], @NonNull String, boolean);
   }
 
-  public static final class InlineSuggestionsRequest.Builder {
-    method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setClientSupported(boolean);
-    method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setServiceSupported(boolean);
-  }
-
   public final class InlineSuggestionsResponse implements android.os.Parcelable {
     method @NonNull public static android.view.inputmethod.InlineSuggestionsResponse newInlineSuggestionsResponse(@NonNull java.util.List<android.view.inputmethod.InlineSuggestion>);
   }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 521bf05..e2ef005 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -771,6 +771,7 @@
 
     /**
      * The set of flags for process capability.
+     * Keep it in sync with ProcessCapability in atoms.proto.
      * @hide
      */
     @IntDef(flag = true, prefix = { "PROCESS_CAPABILITY_" }, value = {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 61d6787..2ae7216 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -403,8 +403,11 @@
     private static final String KEY_SPLASH_SCREEN_STYLE =
             "android.activity.splashScreenStyle";
 
-    /** See {@link #setTransientLaunch()}. */
-    private static final String KEY_TRANSIENT_LAUNCH = "android.activity.transientLaunch";
+    /**
+     * See {@link #setTransientLaunch()}.
+     * @hide
+     */
+    public static final String KEY_TRANSIENT_LAUNCH = "android.activity.transientLaunch";
 
     /** see {@link #makeLaunchIntoPip(PictureInPictureParams)}. */
     private static final String KEY_LAUNCH_INTO_PIP_PARAMS =
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 36e5762..3c6ff28 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -137,7 +137,7 @@
      * activities inside it belong to a managed profile user, and that user has just
      * been locked.
      */
-    void onTaskProfileLocked(in ActivityManager.RunningTaskInfo taskInfo);
+    void onTaskProfileLocked(in ActivityManager.RunningTaskInfo taskInfo, int userId);
 
     /**
      * Called when a task snapshot got updated.
diff --git a/core/java/android/app/IUidObserver.aidl b/core/java/android/app/IUidObserver.aidl
index 0c920f1..60c2eed 100644
--- a/core/java/android/app/IUidObserver.aidl
+++ b/core/java/android/app/IUidObserver.aidl
@@ -58,8 +58,9 @@
      * Report a proc oom adj change associated with a uid.
      *
      * @param uid The uid for which the state change is being reported.
+     * @param adj The minimum OOM adj among all processes with this uid.
      */
-    void onUidProcAdjChanged(int uid);
+    void onUidProcAdjChanged(int uid, int adj);
 
     // =============== End of transactions used on native side as well ============================
 
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 99a7fa2..705b5ee 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -407,7 +407,7 @@
     }
 
     private static void checkPendingIntent(int flags, @NonNull Intent intent,
-            @NonNull Context context) {
+            @NonNull Context context, boolean isActivityResultType) {
         final boolean isFlagImmutableSet = (flags & PendingIntent.FLAG_IMMUTABLE) != 0;
         final boolean isFlagMutableSet = (flags & PendingIntent.FLAG_MUTABLE) != 0;
         final String packageName = context.getPackageName();
@@ -428,11 +428,12 @@
                 throw new IllegalArgumentException(msg);
         }
 
-        // For apps with target SDK < U, warn that creation or retrieval of a mutable
-        // implicit PendingIntent will be blocked from target SDK U onwards for security
-        // reasons. The block itself happens on the server side, but this warning has to
-        // stay here to preserve the client side stack trace for app developers.
-        if (isNewMutableDisallowedImplicitPendingIntent(flags, intent)
+        // For apps with target SDK < U, warn that creation or retrieval of a mutable implicit
+        // PendingIntent that is not of type {@link ActivityManager#INTENT_SENDER_ACTIVITY_RESULT}
+        // will be blocked from target SDK U onwards for security reasons. The block itself
+        // happens on the server side, but this warning has to stay here to preserve the client
+        // side stack trace for app developers.
+        if (isNewMutableDisallowedImplicitPendingIntent(flags, intent, isActivityResultType)
                 && !Compatibility.isChangeEnabled(BLOCK_MUTABLE_IMPLICIT_PENDING_INTENT)) {
             String msg = "New mutable implicit PendingIntent: pkg=" + packageName
                     + ", action=" + intent.getAction()
@@ -445,7 +446,13 @@
 
     /** @hide */
     public static boolean isNewMutableDisallowedImplicitPendingIntent(int flags,
-            @NonNull Intent intent) {
+            @NonNull Intent intent, boolean isActivityResultType) {
+        if (isActivityResultType) {
+            // Pending intents of type {@link ActivityManager#INTENT_SENDER_ACTIVITY_RESULT}
+            // should be ignored as they are intrinsically tied to a target which means they
+            // are already explicit.
+            return false;
+        }
         boolean isFlagNoCreateSet = (flags & PendingIntent.FLAG_NO_CREATE) != 0;
         boolean isFlagMutableSet = (flags & PendingIntent.FLAG_MUTABLE) != 0;
         boolean isImplicit = (intent.getComponent() == null) && (intent.getPackage() == null);
@@ -534,7 +541,7 @@
             @NonNull Intent intent, int flags, Bundle options, UserHandle user) {
         String packageName = context.getPackageName();
         String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver());
-        checkPendingIntent(flags, intent, context);
+        checkPendingIntent(flags, intent, context, /* isActivityResultType */ false);
         try {
             intent.migrateExtraStreamToClipData(context);
             intent.prepareToLeaveProcess(context);
@@ -668,7 +675,7 @@
             intents[i].migrateExtraStreamToClipData(context);
             intents[i].prepareToLeaveProcess(context);
             resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver());
-            checkPendingIntent(flags, intents[i], context);
+            checkPendingIntent(flags, intents[i], context, /* isActivityResultType */ false);
         }
         try {
             IIntentSender target =
@@ -721,7 +728,7 @@
             Intent intent, int flags, UserHandle userHandle) {
         String packageName = context.getPackageName();
         String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver());
-        checkPendingIntent(flags, intent, context);
+        checkPendingIntent(flags, intent, context, /* isActivityResultType */ false);
         try {
             intent.prepareToLeaveProcess(context);
             IIntentSender target =
@@ -800,7 +807,7 @@
             Intent intent, int flags, int serviceKind) {
         String packageName = context.getPackageName();
         String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver());
-        checkPendingIntent(flags, intent, context);
+        checkPendingIntent(flags, intent, context, /* isActivityResultType */ false);
         try {
             intent.prepareToLeaveProcess(context);
             IIntentSender target =
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 774bc06..0290cee 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -154,8 +154,18 @@
     }
 
     @Override
+    public void onTaskProfileLocked(RunningTaskInfo taskInfo, int userId)
+            throws RemoteException {
+        onTaskProfileLocked(taskInfo);
+    }
+
+    /**
+     * @deprecated see {@link #onTaskProfileLocked(RunningTaskInfo, int)}
+     */
+    @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void onTaskProfileLocked(RunningTaskInfo taskInfo) throws RemoteException {
+    public void onTaskProfileLocked(RunningTaskInfo taskInfo)
+            throws RemoteException {
     }
 
     @Override
diff --git a/core/java/android/app/UidObserver.java b/core/java/android/app/UidObserver.java
index 9e92807..5196624 100644
--- a/core/java/android/app/UidObserver.java
+++ b/core/java/android/app/UidObserver.java
@@ -41,7 +41,7 @@
     }
 
     @Override
-    public void onUidProcAdjChanged(int uid) {
+    public void onUidProcAdjChanged(int uid, int adj) {
     }
 
     @Override
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index a34a50c..be1d8b8 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -213,9 +213,17 @@
                     .resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA)
                     .generate();
         } else {
+            // in any case, always use between 5 and 128 clusters
+            int minClusters = 5;
+            int maxClusters = 128;
+
+            // if the bitmap is very small, use bitmapArea/16 clusters instead of 128
+            int minPixelsPerCluster = 16;
+            int numberOfColors = Math.max(minClusters,
+                    Math.min(maxClusters, bitmapArea / minPixelsPerCluster));
             palette = Palette
                     .from(bitmap, new CelebiQuantizer())
-                    .maximumColorCount(128)
+                    .maximumColorCount(numberOfColors)
                     .resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA)
                     .generate();
         }
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 6592019..f673304 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -822,6 +822,10 @@
      */
     @TestApi
     public boolean isLockscreenLiveWallpaperEnabled() {
+        return isLockscreenLiveWallpaperEnabledHelper();
+    }
+
+    private static boolean isLockscreenLiveWallpaperEnabledHelper() {
         if (sGlobals == null) {
             sIsLockscreenLiveWallpaperEnabled = SystemProperties.getBoolean(
                     "persist.wm.debug.lockscreen_live_wallpaper", false);
@@ -2757,7 +2761,7 @@
     public static InputStream openDefaultWallpaper(Context context, @SetWallpaperFlags int which) {
         final String whichProp;
         final int defaultResId;
-        if (which == FLAG_LOCK && !sIsLockscreenLiveWallpaperEnabled) {
+        if (which == FLAG_LOCK && !isLockscreenLiveWallpaperEnabledHelper()) {
             /* Factory-default lock wallpapers are not yet supported
             whichProp = PROP_LOCK_WALLPAPER;
             defaultResId = com.android.internal.R.drawable.default_lock_wallpaper;
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 1ba84c5..d802b46 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -502,6 +502,8 @@
 
     boolean hasSystemFeature(String name, int version);
 
+    List<String> getInitialNonStoppedSystemPackages();
+
     void enterSafeMode();
     @UnsupportedAppUsage
     boolean isSafeMode();
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index de66f05..56f6f82 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2072,6 +2072,25 @@
         return new InstallInfo(result);
     }
 
+    /**
+     * Parse a single APK file passed as an FD to get install relevant information about
+     * the package wrapped in {@link InstallInfo}.
+     * @throws PackageParsingException if the package source file(s) provided is(are) not valid,
+     * or the parser isn't able to parse the supplied source(s).
+     * @hide
+     */
+    @NonNull
+    public InstallInfo readInstallInfo(@NonNull ParcelFileDescriptor pfd,
+            @Nullable String debugPathName, int flags) throws PackageParsingException {
+        final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+        final ParseResult<PackageLite> result = ApkLiteParseUtils.parseMonolithicPackageLite(input,
+                pfd.getFileDescriptor(), debugPathName, flags);
+        if (result.isError()) {
+            throw new PackageParsingException(result.getErrorCode(), result.getErrorMessage());
+        }
+        return new InstallInfo(result);
+    }
+
     // (b/239722738) This class serves as a bridge between the PackageLite class, which
     // is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java)
     // This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or
@@ -2125,6 +2144,21 @@
         public long calculateInstalledSize(@NonNull SessionParams params) throws IOException {
             return InstallLocationUtils.calculateInstalledSize(mPkg, params.abiOverride);
         }
+
+        /**
+         * @param params {@link SessionParams} of the installation
+         * @param pfd of an APK opened for read
+         * @return Total disk space occupied by an application after installation.
+         * Includes the size of the raw APKs, possibly unpacked resources, raw dex metadata files,
+         * and all relevant native code.
+         * @throws IOException when size of native binaries cannot be calculated.
+         * @hide
+         */
+        public long calculateInstalledSize(@NonNull SessionParams params,
+                @NonNull ParcelFileDescriptor pfd) throws IOException {
+            return InstallLocationUtils.calculateInstalledSize(mPkg, params.abiOverride,
+                    pfd.getFileDescriptor());
+        }
     }
 
     /**
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 295df5c..be40143 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -50,7 +50,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
-import java.lang.IllegalArgumentException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -285,6 +284,12 @@
      */
     public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103;
 
+    /**
+     * The maximum length of Shortcut ID. IDs will be truncated at this limit.
+     * @hide
+     */
+    public static final int MAX_ID_LENGTH = 1000;
+
     /** @hide */
     @IntDef(prefix = { "DISABLED_REASON_" }, value = {
             DISABLED_REASON_NOT_DISABLED,
@@ -477,8 +482,7 @@
 
     private ShortcutInfo(Builder b) {
         mUserId = b.mContext.getUserId();
-
-        mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided");
+        mId = getSafeId(Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided"));
 
         // Note we can't do other null checks here because SM.updateShortcuts() takes partial
         // information.
@@ -584,6 +588,14 @@
         return ret;
     }
 
+    @NonNull
+    private static String getSafeId(@NonNull String id) {
+        if (id.length() > MAX_ID_LENGTH) {
+            return id.substring(0, MAX_ID_LENGTH);
+        }
+        return id;
+    }
+
     /**
      * Throws if any of the mandatory fields is not set.
      *
@@ -2342,7 +2354,8 @@
         final ClassLoader cl = getClass().getClassLoader();
 
         mUserId = source.readInt();
-        mId = source.readString8();
+        mId = getSafeId(Preconditions.checkStringNotEmpty(source.readString8(),
+                "Shortcut ID must be provided"));
         mPackageName = source.readString8();
         mActivity = source.readParcelable(cl, android.content.ComponentName.class);
         mFlags = source.readInt();
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index a4339d4..d209b35 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -109,7 +109,7 @@
     }
 
     /**
-     * Parse lightweight details about a single APK files.
+     * Parse lightweight details about a single APK file.
      */
     public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input,
             File packageFile, int flags) {
@@ -135,6 +135,33 @@
     }
 
     /**
+     * Parse lightweight details about a single APK file passed as an FD.
+     */
+    public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input,
+            FileDescriptor packageFd, String debugPathName, int flags) {
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
+        try {
+            final ParseResult<ApkLite> result = parseApkLite(input, packageFd, debugPathName,
+                    flags);
+            if (result.isError()) {
+                return input.error(result);
+            }
+
+            final ApkLite baseApk = result.getResult();
+            final String packagePath = debugPathName;
+            return input.success(
+                    new PackageLite(packagePath, baseApk.getPath(), baseApk, null /* splitNames */,
+                            null /* isFeatureSplits */, null /* usesSplitNames */,
+                            null /* configForSplit */, null /* splitApkPaths */,
+                            null /* splitRevisionCodes */, baseApk.getTargetSdkVersion(),
+                            null /* requiredSplitTypes */, null, /* splitTypes */
+                            baseApk.isAllowUpdateOwnership()));
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+    }
+
+    /**
      * Parse lightweight details about a directory of APKs.
      *
      * @param packageDir is the folder that contains split apks for a regular app
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index 09d2db8..9ebb058 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -30,6 +30,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Contains information about the request that initiated this UX flow.
@@ -64,6 +66,9 @@
     @Nullable
     private final CreateCredentialRequest mCreateCredentialRequest;
 
+    @NonNull
+    private final List<String> mDefaultProviderIds;
+
     @Nullable
     private final GetCredentialRequest mGetCredentialRequest;
 
@@ -83,7 +88,8 @@
             @NonNull String appPackageName) {
         return new RequestInfo(
                 token, TYPE_CREATE, appPackageName, createCredentialRequest, null,
-                /*hasPermissionToOverrideDefault=*/ false);
+                /*hasPermissionToOverrideDefault=*/ false,
+                /*defaultProviderIds=*/ new ArrayList<>());
     }
 
     /**
@@ -94,10 +100,11 @@
     @NonNull
     public static RequestInfo newCreateRequestInfo(
             @NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest,
-            @NonNull String appPackageName, boolean hasPermissionToOverrideDefault) {
+            @NonNull String appPackageName, boolean hasPermissionToOverrideDefault,
+            @NonNull List<String> defaultProviderIds) {
         return new RequestInfo(
                 token, TYPE_CREATE, appPackageName, createCredentialRequest, null,
-                hasPermissionToOverrideDefault);
+                hasPermissionToOverrideDefault, defaultProviderIds);
     }
 
     /** Creates new {@code RequestInfo} for a get-credential flow. */
@@ -107,7 +114,8 @@
             @NonNull String appPackageName) {
         return new RequestInfo(
                 token, TYPE_GET, appPackageName, null, getCredentialRequest,
-                /*hasPermissionToOverrideDefault=*/ false);
+                /*hasPermissionToOverrideDefault=*/ false,
+                /*defaultProviderIds=*/ new ArrayList<>());
     }
 
 
@@ -149,6 +157,20 @@
     }
 
     /**
+     * Returns default provider identifier (flattened component name) configured from the user
+     * settings.
+     *
+     * Will only be possibly non-empty for the create use case. Not meaningful for the sign-in use
+     * case.
+     *
+     * @hide
+     */
+    @NonNull
+    public List<String> getDefaultProviderIds() {
+        return mDefaultProviderIds;
+    }
+
+    /**
      * Returns the non-null GetCredentialRequest when the type of the request is {@link
      * #TYPE_GET}, or null otherwise.
      */
@@ -161,13 +183,15 @@
             @NonNull String appPackageName,
             @Nullable CreateCredentialRequest createCredentialRequest,
             @Nullable GetCredentialRequest getCredentialRequest,
-            boolean hasPermissionToOverrideDefault) {
+            boolean hasPermissionToOverrideDefault,
+            @NonNull List<String> defaultProviderIds) {
         mToken = token;
         mType = type;
         mAppPackageName = appPackageName;
         mCreateCredentialRequest = createCredentialRequest;
         mGetCredentialRequest = getCredentialRequest;
         mHasPermissionToOverrideDefault = hasPermissionToOverrideDefault;
+        mDefaultProviderIds = defaultProviderIds == null ? new ArrayList<>() : defaultProviderIds;
     }
 
     private RequestInfo(@NonNull Parcel in) {
@@ -188,6 +212,7 @@
         mCreateCredentialRequest = createCredentialRequest;
         mGetCredentialRequest = getCredentialRequest;
         mHasPermissionToOverrideDefault = in.readBoolean();
+        mDefaultProviderIds = in.createStringArrayList();
     }
 
     @Override
@@ -198,6 +223,7 @@
         dest.writeTypedObject(mCreateCredentialRequest, flags);
         dest.writeTypedObject(mGetCredentialRequest, flags);
         dest.writeBoolean(mHasPermissionToOverrideDefault);
+        dest.writeStringList(mDefaultProviderIds);
     }
 
     @Override
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 888047d..21fe686 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -32,10 +32,12 @@
 import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.RecognitionMode;
 import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.os.ParcelFileDescriptor;
-import android.system.ErrnoException;
 import android.os.SharedMemory;
+import android.system.ErrnoException;
 
 import java.nio.ByteBuffer;
 import java.util.Arrays;
@@ -219,36 +221,40 @@
         return new SoundTrigger.ConfidenceLevel(apiLevel.userId, apiLevel.levelPercent);
     }
 
-    public static SoundTrigger.RecognitionEvent aidl2apiRecognitionEvent(
-            int modelHandle, int captureSession, RecognitionEvent aidlEvent) {
+    public static SoundTrigger.RecognitionEvent aidl2apiRecognitionEvent(int modelHandle,
+            int captureSession, RecognitionEventSys aidlEvent) {
+        RecognitionEvent recognitionEvent = aidlEvent.recognitionEvent;
         // The API recognition event doesn't allow for a null audio format, even though it doesn't
         // always make sense. We thus replace it with a default.
-        AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(aidlEvent.audioConfig,
+        AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(recognitionEvent.audioConfig,
                 true /*isInput*/);
-        // TODO(b/265852186) propagate a timestamp from aidl interfaces
-        return new SoundTrigger.GenericRecognitionEvent(aidlEvent.status, modelHandle,
-                aidlEvent.captureAvailable, captureSession, aidlEvent.captureDelayMs,
-                aidlEvent.capturePreambleMs, aidlEvent.triggerInData, audioFormat, aidlEvent.data,
-                aidlEvent.recognitionStillActive, -1 /* halEventReceivedMillis */);
+        return new SoundTrigger.GenericRecognitionEvent(recognitionEvent.status, modelHandle,
+                recognitionEvent.captureAvailable, captureSession, recognitionEvent.captureDelayMs,
+                recognitionEvent.capturePreambleMs, recognitionEvent.triggerInData, audioFormat,
+                recognitionEvent.data,
+                recognitionEvent.recognitionStillActive, aidlEvent.halEventReceivedMillis);
     }
 
     public static SoundTrigger.RecognitionEvent aidl2apiPhraseRecognitionEvent(
-            int modelHandle, int captureSession,
-            PhraseRecognitionEvent aidlEvent) {
+            int modelHandle, int captureSession, PhraseRecognitionEventSys aidlEvent) {
+        PhraseRecognitionEvent recognitionEvent = aidlEvent.phraseRecognitionEvent;
         SoundTrigger.KeyphraseRecognitionExtra[] apiExtras =
-                new SoundTrigger.KeyphraseRecognitionExtra[aidlEvent.phraseExtras.length];
-        for (int i = 0; i < aidlEvent.phraseExtras.length; ++i) {
-            apiExtras[i] = aidl2apiPhraseRecognitionExtra(aidlEvent.phraseExtras[i]);
+                new SoundTrigger.KeyphraseRecognitionExtra[recognitionEvent.phraseExtras.length];
+        for (int i = 0; i < recognitionEvent.phraseExtras.length; ++i) {
+            apiExtras[i] = aidl2apiPhraseRecognitionExtra(recognitionEvent.phraseExtras[i]);
         }
         // The API recognition event doesn't allow for a null audio format, even though it doesn't
         // always make sense. We thus replace it with a default.
-        AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(aidlEvent.common.audioConfig,
+        AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(
+                recognitionEvent.common.audioConfig,
                 true /*isInput*/);
-        // TODO(b/265852186) propagate a timestamp from aidl interfaces
-        return new SoundTrigger.KeyphraseRecognitionEvent(aidlEvent.common.status, modelHandle,
-                aidlEvent.common.captureAvailable, captureSession, aidlEvent.common.captureDelayMs,
-                aidlEvent.common.capturePreambleMs, aidlEvent.common.triggerInData, audioFormat,
-                aidlEvent.common.data, apiExtras, -1 /* halEventReceivedMillis */);
+        return new SoundTrigger.KeyphraseRecognitionEvent(recognitionEvent.common.status,
+                modelHandle,
+                recognitionEvent.common.captureAvailable, captureSession,
+                recognitionEvent.common.captureDelayMs,
+                recognitionEvent.common.capturePreambleMs, recognitionEvent.common.triggerInData,
+                audioFormat,
+                recognitionEvent.common.data, apiExtras, aidlEvent.halEventReceivedMillis);
     }
 
     // In case of a null input returns a non-null valid output.
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index 37c5213..5cdbe23 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -22,13 +22,13 @@
 import android.media.permission.ClearCallingIdentityContext;
 import android.media.permission.Identity;
 import android.media.permission.SafeCloseable;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
@@ -398,7 +398,7 @@
         }
 
         @Override
-        public synchronized void onRecognition(int handle, RecognitionEvent event,
+        public synchronized void onRecognition(int handle, RecognitionEventSys event,
                 int captureSession)
                 throws RemoteException {
             Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
@@ -407,7 +407,7 @@
         }
 
         @Override
-        public synchronized void onPhraseRecognition(int handle, PhraseRecognitionEvent event,
+        public synchronized void onPhraseRecognition(int handle, PhraseRecognitionEventSys event,
                 int captureSession)
                 throws RemoteException {
             Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
diff --git a/core/java/android/net/metrics/WakeupStats.java b/core/java/android/net/metrics/WakeupStats.java
index bb36536..fac747c 100644
--- a/core/java/android/net/metrics/WakeupStats.java
+++ b/core/java/android/net/metrics/WakeupStats.java
@@ -80,18 +80,20 @@
                 break;
         }
 
-        switch (ev.dstHwAddr.getAddressType()) {
-            case MacAddress.TYPE_UNICAST:
-                l2UnicastCount++;
-                break;
-            case MacAddress.TYPE_MULTICAST:
-                l2MulticastCount++;
-                break;
-            case MacAddress.TYPE_BROADCAST:
-                l2BroadcastCount++;
-                break;
-            default:
-                break;
+        if (ev.dstHwAddr != null) {
+            switch (ev.dstHwAddr.getAddressType()) {
+                case MacAddress.TYPE_UNICAST:
+                    l2UnicastCount++;
+                    break;
+                case MacAddress.TYPE_MULTICAST:
+                    l2MulticastCount++;
+                    break;
+                case MacAddress.TYPE_BROADCAST:
+                    l2BroadcastCount++;
+                    break;
+                default:
+                    break;
+            }
         }
 
         increment(ethertypes, ev.ethertype);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 24e28e9..5bcbaa1 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -65,7 +65,6 @@
 import android.view.WindowManager.LayoutParams;
 
 import com.android.internal.R;
-import com.android.internal.util.FrameworkStatsLog;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -2533,38 +2532,6 @@
     }
 
     /**
-     * Returns the enum defined in the statsd UserLifecycleJourneyReported atom corresponding to the
-     * user type.
-     * @hide
-     */
-    public static int getUserTypeForStatsd(@NonNull String userType) {
-        switch (userType) {
-            case USER_TYPE_FULL_SYSTEM:
-                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SYSTEM;
-            case USER_TYPE_FULL_SECONDARY:
-                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY;
-            case USER_TYPE_FULL_GUEST:
-                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_GUEST;
-            case USER_TYPE_FULL_DEMO:
-                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_DEMO;
-            case USER_TYPE_FULL_RESTRICTED:
-                return FrameworkStatsLog
-                        .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_RESTRICTED;
-            case USER_TYPE_PROFILE_MANAGED:
-                return FrameworkStatsLog
-                        .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_MANAGED;
-            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;
-        }
-    }
-
-    /**
      * @hide
      * @deprecated Use {@link #isRestrictedProfile()}
      */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 79e7574..329a2fa5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4677,16 +4677,22 @@
                 "display_color_mode_vendor_hint";
 
         /**
-         * Whether or not the peak refresh rate should be forced. 0=no, 1=yes
+         * The user selected min refresh rate in frames per second.
+         *
+         * If this isn't set, 0 will be used.
          * @hide
          */
-        public static final String FORCE_PEAK_REFRESH_RATE = "force_peak_refresh_rate";
+        @Readable
+        public static final String MIN_REFRESH_RATE = "min_refresh_rate";
 
         /**
-         * Whether or not the peak refresh rate should be used for some content. 0=no, 1=yes
+         * The user selected peak refresh rate in frames per second.
+         *
+         * If this isn't set, the system falls back to a device specific default.
          * @hide
          */
-        public static final String SMOOTH_DISPLAY = "smooth_display";
+        @Readable
+        public static final String PEAK_REFRESH_RATE = "peak_refresh_rate";
 
         /**
          * The amount of time in milliseconds before the device goes to sleep or begins
@@ -7169,6 +7175,13 @@
         public static final String CREDENTIAL_SERVICE = "credential_service";
 
         /**
+         * The currently selected primary credential service flattened ComponentName.
+         *
+         * @hide
+         */
+        public static final String CREDENTIAL_SERVICE_PRIMARY = "credential_service_primary";
+
+        /**
          * The currently selected autofill service flattened ComponentName.
          * @hide
          */
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index 4a848dd..8afae74 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -97,8 +97,6 @@
      */
     public static final @RequestFlags int FLAG_VIEW_NOT_FOCUSED = 0x10;
 
-    // The flag value 0x20 has been defined in AutofillManager.
-
     /**
      * Indicates the request supports fill dialog presentation for the fields, the
      * system will send the request when the activity just started.
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index 1a1df6f..751c675 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -75,12 +75,13 @@
     /**
      * Constructs an information instance of the credential provider.
      *
-     * @param context the context object
+     * @param context          the context object
      * @param serviceComponent the serviceComponent of the provider service
-     * @param userId the android userId for which the current process is running
+     * @param userId           the android userId for which the current process is running
      * @param isSystemProvider whether this provider is a system provider
      * @throws PackageManager.NameNotFoundException If provider service is not found
-     * @throws SecurityException If provider does not require the relevant permission
+     * @throws SecurityException                    If provider does not require the relevant
+     *                                              permission
      */
     public static CredentialProviderInfo create(
             @NonNull Context context,
@@ -99,13 +100,15 @@
     /**
      * Constructs an information instance of the credential provider.
      *
-     * @param context the context object
-     * @param serviceInfo the service info for the provider app. This must be retrieved from the
-     *     {@code PackageManager}
-     * @param isSystemProvider whether the provider app is a system provider
+     * @param context                              the context object
+     * @param serviceInfo                          the service info for the provider app. This must
+     *                                             be retrieved from the
+     *                                             {@code PackageManager}
+     * @param isSystemProvider                     whether the provider app is a system provider
      * @param disableSystemAppVerificationForTests whether to disable system app permission
-     *     verification so that tests can install system providers
-     * @param isEnabled whether the user enabled this provider
+     *                                             verification so that tests can install system
+     *                                             providers
+     * @param isEnabled                            whether the user enabled this provider
      * @throws SecurityException If provider does not require the relevant permission
      */
     public static CredentialProviderInfo create(
@@ -374,7 +377,6 @@
                 if (appInfo == null || serviceInfo == null) {
                     continue;
                 }
-
                 services.add(serviceInfo);
             } catch (SecurityException | PackageManager.NameNotFoundException e) {
                 Slog.e(TAG, "Error getting info for " + serviceInfo, e);
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index 53a5fd5..cf2e6a6 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -18,7 +18,6 @@
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
-import android.Manifest;
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
@@ -35,7 +34,7 @@
 import android.os.Looper;
 import android.os.OutcomeReceiver;
 import android.os.RemoteException;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.Objects;
 
@@ -226,7 +225,7 @@
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
             return mInterface.asBinder();
         }
-        Log.d(TAG, "Failed to bind with intent: " + intent);
+        Slog.w(TAG, "Failed to bind with intent: " + intent);
         return null;
     }
 
@@ -252,11 +251,6 @@
                             GetCredentialException>() {
                         @Override
                         public void onResult(BeginGetCredentialResponse result) {
-                            // If provider service does not possess the HYBRID permission, this
-                            // check will throw an exception in the provider process.
-                            if (result.getRemoteCredentialEntry() != null) {
-                                enforceRemoteEntryPermission();
-                            }
                             try {
                                 callback.onSuccess(result);
                             } catch (RemoteException e) {
@@ -274,15 +268,6 @@
                     }
             ));
         }
-        private void enforceRemoteEntryPermission() {
-            String permission =
-                    Manifest.permission.PROVIDE_REMOTE_CREDENTIALS;
-            getApplicationContext().enforceCallingOrSelfPermission(
-                    permission,
-                    String.format("Provider must have %s, in order to set a "
-                            + "remote entry", permission)
-            );
-        }
 
         @Override
         public void onBeginCreateCredential(BeginCreateCredentialRequest request,
@@ -305,11 +290,6 @@
                             BeginCreateCredentialResponse, CreateCredentialException>() {
                         @Override
                         public void onResult(BeginCreateCredentialResponse result) {
-                            // If provider service does not possess the HYBRID permission, this
-                            // check will throw an exception in the provider process.
-                            if (result.getRemoteCreateEntry() != null) {
-                                enforceRemoteEntryPermission();
-                            }
                             try {
                                 callback.onSuccess(result);
                             } catch (RemoteException e) {
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 6061a0f..7822dde 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -1063,21 +1063,6 @@
                 mActiveVisualQueryDetector = null;
             }
             mActiveDetectors.remove(detector);
-            shutdownHotwordDetectionServiceIfRequiredLocked();
-        }
-    }
-
-    private void shutdownHotwordDetectionServiceIfRequiredLocked() {
-        for (HotwordDetector detector : mActiveDetectors) {
-            if (detector.isUsingSandboxedDetectionService()) {
-                return;
-            }
-        }
-
-        try {
-            mSystemService.shutdownHotwordDetectionService();
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
         }
     }
 
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index a69af24..470c280 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -62,9 +62,7 @@
      */
     public static final int SOURCE_ARBITRARY_RECTANGLE = 3;
 
-    private final IBinder mOwner;
-    private final int mIndex;
-    private final @InsetsType int mType;
+    private final int mId;
 
     /**
      * The selection of the starting rectangle to be converted into source frame.
@@ -122,30 +120,30 @@
      * @param type the {@link InsetsType}.
      * @see InsetsSource#createId(Object, int, int)
      */
-    public InsetsFrameProvider(IBinder owner, @IntRange(from = 0, to = 2047) int index,
+    public InsetsFrameProvider(Object owner, @IntRange(from = 0, to = 2047) int index,
             @InsetsType int type) {
-        if (index < 0 || index >= 2048) {
-            throw new IllegalArgumentException();
-        }
-
-        // This throws IllegalArgumentException if the type is not valid.
-        WindowInsets.Type.indexOf(type);
-
-        mOwner = owner;
-        mIndex = index;
-        mType = type;
+        mId = InsetsSource.createId(owner, index, type);
     }
 
-    public IBinder getOwner() {
-        return mOwner;
+    /**
+     * Returns an unique integer which identifies the insets source.
+     */
+    public int getId() {
+        return mId;
     }
 
+    /**
+     * Returns the index specified in {@link #InsetsFrameProvider(IBinder, int, int)}.
+     */
     public int getIndex() {
-        return mIndex;
+        return InsetsSource.getIndex(mId);
     }
 
+    /**
+     * Returns the {@link InsetsType} specified in {@link #InsetsFrameProvider(IBinder, int, int)}.
+     */
     public int getType() {
-        return mType;
+        return InsetsSource.getType(mId);
     }
 
     public InsetsFrameProvider setSource(int source) {
@@ -211,9 +209,9 @@
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder("InsetsFrameProvider: {");
-        sb.append("owner=").append(mOwner);
-        sb.append(", index=").append(mIndex);
-        sb.append(", type=").append(WindowInsets.Type.toString(mType));
+        sb.append("id=#").append(Integer.toHexString(mId));
+        sb.append(", index=").append(getIndex());
+        sb.append(", type=").append(WindowInsets.Type.toString(getType()));
         sb.append(", source=").append(sourceToString(mSource));
         sb.append(", flags=[").append(InsetsSource.flagsToString(mFlags)).append("]");
         if (mInsetsSize != null) {
@@ -244,9 +242,7 @@
     }
 
     public InsetsFrameProvider(Parcel in) {
-        mOwner = in.readStrongBinder();
-        mIndex = in.readInt();
-        mType = in.readInt();
+        mId = in.readInt();
         mSource = in.readInt();
         mFlags = in.readInt();
         mInsetsSize = in.readTypedObject(Insets.CREATOR);
@@ -256,9 +252,7 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeStrongBinder(mOwner);
-        out.writeInt(mIndex);
-        out.writeInt(mType);
+        out.writeInt(mId);
         out.writeInt(mSource);
         out.writeInt(mFlags);
         out.writeTypedObject(mInsetsSize, flags);
@@ -267,7 +261,7 @@
     }
 
     public boolean idEquals(InsetsFrameProvider o) {
-        return Objects.equals(mOwner, o.mOwner) && mIndex == o.mIndex && mType == o.mType;
+        return mId == o.mId;
     }
 
     @Override
@@ -279,8 +273,7 @@
             return false;
         }
         final InsetsFrameProvider other = (InsetsFrameProvider) o;
-        return Objects.equals(mOwner, other.mOwner) && mIndex == other.mIndex
-                && mType == other.mType && mSource == other.mSource && mFlags == other.mFlags
+        return mId == other.mId && mSource == other.mSource && mFlags == other.mFlags
                 && Objects.equals(mInsetsSize, other.mInsetsSize)
                 && Arrays.equals(mInsetsSizeOverrides, other.mInsetsSizeOverrides)
                 && Objects.equals(mArbitraryRectangle, other.mArbitraryRectangle);
@@ -288,7 +281,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mOwner, mIndex, mType, mSource, mFlags, mInsetsSize,
+        return Objects.hash(mId, mSource, mFlags, mInsetsSize,
                 Arrays.hashCode(mInsetsSizeOverrides), mArbitraryRectangle);
     }
 
@@ -319,7 +312,7 @@
 
         protected InsetsSizeOverride(Parcel in) {
             mWindowType = in.readInt();
-            mInsetsSize = in.readParcelable(null, Insets.class);
+            mInsetsSize = in.readTypedObject(Insets.CREATOR);
         }
 
         public InsetsSizeOverride(int windowType, Insets insetsSize) {
@@ -354,7 +347,7 @@
         @Override
         public void writeToParcel(Parcel out, int flags) {
             out.writeInt(mWindowType);
-            out.writeParcelable(mInsetsSize, flags);
+            out.writeTypedObject(mInsetsSize, flags);
         }
 
         @Override
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index bd48771..114f4ed 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -271,7 +271,7 @@
      * @param index An owner may have multiple sources with the same type. For example, the system
      *              server might have multiple display cutout sources. This is used to identify
      *              which one is which. The value must be in a range of [0, 2047].
-     * @param type The {@link WindowInsets.Type.InsetsType type} of the source.
+     * @param type The {@link InsetsType type} of the source.
      * @return a unique integer as the identifier.
      */
     public static int createId(Object owner, @IntRange(from = 0, to = 2047) int index,
@@ -282,11 +282,29 @@
         // owner takes top 16 bits;
         // index takes 11 bits since the 6th bit;
         // type takes bottom 5 bits.
-        return (((owner != null ? owner.hashCode() : 1) % (1 << 16)) << 16)
+        return ((System.identityHashCode(owner) % (1 << 16)) << 16)
                 + (index << 5)
                 + WindowInsets.Type.indexOf(type);
     }
 
+    /**
+     * Gets the index from the ID.
+     *
+     * @see #createId(Object, int, int)
+     */
+    public static int getIndex(int id) {
+        return (id % (1 << 16)) >> 5;
+    }
+
+    /**
+     * Gets the {@link InsetsType} from the ID.
+     *
+     * @see #createId(Object, int, int)
+     */
+    public static int getType(int id) {
+        return 1 << (id % 32);
+    }
+
     public static String flagsToString(@Flags int flags) {
         final StringJoiner joiner = new StringJoiner(" ");
         if ((flags & FLAG_SUPPRESS_SCRIM) != 0) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8f20e2d..153bfde 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -11325,7 +11325,7 @@
                         // to sync the same frame in the same BBQ. That shouldn't be possible, but
                         // if it did happen, invoke markSyncReady so the active SSG doesn't get
                         // stuck.
-                        Log.e(mTag, "Unable to syncNextTransaction. Possibly something else is"
+                        Log.w(mTag, "Unable to syncNextTransaction. Possibly something else is"
                                 + " trying to sync?");
                         surfaceSyncGroup.markSyncReady();
                     }
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index f7b7d33..6ff4b74 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -16,7 +16,6 @@
 
 package android.view.autofill;
 
-import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS;
 import static android.service.autofill.FillRequest.FLAG_IME_SHOWING;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
@@ -30,7 +29,6 @@
 import static android.view.autofill.Helper.toList;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
-import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -52,21 +50,16 @@
 import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.metrics.LogMaker;
-import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.ICancellationSignal;
 import android.os.Looper;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.service.autofill.AutofillService;
-import android.service.autofill.FillCallback;
 import android.service.autofill.FillEventHistory;
-import android.service.autofill.IFillCallback;
 import android.service.autofill.UserData;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -87,7 +80,6 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
 import android.view.accessibility.AccessibilityWindowInfo;
-import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.CheckBox;
 import android.widget.DatePicker;
@@ -187,12 +179,6 @@
  * shows an autofill save UI if the value of savable views have changed. If the user selects the
  * option to Save, the current value of the views is then sent to the autofill service.
  *
- * <p>There is another choice for the application to provide it's datasets to the Autofill framework
- * by setting an {@link AutofillRequestCallback} through
- * {@link #setAutofillRequestCallback(Executor, AutofillRequestCallback)}. The application can use
- * its callback instead of the default {@link AutofillService}. See
- * {@link AutofillRequestCallback} for more details.
- *
  * <h3 id="additional-notes">Additional notes</h3>
  *
  * <p>It is safe to call <code>AutofillManager</code> methods from any thread.
@@ -326,7 +312,6 @@
     /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2;
     /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4;
     /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY = 0x8;
-    /** @hide */ public static final int FLAG_ENABLED_CLIENT_SUGGESTIONS = 0x20;
 
     // NOTE: flag below is used by the session start receiver only, hence it can have values above
     /** @hide */ public static final int RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY = 0x1;
@@ -653,11 +638,6 @@
     @GuardedBy("mLock")
     private boolean mEnabledForAugmentedAutofillOnly;
 
-    @GuardedBy("mLock")
-    @Nullable private AutofillRequestCallback mAutofillRequestCallback;
-    @GuardedBy("mLock")
-    @Nullable private Executor mRequestCallbackExecutor;
-
     /**
      * Indicates whether there is already a field to do a fill request after
      * the activity started.
@@ -1497,14 +1477,22 @@
         // to PCC classification service.
         if (AutofillFeatureFlags.isAutofillPccClassificationEnabled()) {
             synchronized (mLock) {
-                final boolean clientAdded = tryAddServiceClientIfNeededLocked();
-                if (clientAdded){
-                    startSessionLocked(/* id= */ AutofillId.NO_AUTOFILL_ID,
-                        /* bounds= */ null, /* value= */ null, /* flags= */ FLAG_PCC_DETECTION);
-                } else {
-                    if (sVerbose) {
-                        Log.v(TAG, "not starting session: no service client");
+                // If session has already been created, that'd mean we already have issued the
+                // detection request previously. It is possible in cases like autofocus that this
+                // method isn't invoked, so the server should still handle such cases where fill
+                // request comes in but PCC Detection hasn't been triggered. There is no benefit to
+                // trigger PCC Detection separately in those cases.
+                if (!isActiveLocked()) {
+                    final boolean clientAdded = tryAddServiceClientIfNeededLocked();
+                    if (clientAdded) {
+                        startSessionLocked(/* id= */ AutofillId.NO_AUTOFILL_ID, /* bounds= */ null,
+                                /* value= */ null, /* flags= */ FLAG_PCC_DETECTION);
+                    } else {
+                        if (sVerbose) {
+                            Log.v(TAG, "not starting session: no service client");
+                        }
                     }
+
                 }
             }
         }
@@ -2338,44 +2326,6 @@
         return new AutofillId(parent.getAutofillViewId(), virtualId);
     }
 
-    /**
-     * Sets the client's suggestions callback for autofill.
-     *
-     * @see AutofillRequestCallback
-     *
-     * @param executor specifies the thread upon which the callbacks will be invoked.
-     * @param callback which handles autofill request to provide client's suggestions.
-     *
-     * @hide
-     */
-    @TestApi
-    @RequiresPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
-    public void setAutofillRequestCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull AutofillRequestCallback callback) {
-        if (mContext.checkSelfPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires PROVIDE_OWN_AUTOFILL_SUGGESTIONS permission!");
-        }
-
-        synchronized (mLock) {
-            mRequestCallbackExecutor = executor;
-            mAutofillRequestCallback = callback;
-        }
-    }
-
-    /**
-     * clears the client's suggestions callback for autofill.
-     *
-     * @hide
-     */
-    @TestApi
-    public void clearAutofillRequestCallback() {
-        synchronized (mLock) {
-            mRequestCallbackExecutor = null;
-            mAutofillRequestCallback = null;
-        }
-    }
-
     @GuardedBy("mLock")
     private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds,
             @NonNull AutofillValue value, int flags) {
@@ -2436,13 +2386,6 @@
                 }
             }
 
-            if (mAutofillRequestCallback != null) {
-                if (sDebug) {
-                    Log.d(TAG, "startSession with the client suggestions provider");
-                }
-                flags |= FLAG_ENABLED_CLIENT_SUGGESTIONS;
-            }
-
             mService.startSession(client.autofillClientGetActivityToken(),
                     mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
                     mCallback != null, flags, clientActivity,
@@ -2796,28 +2739,6 @@
         }
     }
 
-    private void onFillRequest(InlineSuggestionsRequest request,
-            CancellationSignal cancellationSignal, FillCallback callback) {
-        final AutofillRequestCallback autofillRequestCallback;
-        final Executor executor;
-        synchronized (mLock) {
-            autofillRequestCallback = mAutofillRequestCallback;
-            executor = mRequestCallbackExecutor;
-        }
-        if (autofillRequestCallback != null && executor != null) {
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                executor.execute(() ->
-                        autofillRequestCallback.onFillRequest(
-                                request, cancellationSignal, callback));
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        } else {
-            callback.onSuccess(null);
-        }
-    }
-
     /** @hide */
     public static final int SET_STATE_FLAG_ENABLED = 0x01;
     /** @hide */
@@ -4374,23 +4295,6 @@
         }
 
         @Override
-        public void requestFillFromClient(int id, InlineSuggestionsRequest request,
-                IFillCallback callback) {
-            final AutofillManager afm = mAfm.get();
-            if (afm != null) {
-                ICancellationSignal transport = CancellationSignal.createTransport();
-                try {
-                    callback.onCancellable(transport);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Error requesting a cancellation", e);
-                }
-
-                afm.onFillRequest(request, CancellationSignal.fromTransport(transport),
-                        new FillCallback(callback, id));
-            }
-        }
-
-        @Override
         public void notifyFillDialogTriggerIds(List<AutofillId> ids) {
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
diff --git a/core/java/android/view/autofill/AutofillRequestCallback.java b/core/java/android/view/autofill/AutofillRequestCallback.java
deleted file mode 100644
index 10a088b..0000000
--- a/core/java/android/view/autofill/AutofillRequestCallback.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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 android.view.autofill;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.TestApi;
-import android.os.CancellationSignal;
-import android.service.autofill.FillCallback;
-import android.view.inputmethod.InlineSuggestionsRequest;
-
-/**
- * <p>This class is used to provide some input suggestions to the Autofill framework.
- *
- * <P>When the user is requested to input something, Autofill will try to query input suggestions
- * for the user choosing. If the application want to provide some internal input suggestions,
- * implements this callback and register via
- * {@link AutofillManager#setAutofillRequestCallback(java.util.concurrent.Executor,
- * AutofillRequestCallback)}. Autofill will callback the
- * {@link #onFillRequest(InlineSuggestionsRequest, CancellationSignal, FillCallback)} to request
- * input suggestions.
- *
- * <P>To make sure the callback to take effect, must register before the autofill session starts.
- * If the autofill session is started, calls {@link AutofillManager#cancel()} to finish current
- * session, and then the callback will be used at the next restarted session.
- *
- * <P>To create a {@link android.service.autofill.FillResponse}, application should fetch
- * {@link AutofillId}s from its view structure. Below is an example:
- * <pre class="prettyprint">
- * AutofillId usernameId = findViewById(R.id.username).getAutofillId();
- * AutofillId passwordId = findViewById(R.id.password).getAutofillId();
- * </pre>
- * To learn more about creating a {@link android.service.autofill.FillResponse}, read
- * <a href="/guide/topics/text/autofill-services#fill">Fill out client views</a>.
- *
- * <P>To fallback to the default {@link android.service.autofill.AutofillService}, just respond
- * a null of the {@link android.service.autofill.FillResponse}. And then Autofill will do a fill
- * request with the default {@link android.service.autofill.AutofillService}. Or clear the callback
- * from {@link AutofillManager} via {@link AutofillManager#clearAutofillRequestCallback()}. If the
- * client would like to keep no suggestions for the field, respond with an empty
- * {@link android.service.autofill.FillResponse} which has no dataset.
- *
- * <P>IMPORTANT: This should not be used for displaying anything other than input suggestions, or
- * the keyboard may choose to block your app from the inline strip.
- *
- * @hide
- */
-@TestApi
-public interface AutofillRequestCallback {
-    /**
-     * Called by the Android system to decide if a screen can be autofilled by the callback.
-     *
-     * @param inlineSuggestionsRequest the {@link InlineSuggestionsRequest request} to handle if
-     *     currently inline suggestions are supported and can be displayed.
-     * @param cancellationSignal signal for observing cancellation requests. The system will use
-     *     this to notify you that the fill result is no longer needed and you should stop
-     *     handling this fill request in order to save resources.
-     * @param callback object used to notify the result of the request.
-     */
-    void onFillRequest(@Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
-            @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback);
-}
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 2e5967c..51afe4c 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -24,11 +24,9 @@
 import android.content.IntentSender;
 import android.graphics.Rect;
 import android.os.IBinder;
-import android.service.autofill.IFillCallback;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutofillWindowPresenter;
-import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.KeyEvent;
 
 import com.android.internal.os.IResultReceiver;
@@ -144,12 +142,6 @@
    void requestShowSoftInput(in AutofillId id);
 
     /**
-     * Requests to determine if a screen can be autofilled by the client app.
-     */
-    void requestFillFromClient(int id, in InlineSuggestionsRequest request,
-            in IFillCallback callback);
-
-    /**
      * Notifies autofill ids that require to show the fill dialog.
      */
     void notifyFillDialogTriggerIds(in List<AutofillId> ids);
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 77a2b5b..581feca 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -112,22 +112,6 @@
     private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
 
     /**
-     * Whether the IME supports inline suggestions from the default Autofill service that
-     * provides the input view.
-     *
-     * Note: The default value is {@code true}.
-     */
-    private boolean mServiceSupported;
-
-    /**
-     * Whether the IME supports inline suggestions from the application that provides the
-     * input view.
-     *
-     * Note: The default value is {@code true}.
-     */
-    private boolean mClientSupported;
-
-    /**
      * @hide
      * @see {@link #mHostInputToken}.
      */
@@ -221,15 +205,9 @@
         return Bundle.EMPTY;
     }
 
-    private static boolean defaultServiceSupported() {
-        return true;
-    }
-
-    private static boolean defaultClientSupported() {
-        return true;
-    }
-
-    /** @hide */
+    /**
+     * @hide
+     */
     abstract static class BaseBuilder {
         abstract Builder setInlinePresentationSpecs(
                 @NonNull List<android.widget.inline.InlinePresentationSpec> specs);
@@ -241,25 +219,14 @@
         abstract Builder setHostDisplayId(int value);
     }
 
-    /** @hide */
-    public boolean isServiceSupported() {
-        return mServiceSupported;
-    }
 
-    /** @hide */
-    public boolean isClientSupported() {
-        return mClientSupported;
-    }
-
-
-
-    // Code below generated by codegen v1.0.22.
+    // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+    // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -275,9 +242,7 @@
             @NonNull Bundle extras,
             @Nullable IBinder hostInputToken,
             int hostDisplayId,
-            @Nullable InlinePresentationSpec inlineTooltipPresentationSpec,
-            boolean serviceSupported,
-            boolean clientSupported) {
+            @Nullable InlinePresentationSpec inlineTooltipPresentationSpec) {
         this.mMaxSuggestionCount = maxSuggestionCount;
         this.mInlinePresentationSpecs = inlinePresentationSpecs;
         com.android.internal.util.AnnotationValidations.validate(
@@ -294,8 +259,6 @@
         this.mHostInputToken = hostInputToken;
         this.mHostDisplayId = hostDisplayId;
         this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
-        this.mServiceSupported = serviceSupported;
-        this.mClientSupported = clientSupported;
 
         onConstructed();
     }
@@ -379,9 +342,7 @@
     }
 
     /**
-     * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response.
-     *
-     * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)
+     * Specifies the UI specification for the inline suggestion tooltip in the response.
      */
     @DataClass.Generated.Member
     public @Nullable InlinePresentationSpec getInlineTooltipPresentationSpec() {
@@ -402,9 +363,7 @@
                 "extras = " + mExtras + ", " +
                 "hostInputToken = " + mHostInputToken + ", " +
                 "hostDisplayId = " + mHostDisplayId + ", " +
-                "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec + ", " +
-                "serviceSupported = " + mServiceSupported + ", " +
-                "clientSupported = " + mClientSupported +
+                "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec +
         " }";
     }
 
@@ -428,9 +387,7 @@
                 && extrasEquals(that.mExtras)
                 && java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
                 && mHostDisplayId == that.mHostDisplayId
-                && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec)
-                && mServiceSupported == that.mServiceSupported
-                && mClientSupported == that.mClientSupported;
+                && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec);
     }
 
     @Override
@@ -448,8 +405,6 @@
         _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
         _hash = 31 * _hash + mHostDisplayId;
         _hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipPresentationSpec);
-        _hash = 31 * _hash + Boolean.hashCode(mServiceSupported);
-        _hash = 31 * _hash + Boolean.hashCode(mClientSupported);
         return _hash;
     }
 
@@ -460,8 +415,6 @@
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
         int flg = 0;
-        if (mServiceSupported) flg |= 0x100;
-        if (mClientSupported) flg |= 0x200;
         if (mHostInputToken != null) flg |= 0x20;
         if (mInlineTooltipPresentationSpec != null) flg |= 0x80;
         dest.writeInt(flg);
@@ -487,11 +440,9 @@
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
         int flg = in.readInt();
-        boolean serviceSupported = (flg & 0x100) != 0;
-        boolean clientSupported = (flg & 0x200) != 0;
         int maxSuggestionCount = in.readInt();
         List<InlinePresentationSpec> inlinePresentationSpecs = new ArrayList<>();
-        in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader(), android.widget.inline.InlinePresentationSpec.class);
+        in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader());
         String hostPackageName = in.readString();
         LocaleList supportedLocales = (LocaleList) in.readTypedObject(LocaleList.CREATOR);
         Bundle extras = in.readBundle();
@@ -515,8 +466,6 @@
         this.mHostInputToken = hostInputToken;
         this.mHostDisplayId = hostDisplayId;
         this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
-        this.mServiceSupported = serviceSupported;
-        this.mClientSupported = clientSupported;
 
         onConstructed();
     }
@@ -550,8 +499,6 @@
         private @Nullable IBinder mHostInputToken;
         private int mHostDisplayId;
         private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
-        private boolean mServiceSupported;
-        private boolean mClientSupported;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -684,9 +631,7 @@
         }
 
         /**
-         * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response.
-         *
-         * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)s
+         * Specifies the UI specification for the inline suggestion tooltip in the response.
          */
         @DataClass.Generated.Member
         public @NonNull Builder setInlineTooltipPresentationSpec(@NonNull InlinePresentationSpec value) {
@@ -696,44 +641,10 @@
             return this;
         }
 
-        /**
-         * Whether the IME supports inline suggestions from the default Autofill service that
-         * provides the input view.
-         *
-         * Note: The default value is {@code true}.
-         *
-         * @hide
-         */
-        @TestApi
-        @DataClass.Generated.Member
-        public @NonNull Builder setServiceSupported(boolean value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x100;
-            mServiceSupported = value;
-            return this;
-        }
-
-        /**
-         * Whether the IME supports inline suggestions from the application that provides the
-         * input view.
-         *
-         * Note: The default value is {@code true}.
-         *
-         * @hide
-         */
-        @TestApi
-        @DataClass.Generated.Member
-        public @NonNull Builder setClientSupported(boolean value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x200;
-            mClientSupported = value;
-            return this;
-        }
-
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull InlineSuggestionsRequest build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x400; // Mark builder used
+            mBuilderFieldsSet |= 0x100; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mMaxSuggestionCount = defaultMaxSuggestionCount();
@@ -756,12 +667,6 @@
             if ((mBuilderFieldsSet & 0x80) == 0) {
                 mInlineTooltipPresentationSpec = defaultInlineTooltipPresentationSpec();
             }
-            if ((mBuilderFieldsSet & 0x100) == 0) {
-                mServiceSupported = defaultServiceSupported();
-            }
-            if ((mBuilderFieldsSet & 0x200) == 0) {
-                mClientSupported = defaultClientSupported();
-            }
             InlineSuggestionsRequest o = new InlineSuggestionsRequest(
                     mMaxSuggestionCount,
                     mInlinePresentationSpecs,
@@ -770,14 +675,12 @@
                     mExtras,
                     mHostInputToken,
                     mHostDisplayId,
-                    mInlineTooltipPresentationSpec,
-                    mServiceSupported,
-                    mClientSupported);
+                    mInlineTooltipPresentationSpec);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x400) != 0) {
+            if ((mBuilderFieldsSet & 0x100) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -785,10 +688,10 @@
     }
 
     @DataClass.Generated(
-            time = 1615798784918L,
-            codegenVersion = "1.0.22",
+            time = 1682382296877L,
+            codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
-            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate  int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate  boolean mServiceSupported\nprivate  boolean mClientSupported\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic  void setHostInputToken(android.os.IBinder)\nprivate  boolean extrasEquals(android.os.Bundle)\nprivate  void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic  void setHostDisplayId(int)\nprivate  void onConstructed()\npublic  void filterContentTypes()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static  android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nprivate static  boolean defaultServiceSupported()\nprivate static  boolean defaultClientSupported()\npublic  boolean isServiceSupported()\npublic  boolean isClientSupported()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate  int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic  void setHostInputToken(android.os.IBinder)\nprivate  boolean extrasEquals(android.os.Bundle)\nprivate  void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic  void setHostDisplayId(int)\nprivate  void onConstructed()\npublic  void filterContentTypes()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static  android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 8f270f5..62f3c90 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -412,4 +412,32 @@
     public boolean setImeConsumesInput(boolean imeConsumesInput) {
         return mTarget.setImeConsumesInput(imeConsumesInput);
     }
+
+    /**
+     * Called by the system when it needs to take a snapshot of multiple text-related data in an
+     * atomic manner.
+     *
+     * <p><strong>Editor authors</strong>: Supporting this method is strongly encouraged. Atomically
+     * taken {@link TextSnapshot} is going to be really helpful for the system when optimizing IPCs
+     * in a safe and deterministic manner.  Return {@code null} if an atomically taken
+     * {@link TextSnapshot} is unavailable.  The system continues supporting such a scenario
+     * gracefully.</p>
+     *
+     * <p><strong>IME authors</strong>: Currently IMEs cannot call this method directly and always
+     * receive {@code null} as the result.</p>
+     *
+     * <p>Beware that there is a bug that this method was not overridden in
+     * {@link InputConnectionWrapper}, which ended up always returning {@code null} when gets
+     * called even if the wrapped {@link InputConnection} implements this method.  The bug was
+     * fixed in {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}.</p>
+     *
+     * @return {@code null} if {@link TextSnapshot} is unavailable and/or this API is called from
+     *         IMEs. Beware the bug in older devices mentioned above.
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Nullable
+    @Override
+    public TextSnapshot takeSnapshot() {
+        return mTarget.takeSnapshot();
+    }
 }
diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index 95451a9..fa7f577 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -198,17 +198,21 @@
          * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object.
          *
          * @param hardwareBuffer       The existing HardwareBuffer object
-         * @param namedColorSpace      Integer value of a named color space {@link ColorSpace.Named}
+         * @param dataspace            Dataspace describing the content.
+         *                             {@see android.hardware.DataSpace}
          * @param containsSecureLayers Indicates whether this graphic buffer contains captured
          *                             contents of secure layers, in which case the screenshot
          *                             should not be persisted.
          * @param containsHdrLayers    Indicates whether this graphic buffer contains HDR content.
          */
         private static ScreenshotHardwareBuffer createFromNative(HardwareBuffer hardwareBuffer,
-                int namedColorSpace, boolean containsSecureLayers, boolean containsHdrLayers) {
-            ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.values()[namedColorSpace]);
+                int dataspace, boolean containsSecureLayers, boolean containsHdrLayers) {
+            ColorSpace colorSpace = ColorSpace.getFromDataSpace(dataspace);
             return new ScreenshotHardwareBuffer(
-                    hardwareBuffer, colorSpace, containsSecureLayers, containsHdrLayers);
+                    hardwareBuffer,
+                    colorSpace != null ? colorSpace : ColorSpace.get(ColorSpace.Named.SRGB),
+                    containsSecureLayers,
+                    containsHdrLayers);
         }
 
         public ColorSpace getColorSpace() {
@@ -271,8 +275,8 @@
         public final boolean mAllowProtected;
         public final long mUid;
         public final boolean mGrayscale;
-
         final SurfaceControl[] mExcludeLayers;
+        public final boolean mHintForSeamlessTransition;
 
         private CaptureArgs(CaptureArgs.Builder<? extends CaptureArgs.Builder<?>> builder) {
             mPixelFormat = builder.mPixelFormat;
@@ -284,6 +288,7 @@
             mUid = builder.mUid;
             mGrayscale = builder.mGrayscale;
             mExcludeLayers = builder.mExcludeLayers;
+            mHintForSeamlessTransition = builder.mHintForSeamlessTransition;
         }
 
         private CaptureArgs(Parcel in) {
@@ -305,6 +310,7 @@
             } else {
                 mExcludeLayers = null;
             }
+            mHintForSeamlessTransition = in.readBoolean();
         }
 
         /** Release any layers if set using {@link Builder#setExcludeLayers(SurfaceControl[])}. */
@@ -352,6 +358,7 @@
             private long mUid = -1;
             private boolean mGrayscale;
             private SurfaceControl[] mExcludeLayers;
+            private boolean mHintForSeamlessTransition;
 
             /**
              * Construct a new {@link CaptureArgs} with the set parameters. The builder remains
@@ -449,6 +456,21 @@
             }
 
             /**
+             * Set whether the screenshot will be used in a system animation.
+             * This hint is used for picking the "best" colorspace for the screenshot, in particular
+             * for mixing HDR and SDR content.
+             * E.g., hintForSeamlessTransition is false, then a colorspace suitable for file
+             * encoding, such as BT2100, may be chosen. Otherwise, then the display's color space
+             * would be chosen, with the possibility of having an extended brightness range. This
+             * is important for screenshots that are directly re-routed to a SurfaceControl in
+             * order to preserve accurate colors.
+             */
+            public T setHintForSeamlessTransition(boolean hintForSeamlessTransition) {
+                mHintForSeamlessTransition = hintForSeamlessTransition;
+                return getThis();
+            }
+
+            /**
              * Each sub class should return itself to allow the builder to chain properly
              */
             T getThis() {
@@ -471,7 +493,6 @@
             dest.writeBoolean(mAllowProtected);
             dest.writeLong(mUid);
             dest.writeBoolean(mGrayscale);
-
             if (mExcludeLayers != null) {
                 dest.writeInt(mExcludeLayers.length);
                 for (SurfaceControl excludeLayer : mExcludeLayers) {
@@ -480,6 +501,7 @@
             } else {
                 dest.writeInt(0);
             }
+            dest.writeBoolean(mHintForSeamlessTransition);
         }
 
         public static final Parcelable.Creator<CaptureArgs> CREATOR =
@@ -627,6 +649,7 @@
                 setUid(args.mUid);
                 setGrayscale(args.mGrayscale);
                 setExcludeLayers(args.mExcludeLayers);
+                setHintForSeamlessTransition(args.mHintForSeamlessTransition);
             }
 
             public Builder(SurfaceControl layer) {
diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java
index 5181236..6999e5b 100644
--- a/core/java/android/window/StartingWindowRemovalInfo.java
+++ b/core/java/android/window/StartingWindowRemovalInfo.java
@@ -16,6 +16,7 @@
 
 package android.window;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Rect;
@@ -23,6 +24,9 @@
 import android.os.Parcelable;
 import android.view.SurfaceControl;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Information when removing a starting window of a particular task.
  * @hide
@@ -55,11 +59,28 @@
      */
     public boolean playRevealAnimation;
 
+    /** The mode is no need to defer removing the starting window for IME */
+    public static final int DEFER_MODE_NONE = 0;
+
+    /** The mode to defer removing the starting window until IME has drawn */
+    public static final int DEFER_MODE_NORMAL = 1;
+
+    /** The mode to defer the starting window removal until IME drawn and finished the rotation */
+    public static final int DEFER_MODE_ROTATION = 2;
+
+    @IntDef(prefix = { "DEFER_MODE_" }, value = {
+            DEFER_MODE_NONE,
+            DEFER_MODE_NORMAL,
+            DEFER_MODE_ROTATION,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DeferMode {}
+
     /**
      * Whether need to defer removing the starting window for IME.
      * @hide
      */
-    public boolean deferRemoveForIme;
+    public @DeferMode int deferRemoveForImeMode;
 
     /**
      * The rounded corner radius
@@ -95,7 +116,7 @@
         windowAnimationLeash = source.readTypedObject(SurfaceControl.CREATOR);
         mainFrame = source.readTypedObject(Rect.CREATOR);
         playRevealAnimation = source.readBoolean();
-        deferRemoveForIme = source.readBoolean();
+        deferRemoveForImeMode = source.readInt();
         roundedCornerRadius = source.readFloat();
         windowlessSurface = source.readBoolean();
         removeImmediately = source.readBoolean();
@@ -107,7 +128,7 @@
         dest.writeTypedObject(windowAnimationLeash, flags);
         dest.writeTypedObject(mainFrame, flags);
         dest.writeBoolean(playRevealAnimation);
-        dest.writeBoolean(deferRemoveForIme);
+        dest.writeInt(deferRemoveForImeMode);
         dest.writeFloat(roundedCornerRadius);
         dest.writeBoolean(windowlessSurface);
         dest.writeBoolean(removeImmediately);
@@ -119,7 +140,7 @@
                 + " frame=" + mainFrame
                 + " playRevealAnimation=" + playRevealAnimation
                 + " roundedCornerRadius=" + roundedCornerRadius
-                + " deferRemoveForIme=" + deferRemoveForIme
+                + " deferRemoveForImeMode=" + deferRemoveForImeMode
                 + " windowlessSurface=" + windowlessSurface
                 + " removeImmediately=" + removeImmediately + "}";
     }
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 628fc31..c0370cc 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -21,6 +21,7 @@
 import static android.app.ActivityOptions.ANIM_FROM_STYLE;
 import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
 import static android.app.ActivityOptions.ANIM_SCALE_UP;
+import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
@@ -1067,6 +1068,11 @@
             return options;
         }
 
+        public static AnimationOptions makeSceneTransitionAnimOptions() {
+            AnimationOptions options = new AnimationOptions(ANIM_SCENE_TRANSITION);
+            return options;
+        }
+
         public int getType() {
             return mType;
         }
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 51382a4..4d0132e 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -21,6 +21,8 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -421,36 +423,45 @@
                 return false;
             }
 
-            boolean requestsPredictiveBack;
+            boolean requestsPredictiveBack = false;
 
             // Check if the context is from an activity.
             while ((context instanceof ContextWrapper) && !(context instanceof Activity)) {
                 context = ((ContextWrapper) context).getBaseContext();
             }
 
+            boolean shouldCheckActivity = false;
+
             if (context instanceof Activity) {
                 final Activity activity = (Activity) context;
 
-                if (activity.getActivityInfo().hasOnBackInvokedCallbackEnabled()) {
-                    requestsPredictiveBack =
-                            activity.getActivityInfo().isOnBackInvokedCallbackEnabled();
-                } else {
-                    requestsPredictiveBack =
-                            context.getApplicationInfo().isOnBackInvokedCallbackEnabled();
-                }
+                final ActivityInfo activityInfo = activity.getActivityInfo();
+                if (activityInfo != null) {
+                    if (activityInfo.hasOnBackInvokedCallbackEnabled()) {
+                        shouldCheckActivity = true;
+                        requestsPredictiveBack = activityInfo.isOnBackInvokedCallbackEnabled();
 
-                if (DEBUG) {
-                    Log.d(TAG, TextUtils.formatSimple("Activity: %s isPredictiveBackEnabled=%s",
-                            activity.getComponentName(),
-                            requestsPredictiveBack));
+                        if (DEBUG) {
+                            Log.d(TAG, TextUtils.formatSimple(
+                                    "Activity: %s isPredictiveBackEnabled=%s",
+                                    activity.getComponentName(),
+                                    requestsPredictiveBack));
+                        }
+                    }
+                } else {
+                    Log.w(TAG, "The ActivityInfo is null, so we cannot verify if this Activity"
+                            + " has the 'android:enableOnBackInvokedCallback' attribute."
+                            + " The application attribute will be used as a fallback.");
                 }
-            } else {
-                requestsPredictiveBack =
-                        context.getApplicationInfo().isOnBackInvokedCallbackEnabled();
+            }
+
+            if (!shouldCheckActivity) {
+                final ApplicationInfo applicationInfo = context.getApplicationInfo();
+                requestsPredictiveBack = applicationInfo.isOnBackInvokedCallbackEnabled();
 
                 if (DEBUG) {
                     Log.d(TAG, TextUtils.formatSimple("App: %s requestsPredictiveBack=%s",
-                            context.getApplicationInfo().packageName,
+                            applicationInfo.packageName,
                             requestsPredictiveBack));
                 }
             }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 853fe2f..86c2893 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -76,7 +76,7 @@
 
         /** Gating the removal of sorting-notifications-by-interruptiveness. */
         public static final Flag NO_SORT_BY_INTERRUPTIVENESS =
-                devFlag("persist.sysui.notification.no_sort_by_interruptiveness");
+                releasedFlag("persist.sysui.notification.no_sort_by_interruptiveness");
 
         /** Gating the logging of DND state change events. */
         public static final Flag LOG_DND_STATE_EVENTS =
@@ -115,7 +115,7 @@
     }
 
     /**
-     * Creates a flag that is enabled by default in debuggable builds.
+     * Creates a flag that is disabled by default in debuggable builds.
      * It can be enabled by setting this flag's SystemProperty to 1.
      *
      * This flag is ALWAYS disabled in release builds.
diff --git a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
deleted file mode 100644
index 39d8380..0000000
--- a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.display;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.hardware.display.DisplayManager;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.Display;
-
-/**
- * Constants and utility methods for refresh rate settings.
- */
-public class RefreshRateSettingsUtils {
-
-    private static final String TAG = "RefreshRateSettingsUtils";
-
-    public static final float DEFAULT_REFRESH_RATE = 60f;
-
-    /**
-     * Find the highest refresh rate among all the modes of the default display.
-     * @param context The context
-     * @return The highest refresh rate
-     */
-    public static float findHighestRefreshRateForDefaultDisplay(Context context) {
-        final DisplayManager dm = context.getSystemService(DisplayManager.class);
-        final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
-
-        if (display == null) {
-            Log.w(TAG, "No valid default display device");
-            return DEFAULT_REFRESH_RATE;
-        }
-
-        float maxRefreshRate = DEFAULT_REFRESH_RATE;
-        for (Display.Mode mode : display.getSupportedModes()) {
-            if (Math.round(mode.getRefreshRate()) > maxRefreshRate) {
-                maxRefreshRate = mode.getRefreshRate();
-            }
-        }
-        return maxRefreshRate;
-    }
-
-    /**
-     * Get the min refresh rate which is determined by
-     * {@link Settings.System.FORCE_PEAK_REFRESH_RATE}.
-     * @param context The context
-     * @return The min refresh rate
-     */
-    public static float getMinRefreshRate(Context context) {
-        final ContentResolver cr = context.getContentResolver();
-        int forcePeakRefreshRateSetting = Settings.System.getIntForUser(cr,
-                Settings.System.FORCE_PEAK_REFRESH_RATE, -1, cr.getUserId());
-        return forcePeakRefreshRateSetting == 1
-                ? findHighestRefreshRateForDefaultDisplay(context)
-                : 0;
-    }
-
-    /**
-     * Get the peak refresh rate which is determined by {@link Settings.System.SMOOTH_DISPLAY}.
-     * @param context The context
-     * @param defaultPeakRefreshRate The refresh rate to return if the setting doesn't have a value
-     * @return The peak refresh rate
-     */
-    public static float getPeakRefreshRate(Context context, float defaultPeakRefreshRate) {
-        final ContentResolver cr = context.getContentResolver();
-        int smoothDisplaySetting = Settings.System.getIntForUser(cr,
-                Settings.System.SMOOTH_DISPLAY, -1, cr.getUserId());
-        switch (smoothDisplaySetting) {
-            case 0:
-                return DEFAULT_REFRESH_RATE;
-            case 1:
-                return findHighestRefreshRateForDefaultDisplay(context);
-            default:
-                return defaultPeakRefreshRate;
-        }
-    }
-}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index fbad4b9..a554d0e 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -731,7 +731,7 @@
         return DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION,
                 FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
-                /* defaultValue= */ false);
+                /* defaultValue= */ true);
     }
 
     /** Returns if the given quality maps to an alphabetic password */
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 6fcff99..0f41229 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -374,7 +374,7 @@
                     }
                     inputEventObj =
                             android_view_KeyEvent_fromNative(env,
-                                                             static_cast<KeyEvent*>(inputEvent));
+                                                             static_cast<KeyEvent&>(*inputEvent));
                     break;
 
                 case InputEventType::MOTION: {
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index ad54004..15270ef 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -353,8 +353,7 @@
         jint seq, jobject eventObj) {
     sp<NativeInputEventSender> sender =
             reinterpret_cast<NativeInputEventSender*>(senderPtr);
-    KeyEvent event;
-    android_view_KeyEvent_toNative(env, eventObj, &event);
+    const KeyEvent event = android_view_KeyEvent_toNative(env, eventObj);
     status_t status = sender->sendKeyEvent(seq, &event);
     return !status;
 }
diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp
index 2c4966e..21db37e 100644
--- a/core/jni/android_view_InputQueue.cpp
+++ b/core/jni/android_view_InputQueue.cpp
@@ -221,12 +221,7 @@
         jboolean predispatch) {
     InputQueue* queue = reinterpret_cast<InputQueue*>(ptr);
     KeyEvent* event = queue->createKeyEvent();
-    status_t status = android_view_KeyEvent_toNative(env, eventObj, event);
-    if (status) {
-        queue->recycleInputEvent(event);
-        jniThrowRuntimeException(env, "Could not read contents of KeyEvent object.");
-        return -1;
-    }
+    *event = android_view_KeyEvent_toNative(env, eventObj);
 
     if (predispatch) {
         event->setFlags(event->getFlags() | AKEY_EVENT_FLAG_PREDISPATCH);
diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp
index 469e577..8fa03cf 100644
--- a/core/jni/android_view_KeyCharacterMap.cpp
+++ b/core/jni/android_view_KeyCharacterMap.cpp
@@ -217,7 +217,7 @@
         result = env->NewObjectArray(jsize(events.size()), gKeyEventClassInfo.clazz, NULL);
         if (result) {
             for (size_t i = 0; i < events.size(); i++) {
-                jobject keyEventObj = android_view_KeyEvent_fromNative(env, &events.itemAt(i));
+                jobject keyEventObj = android_view_KeyEvent_fromNative(env, events.itemAt(i));
                 if (!keyEventObj) break; // threw OOM exception
                 env->SetObjectArrayElement(result, jsize(i), keyEventObj);
                 env->DeleteLocalRef(keyEventObj);
diff --git a/core/jni/android_view_KeyEvent.cpp b/core/jni/android_view_KeyEvent.cpp
index d5568df..a9c9919 100644
--- a/core/jni/android_view_KeyEvent.cpp
+++ b/core/jni/android_view_KeyEvent.cpp
@@ -94,16 +94,16 @@
 
 // ----------------------------------------------------------------------------
 
-jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent* event) {
-    ScopedLocalRef<jbyteArray> hmac = toJbyteArray(env, event->getHmac());
+jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent& event) {
+    ScopedLocalRef<jbyteArray> hmac = toJbyteArray(env, event.getHmac());
     jobject eventObj =
             env->CallStaticObjectMethod(gKeyEventClassInfo.clazz, gKeyEventClassInfo.obtain,
-                                        event->getId(), event->getDownTime(), event->getEventTime(),
-                                        event->getAction(), event->getKeyCode(),
-                                        event->getRepeatCount(), event->getMetaState(),
-                                        event->getDeviceId(), event->getScanCode(),
-                                        event->getFlags(), event->getSource(),
-                                        event->getDisplayId(), hmac.get(), nullptr);
+                                        event.getId(), event.getDownTime(), event.getEventTime(),
+                                        event.getAction(), event.getKeyCode(),
+                                        event.getRepeatCount(), event.getMetaState(),
+                                        event.getDeviceId(), event.getScanCode(), event.getFlags(),
+                                        event.getSource(), event.getDisplayId(), hmac.get(),
+                                        nullptr);
     if (env->ExceptionCheck()) {
         ALOGE("An exception occurred while obtaining a key event.");
         LOGE_EX(env);
@@ -113,8 +113,7 @@
     return eventObj;
 }
 
-status_t android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj,
-        KeyEvent* event) {
+KeyEvent android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj) {
     jint id = env->GetIntField(eventObj, gKeyEventClassInfo.mId);
     jint deviceId = env->GetIntField(eventObj, gKeyEventClassInfo.mDeviceId);
     jint source = env->GetIntField(eventObj, gKeyEventClassInfo.mSource);
@@ -133,9 +132,10 @@
     jlong downTime = env->GetLongField(eventObj, gKeyEventClassInfo.mDownTime);
     jlong eventTime = env->GetLongField(eventObj, gKeyEventClassInfo.mEventTime);
 
-    event->initialize(id, deviceId, source, displayId, *hmac, action, flags, keyCode, scanCode,
-                      metaState, repeatCount, downTime, eventTime);
-    return OK;
+    KeyEvent event;
+    event.initialize(id, deviceId, source, displayId, *hmac, action, flags, keyCode, scanCode,
+                     metaState, repeatCount, downTime, eventTime);
+    return event;
 }
 
 status_t android_view_KeyEvent_recycle(JNIEnv* env, jobject eventObj) {
diff --git a/core/jni/android_view_KeyEvent.h b/core/jni/android_view_KeyEvent.h
index dab6bb7..bc4876a 100644
--- a/core/jni/android_view_KeyEvent.h
+++ b/core/jni/android_view_KeyEvent.h
@@ -27,12 +27,11 @@
 
 /* Obtains an instance of a DVM KeyEvent object as a copy of a native KeyEvent instance.
  * Returns NULL on error. */
-extern jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent* event);
+extern jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent& event);
 
 /* Copies the contents of a DVM KeyEvent object to a native KeyEvent instance.
  * Returns non-zero on error. */
-extern status_t android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj,
-        KeyEvent* event);
+extern KeyEvent android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj);
 
 /* Recycles a DVM KeyEvent object.
  * Key events should only be recycled if they are owned by the system since user
diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index 1b67a0d..986dbe9 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -46,6 +46,7 @@
     jfieldID uid;
     jfieldID grayscale;
     jmethodID getNativeExcludeLayers;
+    jfieldID hintForSeamlessTransition;
 } gCaptureArgsClassInfo;
 
 static struct {
@@ -69,23 +70,6 @@
     jmethodID builder;
 } gScreenshotHardwareBufferClassInfo;
 
-enum JNamedColorSpace : jint {
-    // ColorSpace.Named.SRGB.ordinal() = 0;
-    SRGB = 0,
-
-    // ColorSpace.Named.DISPLAY_P3.ordinal() = 7;
-    DISPLAY_P3 = 7,
-};
-
-constexpr jint fromDataspaceToNamedColorSpaceValue(const ui::Dataspace dataspace) {
-    switch (dataspace) {
-        case ui::Dataspace::DISPLAY_P3:
-            return JNamedColorSpace::DISPLAY_P3;
-        default:
-            return JNamedColorSpace::SRGB;
-    }
-}
-
 static void checkAndClearException(JNIEnv* env, const char* methodName) {
     if (env->ExceptionCheck()) {
         ALOGE("An exception was thrown by callback '%s'.", methodName);
@@ -119,12 +103,11 @@
         captureResults.fenceResult.value()->waitForever(LOG_TAG);
         jobject jhardwareBuffer = android_hardware_HardwareBuffer_createFromAHardwareBuffer(
                 env, captureResults.buffer->toAHardwareBuffer());
-        const jint namedColorSpace =
-                fromDataspaceToNamedColorSpaceValue(captureResults.capturedDataspace);
         jobject screenshotHardwareBuffer =
                 env->CallStaticObjectMethod(gScreenshotHardwareBufferClassInfo.clazz,
                                             gScreenshotHardwareBufferClassInfo.builder,
-                                            jhardwareBuffer, namedColorSpace,
+                                            jhardwareBuffer,
+                                            static_cast<jint>(captureResults.capturedDataspace),
                                             captureResults.capturedSecureLayers,
                                             captureResults.capturedHdrLayers);
         checkAndClearException(env, "builder");
@@ -185,6 +168,9 @@
             captureArgs.excludeHandles.emplace(excludeObject->getHandle());
         }
     }
+    captureArgs.hintForSeamlessTransition =
+            env->GetBooleanField(captureArgsObject,
+                                 gCaptureArgsClassInfo.hintForSeamlessTransition);
 }
 
 static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env,
@@ -318,9 +304,10 @@
             GetFieldIDOrDie(env, captureArgsClazz, "mAllowProtected", "Z");
     gCaptureArgsClassInfo.uid = GetFieldIDOrDie(env, captureArgsClazz, "mUid", "J");
     gCaptureArgsClassInfo.grayscale = GetFieldIDOrDie(env, captureArgsClazz, "mGrayscale", "Z");
-
     gCaptureArgsClassInfo.getNativeExcludeLayers =
             GetMethodIDOrDie(env, captureArgsClazz, "getNativeExcludeLayers", "()[J");
+    gCaptureArgsClassInfo.hintForSeamlessTransition =
+            GetFieldIDOrDie(env, captureArgsClazz, "mHintForSeamlessTransition", "Z");
 
     jclass displayCaptureArgsClazz =
             FindClassOrDie(env, "android/window/ScreenCapture$DisplayCaptureArgs");
diff --git a/core/proto/android/companion/telecom.proto b/core/proto/android/companion/telecom.proto
index 9ccadbf..02ba7c5 100644
--- a/core/proto/android/companion/telecom.proto
+++ b/core/proto/android/companion/telecom.proto
@@ -20,12 +20,12 @@
 
 option java_multiple_files = true;
 
-// Next index: 2
+// Next index: 4
 message Telecom {
-  // Next index: 5
+  // Next index: 6
   message Call {
     // UUID representing this call
-    int64 id = 1;
+    string id = 1;
 
     message Origin {
       // Caller's name and/or phone number; what a user would see displayed when receiving an
@@ -34,6 +34,8 @@
       // Human-readable name of the app processing this call
       string app_name = 2;
       bytes app_icon = 3;
+      // Unique identifier for this app, such as a package name.
+      string app_identifier = 4;
     }
     Origin origin = 2;
 
@@ -46,22 +48,25 @@
     }
     Status status = 3;
 
-    enum Control {
-      UNKNOWN_CONTROL = 0;
-      ACCEPT = 1;
-      REJECT = 2;
-      SILENCE = 3;
-      MUTE = 4;
-      UNMUTE = 5;
-      END = 6;
-      PUT_ON_HOLD = 7;
-      TAKE_OFF_HOLD = 8;
-      REJECT_AND_BLOCK = 9;
-      IGNORE = 10;
-    }
-    repeated Control controls_available = 4;
+    repeated Control controls = 4;
+  }
+
+  enum Control {
+    UNKNOWN_CONTROL = 0;
+    ACCEPT = 1;
+    REJECT = 2;
+    SILENCE = 3;
+    MUTE = 4;
+    UNMUTE = 5;
+    END = 6;
+    PUT_ON_HOLD = 7;
+    TAKE_OFF_HOLD = 8;
+    REJECT_AND_BLOCK = 9;
+    IGNORE = 10;
   }
 
   // The list of active calls.
   repeated Call calls = 1;
+  // The list of requested calls or call changes.
+  repeated Call requests = 2;
 }
diff --git a/core/res/res/drawable-hdpi/pointer_all_scroll.png b/core/res/res/drawable-hdpi/pointer_all_scroll.png
index 095aadc..4af84c3 100644
--- a/core/res/res/drawable-hdpi/pointer_all_scroll.png
+++ b/core/res/res/drawable-hdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_context_menu.png b/core/res/res/drawable-hdpi/pointer_context_menu.png
index c45d29b..60d37c4 100644
--- a/core/res/res/drawable-hdpi/pointer_context_menu.png
+++ b/core/res/res/drawable-hdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_help.png b/core/res/res/drawable-hdpi/pointer_help.png
index a3afdb6..2d9d20c 100644
--- a/core/res/res/drawable-hdpi/pointer_help.png
+++ b/core/res/res/drawable-hdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png
index 9388f16..c4018c8 100644
--- a/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png
+++ b/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png
index ab52bff..58bb0d4 100644
--- a/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png
+++ b/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png
index 1250d35..1981d41 100644
--- a/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png
+++ b/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png
index 6730c7b..d4ba79a 100644
--- a/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png
+++ b/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_all_scroll.png b/core/res/res/drawable-mdpi/pointer_all_scroll.png
index 3db456e..1b81d0a 100644
--- a/core/res/res/drawable-mdpi/pointer_all_scroll.png
+++ b/core/res/res/drawable-mdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_all_scroll_large.png b/core/res/res/drawable-mdpi/pointer_all_scroll_large.png
index 120e1d7..9e1f5c9 100644
--- a/core/res/res/drawable-mdpi/pointer_all_scroll_large.png
+++ b/core/res/res/drawable-mdpi/pointer_all_scroll_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_context_menu.png b/core/res/res/drawable-mdpi/pointer_context_menu.png
index e0e849d..d87d040 100644
--- a/core/res/res/drawable-mdpi/pointer_context_menu.png
+++ b/core/res/res/drawable-mdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_context_menu_large.png b/core/res/res/drawable-mdpi/pointer_context_menu_large.png
index e8c9be4..15266a6 100644
--- a/core/res/res/drawable-mdpi/pointer_context_menu_large.png
+++ b/core/res/res/drawable-mdpi/pointer_context_menu_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_help.png b/core/res/res/drawable-mdpi/pointer_help.png
index 286242c..bd04bbd 100644
--- a/core/res/res/drawable-mdpi/pointer_help.png
+++ b/core/res/res/drawable-mdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_help_large.png b/core/res/res/drawable-mdpi/pointer_help_large.png
index 27f4a84..f9bd2b7 100644
--- a/core/res/res/drawable-mdpi/pointer_help_large.png
+++ b/core/res/res/drawable-mdpi/pointer_help_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png
index 20f319a..d1b3441 100644
--- a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png
index 33ef5c9..4e26371 100644
--- a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png
index fe7d496..34c0c6a 100644
--- a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png
index 7b2e20c..87ec184 100644
--- a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png
index 95a6620..40b9c7e 100644
--- a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png
index 2e2904b..6a85b49 100644
--- a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png
index ae6bfed..9bd89bf 100644
--- a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png
index 3beb1d1..5a69bbc 100644
--- a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_all_scroll.png b/core/res/res/drawable-xhdpi/pointer_all_scroll.png
index e9d05d5..85aa022 100644
--- a/core/res/res/drawable-xhdpi/pointer_all_scroll.png
+++ b/core/res/res/drawable-xhdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png b/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png
index 1fd54fb..7448339 100644
--- a/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_context_menu.png b/core/res/res/drawable-xhdpi/pointer_context_menu.png
index d4b2bde..15d1e33 100644
--- a/core/res/res/drawable-xhdpi/pointer_context_menu.png
+++ b/core/res/res/drawable-xhdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_context_menu_large.png b/core/res/res/drawable-xhdpi/pointer_context_menu_large.png
index 977df10..e4ff7a6 100644
--- a/core/res/res/drawable-xhdpi/pointer_context_menu_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_context_menu_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_help.png b/core/res/res/drawable-xhdpi/pointer_help.png
index 5a6805c..952a4ee 100644
--- a/core/res/res/drawable-xhdpi/pointer_help.png
+++ b/core/res/res/drawable-xhdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_help_large.png b/core/res/res/drawable-xhdpi/pointer_help_large.png
index 4bdc3d1..1d68437 100644
--- a/core/res/res/drawable-xhdpi/pointer_help_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_help_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png
index caf2a97..dd37f92 100644
--- a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png
index 2f22640..9e031e8 100644
--- a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png
index a36deb3..150d80d 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png
index 6870e23..bae907a 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png
index c8d6d1f..3b23143 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png
index 5bfb771..a90b286 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png
index 720df91..3e7f850 100644
--- a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png
index 82b30d1..090e3ca 100644
--- a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_all_scroll.png b/core/res/res/drawable-xxhdpi/pointer_all_scroll.png
index 808143a..92aae72 100644
--- a/core/res/res/drawable-xxhdpi/pointer_all_scroll.png
+++ b/core/res/res/drawable-xxhdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_context_menu.png b/core/res/res/drawable-xxhdpi/pointer_context_menu.png
index 6ebfaab..4cd20f5 100644
--- a/core/res/res/drawable-xxhdpi/pointer_context_menu.png
+++ b/core/res/res/drawable-xxhdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_help.png b/core/res/res/drawable-xxhdpi/pointer_help.png
index 96b2a71..0c7a264 100644
--- a/core/res/res/drawable-xxhdpi/pointer_help.png
+++ b/core/res/res/drawable-xxhdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png
index 677ccad..b1e2509 100644
--- a/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png
+++ b/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png
index e01aa64..2d1217c 100644
--- a/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png
+++ b/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png
index e947e0e..a99fb24 100644
--- a/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png
+++ b/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png
index c867247..1f065fa 100644
--- a/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png
+++ b/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable/pointer_context_menu_large_icon.xml b/core/res/res/drawable/pointer_context_menu_large_icon.xml
index e07e5b6..325ea669 100644
--- a/core/res/res/drawable/pointer_context_menu_large_icon.xml
+++ b/core/res/res/drawable/pointer_context_menu_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_context_menu_large"
-    android:hotSpotX="13.5dp"
-    android:hotSpotY="10.5dp" />
+    android:hotSpotX="10.5dp"
+    android:hotSpotY="8dp" />
diff --git a/core/res/res/drawable/pointer_help_large_icon.xml b/core/res/res/drawable/pointer_help_large_icon.xml
index 43a1261..20f0c5336 100644
--- a/core/res/res/drawable/pointer_help_large_icon.xml
+++ b/core/res/res/drawable/pointer_help_large_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
     android:bitmap="@drawable/pointer_help_large"
-    android:hotSpotX="13.5dp"
-    android:hotSpotY="10.5dp" />
+    android:hotSpotX="10.5dp"
+    android:hotSpotY="8dp" />
diff --git a/core/res/res/layout-television/user_switching_dialog.xml b/core/res/res/layout-television/user_switching_dialog.xml
deleted file mode 100644
index 72150e3..0000000
--- a/core/res/res/layout-television/user_switching_dialog.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 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.
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/message"
-        style="?attr/textAppearanceListItem"
-        android:layout_width="match_parent"
-        android:background="@color/background_leanback_dark"
-        android:textColor="@color/primary_text_leanback_dark"
-        android:layout_height="match_parent"
-        android:gravity="center"
-        android:paddingStart="?attr/dialogPreferredPadding"
-        android:paddingEnd="?attr/dialogPreferredPadding"
-        android:paddingTop="24dp"
-        android:paddingBottom="24dp" />
diff --git a/core/res/res/values-mcc310/config.xml b/core/res/res/values-mcc310/config.xml
index df398f9..76abcee 100644
--- a/core/res/res/values-mcc310/config.xml
+++ b/core/res/res/values-mcc310/config.xml
@@ -22,4 +22,7 @@
     <!-- Whether safe headphone volume is enabled or not (country specific). -->
     <bool name="config_safe_media_volume_enabled">false</bool>
 
+    <!-- Whether safe headphone sound dosage warning is enabled or not (country specific). -->
+    <bool name="config_safe_sound_dosage_mcc_enabled">false</bool>
+
 </resources>
diff --git a/core/res/res/values-mcc311/config.xml b/core/res/res/values-mcc311/config.xml
index df398f9..6e0b678 100644
--- a/core/res/res/values-mcc311/config.xml
+++ b/core/res/res/values-mcc311/config.xml
@@ -22,4 +22,7 @@
     <!-- Whether safe headphone volume is enabled or not (country specific). -->
     <bool name="config_safe_media_volume_enabled">false</bool>
 
+    <!-- Whether safe headphone sound dosage warning is enabled or not (country specific). -->
+    <bool name="config_safe_sound_dosage_enabled">false</bool>
+
 </resources>
diff --git a/core/res/res/values-mcc312/config.xml b/core/res/res/values-mcc312/config.xml
index df398f9..6e0b678 100644
--- a/core/res/res/values-mcc312/config.xml
+++ b/core/res/res/values-mcc312/config.xml
@@ -22,4 +22,7 @@
     <!-- Whether safe headphone volume is enabled or not (country specific). -->
     <bool name="config_safe_media_volume_enabled">false</bool>
 
+    <!-- Whether safe headphone sound dosage warning is enabled or not (country specific). -->
+    <bool name="config_safe_sound_dosage_enabled">false</bool>
+
 </resources>
diff --git a/core/res/res/values-mcc313/config.xml b/core/res/res/values-mcc313/config.xml
index df398f9..6e0b678 100644
--- a/core/res/res/values-mcc313/config.xml
+++ b/core/res/res/values-mcc313/config.xml
@@ -22,4 +22,7 @@
     <!-- Whether safe headphone volume is enabled or not (country specific). -->
     <bool name="config_safe_media_volume_enabled">false</bool>
 
+    <!-- Whether safe headphone sound dosage warning is enabled or not (country specific). -->
+    <bool name="config_safe_sound_dosage_enabled">false</bool>
+
 </resources>
diff --git a/core/res/res/values-mcc314/config.xml b/core/res/res/values-mcc314/config.xml
index df398f9..6e0b678 100644
--- a/core/res/res/values-mcc314/config.xml
+++ b/core/res/res/values-mcc314/config.xml
@@ -22,4 +22,7 @@
     <!-- Whether safe headphone volume is enabled or not (country specific). -->
     <bool name="config_safe_media_volume_enabled">false</bool>
 
+    <!-- Whether safe headphone sound dosage warning is enabled or not (country specific). -->
+    <bool name="config_safe_sound_dosage_enabled">false</bool>
+
 </resources>
diff --git a/core/res/res/values-mcc315/config.xml b/core/res/res/values-mcc315/config.xml
index df398f9..6e0b678 100644
--- a/core/res/res/values-mcc315/config.xml
+++ b/core/res/res/values-mcc315/config.xml
@@ -22,4 +22,7 @@
     <!-- Whether safe headphone volume is enabled or not (country specific). -->
     <bool name="config_safe_media_volume_enabled">false</bool>
 
+    <!-- Whether safe headphone sound dosage warning is enabled or not (country specific). -->
+    <bool name="config_safe_sound_dosage_enabled">false</bool>
+
 </resources>
diff --git a/core/res/res/values-mcc316/config.xml b/core/res/res/values-mcc316/config.xml
index df398f9..6e0b678 100644
--- a/core/res/res/values-mcc316/config.xml
+++ b/core/res/res/values-mcc316/config.xml
@@ -22,4 +22,7 @@
     <!-- Whether safe headphone volume is enabled or not (country specific). -->
     <bool name="config_safe_media_volume_enabled">false</bool>
 
+    <!-- Whether safe headphone sound dosage warning is enabled or not (country specific). -->
+    <bool name="config_safe_sound_dosage_enabled">false</bool>
+
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 12dad7e..50aec83 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -991,6 +991,25 @@
         <!-- Nominal White Z --> <item>1.089058</item>
     </string-array>
 
+    <!-- The CCT closest to the white coordinates (primary) above and in SurfaceControl. -->
+    <integer name="config_displayWhiteBalanceDisplayNominalWhiteCct">6500</integer>
+
+    <!-- Range minimums corresponding to config_displayWhiteBalanceDisplaySteps. For example, if the
+         range minimums are [0, 3000] and the steps are [10, 20] then between 0 and 3000, exclusive,
+         the step between them will be 10 (i.e. 0, 10, 20, etc.) and the step between 3000 and the
+         maximum value is 20 (i.e. 3000, 3020, 3040, etc.). -->
+    <integer-array name="config_displayWhiteBalanceDisplayRangeMinimums">
+        <item>0</item>
+    </integer-array>
+
+    <!-- Steps corresponding to config_displayWhiteBalanceDisplayRangeMinimums. For example, if the
+         range minimums are [0, 3000] and the steps are [10, 20] then between 0 and 3000, exclusive,
+         the step between them will be 10 (i.e. 0, 10, 20, etc.) and the step between 3000 and the
+         maximum value is 20 (i.e. 3000, 3020, 3040, etc.). -->
+    <integer-array name="config_displayWhiteBalanceDisplaySteps">
+        <item>1</item>
+    </integer-array>
+
     <!-- Boolean indicating whether light mode is allowed when DWB is turned on. -->
     <bool name="config_displayWhiteBalanceLightModeAllowed">true</bool>
 
@@ -2105,9 +2124,6 @@
     <!-- The default volume for the ring stream -->
     <integer name="config_audio_ring_vol_default">5</integer>
 
-    <!-- Enable sound dose computation and warnings -->
-    <bool name="config_audio_csd_enabled_default">true</bool>
-
     <!-- The default value for whether head tracking for
          spatial audio is enabled for a newly connected audio device -->
     <bool name="config_spatial_audio_head_tracking_enabled_default">false</bool>
@@ -2939,6 +2955,9 @@
     <!-- Whether safe headphone volume is enabled or not (country specific). -->
     <bool name="config_safe_media_volume_enabled">true</bool>
 
+    <!-- Whether safe headphone sound dosage warning is enabled or not (country specific). -->
+    <bool name="config_safe_sound_dosage_enabled">true</bool>
+
     <!-- Whether safe headphone volume warning dialog is disabled on Vol+ (operator specific). -->
     <bool name="config_safe_media_disable_on_volume_up">true</bool>
 
@@ -6396,7 +6415,7 @@
         Packages can be added by OEMs in an allowlist, to prevent them from being scanned as
         "stopped" during initial boot of a device, or after an OTA update. Stopped state of
         an app is not changed during subsequent reboots.  -->
-    <bool name="config_stopSystemPackagesByDefault">false</bool>
+    <bool name="config_stopSystemPackagesByDefault">true</bool>
 
     <!-- Whether to show weather on the lock screen by default. -->
     <bool name="config_lockscreenWeatherEnabledByDefault">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index bb10f7a..218bbc2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -283,7 +283,6 @@
   <java-symbol type="attr" name="autofillSaveCustomSubtitleMaxHeight"/>
   <java-symbol type="bool" name="action_bar_embed_tabs" />
   <java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" />
-  <java-symbol type="bool" name="config_audio_csd_enabled_default" />
   <java-symbol type="integer" name="config_audio_notif_vol_default" />
   <java-symbol type="integer" name="config_audio_notif_vol_steps" />
   <java-symbol type="integer" name="config_audio_ring_vol_default" />
@@ -349,6 +348,7 @@
   <java-symbol type="bool" name="config_useDevInputEventForAudioJack" />
   <java-symbol type="bool" name="config_safe_media_volume_enabled" />
   <java-symbol type="bool" name="config_safe_media_disable_on_volume_up" />
+  <java-symbol type="bool" name="config_safe_sound_dosage_enabled" />
   <java-symbol type="bool" name="config_camera_sound_forced" />
   <java-symbol type="bool" name="config_dontPreferApn" />
   <java-symbol type="bool" name="config_restartRadioAfterProvisioning" />
@@ -3423,6 +3423,9 @@
   <java-symbol type="integer" name="config_displayWhiteBalanceColorTemperatureDefault" />
   <java-symbol type="array" name="config_displayWhiteBalanceDisplayPrimaries" />
   <java-symbol type="array" name="config_displayWhiteBalanceDisplayNominalWhite" />
+  <java-symbol type="integer" name="config_displayWhiteBalanceDisplayNominalWhiteCct" />
+  <java-symbol type="array" name="config_displayWhiteBalanceDisplayRangeMinimums" />
+  <java-symbol type="array" name="config_displayWhiteBalanceDisplaySteps" />
   <java-symbol type="bool" name="config_displayWhiteBalanceLightModeAllowed" />
   <java-symbol type="integer" name="config_displayWhiteBalanceTransitionTime" />
 
diff --git a/core/tests/coretests/jni/NativeWorkSourceParcelTest.cpp b/core/tests/coretests/jni/NativeWorkSourceParcelTest.cpp
index db1f7bd..187e2c1 100644
--- a/core/tests/coretests/jni/NativeWorkSourceParcelTest.cpp
+++ b/core/tests/coretests/jni/NativeWorkSourceParcelTest.cpp
@@ -77,15 +77,18 @@
     Parcel* parcel = nativeGetParcelData(env, wsParcel);
     int32_t endMarker;
 
-    // read WorkSource and if no error read end marker
-    status_t err = ws.readFromParcel(parcel) ?: parcel->readInt32(&endMarker);
-    int32_t dataAvailable = parcel->dataAvail();
-
+    status_t err = ws.readFromParcel(parcel);
     if (err != OK) {
-        ALOGE("WorkSource readFromParcel failed %d", err);
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                StringPrintf("WorkSource readFromParcel failed: %d", err).c_str());
     }
-
+    err = parcel->readInt32(&endMarker);
+    if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                StringPrintf("Failed to read endMarker: %d", err).c_str());
+    }
     // Now we have a native WorkSource object, verify it.
+    int32_t dataAvailable = parcel->dataAvail();
     if (dataAvailable > 0) { // not all data read from the parcel
         jniThrowException(env, "java/lang/IllegalArgumentException",
                 StringPrintf("WorkSource contains more data than native read (%d)",
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index 6fa8f11..55680ab 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -227,5 +227,25 @@
         assertEquals(numTotalSources, sources.size());
     }
 
+    @Test
+    public void testGetIndex() {
+        for (int index = 0; index < 2048; index++) {
+            for (int type = FIRST; type <= LAST; type = type << 1) {
+                final int id = InsetsSource.createId(null, index, type);
+                assertEquals(index, InsetsSource.getIndex(id));
+            }
+        }
+    }
+
+    @Test
+    public void testGetType() {
+        for (int index = 0; index < 2048; index++) {
+            for (int type = FIRST; type <= LAST; type = type << 1) {
+                final int id = InsetsSource.createId(null, index, type);
+                assertEquals(type, InsetsSource.getType(id));
+            }
+        }
+    }
+
     // Parcel and equals already tested via InsetsStateTest
 }
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 922dbb5..43683ff 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -31,6 +31,7 @@
         <permission name="android.permission.DUMP"/>
         <permission name="android.permission.GET_APP_OPS_STATS"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+        <permission name="android.permission.LOCATION_HARDWARE"/>
         <permission name="android.permission.MANAGE_DEBUGGING"/>
         <permission name="android.permission.MANAGE_GAME_MODE" />
         <permission name="android.permission.MANAGE_SENSOR_PRIVACY"/>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 40cb7f2..1f1239e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -520,6 +520,10 @@
         <permission name="android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER"/>
         <!-- Permission required for CTS test - SatelliteManagerTest -->
         <permission name="android.permission.SATELLITE_COMMUNICATION"/>
+        <!-- Permission required for GTS test - GtsAttestationVerificationDeviceSideTestCases -->
+        <permission name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE" />
+        <!-- Permission required for GTS test - GtsCredentialsTestCases -->
+        <permission name="android.permission.LAUNCH_CREDENTIAL_SELECTOR"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 596f351..7c2759a 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1183,6 +1183,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
+    "-1005167552": {
+      "message": "Playing #%d in parallel on track #%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "-1003678883": {
       "message": "Cleaning splash screen token=%s",
       "level": "VERBOSE",
@@ -1447,6 +1453,12 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
+    "-774908272": {
+      "message": "Marking #%d animation as SYNC.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "-771177730": {
       "message": "Removing focused app token:%s displayId=%d",
       "level": "VERBOSE",
@@ -1495,12 +1507,6 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "-741766551": {
-      "message": "Content Recording: Ignoring session on invalid virtual display",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_CONTENT_RECORDING",
-      "at": "com\/android\/server\/wm\/ContentRecordingController.java"
-    },
     "-732715767": {
       "message": "Unable to retrieve window container to start recording for display %d",
       "level": "VERBOSE",
@@ -2017,6 +2023,12 @@
       "group": "WM_DEBUG_WALLPAPER",
       "at": "com\/android\/server\/wm\/WallpaperController.java"
     },
+    "-266707683": {
+      "message": "Moving #%d from collecting to waiting.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "-262984451": {
       "message": "Relaunch failed %s",
       "level": "INFO",
@@ -2089,6 +2101,12 @@
       "group": "WM_DEBUG_WINDOW_MOVEMENT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-186693085": {
+      "message": "Starting a Recents transition which can be parallel.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-182877285": {
       "message": "Wallpaper layer changed: assigning layers + relayout",
       "level": "VERBOSE",
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 302c72e..dd4b58e 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -40,6 +40,7 @@
 import android.graphics.drawable.NinePatchDrawable;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecList;
+import android.media.MediaFormat;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Trace;
@@ -914,8 +915,6 @@
             case "image/jpeg":
             case "image/webp":
             case "image/gif":
-            case "image/heif":
-            case "image/heic":
             case "image/bmp":
             case "image/x-ico":
             case "image/vnd.wap.wbmp":
@@ -930,6 +929,9 @@
             case "image/x-pentax-pef":
             case "image/x-samsung-srw":
                 return true;
+            case "image/heif":
+            case "image/heic":
+                return isHevcDecoderSupported();
             case "image/avif":
                 return isP010SupportedForAV1();
             default:
@@ -2067,6 +2069,28 @@
         return decodeBitmapImpl(src, null);
     }
 
+    private static boolean sIsHevcDecoderSupported = false;
+    private static boolean sIsHevcDecoderSupportedInitialized = false;
+    private static final Object sIsHevcDecoderSupportedLock = new Object();
+
+    /*
+     * Check if HEVC decoder is supported by the device.
+     */
+    @SuppressWarnings("AndroidFrameworkCompatChange")
+    private static boolean isHevcDecoderSupported() {
+        synchronized (sIsHevcDecoderSupportedLock) {
+            if (sIsHevcDecoderSupportedInitialized) {
+                return sIsHevcDecoderSupported;
+            }
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_HEVC);
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            sIsHevcDecoderSupported = mcl.findDecoderForFormat(format) != null;
+            sIsHevcDecoderSupportedInitialized = true;
+            return sIsHevcDecoderSupported;
+        }
+    }
+
     private static boolean sIsP010SupportedForAV1 = false;
     private static boolean sIsP010SupportedForHEVC = false;
     private static boolean sIsP010SupportedFlagsInitialized = false;
@@ -2105,20 +2129,20 @@
      * Checks if the device supports decoding 10-bit for the given mime type.
      */
     private static void checkP010SupportforAV1HEVC() {
-        MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
         for (MediaCodecInfo mediaCodecInfo : codecList.getCodecInfos()) {
             if (mediaCodecInfo.isEncoder()) {
                 continue;
             }
             for (String mediaType : mediaCodecInfo.getSupportedTypes()) {
-                if (mediaType.equalsIgnoreCase("video/av01")
-                        || mediaType.equalsIgnoreCase("video/hevc")) {
+                if (mediaType.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)
+                        || mediaType.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
                     MediaCodecInfo.CodecCapabilities codecCapabilities =
                         mediaCodecInfo.getCapabilitiesForType(mediaType);
                     for (int i = 0; i < codecCapabilities.colorFormats.length; ++i) {
                         if (codecCapabilities.colorFormats[i]
                             == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010) {
-                            if (mediaType.equalsIgnoreCase("video/av01")) {
+                            if (mediaType.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) {
                                 sIsP010SupportedForAV1 = true;
                             } else {
                                 sIsP010SupportedForHEVC = true;
diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java
index 70311fd..b1aae7f 100644
--- a/graphics/java/android/graphics/MeshSpecification.java
+++ b/graphics/java/android/graphics/MeshSpecification.java
@@ -28,11 +28,40 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Class responsible for holding specifications for {@link Mesh} creations. This class
- * generates a {@link MeshSpecification} via the Make method, where multiple parameters to set up
- * the mesh are supplied, including attributes, vertex stride, varyings, and
- * vertex/fragment shaders. There are also additional methods to provide an optional
- * {@link ColorSpace} as well as an alpha type.
+ * Class responsible for holding specifications for {@link Mesh} creations. This class generates a
+ * {@link MeshSpecification} via the
+ * {@link MeshSpecification#make(Attribute[], int, Varying[], String, String)} method,
+ * where multiple parameters to set up the mesh are supplied, including attributes, vertex stride,
+ * {@link Varying}, and vertex/fragment shaders. There are also additional methods to provide an
+ * optional {@link ColorSpace} as well as an alpha type.
+ *
+ * For example a vertex shader that leverages a {@link Varying} may look like the following:
+ *
+ * <pre>
+ *        Varyings main(const Attributes attributes) {
+ *             Varyings varyings;
+ *             varyings.position = attributes.position;
+ *             return varyings;
+ *        }
+ * </pre>
+ *
+ * The corresponding fragment shader that may consume the varying look like the following:
+ *
+ * <pre>
+ *      float2 main(const Varyings varyings, out float4 color) {
+ *             color = vec4(1.0, 0.0, 0.0, 1.0);
+ *             return varyings.position;
+ *      }
+ * </pre>
+ *
+ * The color returned from this fragment shader is blended with the other parameters that are
+ * configured on the Paint object (ex. {@link Paint#setBlendMode(BlendMode)} used to draw the mesh.
+ *
+ * The position returned in the fragment shader can be consumed by any following fragment shaders in
+ * the shader chain.
+ *
+ * See https://developer.android.com/develop/ui/views/graphics/agsl for more information
+ * regarding Android Graphics Shader Language.
  *
  * Note that there are several limitations on various mesh specifications:
  * 1. The max amount of attributes allowed is 8.
@@ -118,7 +147,11 @@
     public static final int TYPE_UBYTE4 = 4;
 
     /**
-     * Data class to represent a single attribute in a shader.
+     * Data class to represent a single attribute in a shader. An attribute is a variable that
+     * accompanies a vertex, this can be a color or texture coordinates.
+     *
+     * See https://developer.android.com/develop/ui/views/graphics/agsl for more information
+     * regarding Android Graphics Shader Language.
      *
      * Note that offset is the offset in number of bytes. For example, if we had two attributes
      *
@@ -128,6 +161,10 @@
      * </pre>
      *
      * att1 would have an offset of 0, while att2 would have an offset of 12 bytes.
+     *
+     * This is consumed as part of
+     * {@link MeshSpecification#make(Attribute[], int, Varying[], String, String, ColorSpace, int)}
+     * to create a {@link MeshSpecification} instance.
      */
     public static class Attribute {
         @Type
@@ -175,7 +212,15 @@
     }
 
     /**
-     * Data class to represent a single varying variable.
+     * Data class to represent a single varying variable. A Varying variable can be altered by the
+     * vertex shader defined on the mesh but not by the fragment shader defined by AGSL.
+     *
+     * See https://developer.android.com/develop/ui/views/graphics/agsl for more information
+     * regarding Android Graphics Shader Language.
+     *
+     * This is consumed as part of
+     * {@link MeshSpecification#make(Attribute[], int, Varying[], String, String, ColorSpace, int)}
+     * to create a {@link MeshSpecification} instance.
      */
     public static class Varying {
         @Type
@@ -220,7 +265,7 @@
 
     /**
      * Creates a {@link MeshSpecification} object for use within {@link Mesh}. This uses a default
-     * color space of {@link ColorSpace.Named#SRGB} and {@link AlphaType} of
+     * color space of {@link ColorSpace.Named#SRGB} and alphaType of
      * {@link #ALPHA_TYPE_PREMULTIPLIED}.
      *
      * @param attributes     list of attributes represented by {@link Attribute}. Can hold a max of
@@ -233,7 +278,11 @@
      *                       the 6 varyings allowed.
      * @param vertexShader   vertex shader to be supplied to the mesh. Ensure that the position
      *                       varying is set within the shader to get proper results.
+     *                       See {@link MeshSpecification} for an example vertex shader
+     *                       implementation
      * @param fragmentShader fragment shader to be supplied to the mesh.
+     *                       See {@link MeshSpecification} for an example fragment shader
+     *                       implementation
      * @return {@link MeshSpecification} object for use when creating {@link Mesh}
      */
     @NonNull
@@ -253,7 +302,7 @@
     }
 
     /**
-     * Creates a {@link MeshSpecification} object.  This uses a default {@link AlphaType} of
+     * Creates a {@link MeshSpecification} object.  This uses a default alphaType of
      * {@link #ALPHA_TYPE_PREMULTIPLIED}.
      *
      * @param attributes     list of attributes represented by {@link Attribute}. Can hold a max of
@@ -266,7 +315,11 @@
      *                       the 6 varyings allowed.
      * @param vertexShader   vertex shader to be supplied to the mesh. Ensure that the position
      *                       varying is set within the shader to get proper results.
+     *                       See {@link MeshSpecification} for an example vertex shader
+     *                       implementation
      * @param fragmentShader fragment shader to be supplied to the mesh.
+     *                       See {@link MeshSpecification} for an example fragment shader
+     *                       implementation
      * @param colorSpace     {@link ColorSpace} to tell what color space to work in.
      * @return {@link MeshSpecification} object for use when creating {@link Mesh}
      */
@@ -301,7 +354,11 @@
      *                       the 6 varyings allowed.
      * @param vertexShader   vertex shader to be supplied to the mesh. Ensure that the position
      *                       varying is set within the shader to get proper results.
+     *                       See {@link MeshSpecification} for an example vertex shader
+     *                       implementation
      * @param fragmentShader fragment shader to be supplied to the mesh.
+     *                       See {@link MeshSpecification} for an example fragment shader
+     *                       implementation
      * @param colorSpace     {@link ColorSpace} to tell what color space to work in.
      * @param alphaType      Describes how to interpret the alpha component for a pixel. Must be
      *                       one of
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 6a79bc1..54978bd 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -125,34 +125,6 @@
 
 // End ProtoLog
 
-gensrcs {
-    name: "wm-shell-protos",
-
-    tools: [
-        "aprotoc",
-        "protoc-gen-javastream",
-        "soong_zip",
-    ],
-
-    tool_files: [
-        ":libprotobuf-internal-protos",
-    ],
-
-    cmd: "mkdir -p $(genDir)/$(in) " +
-        "&& $(location aprotoc) " +
-        "  --plugin=$(location protoc-gen-javastream) " +
-        "  --javastream_out=$(genDir)/$(in) " +
-        "  -Iexternal/protobuf/src " +
-        "  -I . " +
-        "  $(in) " +
-        "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
-
-    srcs: [
-        "proto/**/*.proto",
-    ],
-    output_extension: "srcjar",
-}
-
 java_library {
     name: "WindowManager-Shell-proto",
 
@@ -170,7 +142,6 @@
         // TODO(b/168581922) protologtool do not support kotlin(*.kt)
         ":wm_shell-sources-kt",
         ":wm_shell-aidls",
-        ":wm-shell-protos",
     ],
     resource_dirs: [
         "res",
diff --git a/libs/WindowManager/Shell/res/color/taskbar_background.xml b/libs/WindowManager/Shell/res/color/taskbar_background.xml
deleted file mode 100644
index 876ee02..0000000
--- a/libs/WindowManager/Shell/res/color/taskbar_background.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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.
-  -->
-<!-- Should be the same as in packages/apps/Launcher3/res/color-v31/taskbar_background.xml -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="98" />
-</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color-night/taskbar_background.xml b/libs/WindowManager/Shell/res/color/taskbar_background_dark.xml
similarity index 100%
rename from libs/WindowManager/Shell/res/color-night/taskbar_background.xml
rename to libs/WindowManager/Shell/res/color/taskbar_background_dark.xml
diff --git a/libs/WindowManager/Shell/res/values-night/colors.xml b/libs/WindowManager/Shell/res/values-night/colors.xml
index 5c6bb57..83c4d93 100644
--- a/libs/WindowManager/Shell/res/values-night/colors.xml
+++ b/libs/WindowManager/Shell/res/values-night/colors.xml
@@ -15,7 +15,6 @@
   -->
 
 <resources>
-    <color name="docked_divider_handle">#ffffff</color>
     <!-- Bubbles -->
     <color name="bubbles_icon_tint">@color/GM2_grey_200</color>
     <!-- Splash screen-->
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index c487e4a..54a8f33 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -17,8 +17,8 @@
  */
 -->
 <resources>
-    <color name="docked_divider_handle">#000000</color>
-    <color name="split_divider_background">@color/taskbar_background</color>
+    <color name="docked_divider_handle">#ffffff</color>
+    <color name="split_divider_background">@color/taskbar_background_dark</color>
     <drawable name="forced_resizable_background">#59000000</drawable>
     <color name="minimize_dock_shadow_start">#60000000</color>
     <color name="minimize_dock_shadow_end">#00000000</color>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index 544d757..410ae78d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -181,6 +181,17 @@
     }
 
     /**
+     * Returns the list of display ids that are tracked by a {@link DisplayAreaInfo}
+     */
+    public int[] getDisplayIds() {
+        int[] displayIds = new int[mDisplayAreasInfo.size()];
+        for (int i = 0; i < mDisplayAreasInfo.size(); i++) {
+            displayIds[i] = mDisplayAreasInfo.keyAt(i);
+        }
+        return displayIds;
+    }
+
+    /**
      * Returns the {@link DisplayAreaInfo} of the {@link DisplayAreaInfo#displayId}.
      */
     @Nullable
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index b6fd0bb..9aac694 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -421,7 +421,7 @@
     /**
      * Removes listener.
      */
-    public void removeLocusIdListener(FocusListener listener) {
+    public void removeFocusListener(FocusListener listener) {
         synchronized (mLock) {
             mFocusListeners.remove(listener);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index c767376..18615f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -19,6 +19,8 @@
 import static android.graphics.Matrix.MTRANS_X;
 import static android.graphics.Matrix.MTRANS_Y;
 
+import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.shouldUseSnapshotAnimationForClosingChange;
+
 import android.annotation.CallSuper;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -97,10 +99,17 @@
             final Rect startBounds = change.getStartAbsBounds();
             final Rect endBounds = change.getEndAbsBounds();
             mContentBounds.set(startBounds);
-            mContentRelOffset.set(change.getEndRelOffset());
-            mContentRelOffset.offset(
-                    startBounds.left - endBounds.left,
-                    startBounds.top - endBounds.top);
+            // Put the transition to the top left for snapshot animation.
+            if (shouldUseSnapshotAnimationForClosingChange(mChange)) {
+                // TODO(b/275034335): Fix an issue that black hole when closing the right container
+                //  in bounds change transition.
+                mContentRelOffset.set(0, 0);
+            } else {
+                mContentRelOffset.set(change.getEndRelOffset());
+                mContentRelOffset.offset(
+                        startBounds.left - endBounds.left,
+                        startBounds.top - endBounds.top);
+            }
         } else {
             mContentBounds.set(change.getEndAbsBounds());
             mContentRelOffset.set(change.getEndRelOffset());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 1df6ecd..ab7c7d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -21,6 +21,7 @@
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 
+import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationSpec.createShowSnapshotForClosingAnimation;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
@@ -42,6 +43,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter;
 import com.android.wm.shell.common.ScreenshotUtils;
 import com.android.wm.shell.util.TransitionUtil;
 
@@ -185,23 +187,23 @@
             return createChangeAnimationAdapters(info, startTransaction);
         }
         if (TransitionUtil.isClosingType(info.getType())) {
-            return createCloseAnimationAdapters(info);
+            return createCloseAnimationAdapters(info, startTransaction);
         }
-        return createOpenAnimationAdapters(info);
+        return createOpenAnimationAdapters(info, startTransaction);
     }
 
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters(
-            @NonNull TransitionInfo info) {
+            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
         return createOpenCloseAnimationAdapters(info, true /* isOpening */,
-                mAnimationSpec::loadOpenAnimation);
+                mAnimationSpec::loadOpenAnimation, startTransaction);
     }
 
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters(
-            @NonNull TransitionInfo info) {
+            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
         return createOpenCloseAnimationAdapters(info, false /* isOpening */,
-                mAnimationSpec::loadCloseAnimation);
+                mAnimationSpec::loadCloseAnimation, startTransaction);
     }
 
     /**
@@ -211,7 +213,8 @@
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters(
             @NonNull TransitionInfo info, boolean isOpening,
-            @NonNull AnimationProvider animationProvider) {
+            @NonNull AnimationProvider animationProvider,
+            @NonNull SurfaceControl.Transaction startTransaction) {
         // We need to know if the change window is only a partial of the whole animation screen.
         // If so, we will need to adjust it to make the whole animation screen looks like one.
         final List<TransitionInfo.Change> openingChanges = new ArrayList<>();
@@ -224,6 +227,8 @@
                 openingWholeScreenBounds.union(change.getEndAbsBounds());
             } else {
                 closingChanges.add(change);
+                // Also union with the start bounds because the closing transition may be shrunk.
+                closingWholeScreenBounds.union(change.getStartAbsBounds());
                 closingWholeScreenBounds.union(change.getEndAbsBounds());
             }
         }
@@ -241,6 +246,17 @@
             adapters.add(adapter);
         }
         for (TransitionInfo.Change change : closingChanges) {
+            if (shouldUseSnapshotAnimationForClosingChange(change)) {
+                SurfaceControl screenshot = getOrCreateScreenshot(change, change, startTransaction);
+                if (screenshot != null) {
+                    final SnapshotAdapter snapshotAdapter = new SnapshotAdapter(
+                            createShowSnapshotForClosingAnimation(), change, screenshot);
+                    if (!isOpening) {
+                        snapshotAdapter.overrideLayer(offsetLayer++);
+                    }
+                    adapters.add(snapshotAdapter);
+                }
+            }
             final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
                     info, change, animationProvider, closingWholeScreenBounds);
             if (!isOpening) {
@@ -251,6 +267,22 @@
         return adapters;
     }
 
+    /**
+     * Returns whether we should use snapshot animation for the closing change.
+     * It's usually because the end bounds of the closing change are shrunk, which leaves a black
+     * area in the transition.
+     */
+    static boolean shouldUseSnapshotAnimationForClosingChange(
+            @NonNull TransitionInfo.Change closingChange) {
+        // Only check closing type because we only take screenshot for closing bounds-changing
+        // changes.
+        if (!TransitionUtil.isClosingType(closingChange.getMode())) {
+            return false;
+        }
+        // Don't need to take screenshot if there's no bounds change.
+        return !closingChange.getStartAbsBounds().equals(closingChange.getEndAbsBounds());
+    }
+
     /** Sets the first frame to the {@code startTransaction} to avoid any flicker on start. */
     private void prepareForFirstFrame(@NonNull SurfaceControl.Transaction startTransaction,
             @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index cb8342a..19eff0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -77,6 +77,15 @@
         return new AlphaAnimation(alpha, alpha);
     }
 
+    /**
+     * Animation that intended to show snapshot for closing animation because the closing end bounds
+     * are changed.
+     */
+    @NonNull
+    static Animation createShowSnapshotForClosingAnimation() {
+        return new AlphaAnimation(1f, 1f);
+    }
+
     /** Animation for window that is opening in a change transition. */
     @NonNull
     Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index d3f3958..24fd86b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -297,7 +297,7 @@
     public BubbleInfo asBubbleBarBubble() {
         return new BubbleInfo(getKey(),
                 getFlags(),
-                getShortcutInfo().getId(),
+                getShortcutId(),
                 getIcon(),
                 getUser().getIdentifier(),
                 getPackageName());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index b9ff5f0..e7ec7aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -2884,6 +2884,7 @@
     /** Hide or show the manage menu for the currently expanded bubble. */
     @VisibleForTesting
     public void showManageMenu(boolean show) {
+        if ((mManageMenu.getVisibility() == VISIBLE) == show) return;
         mShowingManage = show;
 
         // This should not happen, since the manage menu is only visible when there's an expanded
@@ -3319,7 +3320,9 @@
      * @return the normalized x-axis position of the bubble stack rounded to 4 decimal places.
      */
     public float getNormalizedXPosition() {
-        return new BigDecimal(getStackPosition().x / mPositioner.getAvailableRect().width())
+        int width = mPositioner.getAvailableRect().width();
+        float stackPosition = width > 0 ? getStackPosition().x / width : 0;
+        return new BigDecimal(stackPosition)
                 .setScale(4, RoundingMode.CEILING.HALF_UP)
                 .floatValue();
     }
@@ -3328,7 +3331,9 @@
      * @return the normalized y-axis position of the bubble stack rounded to 4 decimal places.
      */
     public float getNormalizedYPosition() {
-        return new BigDecimal(getStackPosition().y / mPositioner.getAvailableRect().height())
+        int height = mPositioner.getAvailableRect().height();
+        float stackPosition = height > 0 ? getStackPosition().y / height : 0;
+        return new BigDecimal(stackPosition)
                 .setScale(4, RoundingMode.CEILING.HALF_UP)
                 .floatValue();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
index 0f9260c..9abf0f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
@@ -38,7 +38,7 @@
 
     default void onTaskStackChanged() { }
 
-    default void onTaskProfileLocked(RunningTaskInfo taskInfo) { }
+    default void onTaskProfileLocked(RunningTaskInfo taskInfo, int userId) { }
 
     default void onTaskDisplayChanged(int taskId, int newDisplayId) { }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
index e2106e4..d8859ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
@@ -150,8 +150,8 @@
     }
 
     @Override
-    public void onTaskProfileLocked(ActivityManager.RunningTaskInfo taskInfo) {
-        mMainHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskInfo).sendToTarget();
+    public void onTaskProfileLocked(ActivityManager.RunningTaskInfo taskInfo, int userId) {
+        mMainHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, userId, 0, taskInfo).sendToTarget();
     }
 
     @Override
@@ -348,8 +348,9 @@
                 case ON_TASK_PROFILE_LOCKED: {
                     final ActivityManager.RunningTaskInfo
                             info = (ActivityManager.RunningTaskInfo) msg.obj;
+                    final int userId = msg.arg1;
                     for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                        mTaskStackListeners.get(i).onTaskProfileLocked(info);
+                        mTaskStackListeners.get(i).onTaskProfileLocked(info, userId);
                     }
                     break;
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
index b0dea72..d27d05b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
@@ -35,6 +35,7 @@
 
     private String mKey; // Same key as the Notification
     private int mFlags;  // Flags from BubbleMetadata
+    @Nullable
     private String mShortcutId;
     private int mUserId;
     private String mPackageName;
@@ -46,7 +47,7 @@
     @Nullable
     private Icon mIcon;
 
-    public BubbleInfo(String key, int flags, String shortcutId, @Nullable Icon icon,
+    public BubbleInfo(String key, int flags, @Nullable String shortcutId, @Nullable Icon icon,
             int userId, String packageName) {
         mKey = key;
         mFlags = flags;
@@ -69,10 +70,12 @@
         return mKey;
     }
 
+    @Nullable
     public String getShortcutId() {
         return mShortcutId;
     }
 
+    @Nullable
     public Icon getIcon() {
         return mIcon;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index b8204d0..0bcafe5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -59,4 +59,17 @@
 
     /** Flag applied to a transition change to identify it as a divider bar for animation. */
     public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
+
+    public static final String splitPositionToString(@SplitPosition int pos) {
+        switch (pos) {
+            case SPLIT_POSITION_UNDEFINED:
+                return "SPLIT_POSITION_UNDEFINED";
+            case SPLIT_POSITION_TOP_OR_LEFT:
+                return "SPLIT_POSITION_TOP_OR_LEFT";
+            case SPLIT_POSITION_BOTTOM_OR_RIGHT:
+                return "SPLIT_POSITION_BOTTOM_OR_RIGHT";
+            default:
+                return "UNKNOWN";
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 80e920f..28368ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -189,12 +189,13 @@
     static Optional<DragAndDropController> provideDragAndDropController(Context context,
             ShellInit shellInit,
             ShellController shellController,
+            ShellCommandHandler shellCommandHandler,
             DisplayController displayController,
             UiEventLogger uiEventLogger,
             IconProvider iconProvider,
             @ShellMainThread ShellExecutor mainExecutor) {
         return Optional.ofNullable(DragAndDropController.create(context, shellInit, shellController,
-                displayController, uiEventLogger, iconProvider, mainExecutor));
+                shellCommandHandler, displayController, uiEventLogger, iconProvider, mainExecutor));
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 86ea725..b9d2be2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -264,10 +264,10 @@
     /**
      * Show apps on desktop
      */
-    void showDesktopApps() {
+    void showDesktopApps(int displayId) {
         // Bring apps to front, ignoring their visibility status to always ensure they are on top.
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        bringDesktopAppsToFront(wct);
+        bringDesktopAppsToFront(displayId, wct);
 
         if (!wct.isEmpty()) {
             if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -280,12 +280,12 @@
     }
 
     /** Get number of tasks that are marked as visible */
-    int getVisibleTaskCount() {
-        return mDesktopModeTaskRepository.getVisibleTaskCount();
+    int getVisibleTaskCount(int displayId) {
+        return mDesktopModeTaskRepository.getVisibleTaskCount(displayId);
     }
 
-    private void bringDesktopAppsToFront(WindowContainerTransaction wct) {
-        final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
+    private void bringDesktopAppsToFront(int displayId, WindowContainerTransaction wct) {
+        final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks(displayId);
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
 
         final List<RunningTaskInfo> taskInfos = new ArrayList<>();
@@ -386,6 +386,7 @@
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
             @NonNull TransitionRequestInfo request) {
+        RunningTaskInfo triggerTask = request.getTriggerTask();
         // Only do anything if we are in desktop mode and opening/moving-to-front a task/app in
         // freeform
         if (!DesktopModeStatus.isActive(mContext)) {
@@ -399,16 +400,15 @@
                     WindowManager.transitTypeToString(request.getType()));
             return null;
         }
-        if (request.getTriggerTask() == null
-                || request.getTriggerTask().getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+        if (triggerTask == null || triggerTask.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
             ProtoLog.d(WM_SHELL_DESKTOP_MODE, "skip shell transition request: not freeform task");
             return null;
         }
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "handle shell transition request: %s", request);
 
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        bringDesktopAppsToFront(wct);
-        wct.reorder(request.getTriggerTask().token, true /* onTop */);
+        bringDesktopAppsToFront(triggerTask.displayId, wct);
+        wct.reorder(triggerTask.token, true /* onTop */);
 
         return wct;
     }
@@ -493,16 +493,17 @@
             mController = null;
         }
 
-        public void showDesktopApps() {
+        @Override
+        public void showDesktopApps(int displayId) {
             executeRemoteCallWithTaskPermission(mController, "showDesktopApps",
-                    DesktopModeController::showDesktopApps);
+                    controller -> controller.showDesktopApps(displayId));
         }
 
         @Override
-        public int getVisibleTaskCount() throws RemoteException {
+        public int getVisibleTaskCount(int displayId) throws RemoteException {
             int[] result = new int[1];
             executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount",
-                    controller -> result[0] = controller.getVisibleTaskCount(),
+                    controller -> result[0] = controller.getVisibleTaskCount(displayId),
                     true /* blocking */
             );
             return result[0];
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index d1760ed..76ca68b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -49,6 +49,12 @@
             "persist.wm.debug.desktop_veiled_resizing", true);
 
     /**
+     * Flag to indicate is moving task to another display is enabled.
+     */
+    public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean(
+            "persist.wm.debug.desktop_change_display", false);
+
+    /**
      * Return {@code true} if desktop mode support is enabled
      */
     public static boolean isProto1Enabled() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 12f8ea2..00cc57f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -20,6 +20,8 @@
 import android.util.ArrayMap
 import android.util.ArraySet
 import android.util.SparseArray
+import androidx.core.util.forEach
+import androidx.core.util.keyIterator
 import androidx.core.util.valueIterator
 import java.util.concurrent.Executor
 import java.util.function.Consumer
@@ -29,14 +31,18 @@
  */
 class DesktopModeTaskRepository {
 
-    /**
-     * Set of task ids that are marked as active in desktop mode.
-     * Active tasks in desktop mode are freeform tasks that are visible or have been visible after
-     * desktop mode was activated.
-     * Task gets removed from this list when it vanishes. Or when desktop mode is turned off.
-     */
-    private val activeTasks = ArraySet<Int>()
-    private val visibleTasks = ArraySet<Int>()
+    /** Task data that is tracked per display */
+    private data class DisplayData(
+        /**
+         * Set of task ids that are marked as active in desktop mode. Active tasks in desktop mode
+         * are freeform tasks that are visible or have been visible after desktop mode was
+         * activated. Task gets removed from this list when it vanishes. Or when desktop mode is
+         * turned off.
+         */
+        val activeTasks: ArraySet<Int> = ArraySet(),
+        val visibleTasks: ArraySet<Int> = ArraySet(),
+    )
+
     // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
     private val freeformTasksInZOrder = mutableListOf<Int>()
     private val activeTasksListeners = ArraySet<ActiveTasksListener>()
@@ -47,9 +53,22 @@
     private var desktopGestureExclusionListener: Consumer<Region>? = null
     private var desktopGestureExclusionExecutor: Executor? = null
 
-    /**
-     * Add a [ActiveTasksListener] to be notified of updates to active tasks in the repository.
-     */
+    private val displayData =
+        object : SparseArray<DisplayData>() {
+            /**
+             * Get the [DisplayData] associated with this [displayId]
+             *
+             * Creates a new instance if one does not exist
+             */
+            fun getOrCreate(displayId: Int): DisplayData {
+                if (!contains(displayId)) {
+                    put(displayId, DisplayData())
+                }
+                return get(displayId)
+            }
+        }
+
+    /** Add a [ActiveTasksListener] to be notified of updates to active tasks in the repository. */
     fun addActiveTaskListener(activeTasksListener: ActiveTasksListener) {
         activeTasksListeners.add(activeTasksListener)
     }
@@ -57,10 +76,17 @@
     /**
      * Add a [VisibleTasksListener] to be notified when freeform tasks are visible or not.
      */
-    fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) {
-        visibleTasksListeners.put(visibleTasksListener, executor)
-        executor.execute(
-                Runnable { visibleTasksListener.onVisibilityChanged(visibleTasks.size > 0) })
+    fun addVisibleTasksListener(
+        visibleTasksListener: VisibleTasksListener,
+        executor: Executor
+    ) {
+        visibleTasksListeners[visibleTasksListener] = executor
+        displayData.keyIterator().forEach { displayId ->
+            val visibleTasks = getVisibleTaskCount(displayId)
+            executor.execute {
+                visibleTasksListener.onVisibilityChanged(displayId, visibleTasks > 0)
+            }
+        }
     }
 
     /**
@@ -100,14 +126,21 @@
     }
 
     /**
-     * Mark a task with given [taskId] as active.
+     * Mark a task with given [taskId] as active on given [displayId]
      *
-     * @return `true` if the task was not active
+     * @return `true` if the task was not active on given [displayId]
      */
-    fun addActiveTask(taskId: Int): Boolean {
-        val added = activeTasks.add(taskId)
+    fun addActiveTask(displayId: Int, taskId: Int): Boolean {
+        // Check if task is active on another display, if so, remove it
+        displayData.forEach { id, data ->
+            if (id != displayId && data.activeTasks.remove(taskId)) {
+                activeTasksListeners.onEach { it.onActiveTasksChanged(id) }
+            }
+        }
+
+        val added = displayData.getOrCreate(displayId).activeTasks.add(taskId)
         if (added) {
-            activeTasksListeners.onEach { it.onActiveTasksChanged() }
+            activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) }
         }
         return added
     }
@@ -118,65 +151,93 @@
      * @return `true` if the task was active
      */
     fun removeActiveTask(taskId: Int): Boolean {
-        val removed = activeTasks.remove(taskId)
-        if (removed) {
-            activeTasksListeners.onEach { it.onActiveTasksChanged() }
+        var result = false
+        displayData.forEach { displayId, data ->
+            if (data.activeTasks.remove(taskId)) {
+                activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) }
+                result = true
+            }
         }
-        return removed
+        return result
     }
 
     /**
      * Check if a task with the given [taskId] was marked as an active task
      */
     fun isActiveTask(taskId: Int): Boolean {
-        return activeTasks.contains(taskId)
+        return displayData.valueIterator().asSequence().any { data ->
+            data.activeTasks.contains(taskId)
+        }
     }
 
     /**
      * Whether a task is visible.
      */
     fun isVisibleTask(taskId: Int): Boolean {
-        return visibleTasks.contains(taskId)
+        return displayData.valueIterator().asSequence().any { data ->
+            data.visibleTasks.contains(taskId)
+        }
     }
 
     /**
-     * Get a set of the active tasks
+     * Get a set of the active tasks for given [displayId]
      */
-    fun getActiveTasks(): ArraySet<Int> {
-        return ArraySet(activeTasks)
+    fun getActiveTasks(displayId: Int): ArraySet<Int> {
+        return ArraySet(displayData[displayId]?.activeTasks)
     }
 
     /**
      * Get a list of freeform tasks, ordered from top-bottom (top at index 0).
      */
+     // TODO(b/278084491): pass in display id
     fun getFreeformTasksInZOrder(): List<Int> {
         return freeformTasksInZOrder
     }
 
     /**
      * Updates whether a freeform task with this id is visible or not and notifies listeners.
+     *
+     * If the task was visible on a different display with a different displayId, it is removed from
+     * the set of visible tasks on that display. Listeners will be notified.
      */
-    fun updateVisibleFreeformTasks(taskId: Int, visible: Boolean) {
-        val prevCount: Int = visibleTasks.size
+    fun updateVisibleFreeformTasks(displayId: Int, taskId: Int, visible: Boolean) {
         if (visible) {
-            visibleTasks.add(taskId)
-        } else {
-            visibleTasks.remove(taskId)
-        }
-        if (prevCount == 0 && visibleTasks.size == 1 ||
-                prevCount > 0 && visibleTasks.size == 0) {
-            for ((listener, executor) in visibleTasksListeners) {
-                executor.execute(
-                        Runnable { listener.onVisibilityChanged(visibleTasks.size > 0) })
+            // Task is visible. Check if we need to remove it from any other display.
+            val otherDisplays = displayData.keyIterator().asSequence().filter { it != displayId }
+            for (otherDisplayId in otherDisplays) {
+                if (displayData[otherDisplayId].visibleTasks.remove(taskId)) {
+                    // Task removed from other display, check if we should notify listeners
+                    if (displayData[otherDisplayId].visibleTasks.isEmpty()) {
+                        notifyVisibleTaskListeners(otherDisplayId, hasVisibleFreeformTasks = false)
+                    }
+                }
             }
         }
+
+        val prevCount = getVisibleTaskCount(displayId)
+        if (visible) {
+            displayData.getOrCreate(displayId).visibleTasks.add(taskId)
+        } else {
+            displayData[displayId]?.visibleTasks?.remove(taskId)
+        }
+        val newCount = getVisibleTaskCount(displayId)
+        // Check if count changed and if there was no tasks or this is the first task
+        if (prevCount != newCount && (prevCount == 0 || newCount == 0)) {
+            notifyVisibleTaskListeners(displayId, newCount > 0)
+        }
+    }
+
+    private fun notifyVisibleTaskListeners(displayId: Int, hasVisibleFreeformTasks: Boolean) {
+        visibleTasksListeners.forEach { (listener, executor) ->
+            executor.execute { listener.onVisibilityChanged(displayId, hasVisibleFreeformTasks) }
+        }
     }
 
     /**
-     * Get number of tasks that are marked as visible
+     * Get number of tasks that are marked as visible on given [displayId]
      */
-    fun getVisibleTaskCount(): Int {
-        return visibleTasks.size
+    fun getVisibleTaskCount(displayId: Int): Int {
+        return displayData[displayId]?.visibleTasks?.size ?: 0
     }
 
     /**
@@ -226,7 +287,7 @@
          * Called when the active tasks change in desktop mode.
          */
         @JvmDefault
-        fun onActiveTasksChanged() {}
+        fun onActiveTasksChanged(displayId: Int) {}
     }
 
     /**
@@ -237,6 +298,6 @@
          * Called when the desktop starts or stops showing freeform tasks.
          */
         @JvmDefault
-        fun onVisibilityChanged(hasVisibleFreeformTasks: Boolean) {}
+        fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {}
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index fb0a91f..0f0d572 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -162,7 +162,7 @@
     /**
      * Release the indicator and its components when it is no longer needed.
      */
-    public void releaseVisualIndicator() {
+    public void releaseVisualIndicator(SurfaceControl.Transaction t) {
         if (mViewHost == null) return;
         if (mViewHost != null) {
             mViewHost.release();
@@ -170,13 +170,8 @@
         }
 
         if (mLeash != null) {
-            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
             t.remove(mLeash);
             mLeash = null;
-            mSyncQueue.runInSync(transaction -> {
-                transaction.merge(t);
-                t.close();
-            });
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 0d56023..ee94f30 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -78,6 +78,11 @@
 
     private val desktopMode: DesktopModeImpl
     private var visualIndicator: DesktopModeVisualIndicator? = null
+    private val mOnAnimationFinishedCallback = Consumer<SurfaceControl.Transaction> {
+        t: SurfaceControl.Transaction ->
+        visualIndicator?.releaseVisualIndicator(t)
+        visualIndicator = null
+    }
 
     init {
         desktopMode = DesktopModeImpl()
@@ -97,10 +102,11 @@
     }
 
     /** Show all tasks, that are part of the desktop, on top of launcher */
-    fun showDesktopApps() {
+    fun showDesktopApps(displayId: Int) {
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "showDesktopApps")
         val wct = WindowContainerTransaction()
-        bringDesktopAppsToFront(wct)
+        // TODO(b/278084491): pass in display id
+        bringDesktopAppsToFront(displayId, wct)
 
         // Execute transaction if there are pending operations
         if (!wct.isEmpty) {
@@ -114,8 +120,8 @@
     }
 
     /** Get number of tasks that are marked as visible */
-    fun getVisibleTaskCount(): Int {
-        return desktopModeTaskRepository.getVisibleTaskCount()
+    fun getVisibleTaskCount(displayId: Int): Int {
+        return desktopModeTaskRepository.getVisibleTaskCount(displayId)
     }
 
     /** Move a task with given `taskId` to desktop */
@@ -129,7 +135,7 @@
 
         val wct = WindowContainerTransaction()
         // Bring other apps to front first
-        bringDesktopAppsToFront(wct)
+        bringDesktopAppsToFront(task.displayId, wct)
         addMoveToDesktopChanges(wct, task.token)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
@@ -153,27 +159,28 @@
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             enterDesktopTaskTransitionHandler.startTransition(
-                    Transitions.TRANSIT_ENTER_FREEFORM, wct)
+                    Transitions.TRANSIT_ENTER_FREEFORM, wct, mOnAnimationFinishedCallback)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
         }
     }
 
     /** Brings apps to front and sets freeform task bounds */
-    fun moveToDesktopWithAnimation(
+    private fun moveToDesktopWithAnimation(
             taskInfo: RunningTaskInfo,
             freeformBounds: Rect
     ) {
         val wct = WindowContainerTransaction()
-        bringDesktopAppsToFront(wct)
+        bringDesktopAppsToFront(taskInfo.displayId, wct)
         addMoveToDesktopChanges(wct, taskInfo.getToken())
         wct.setBounds(taskInfo.token, freeformBounds)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             enterDesktopTaskTransitionHandler.startTransition(
-                Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct)
+                Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct, mOnAnimationFinishedCallback)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
+            releaseVisualIndicator()
         }
     }
 
@@ -204,21 +211,24 @@
         val wct = WindowContainerTransaction()
         addMoveToFullscreenChanges(wct, task.token)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, startPosition)
+            enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, startPosition,
+                    mOnAnimationFinishedCallback)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
+            releaseVisualIndicator()
         }
     }
 
-    fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) {
+    private fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) {
         val wct = WindowContainerTransaction()
         addMoveToFullscreenChanges(wct, task.token)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             exitDesktopTaskTransitionHandler.startTransition(
-            Transitions.TRANSIT_EXIT_DESKTOP_MODE, wct)
+            Transitions.TRANSIT_EXIT_DESKTOP_MODE, wct, mOnAnimationFinishedCallback)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
+            releaseVisualIndicator()
         }
     }
 
@@ -234,6 +244,69 @@
     }
 
     /**
+     * Move task to the next display.
+     *
+     * Queries all current known display ids and sorts them in ascending order. Then iterates
+     * through the list and looks for the display id that is larger than the display id for
+     * the passed in task. If a display with a higher id is not found, iterates through the list and
+     * finds the first display id that is not the display id for the passed in task.
+     *
+     * If a display matching the above criteria is found, re-parents the task to that display.
+     * No-op if no such display is found.
+     */
+    fun moveToNextDisplay(taskId: Int) {
+        val task = shellTaskOrganizer.getRunningTaskInfo(taskId)
+        if (task == null) {
+            ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId)
+            return
+        }
+        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d taskDisplayId=%d",
+                taskId, task.displayId)
+
+        val displayIds = rootTaskDisplayAreaOrganizer.displayIds.sorted()
+        // Get the first display id that is higher than current task display id
+        var newDisplayId = displayIds.firstOrNull { displayId -> displayId > task.displayId }
+        if (newDisplayId == null) {
+            // No display with a higher id, get the first display id that is not the task display id
+            newDisplayId = displayIds.firstOrNull { displayId -> displayId < task.displayId }
+        }
+        if (newDisplayId == null) {
+            ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: next display not found")
+            return
+        }
+        moveToDisplay(task, newDisplayId)
+    }
+
+    /**
+     * Move [task] to display with [displayId].
+     *
+     * No-op if task is already on that display per [RunningTaskInfo.displayId].
+     */
+    private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) {
+        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDisplay: taskId=%d displayId=%d",
+                task.taskId, displayId)
+
+        if (task.displayId == displayId) {
+            ProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display")
+            return
+        }
+
+        val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
+        if (displayAreaInfo == null) {
+            ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToDisplay: display not found")
+            return
+        }
+
+        val wct = WindowContainerTransaction()
+        wct.reparent(task.token, displayAreaInfo.token, true /* onTop */)
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+        } else {
+            shellTaskOrganizer.applyTransaction(wct)
+        }
+    }
+
+    /**
      * Get windowing move for a given `taskId`
      *
      * @return [WindowingMode] for the task or [WINDOWING_MODE_UNDEFINED] if task is not found
@@ -244,9 +317,9 @@
             ?: WINDOWING_MODE_UNDEFINED
     }
 
-    private fun bringDesktopAppsToFront(wct: WindowContainerTransaction) {
+    private fun bringDesktopAppsToFront(displayId: Int, wct: WindowContainerTransaction) {
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront")
-        val activeTasks = desktopModeTaskRepository.getActiveTasks()
+        val activeTasks = desktopModeTaskRepository.getActiveTasks(displayId)
 
         // First move home to front and then other tasks on top of it
         moveHomeTaskToFront(wct)
@@ -266,6 +339,16 @@
             ?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) }
     }
 
+    private fun releaseVisualIndicator() {
+        val t = SurfaceControl.Transaction()
+        visualIndicator?.releaseVisualIndicator(t)
+        visualIndicator = null
+        syncQueue.runInSync { transaction ->
+            transaction.merge(t)
+            t.close()
+        }
+    }
+
     override fun getContext(): Context {
         return context
     }
@@ -290,18 +373,17 @@
         request: TransitionRequestInfo
     ): WindowContainerTransaction? {
         // Check if we should skip handling this transition
-        val task: RunningTaskInfo? = request.triggerTask
         val shouldHandleRequest =
             when {
                 // Only handle open or to front transitions
                 request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> false
                 // Only handle when it is a task transition
-                task == null -> false
+                request.triggerTask == null -> false
                 // Only handle standard type tasks
-                task.activityType != ACTIVITY_TYPE_STANDARD -> false
+                request.triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> false
                 // Only handle fullscreen or freeform tasks
-                task.windowingMode != WINDOWING_MODE_FULLSCREEN &&
-                    task.windowingMode != WINDOWING_MODE_FREEFORM -> false
+                request.triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN &&
+                        request.triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> false
                 // Otherwise process it
                 else -> true
             }
@@ -310,10 +392,11 @@
             return null
         }
 
-        val activeTasks = desktopModeTaskRepository.getActiveTasks()
+        val task: RunningTaskInfo = request.triggerTask
+        val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
 
         // Check if we should switch a fullscreen task to freeform
-        if (task?.windowingMode == WINDOWING_MODE_FULLSCREEN) {
+        if (task.windowingMode == WINDOWING_MODE_FULLSCREEN) {
             // If there are any visible desktop tasks, switch the task to freeform
             if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
                 ProtoLog.d(
@@ -329,7 +412,7 @@
         }
 
         // CHeck if we should switch a freeform task to fullscreen
-        if (task?.windowingMode == WINDOWING_MODE_FREEFORM) {
+        if (task.windowingMode == WINDOWING_MODE_FREEFORM) {
             // If no visible desktop tasks, switch this task to freeform as the transition came
             // outside of this controller
             if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
@@ -407,8 +490,7 @@
                         rootTaskDisplayAreaOrganizer)
                 visualIndicator?.createFullscreenIndicatorWithAnimatedBounds()
             } else if (y > statusBarHeight && visualIndicator != null) {
-                visualIndicator?.releaseVisualIndicator()
-                visualIndicator = null
+                releaseVisualIndicator()
             }
         }
     }
@@ -425,8 +507,6 @@
     ) {
         val statusBarHeight = getStatusBarHeight(taskInfo)
         if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
-            visualIndicator?.releaseVisualIndicator()
-            visualIndicator = null
             moveToFullscreenWithAnimation(taskInfo)
         }
     }
@@ -444,6 +524,11 @@
             taskSurface: SurfaceControl,
             y: Float
     ) {
+        // If the motion event is above the status bar, return since we do not need to show the
+        // visual indicator at this point.
+        if (y < getStatusBarHeight(taskInfo)) {
+            return
+        }
         if (visualIndicator == null) {
             visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
                     displayController, context, taskSurface, shellTaskOrganizer,
@@ -471,11 +556,8 @@
             freeformBounds: Rect
     ) {
         moveToDesktopWithAnimation(taskInfo, freeformBounds)
-        visualIndicator?.releaseVisualIndicator()
-        visualIndicator = null
     }
 
-
     private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
         return displayController.getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
     }
@@ -502,7 +584,6 @@
         desktopModeTaskRepository.removeTaskCorners(taskId)
     }
 
-
     /**
      * Adds a listener to find out about changes in the visibility of freeform tasks.
      *
@@ -559,20 +640,19 @@
             controller = null
         }
 
-        override fun showDesktopApps() {
+        override fun showDesktopApps(displayId: Int) {
             ExecutorUtils.executeRemoteCallWithTaskPermission(
                 controller,
-                "showDesktopApps",
-                Consumer(DesktopTasksController::showDesktopApps)
-            )
+                "showDesktopApps"
+            ) { c -> c.showDesktopApps(displayId) }
         }
 
-        override fun getVisibleTaskCount(): Int {
+        override fun getVisibleTaskCount(displayId: Int): Int {
             val result = IntArray(1)
             ExecutorUtils.executeRemoteCallWithTaskPermission(
                 controller,
                 "getVisibleTaskCount",
-                { controller -> result[0] = controller.getVisibleTaskCount() },
+                { controller -> result[0] = controller.getVisibleTaskCount(displayId) },
                 true /* blocking */
             )
             return result[0]
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 9467578..d55fddd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -39,6 +39,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 /**
@@ -58,6 +59,7 @@
 
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
     private Point mStartPosition;
+    private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
 
     public EnterDesktopTaskTransitionHandler(
             Transitions transitions) {
@@ -75,9 +77,12 @@
      * Starts Transition of a given type
      * @param type Transition type
      * @param wct WindowContainerTransaction for transition
+     * @param onAnimationEndCallback to be called after animation
      */
     public void startTransition(@WindowManager.TransitionType int type,
-                @NonNull WindowContainerTransaction wct) {
+            @NonNull WindowContainerTransaction wct,
+            Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+        mOnAnimationFinishedCallback = onAnimationEndCallback;
         final IBinder token = mTransitions.startTransition(type, wct, this);
         mPendingTransitionTokens.add(token);
     }
@@ -86,11 +91,14 @@
      * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
      * @param wct WindowContainerTransaction for transition
      * @param startPosition Position of task when transition is triggered
+     * @param onAnimationEndCallback to be called after animation
      */
     public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct,
-            Point startPosition) {
+            Point startPosition,
+            Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
         mStartPosition = startPosition;
-        startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct);
+        startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct,
+                mOnAnimationFinishedCallback);
     }
 
     @Override
@@ -111,7 +119,7 @@
 
             if (change.getMode() == WindowManager.TRANSIT_CHANGE) {
                 transitionHandled |= startChangeTransition(
-                        transition, info.getType(), change, startT, finishCallback);
+                        transition, info.getType(), change, startT, finishT, finishCallback);
             }
         }
 
@@ -125,6 +133,7 @@
             @WindowManager.TransitionType int type,
             @NonNull TransitionInfo.Change change,
             @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         if (!mPendingTransitionTokens.contains(transition)) {
             return false;
@@ -178,6 +187,9 @@
             animator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
+                    if (mOnAnimationFinishedCallback != null) {
+                        mOnAnimationFinishedCallback.accept(finishT);
+                    }
                     mTransitions.getMainExecutor().execute(
                             () -> finishCallback.onTransitionFinished(null, null));
                 }
@@ -204,7 +216,7 @@
             animator.setDuration(FREEFORM_ANIMATION_DURATION);
             final SurfaceControl.Transaction t = mTransactionSupplier.get();
             animator.addUpdateListener(animation -> {
-                final float scale = animation.getAnimatedFraction();
+                final float scale = (float) animation.getAnimatedValue();
                 t.setPosition(sc, mStartPosition.x * (1 - scale), mStartPosition.y * (1 - scale))
                         .setScale(sc, scale, scale)
                         .show(sc)
@@ -213,6 +225,9 @@
             animator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
+                    if (mOnAnimationFinishedCallback != null) {
+                        mOnAnimationFinishedCallback.accept(finishT);
+                    }
                     mTransitions.getMainExecutor().execute(
                             () -> finishCallback.onTransitionFinished(null, null));
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index fa3eee2..160a83d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -42,6 +42,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 
@@ -54,7 +55,7 @@
     private final Context mContext;
     private final Transitions mTransitions;
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
-
+    private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
     private Supplier<SurfaceControl.Transaction> mTransactionSupplier;
 
     public ExitDesktopTaskTransitionHandler(
@@ -76,9 +77,12 @@
      * Starts Transition of a given type
      * @param type Transition type
      * @param wct WindowContainerTransaction for transition
+     * @param onAnimationEndCallback to be called after animation
      */
     public void startTransition(@WindowManager.TransitionType int type,
-            @NonNull WindowContainerTransaction wct) {
+            @NonNull WindowContainerTransaction wct,
+            Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+        mOnAnimationFinishedCallback = onAnimationEndCallback;
         final IBinder token = mTransitions.startTransition(type, wct, this);
         mPendingTransitionTokens.add(token);
     }
@@ -101,7 +105,7 @@
 
             if (change.getMode() == WindowManager.TRANSIT_CHANGE) {
                 transitionHandled |= startChangeTransition(
-                        transition, info.getType(), change, startT, finishCallback);
+                        transition, info.getType(), change, startT, finishT, finishCallback);
             }
         }
 
@@ -116,6 +120,7 @@
             @WindowManager.TransitionType int type,
             @NonNull TransitionInfo.Change change,
             @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         if (!mPendingTransitionTokens.contains(transition)) {
             return false;
@@ -156,6 +161,9 @@
             animator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
+                    if (mOnAnimationFinishedCallback != null) {
+                        mOnAnimationFinishedCallback.accept(finishT);
+                    }
                     mTransitions.getMainExecutor().execute(
                             () -> finishCallback.onTransitionFinished(null, null));
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index d0739e1..899d672 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -21,9 +21,9 @@
  */
 interface IDesktopMode {
 
-    /** Show apps on the desktop */
-    void showDesktopApps();
+    /** Show apps on the desktop on the given display */
+    void showDesktopApps(int displayId);
 
-    /** Get count of visible desktop tasks */
-    int getVisibleTaskCount();
+    /** Get count of visible desktop tasks on the given display */
+    int getVisibleTaskCount(int displayId);
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 091de3a..be2489d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -35,10 +35,14 @@
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
+
 import android.content.ClipDescription;
 import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.HardwareRenderer;
 import android.graphics.PixelFormat;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -50,6 +54,8 @@
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 
+import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.InstanceId;
@@ -58,25 +64,31 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
+import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ExternalMainThread;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 
 /**
  * Handles the global drag and drop handling for the Shell.
  */
-public class DragAndDropController implements DisplayController.OnDisplaysChangedListener,
+public class DragAndDropController implements RemoteCallable<DragAndDropController>,
+        DisplayController.OnDisplaysChangedListener,
         View.OnDragListener, ComponentCallbacks2 {
 
     private static final String TAG = DragAndDropController.class.getSimpleName();
 
     private final Context mContext;
     private final ShellController mShellController;
+    private final ShellCommandHandler mShellCommandHandler;
     private final DisplayController mDisplayController;
     private final DragAndDropEventLogger mLogger;
     private final IconProvider mIconProvider;
@@ -100,6 +112,7 @@
     public static DragAndDropController create(Context context,
             ShellInit shellInit,
             ShellController shellController,
+            ShellCommandHandler shellCommandHandler,
             DisplayController displayController,
             UiEventLogger uiEventLogger,
             IconProvider iconProvider,
@@ -107,19 +120,21 @@
         if (!context.getResources().getBoolean(R.bool.config_enableShellDragDrop)) {
             return null;
         }
-        return new DragAndDropController(context, shellInit, shellController, displayController,
-                uiEventLogger, iconProvider, mainExecutor);
+        return new DragAndDropController(context, shellInit, shellController, shellCommandHandler,
+                displayController, uiEventLogger, iconProvider, mainExecutor);
     }
 
     DragAndDropController(Context context,
             ShellInit shellInit,
             ShellController shellController,
+            ShellCommandHandler shellCommandHandler,
             DisplayController displayController,
             UiEventLogger uiEventLogger,
             IconProvider iconProvider,
             ShellExecutor mainExecutor) {
         mContext = context;
         mShellController = shellController;
+        mShellCommandHandler = shellCommandHandler;
         mDisplayController = displayController;
         mLogger = new DragAndDropEventLogger(uiEventLogger);
         mIconProvider = iconProvider;
@@ -137,6 +152,23 @@
         mMainExecutor.executeDelayed(() -> {
             mDisplayController.addDisplayWindowListener(this);
         }, 0);
+        mShellController.addExternalInterface(KEY_EXTRA_SHELL_DRAG_AND_DROP,
+                this::createExternalInterface, this);
+        mShellCommandHandler.addDumpCallback(this::dump, this);
+    }
+
+    private ExternalInterfaceBinder createExternalInterface() {
+        return new IDragAndDropImpl(this);
+    }
+
+    @Override
+    public Context getContext() {
+        return mContext;
+    }
+
+    @Override
+    public ShellExecutor getRemoteCallExecutor() {
+        return mMainExecutor;
     }
 
     /**
@@ -156,7 +188,7 @@
         mListeners.remove(listener);
     }
 
-    private void notifyListeners() {
+    private void notifyDragStarted() {
         for (int i = 0; i < mListeners.size(); i++) {
             mListeners.get(i).onDragStarted();
         }
@@ -273,7 +305,7 @@
                 pd.dragLayout.prepare(mDisplayController.getDisplayLayout(displayId),
                         event.getClipData(), loggerSessionId);
                 setDropTargetWindowVisibility(pd, View.VISIBLE);
-                notifyListeners();
+                notifyDragStarted();
                 break;
             case ACTION_DRAG_ENTERED:
                 pd.dragLayout.show();
@@ -327,13 +359,7 @@
     }
 
     private void setDropTargetWindowVisibility(PerDisplay pd, int visibility) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
-                "Set drop target window visibility: displayId=%d visibility=%d",
-                pd.displayId, visibility);
-        pd.rootView.setVisibility(visibility);
-        if (visibility == View.VISIBLE) {
-            pd.rootView.requestApplyInsets();
-        }
+        pd.setWindowVisibility(visibility);
     }
 
     private String getMimeTypes(ClipDescription description) {
@@ -347,6 +373,18 @@
         return mimeTypes;
     }
 
+    /**
+     * Returns if any displays are currently ready to handle a drag/drop.
+     */
+    private boolean isReadyToHandleDrag() {
+        for (int i = 0; i < mDisplayDropTargets.size(); i++) {
+            if (mDisplayDropTargets.valueAt(i).mHasDrawn) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     // Note: Component callbacks are always called on the main thread of the process
     @ExternalMainThread
     @Override
@@ -372,12 +410,53 @@
         // Do nothing
     }
 
-    private static class PerDisplay {
+    /**
+     * Dumps information about this controller.
+     */
+    public void dump(@NonNull PrintWriter pw, String prefix) {
+        pw.println(prefix + TAG);
+        pw.println(prefix + " listeners=" + mListeners.size());
+    }
+    
+    /**
+     * The interface for calls from outside the host process.
+     */
+    @BinderThread
+    private static class IDragAndDropImpl extends IDragAndDrop.Stub
+            implements ExternalInterfaceBinder {
+        private DragAndDropController mController;
+
+        public IDragAndDropImpl(DragAndDropController controller) {
+            mController = controller;
+        }
+
+        /**
+         * Invalidates this instance, preventing future calls from updating the controller.
+         */
+        @Override
+        public void invalidate() {
+            mController = null;
+        }
+
+        @Override
+        public boolean isReadyToHandleDrag() {
+            boolean[] result = new boolean[1];
+            executeRemoteCallWithTaskPermission(mController, "isReadyToHandleDrag",
+                    controller -> result[0] = controller.isReadyToHandleDrag(),
+                    true /* blocking */
+            );
+            return result[0];
+        }
+    }
+
+    private static class PerDisplay implements HardwareRenderer.FrameDrawingCallback {
         final int displayId;
         final Context context;
         final WindowManager wm;
         final FrameLayout rootView;
         final DragLayout dragLayout;
+        // Tracks whether the window has fully drawn since it was last made visible
+        boolean mHasDrawn;
 
         boolean isHandlingDrag;
         // A count of the number of active drags in progress to ensure that we only hide the window
@@ -391,5 +470,25 @@
             rootView = rv;
             dragLayout = dl;
         }
+
+        private void setWindowVisibility(int visibility) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+                    "Set drop target window visibility: displayId=%d visibility=%d",
+                    displayId, visibility);
+            rootView.setVisibility(visibility);
+            if (visibility == View.VISIBLE) {
+                rootView.requestApplyInsets();
+                if (!mHasDrawn && rootView.getViewRootImpl() != null) {
+                    rootView.getViewRootImpl().registerRtFrameCallback(this);
+                }
+            } else {
+                mHasDrawn = false;
+            }
+        }
+
+        @Override
+        public void onFrameDraw(long frame) {
+            mHasDrawn = true;
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
index 28f59b5..724a130 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
@@ -104,7 +104,7 @@
         setContainerMargin(0, 0, 0, 0); // make sure it's populated
 
         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
-        mMarginColor = getResources().getColor(R.color.taskbar_background);
+        mMarginColor = getResources().getColor(R.color.taskbar_background_dark);
         int c = getResources().getColor(android.R.color.system_accent1_500);
         mHighlightColor =  Color.argb(HIGHLIGHT_ALPHA, Color.red(c), Color.green(c), Color.blue(c));
         mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, 0, 0, 0);
@@ -125,7 +125,7 @@
 
     public void onThemeChange() {
         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(getContext());
-        mMarginColor = getResources().getColor(R.color.taskbar_background);
+        mMarginColor = getResources().getColor(R.color.taskbar_background_dark);
         mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);
 
         if (mMarginPercent > 0) {
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/IDragAndDrop.aidl
similarity index 61%
copy from services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java
copy to libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/IDragAndDrop.aidl
index 7c339d2..aeb0c63 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/IDragAndDrop.aidl
@@ -14,14 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.server.companion.datatransfer.contextsync;
+package com.android.wm.shell.draganddrop;
 
-/** Callback for call metadata syncing. */
-public abstract class CallMetadataSyncCallback {
-
-    abstract void processCallControlAction(int crossDeviceCallId, int callControlAction);
-
-    abstract void requestCrossDeviceSync(int userId);
-
-    abstract void updateStatus(int userId, boolean shouldSyncCallMetadata);
+/**
+ * Interface that is exposed to remote callers to manipulate drag and drop.
+ */
+interface IDragAndDrop {
+    /**
+     * Returns whether the shell drop target is showing and will handle a drag/drop.
+     */
+    boolean isReadyToHandleDrag() = 1;
 }
+// Last id = 1
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 48487bc..22541bbd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -94,11 +94,12 @@
             mDesktopModeTaskRepository.ifPresent(repository -> {
                 repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
                 if (taskInfo.isVisible) {
-                    if (repository.addActiveTask(taskInfo.taskId)) {
+                    if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) {
                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                                 "Adding active freeform task: #%d", taskInfo.taskId);
                     }
-                    repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
+                    repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId,
+                            true);
                 }
             });
         }
@@ -117,7 +118,7 @@
                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                             "Removing active freeform task: #%d", taskInfo.taskId);
                 }
-                repository.updateVisibleFreeformTasks(taskInfo.taskId, false);
+                repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId, false);
             });
         }
 
@@ -137,12 +138,13 @@
         if (DesktopModeStatus.isAnyEnabled()) {
             mDesktopModeTaskRepository.ifPresent(repository -> {
                 if (taskInfo.isVisible) {
-                    if (repository.addActiveTask(taskInfo.taskId)) {
+                    if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) {
                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                                 "Adding active freeform task: #%d", taskInfo.taskId);
                     }
                 }
-                repository.updateVisibleFreeformTasks(taskInfo.taskId, taskInfo.isVisible);
+                repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId,
+                        taskInfo.isVisible);
             });
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index e1a56a1..6b6a7bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -153,6 +153,8 @@
 
     @Override
     public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
+        mWindowDecorViewModel.onTransitionMerged(merged, playing);
+
         final List<ActivityManager.RunningTaskInfo> infoOfMerged =
                 mTransitionToTaskInfo.get(merged);
         if (infoOfMerged == null) {
@@ -169,8 +171,6 @@
         } else {
             mTransitionToTaskInfo.put(playing, infoOfMerged);
         }
-
-        mWindowDecorViewModel.onTransitionMerged(merged, playing);
     }
 
     @Override
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 566c130..6cedcf5 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
@@ -27,6 +27,7 @@
 import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
 import static com.android.wm.shell.pip.PipAnimationController.FRACTION_START;
@@ -72,7 +73,6 @@
 import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransactionCallback;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
@@ -139,17 +139,6 @@
     // the runnable to execute after WindowContainerTransactions is applied to finish resizing pip
     private Runnable mPipFinishResizeWCTRunnable;
 
-    private final WindowContainerTransactionCallback mPipFinishResizeWCTCallback =
-            new WindowContainerTransactionCallback() {
-        @Override
-        public void onTransactionReady(int id, SurfaceControl.Transaction t) {
-            t.apply();
-
-            // execute the runnable if non-null after WCT is applied to finish resizing pip
-            maybePerformFinishResizeCallback();
-        }
-    };
-
     private void maybePerformFinishResizeCallback() {
         if (mPipFinishResizeWCTRunnable != null) {
             mPipFinishResizeWCTRunnable.run();
@@ -175,6 +164,7 @@
             final int direction = animator.getTransitionDirection();
             if (mIsCancelled) {
                 sendOnPipTransitionFinished(direction);
+                maybePerformFinishResizeCallback();
                 return;
             }
             final int animationType = animator.getAnimationType();
@@ -199,6 +189,10 @@
                     || isRemovePipDirection(direction);
             if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP
                     || isExitPipDirection) {
+                // execute the finish resize callback if needed after the transaction is committed
+                tx.addTransactionCommittedListener(mMainExecutor,
+                        PipTaskOrganizer.this::maybePerformFinishResizeCallback);
+
                 // Finish resize as long as we're not exiting PIP, or, if we are, only if this is
                 // the end of an exit PIP animation.
                 // This is necessary in case there was a resize animation ongoing when exit PIP
@@ -531,12 +525,18 @@
             }
         }
 
-        final Rect destinationBounds = getExitDestinationBounds();
+        final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+        final Rect destinationBounds = new Rect(displayBounds);
         final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit)
                 ? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
                 : TRANSITION_DIRECTION_LEAVE_PIP;
+        // For exiting to fullscreen, the windowing mode of task will be changed to fullscreen
+        // until the animation is finished. Otherwise if the activity is resumed and focused at the
+        // begin of aniamtion, the app may do something too early to distub the animation.
+        final boolean toFullscreen = destinationBounds.equals(displayBounds);
 
-        if (Transitions.ENABLE_SHELL_TRANSITIONS && direction == TRANSITION_DIRECTION_LEAVE_PIP) {
+        if (Transitions.SHELL_TRANSITIONS_ROTATION || (Transitions.ENABLE_SHELL_TRANSITIONS
+                && !toFullscreen)) {
             // When exit to fullscreen with Shell transition enabled, we update the Task windowing
             // mode directly so that it can also trigger display rotation and visibility update in
             // the same transition if there will be any.
@@ -612,7 +612,7 @@
         removePip();
     }
 
-    private void applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction) {
+    void applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction) {
         // Reset the final windowing mode.
         wct.setWindowingMode(mToken, getOutPipWindowingMode());
         // Simply reset the activity mode set prior to the animation running.
@@ -1614,12 +1614,8 @@
         if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
             mSplitScreenOptional.ifPresent(splitScreenController ->
                     splitScreenController.enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct));
-        } else if (direction == TRANSITION_DIRECTION_LEAVE_PIP) {
-            // when leaving PiP we can call the callback without sync
-            maybePerformFinishResizeCallback();
-            mTaskOrganizer.applyTransaction(wct);
         } else {
-            mTaskOrganizer.applySyncTransaction(wct, mPipFinishResizeWCTCallback);
+            mTaskOrganizer.applyTransaction(wct);
         }
     }
 
@@ -1782,14 +1778,26 @@
      * @return {@code true} if destinationBounds is altered for split screen
      */
     private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut, boolean enterSplit) {
-        if (!enterSplit || !mSplitScreenOptional.isPresent()) {
+        if (mSplitScreenOptional.isEmpty()) {
+            return false;
+        }
+        final SplitScreenController split = mSplitScreenOptional.get();
+        final int position = mTaskInfo.lastParentTaskIdBeforePip > 0
+                ? split.getSplitPosition(mTaskInfo.lastParentTaskIdBeforePip)
+                : SPLIT_POSITION_UNDEFINED;
+        if (position == SPLIT_POSITION_UNDEFINED && !enterSplit) {
             return false;
         }
         final Rect topLeft = new Rect();
         final Rect bottomRight = new Rect();
-        mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight);
-        destinationBoundsOut.set(isPipToTopLeft()  ? topLeft : bottomRight);
-        return true;
+        split.getStageBounds(topLeft, bottomRight);
+        if (enterSplit) {
+            destinationBoundsOut.set(isPipToTopLeft() ? topLeft : bottomRight);
+            return true;
+        }
+        // Moving to an existing split task.
+        destinationBoundsOut.set(position == SPLIT_POSITION_TOP_OR_LEFT ? topLeft : bottomRight);
+        return false;
     }
 
     /**
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 99cb6f7..98db707 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
@@ -508,8 +508,16 @@
         currentBounds.offset(-offset.x, -offset.y);
         startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top);
 
+        final WindowContainerToken pipTaskToken = pipChange.getContainer();
+        final boolean toFullscreen = pipChange.getEndAbsBounds().equals(
+                mPipBoundsState.getDisplayBounds());
         mFinishCallback = (wct, wctCB) -> {
             mPipOrganizer.onExitPipFinished(taskInfo);
+            if (!Transitions.SHELL_TRANSITIONS_ROTATION && toFullscreen) {
+                wct = wct != null ? wct : new WindowContainerTransaction();
+                wct.setBounds(pipTaskToken, null);
+                mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP);
+            }
             finishCallback.onTransitionFinished(wct, wctCB);
         };
         mFinishTransaction = finishTransaction;
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 b0bb14b..f8e1435 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
@@ -1060,13 +1060,22 @@
     /** Save the state to restore to on re-entry. */
     public void saveReentryState(Rect pipBounds) {
         float snapFraction = mPipBoundsAlgorithm.getSnapFraction(pipBounds);
-        if (mPipBoundsState.hasUserResizedPip()) {
-            final Rect reentryBounds = mTouchHandler.getUserResizeBounds();
-            final Size reentrySize = new Size(reentryBounds.width(), reentryBounds.height());
-            mPipBoundsState.saveReentryState(reentrySize, snapFraction);
-        } else {
+
+        if (!mPipBoundsState.hasUserResizedPip()) {
             mPipBoundsState.saveReentryState(null /* bounds */, snapFraction);
+            return;
         }
+
+        Size reentrySize = new Size(pipBounds.width(), pipBounds.height());
+
+        // TODO: b/279937014 Investigate why userResizeBounds are empty with shell transitions on
+        // fallback to using the userResizeBounds if userResizeBounds are not empty
+        if (!mTouchHandler.getUserResizeBounds().isEmpty()) {
+            Rect userResizeBounds = mTouchHandler.getUserResizeBounds();
+            reentrySize = new Size(userResizeBounds.width(), userResizeBounds.height());
+        }
+
+        mPipBoundsState.saveReentryState(reentrySize, snapFraction);
     }
 
     @Override
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 956af70..281cae5 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
@@ -104,6 +104,7 @@
     private boolean mAllowGesture;
     private boolean mIsAttached;
     private boolean mIsEnabled;
+    private boolean mEnableTouch;
     private boolean mEnablePinchResize;
     private boolean mEnableDragCornerResize;
     private boolean mIsSysUiStateValid;
@@ -138,6 +139,7 @@
         mPhonePipMenuController = menuActivityController;
         mPipUiEventLogger = pipUiEventLogger;
         mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
+        mEnableTouch = true;
 
         mUpdateResizeBoundsCallback = (rect) -> {
             mUserResizeBounds.set(rect);
@@ -248,6 +250,11 @@
             return;
         }
 
+        if (!mEnableTouch) {
+            // No need to handle anything if touches are not enabled for resizing.
+            return;
+        }
+
         // Don't allow resize when PiP is stashed.
         if (mPipBoundsState.isStashed()) {
             return;
@@ -581,14 +588,13 @@
                         mLastResizeBounds, movementBounds);
                 mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
 
-                // disable the pinch resizing until the final bounds are updated
-                final boolean prevEnablePinchResize = mEnablePinchResize;
-                mEnablePinchResize = false;
+                // disable the resizing until the final bounds are updated
+                mEnableTouch = false;
 
                 mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
                         PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> {
                             // reset the pinch resizing to its default state
-                            mEnablePinchResize = prevEnablePinchResize;
+                            mEnableTouch = true;
                         });
             } else {
                 mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index c5bfd87..5c9709c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -245,7 +245,7 @@
     }
 
     @Override
-    public void onActiveTasksChanged() {
+    public void onActiveTasksChanged(int displayId) {
         notifyRecentTasksChanged();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index bffc51c..3e568e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -48,6 +48,7 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -97,7 +98,8 @@
         mMixers.remove(mixer);
     }
 
-    void startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
+    @VisibleForTesting
+    public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
             IApplicationThread appThread, IRecentsAnimationRunner listener) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                 "RecentsTransitionHandler.startRecentsTransition");
@@ -121,12 +123,13 @@
         if (mixer != null) {
             mixer.setRecentsTransition(transition);
         }
-        if (transition == null) {
+        if (transition != null) {
+            controller.setTransition(transition);
+            mControllers.add(controller);
+        } else {
             controller.cancel("startRecentsTransition");
-            return;
         }
-        controller.setTransition(transition);
-        mControllers.add(controller);
+        return transition;
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 0ef26fc..e5ae10c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -39,6 +39,7 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
@@ -378,40 +379,18 @@
 
     boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition,
             WindowContainerTransaction wct) {
-        StageTaskListener targetStage;
-        int sideStagePosition;
-        if (isSplitScreenVisible()) {
-            // If the split screen is foreground, retrieves target stage based on position.
-            targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
-            sideStagePosition = mSideStagePosition;
+        prepareEnterSplitScreen(wct, task, stagePosition);
+        if (ENABLE_SHELL_TRANSITIONS) {
+            mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct,
+                    null, this, null /* consumedCallback */, null /* finishedCallback */,
+                    isSplitScreenVisible()
+                            ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN);
         } else {
-            targetStage = mSideStage;
-            sideStagePosition = stagePosition;
-        }
-
-        if (!isSplitActive()) {
-            mSplitLayout.init();
-            prepareEnterSplitScreen(wct, task, stagePosition);
             mSyncQueue.queue(wct);
             mSyncQueue.runInSync(t -> {
                 updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
-                setDividerVisibility(true, t);
             });
-        } else {
-            setSideStagePosition(sideStagePosition, wct);
-            targetStage.addTask(task, wct);
-            targetStage.evictAllChildren(wct);
-            if (!isSplitScreenVisible()) {
-                final StageTaskListener anotherStage = targetStage == mMainStage
-                        ? mSideStage : mMainStage;
-                anotherStage.reparentTopTask(wct);
-                anotherStage.evictAllChildren(wct);
-                wct.reorder(mRootTaskInfo.token, true);
-            }
-            setRootForceTranslucent(false, wct);
-            mSyncQueue.queue(wct);
         }
-
         // Due to drag already pip task entering split by this method so need to reset flag here.
         mIsDropEntering = false;
         return true;
@@ -1509,9 +1488,51 @@
      */
     void prepareEnterSplitScreen(WindowContainerTransaction wct,
             @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) {
-        if (mMainStage.isActive()) return;
-
         onSplitScreenEnter();
+        if (isSplitActive()) {
+            prepareBringSplit(wct, taskInfo, startPosition);
+        } else {
+            prepareActiveSplit(wct, taskInfo, startPosition);
+        }
+    }
+
+    private void prepareBringSplit(WindowContainerTransaction wct,
+            @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) {
+        StageTaskListener targetStage;
+        if (isSplitScreenVisible()) {
+            // If the split screen is foreground, retrieves target stage based on position.
+            targetStage = startPosition == mSideStagePosition ? mSideStage : mMainStage;
+        } else {
+            targetStage = mSideStage;
+        }
+
+        if (taskInfo != null) {
+            wct.startTask(taskInfo.taskId,
+                    resolveStartStage(STAGE_TYPE_UNDEFINED, startPosition, null, wct));
+            targetStage.evictAllChildren(wct);
+        }
+        // If running background, we need to reparent current top visible task to another stage
+        // and evict all tasks current under its.
+        if (!isSplitScreenVisible()) {
+            // Recreate so we need to reset position rather than keep position of background split.
+            mSplitLayout.resetDividerPosition();
+            updateWindowBounds(mSplitLayout, wct);
+            final StageTaskListener anotherStage = targetStage == mMainStage
+                    ? mSideStage : mMainStage;
+            anotherStage.evictAllChildren(wct);
+            anotherStage.reparentTopTask(wct);
+            wct.reorder(mRootTaskInfo.token, true);
+            setRootForceTranslucent(false, wct);
+        }
+    }
+
+    private void prepareActiveSplit(WindowContainerTransaction wct,
+            @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) {
+        if (!ENABLE_SHELL_TRANSITIONS) {
+            // Legacy transition we need to create divider here, shell transition case we will
+            // create it on #finishEnterSplitScreen
+            mSplitLayout.init();
+        }
         if (taskInfo != null) {
             setSideStagePosition(startPosition, wct);
             mSideStage.addTask(taskInfo, wct);
@@ -2383,7 +2404,17 @@
                 }
 
                 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-                if (taskInfo == null || !taskInfo.hasParentTask()) continue;
+                if (taskInfo == null) continue;
+                if (taskInfo.token.equals(mRootTaskInfo.token)) {
+                    if (isOpeningType(change.getMode())) {
+                        // Split is opened by someone so set it as visible.
+                        setSplitsVisible(true);
+                    } else if (isClosingType(change.getMode())) {
+                        // Split is closed by someone so set it as invisible.
+                        setSplitsVisible(false);
+                    }
+                    continue;
+                }
                 final StageTaskListener stage = getStageOfTask(taskInfo);
                 if (stage == null) continue;
                 if (isOpeningType(change.getMode())) {
@@ -2823,12 +2854,18 @@
         final String childPrefix = innerPrefix + "  ";
         pw.println(prefix + TAG + " mDisplayId=" + mDisplayId);
         pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
+        pw.println(innerPrefix + "isSplitActive=" + isSplitActive());
+        pw.println(innerPrefix + "isSplitVisible=" + isSplitScreenVisible());
         pw.println(innerPrefix + "MainStage");
-        pw.println(childPrefix + "stagePosition=" + getMainStagePosition());
+        pw.println(childPrefix + "stagePosition=" + splitPositionToString(getMainStagePosition()));
         pw.println(childPrefix + "isActive=" + mMainStage.isActive());
+        mMainStage.dump(pw, childPrefix);
+        pw.println(innerPrefix + "MainStageListener");
         mMainStageListener.dump(pw, childPrefix);
         pw.println(innerPrefix + "SideStage");
-        pw.println(childPrefix + "stagePosition=" + getSideStagePosition());
+        pw.println(childPrefix + "stagePosition=" + splitPositionToString(getSideStagePosition()));
+        mSideStage.dump(pw, childPrefix);
+        pw.println(innerPrefix + "SideStageListener");
         mSideStageListener.dump(pw, childPrefix);
         if (mMainStage.isActive()) {
             pw.println(innerPrefix + "SplitLayout");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index a841b7f..18b09b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -421,6 +421,13 @@
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         final String childPrefix = innerPrefix + "  ";
-        pw.println(prefix + this);
+        if (mChildrenTaskInfo.size() > 0) {
+            pw.println(prefix + "Children list:");
+            for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+                final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
+                pw.println(childPrefix + "Task#" + i + " taskID=" + taskInfo.taskId
+                        + " baseActivity=" + taskInfo.baseActivity);
+            }
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
index 8a4d4c2..ae72220 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
@@ -477,15 +477,15 @@
         }
 
         @Override
-        public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+        public boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
             if (mRootView == null) {
-                return;
+                return true;
             }
             if (mSplashView == null) {
                 // shouldn't happen, the app window may be drawn earlier than starting window?
                 Slog.e(TAG, "Found empty splash screen, remove!");
                 removeWindowInner(mRootView, false);
-                return;
+                return true;
             }
             clearSystemBarColor();
             if (immediately
@@ -503,6 +503,7 @@
                     removeWindowInner(mRootView, true);
                 }
             }
+            return true;
         }
     }
 }
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 ff06db3..7cbf263 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
@@ -18,6 +18,8 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NORMAL;
+import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION;
 
 import android.annotation.CallSuper;
 import android.app.TaskInfo;
@@ -216,7 +218,17 @@
     }
     abstract static class StartingWindowRecord {
         protected int mBGColor;
-        abstract void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately);
+
+        /**
+         * Remove the starting window with the given {@link StartingWindowRemovalInfo} if possible.
+         * @param info The removal info sent from the task organizer controller in the WM core.
+         * @param immediately {@code true} means removing the starting window immediately,
+         *                    {@code false} otherwise.
+         * @return {@code true} means {@link StartingWindowRecordManager} can safely remove the
+         *         record itself. {@code false} means {@link StartingWindowRecordManager} requires
+         *         to manage the record reference and remove it later.
+         */
+        abstract boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately);
         int getBGColor() {
             return mBGColor;
         }
@@ -231,6 +243,15 @@
          * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
          */
         private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
+
+        /**
+         * The max delay time in milliseconds for removing the task snapshot window with IME
+         * visible after the fixed rotation finished.
+         * Ideally the delay time will be shorter when receiving
+         * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
+         */
+        private static final long MAX_DELAY_REMOVAL_TIME_FIXED_ROTATION = 3000;
+
         private final Runnable mScheduledRunnable = this::removeImmediately;
 
         @WindowConfiguration.ActivityType protected final int mActivityType;
@@ -242,24 +263,34 @@
         }
 
         @Override
-        public final void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+        public final boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
             if (immediately) {
                 removeImmediately();
             } else {
-                scheduleRemove(info.deferRemoveForIme);
+                scheduleRemove(info.deferRemoveForImeMode);
+                return false;
             }
+            return true;
         }
 
-        void scheduleRemove(boolean deferRemoveForIme) {
+        void scheduleRemove(@StartingWindowRemovalInfo.DeferMode int deferRemoveForImeMode) {
             // Show the latest content as soon as possible for unlocking to home.
             if (mActivityType == ACTIVITY_TYPE_HOME) {
                 removeImmediately();
                 return;
             }
             mRemoveExecutor.removeCallbacks(mScheduledRunnable);
-            final long delayRemovalTime = hasImeSurface() && deferRemoveForIme
-                    ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
-                    : DELAY_REMOVAL_TIME_GENERAL;
+            final long delayRemovalTime;
+            switch (deferRemoveForImeMode) {
+                case DEFER_MODE_ROTATION:
+                    delayRemovalTime = MAX_DELAY_REMOVAL_TIME_FIXED_ROTATION;
+                    break;
+                case DEFER_MODE_NORMAL:
+                    delayRemovalTime = MAX_DELAY_REMOVAL_TIME_IME_VISIBLE;
+                    break;
+                default:
+                    delayRemovalTime = DELAY_REMOVAL_TIME_GENERAL;
+            }
             mRemoveExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime);
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
                     "Defer removing snapshot surface in %d", delayRemovalTime);
@@ -297,8 +328,10 @@
             final int taskId = removeInfo.taskId;
             final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
             if (record != null) {
-                record.removeIfPossible(removeInfo, immediately);
-                mStartingWindowRecords.remove(taskId);
+                final boolean canRemoveRecord = record.removeIfPossible(removeInfo, immediately);
+                if (canRemoveRecord) {
+                    mStartingWindowRecords.remove(taskId);
+                }
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
index 12a0d40..98a8031 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
@@ -124,7 +124,7 @@
         }
 
         @Override
-        public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+        public boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
             if (!immediately) {
                 mSplashscreenContentDrawer.applyExitAnimation(mSplashView,
                         info.windowAnimationLeash, info.mainFrame,
@@ -132,6 +132,7 @@
             } else {
                 release();
             }
+            return true;
         }
 
         void release() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
index bfa6390..5f54f58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -42,4 +42,6 @@
     public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
     // See IDesktopMode.aidl
     public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
+    // See IDragAndDrop.aidl
+    public static final String KEY_EXTRA_SHELL_DRAG_AND_DROP = "extra_shell_drag_and_drop";
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 7991c52..2ab4c75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -82,6 +82,10 @@
         mGuard.open("release");
     }
 
+    SurfaceControl getSurfaceControl() {
+        return mSurfaceControl;
+    }
+
     /**
      * Sets the provided {@link TaskViewBase}, which is used to notify the client part about the
      * task related changes and getting the current bounds.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 81d69a4..2faed3a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -202,6 +202,7 @@
     }
 
     void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) {
+        if (mTaskViews.get(taskView) == null) return;
         if (mTaskViews.get(taskView).mVisible == visible) return;
         if (taskView.getTaskInfo() == null) {
             // Nothing to update, task is not yet available
@@ -220,17 +221,19 @@
 
     void updateBoundsState(TaskViewTaskController taskView, Rect boundsOnScreen) {
         TaskViewRequestedState state = mTaskViews.get(taskView);
+        if (state == null) return;
         state.mBounds.set(boundsOnScreen);
     }
 
     void updateVisibilityState(TaskViewTaskController taskView, boolean visible) {
         TaskViewRequestedState state = mTaskViews.get(taskView);
+        if (state == null) return;
         state.mVisible = visible;
     }
 
     void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) {
         TaskViewRequestedState state = mTaskViews.get(taskView);
-        if (Objects.equals(boundsOnScreen, state.mBounds)) {
+        if (state == null || Objects.equals(boundsOnScreen, state.mBounds)) {
             return;
         }
         state.mBounds.set(boundsOnScreen);
@@ -336,6 +339,19 @@
                 tv.prepareOpenAnimation(taskIsNew, startTransaction, finishTransaction,
                         chg.getTaskInfo(), chg.getLeash(), wct);
                 changesHandled++;
+            } else if (chg.getMode() == TRANSIT_CHANGE) {
+                TaskViewTaskController tv = findTaskView(chg.getTaskInfo());
+                if (tv == null) {
+                    if (pending != null) {
+                        Slog.w(TAG, "Found a non-TaskView task in a TaskView Transition. This "
+                                + "shouldn't happen, so there may be a visual artifact: "
+                                + chg.getTaskInfo().taskId);
+                    }
+                    continue;
+                }
+                startTransaction.reparent(chg.getLeash(), tv.getSurfaceControl());
+                finishTransaction.reparent(chg.getLeash(), tv.getSurfaceControl());
+                changesHandled++;
             }
         }
         if (stillNeedsMatchingLaunch) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 7c729a4..4942932 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -73,7 +73,7 @@
         /** Pip was entered while handling an intent with its own remoteTransition. */
         static final int TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE = 3;
 
-        /** Recents transition while split-screen active. */
+        /** Recents transition while split-screen foreground. */
         static final int TYPE_RECENTS_DURING_SPLIT = 4;
 
         /** The default animation for this mixed transition. */
@@ -152,7 +152,7 @@
             @NonNull TransitionRequestInfo request) {
         if (mPipHandler.requestHasPipEnter(request) && mSplitHandler.isSplitScreenVisible()) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a PiP-enter request while "
-                    + "Split-Screen is active, so treat it as Mixed.");
+                    + "Split-Screen is foreground, so treat it as Mixed.");
             if (request.getRemoteTransition() != null) {
                 throw new IllegalStateException("Unexpected remote transition in"
                         + "pip-enter-from-split request");
@@ -183,13 +183,13 @@
             mixed.mLeftoversHandler = handler.first;
             mActiveTransitions.add(mixed);
             return handler.second;
-        } else if (mSplitHandler.isSplitActive()
+        } else if (mSplitHandler.isSplitScreenVisible()
                 && isOpeningType(request.getType())
                 && request.getTriggerTask() != null
                 && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
                 && request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a going-home request while "
-                    + "Split-Screen is active, so treat it as Mixed.");
+                    + "Split-Screen is foreground, so treat it as Mixed.");
             Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler =
                     mPlayer.dispatchRequest(transition, request, this);
             if (handler == null) {
@@ -211,7 +211,7 @@
 
     @Override
     public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
-        if (mRecentsHandler != null && mSplitHandler.isSplitActive()) {
+        if (mRecentsHandler != null && mSplitHandler.isSplitScreenVisible()) {
             return this;
         }
         return null;
@@ -219,9 +219,9 @@
 
     @Override
     public void setRecentsTransition(IBinder transition) {
-        if (mSplitHandler.isSplitActive()) {
+        if (mSplitHandler.isSplitScreenVisible()) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
-                    + "Split-Screen is active, so treat it as Mixed.");
+                    + "Split-Screen is foreground, so treat it as Mixed.");
             final MixedTransition mixed = new MixedTransition(
                     MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
             mixed.mLeftoversHandler = mRecentsHandler;
@@ -351,7 +351,7 @@
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
-                + "entering PIP while Split-Screen is active.");
+                + "entering PIP while Split-Screen is foreground.");
         TransitionInfo.Change pipChange = null;
         TransitionInfo.Change wallpaper = null;
         final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 6e9ecda..1ee52ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -21,6 +21,7 @@
 import static android.app.ActivityOptions.ANIM_NONE;
 import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
 import static android.app.ActivityOptions.ANIM_SCALE_UP;
+import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -625,6 +626,9 @@
         } else if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0 && isOpeningType) {
             // This received a transferred starting window, so don't animate
             return null;
+        } else if (overrideType == ANIM_SCENE_TRANSITION) {
+            // If there's a scene-transition, then jump-cut.
+            return null;
         } else {
             a = loadAttributeAnimation(info, change, wallpaperTransit, mTransitionAnimation);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index d25318d..9ce2209 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -144,6 +144,7 @@
                                 .setCaptureSecureLayers(true)
                                 .setAllowProtected(true)
                                 .setSourceCrop(new Rect(0, 0, mStartWidth, mStartHeight))
+                                .setHintForSeamlessTransition(true)
                                 .build();
                 ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
                         ScreenCapture.captureLayers(args);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
index 9d7c39f..0cede90 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
@@ -18,25 +18,29 @@
 
 import static android.os.Build.IS_USER;
 
-import static com.android.wm.shell.WmShellTransitionTraceProto.MAGIC_NUMBER;
-import static com.android.wm.shell.WmShellTransitionTraceProto.MAGIC_NUMBER_H;
-import static com.android.wm.shell.WmShellTransitionTraceProto.MAGIC_NUMBER_L;
+import static com.android.wm.shell.nano.WmShellTransitionTraceProto.MAGIC_NUMBER_H;
+import static com.android.wm.shell.nano.WmShellTransitionTraceProto.MAGIC_NUMBER_L;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.util.Log;
-import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.TraceBuffer;
+import com.android.wm.shell.nano.HandlerMapping;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 
+import com.google.protobuf.nano.MessageNano;
+
 import java.io.File;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Queue;
 
 /**
  * Helper class to collect and dump transition traces.
@@ -54,8 +58,35 @@
     private final Object mEnabledLock = new Object();
     private boolean mActiveTracingEnabled = false;
 
-    private final TraceBuffer mTraceBuffer = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY,
-            (proto) -> handleOnEntryRemovedFromTrace(proto));
+    private final TraceBuffer.ProtoProvider mProtoProvider =
+            new TraceBuffer.ProtoProvider<MessageNano,
+                com.android.wm.shell.nano.WmShellTransitionTraceProto,
+                com.android.wm.shell.nano.Transition>() {
+        @Override
+        public int getItemSize(MessageNano proto) {
+            return proto.getCachedSize();
+        }
+
+        @Override
+        public byte[] getBytes(MessageNano proto) {
+            return MessageNano.toByteArray(proto);
+        }
+
+        @Override
+        public void write(
+                com.android.wm.shell.nano.WmShellTransitionTraceProto encapsulatingProto,
+                Queue<com.android.wm.shell.nano.Transition> buffer, OutputStream os)
+                        throws IOException {
+            encapsulatingProto.transitions = buffer.toArray(
+                    new com.android.wm.shell.nano.Transition[0]);
+            os.write(getBytes(encapsulatingProto));
+        }
+    };
+    private final TraceBuffer<MessageNano,
+            com.android.wm.shell.nano.WmShellTransitionTraceProto,
+            com.android.wm.shell.nano.Transition> mTraceBuffer
+                    = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY, mProtoProvider,
+                            (proto) -> handleOnEntryRemovedFromTrace(proto));
     private final Map<Object, Runnable> mRemovedFromTraceCallbacks = new HashMap<>();
 
     private final Map<Transitions.TransitionHandler, Integer> mHandlerIds = new HashMap<>();
@@ -73,30 +104,25 @@
         if (mHandlerIds.containsKey(handler)) {
             handlerId = mHandlerIds.get(handler);
         } else {
-            handlerId = mHandlerIds.size();
+            // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto
+            handlerId = mHandlerIds.size() + 1;
             mHandlerIds.put(handler, handlerId);
         }
 
-        ProtoOutputStream outputStream = new ProtoOutputStream();
-        final long protoToken =
-                outputStream.start(com.android.wm.shell.WmShellTransitionTraceProto.TRANSITIONS);
-
-        outputStream.write(com.android.wm.shell.Transition.ID, transitionId);
-        outputStream.write(com.android.wm.shell.Transition.DISPATCH_TIME_NS,
-                SystemClock.elapsedRealtimeNanos());
-        outputStream.write(com.android.wm.shell.Transition.HANDLER, handlerId);
-
-        outputStream.end(protoToken);
+        com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
+        proto.id = transitionId;
+        proto.dispatchTimeNs = SystemClock.elapsedRealtimeNanos();
+        proto.handler = handlerId;
 
         final int useCountAfterAdd = mHandlerUseCountInTrace.getOrDefault(handler, 0) + 1;
         mHandlerUseCountInTrace.put(handler, useCountAfterAdd);
 
-        mRemovedFromTraceCallbacks.put(outputStream, () -> {
+        mRemovedFromTraceCallbacks.put(proto, () -> {
             final int useCountAfterRemove = mHandlerUseCountInTrace.get(handler) - 1;
             mHandlerUseCountInTrace.put(handler, useCountAfterRemove);
         });
 
-        mTraceBuffer.add(outputStream);
+        mTraceBuffer.add(proto);
     }
 
     /**
@@ -106,18 +132,12 @@
      * @param playingTransitionId The id of the transition we was to merge the transition into.
      */
     public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
-        ProtoOutputStream outputStream = new ProtoOutputStream();
-        final long protoToken =
-                outputStream.start(com.android.wm.shell.WmShellTransitionTraceProto.TRANSITIONS);
+        com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
+        proto.id = mergeRequestedTransitionId;
+        proto.mergeRequestTimeNs = SystemClock.elapsedRealtimeNanos();
+        proto.mergedInto = playingTransitionId;
 
-        outputStream.write(com.android.wm.shell.Transition.ID, mergeRequestedTransitionId);
-        outputStream.write(com.android.wm.shell.Transition.MERGE_REQUEST_TIME_NS,
-                SystemClock.elapsedRealtimeNanos());
-        outputStream.write(com.android.wm.shell.Transition.MERGED_INTO, playingTransitionId);
-
-        outputStream.end(protoToken);
-
-        mTraceBuffer.add(outputStream);
+        mTraceBuffer.add(proto);
     }
 
     /**
@@ -127,18 +147,12 @@
      * @param playingTransitionId The id of the transition the transition was merged into.
      */
     public void logMerged(int mergedTransitionId, int playingTransitionId) {
-        ProtoOutputStream outputStream = new ProtoOutputStream();
-        final long protoToken =
-                outputStream.start(com.android.wm.shell.WmShellTransitionTraceProto.TRANSITIONS);
+        com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
+        proto.id = mergedTransitionId;
+        proto.mergeTimeNs = SystemClock.elapsedRealtimeNanos();
+        proto.mergedInto = playingTransitionId;
 
-        outputStream.write(com.android.wm.shell.Transition.ID, mergedTransitionId);
-        outputStream.write(
-                com.android.wm.shell.Transition.MERGE_TIME_NS, SystemClock.elapsedRealtimeNanos());
-        outputStream.write(com.android.wm.shell.Transition.MERGED_INTO, playingTransitionId);
-
-        outputStream.end(protoToken);
-
-        mTraceBuffer.add(outputStream);
+        mTraceBuffer.add(proto);
     }
 
     /**
@@ -147,17 +161,11 @@
      * @param transitionId The id of the transition that was aborted.
      */
     public void logAborted(int transitionId) {
-        ProtoOutputStream outputStream = new ProtoOutputStream();
-        final long protoToken =
-                outputStream.start(com.android.wm.shell.WmShellTransitionTraceProto.TRANSITIONS);
+        com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
+        proto.id = transitionId;
+        proto.abortTimeNs = SystemClock.elapsedRealtimeNanos();
 
-        outputStream.write(com.android.wm.shell.Transition.ID, transitionId);
-        outputStream.write(
-                com.android.wm.shell.Transition.ABORT_TIME_NS, SystemClock.elapsedRealtimeNanos());
-
-        outputStream.end(protoToken);
-
-        mTraceBuffer.add(outputStream);
+        mTraceBuffer.add(proto);
     }
 
     /**
@@ -229,8 +237,9 @@
     private void writeTraceToFileLocked(@Nullable PrintWriter pw, File file) {
         Trace.beginSection("TransitionTracer#writeTraceToFileLocked");
         try {
-            ProtoOutputStream proto = new ProtoOutputStream();
-            proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+            com.android.wm.shell.nano.WmShellTransitionTraceProto proto =
+                    new com.android.wm.shell.nano.WmShellTransitionTraceProto();
+            proto.magicNumber = MAGIC_NUMBER_VALUE;
             writeHandlerMappingToProto(proto);
             int pid = android.os.Process.myPid();
             LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath()
@@ -242,19 +251,21 @@
         Trace.endSection();
     }
 
-    private void writeHandlerMappingToProto(ProtoOutputStream outputStream) {
+    private void writeHandlerMappingToProto(
+            com.android.wm.shell.nano.WmShellTransitionTraceProto proto) {
+        ArrayList<com.android.wm.shell.nano.HandlerMapping> handlerMappings = new ArrayList<>();
         for (Transitions.TransitionHandler handler : mHandlerUseCountInTrace.keySet()) {
             final int count = mHandlerUseCountInTrace.get(handler);
             if (count > 0) {
-                final long protoToken = outputStream.start(
-                        com.android.wm.shell.WmShellTransitionTraceProto.HANDLER_MAPPINGS);
-                outputStream.write(com.android.wm.shell.HandlerMapping.ID,
-                        mHandlerIds.get(handler));
-                outputStream.write(com.android.wm.shell.HandlerMapping.NAME,
-                        handler.getClass().getName());
-                outputStream.end(protoToken);
+                com.android.wm.shell.nano.HandlerMapping mapping =
+                        new com.android.wm.shell.nano.HandlerMapping();
+                mapping.id = mHandlerIds.get(handler);
+                mapping.name = handler.getClass().getName();
+                handlerMappings.add(mapping);
             }
         }
+        proto.handlerMappings = handlerMappings.toArray(
+                new com.android.wm.shell.nano.HandlerMapping[0]);
     }
 
     private void handleOnEntryRemovedFromTrace(Object proto) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index f79f1f7..a73ab77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -533,8 +533,12 @@
             final int layer;
             // Put all the OPEN/SHOW on top
             if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
-                // Wallpaper is always at the bottom.
-                layer = -zSplitLine;
+                // Wallpaper is always at the bottom, opening wallpaper on top of closing one.
+                if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
+                    layer = -zSplitLine + numChanges - i;
+                } else {
+                    layer = -zSplitLine - i;
+                }
             } else if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
                 if (isOpening) {
                     // put on top
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 4ef3350..dffbedd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -111,12 +111,6 @@
     private ValueAnimator mDragToDesktopValueAnimator;
     private final Rect mDragToDesktopAnimationStartBounds = new Rect();
     private boolean mDragToDesktopAnimationStarted;
-    private float mCaptionDragStartX;
-
-    // These values keep track of any transitions to freeform to stop relayout from running on
-    // changing task so that shellTransitions has a chance to animate the transition
-    private int mPauseRelayoutForTask = -1;
-    private IBinder mTransitionPausingRelayout;
 
     public DesktopModeWindowDecorViewModel(
             Context context,
@@ -195,22 +189,25 @@
             @NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change) {
         if (change.getMode() == WindowManager.TRANSIT_CHANGE
-                && info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE) {
-            mTransitionPausingRelayout = transition;
+                && (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE)) {
+            mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
+                    .addTransitionPausingRelayout(transition);
         }
     }
 
     @Override
     public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
-        if (merged.equals(mTransitionPausingRelayout)) {
-            mTransitionPausingRelayout = playing;
+        for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
+            final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+            decor.mergeTransitionPausingRelayout(merged, playing);
         }
     }
 
     @Override
     public void onTransitionFinished(@NonNull IBinder transition) {
-        if (transition.equals(mTransitionPausingRelayout)) {
-            mPauseRelayoutForTask = -1;
+        for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
+            final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+            decor.removeTransitionPausingRelayout(transition);
         }
     }
 
@@ -225,12 +222,7 @@
             incrementEventReceiverTasks(taskInfo.displayId);
         }
 
-        // TaskListener callbacks and shell transitions aren't synchronized, so starting a shell
-        // transition can trigger an onTaskInfoChanged call that updates the task's SurfaceControl
-        // and interferes with the transition animation that is playing at the same time.
-        if (taskInfo.taskId != mPauseRelayoutForTask) {
-            decoration.relayout(taskInfo);
-        }
+        decoration.relayout(taskInfo);
     }
 
     @Override
@@ -333,6 +325,13 @@
                 decoration.closeHandleMenu();
             } else if (id == R.id.collapse_menu_button) {
                 decoration.closeHandleMenu();
+            } else if (id == R.id.select_button) {
+                if (DesktopModeStatus.IS_DISPLAY_CHANGE_ENABLED) {
+                    // TODO(b/278084491): dev option to enable display switching
+                    //  remove when select is implemented
+                    mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId));
+                    decoration.closeHandleMenu();
+                }
             }
         }
 
@@ -525,7 +524,6 @@
             DesktopModeWindowDecoration relevantDecor) {
         switch (ev.getActionMasked()) {
             case MotionEvent.ACTION_DOWN: {
-                mCaptionDragStartX = ev.getX();
                 // Begin drag through status bar if applicable.
                 if (relevantDecor != null) {
                     mDragToDesktopAnimationStartBounds.set(
@@ -555,8 +553,7 @@
                             relevantDecor.mTaskInfo.displayId);
                     if (ev.getY() > 2 * statusBarHeight) {
                         if (DesktopModeStatus.isProto2Enabled()) {
-                            mPauseRelayoutForTask = relevantDecor.mTaskInfo.taskId;
-                            centerAndMoveToDesktopWithAnimation(relevantDecor, ev);
+                            animateToDesktop(relevantDecor, ev);
                         } else if (DesktopModeStatus.isProto1Enabled()) {
                             mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
                         }
@@ -581,8 +578,9 @@
                 }
                 if (mTransitionDragActive) {
                     mDesktopTasksController.ifPresent(
-                            c -> c.onDragPositioningMoveThroughStatusBar(relevantDecor.mTaskInfo,
-                            relevantDecor.mTaskSurface, ev.getY()));
+                            c -> c.onDragPositioningMoveThroughStatusBar(
+                                    relevantDecor.mTaskInfo,
+                                    relevantDecor.mTaskSurface, ev.getY()));
                     final int statusBarHeight = getStatusBarHeight(
                             relevantDecor.mTaskInfo.displayId);
                     if (ev.getY() > statusBarHeight) {
@@ -632,6 +630,15 @@
     }
 
     /**
+     * Blocks relayout until transition is finished and transitions to Desktop
+     */
+    private void animateToDesktop(DesktopModeWindowDecoration relevantDecor,
+            MotionEvent ev) {
+        relevantDecor.incrementRelayoutBlock();
+        centerAndMoveToDesktopWithAnimation(relevantDecor, ev);
+    }
+
+    /**
      * Animates a window to the center, grows to freeform size, and transitions to Desktop Mode.
      * @param relevantDecor the window decor of the task to be animated
      * @param ev the motion event that triggers the animation
@@ -658,10 +665,9 @@
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                mDesktopTasksController.ifPresent(
-                        c -> c.onDragPositioningEndThroughStatusBar(
-                                relevantDecor.mTaskInfo,
-                                calculateFreeformBounds(FINAL_FREEFORM_SCALE)));
+                mDesktopTasksController.ifPresent(c ->
+                        c.onDragPositioningEndThroughStatusBar(relevantDecor.mTaskInfo,
+                            calculateFreeformBounds(FINAL_FREEFORM_SCALE)));
             }
         });
         animator.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index b1c3791..95ed42a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -30,6 +30,7 @@
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
+import android.os.IBinder;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.MotionEvent;
@@ -49,6 +50,9 @@
 import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder;
 import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationViewHolder;
 
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
  * {@link DesktopModeWindowDecorViewModel}.
@@ -83,6 +87,9 @@
 
     private TaskCornersListener mCornersListener;
 
+    private final Set<IBinder> mTransitionsPausingRelayout = new HashSet<>();
+    private int mRelayoutBlock;
+
     DesktopModeWindowDecoration(
             Context context,
             DisplayController displayController,
@@ -134,6 +141,13 @@
 
     @Override
     void relayout(ActivityManager.RunningTaskInfo taskInfo) {
+        // TaskListener callbacks and shell transitions aren't synchronized, so starting a shell
+        // transition can trigger an onTaskInfoChanged call that updates the task's SurfaceControl
+        // and interferes with the transition animation that is playing at the same time.
+        if (mRelayoutBlock > 0) {
+            return;
+        }
+
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         // Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
         // synced with the buffer transaction (that draws the View). Both will be shown on screen
@@ -455,6 +469,40 @@
         return cornersRegion;
     }
 
+    /**
+     * If transition exists in mTransitionsPausingRelayout, remove the transition and decrement
+     * mRelayoutBlock
+     */
+    void removeTransitionPausingRelayout(IBinder transition) {
+        if (mTransitionsPausingRelayout.remove(transition)) {
+            mRelayoutBlock--;
+        }
+    }
+
+    /**
+     * Add transition to mTransitionsPausingRelayout
+     */
+    void addTransitionPausingRelayout(IBinder transition) {
+        mTransitionsPausingRelayout.add(transition);
+    }
+
+    /**
+     * If two transitions merge and the merged transition is in mTransitionsPausingRelayout,
+     * remove the merged transition from the set and add the transition it was merged into.
+     */
+    public void mergeTransitionPausingRelayout(IBinder merged, IBinder playing) {
+        if (mTransitionsPausingRelayout.remove(merged)) {
+            mTransitionsPausingRelayout.add(playing);
+        }
+    }
+
+    /**
+     * Increase mRelayoutBlock, stopping relayout if mRelayoutBlock is now greater than 0.
+     */
+    public void incrementRelayoutBlock() {
+        mRelayoutBlock++;
+    }
+
     static class Factory {
 
         DesktopModeWindowDecoration create(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index ed3cca0..ac4a597 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -187,6 +187,8 @@
         final View moreActionsPillView = mMoreActionsPill.mWindowViewHost.getView();
         final Button closeBtn = moreActionsPillView.findViewById(R.id.close_button);
         closeBtn.setOnClickListener(mOnClickListener);
+        final Button selectBtn = moreActionsPillView.findViewById(R.id.select_button);
+        selectBtn.setOnClickListener(mOnClickListener);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index ef8332f..1d416c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -86,9 +86,10 @@
     public void onDragPositioningEnd(float x, float y) {
         PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y,
                 mRepositionStartPoint);
-        if (mHasMoved && DragPositioningCallbackUtility.changeBounds(mCtrlType, mHasMoved,
-                mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
-                mDisplayController, mDesktopWindowDecoration)) {
+        if (mHasMoved) {
+            DragPositioningCallbackUtility.changeBounds(mCtrlType, mHasMoved,
+                    mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
+                    mDisplayController, mDesktopWindowDecoration);
             DragPositioningCallbackUtility.applyTaskBoundsChange(
                     new WindowContainerTransaction(), mDesktopWindowDecoration,
                     mRepositionTaskBounds, mTaskOrganizer);
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index b6d92814..b5937ae 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -21,6 +21,9 @@
         <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
         <!-- Ensure output directory is empty at the start -->
         <option name="run-command" value="rm -rf /sdcard/flicker" />
+        <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+        <option name="run-command" value="cmd window tracing size 20480" />
+        <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
new file mode 100644
index 0000000..e06e074
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.app.Instrumentation
+import android.tools.device.flicker.junit.FlickerBuilderProvider
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+
+abstract class BaseBenchmarkTest
+@JvmOverloads
+constructor(
+    protected open val flicker: FlickerTest,
+    protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+    protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
+) {
+    /** Specification of the test transition to execute */
+    abstract val transition: FlickerBuilder.() -> Unit
+
+    /**
+     * Entry point for the test runner. It will use this method to initialize and cache flicker
+     * executions
+     */
+    @FlickerBuilderProvider
+    fun buildFlicker(): FlickerBuilder {
+        return FlickerBuilder(instrumentation).apply {
+            setup { flicker.scenario.setIsTablet(tapl.isTablet) }
+            transition()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index 86edc25..c98c5a0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -17,24 +17,10 @@
 package com.android.wm.shell.flicker
 
 import android.app.Instrumentation
-import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerBuilderProvider
-import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
-import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
-import com.android.server.wm.flicker.statusBarLayerPositionAtStartAndEnd
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
-import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible
-import org.junit.Assume
-import org.junit.Test
 
 /**
  * Base test class containing common assertions for [ComponentNameMatcher.NAV_BAR],
@@ -44,124 +30,7 @@
 abstract class BaseTest
 @JvmOverloads
 constructor(
-    protected val flicker: FlickerTest,
-    protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
-    protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
-) {
-    /** Specification of the test transition to execute */
-    abstract val transition: FlickerBuilder.() -> Unit
-
-    /**
-     * Entry point for the test runner. It will use this method to initialize and cache flicker
-     * executions
-     */
-    @FlickerBuilderProvider
-    fun buildFlicker(): FlickerBuilder {
-        return FlickerBuilder(instrumentation).apply {
-            setup { flicker.scenario.setIsTablet(tapl.isTablet) }
-            transition()
-        }
-    }
-
-    /** Checks that all parts of the screen are covered during the transition */
-    @Presubmit @Test open fun entireScreenCovered() = flicker.entireScreenCovered()
-
-    /**
-     * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
-     */
-    @Presubmit
-    @Test
-    open fun navBarLayerIsVisibleAtStartAndEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.navBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    /**
-     * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the
-     * transition
-     */
-    @Presubmit
-    @Test
-    open fun navBarLayerPositionAtStartAndEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.navBarLayerPositionAtStartAndEnd()
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition
-     *
-     * Note: Phones only
-     */
-    @Presubmit
-    @Test
-    open fun navBarWindowIsAlwaysVisible() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.navBarWindowIsAlwaysVisible()
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible during the whole transition
-     */
-    @Presubmit
-    @Test
-    open fun taskBarLayerIsVisibleAtStartAndEnd() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
-        flicker.taskBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition
-     *
-     * Note: Large screen only
-     */
-    @Presubmit
-    @Test
-    open fun taskBarWindowIsAlwaysVisible() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
-        flicker.taskBarWindowIsAlwaysVisible()
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible during the whole
-     * transition
-     */
-    @Presubmit
-    @Test
-    open fun statusBarLayerIsVisibleAtStartAndEnd() = flicker.statusBarLayerIsVisibleAtStartAndEnd()
-
-    /**
-     * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
-     * transition
-     */
-    @Presubmit
-    @Test
-    open fun statusBarLayerPositionAtStartAndEnd() = flicker.statusBarLayerPositionAtStartAndEnd()
-
-    /**
-     * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
-     * transition
-     */
-    @Presubmit
-    @Test
-    open fun statusBarWindowIsAlwaysVisible() = flicker.statusBarWindowIsAlwaysVisible()
-
-    /**
-     * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
-     * entries.
-     */
-    @Presubmit
-    @Test
-    open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
-    }
-
-    /**
-     * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive
-     * entries.
-     */
-    @Presubmit
-    @Test
-    open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
-        flicker.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
-    }
-}
+    override val flicker: FlickerTest,
+    instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+    tapl: LauncherInstrumentation = LauncherInstrumentation()
+) : BaseBenchmarkTest(flicker, instrumentation, tapl), ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt
new file mode 100644
index 0000000..02d9a056
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.legacy.FlickerTest
+import com.android.server.wm.flicker.entireScreenCovered
+import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.statusBarLayerPositionAtStartAndEnd
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible
+import org.junit.Assume
+import org.junit.Test
+
+interface ICommonAssertions {
+    val flicker: FlickerTest
+
+    /** Checks that all parts of the screen are covered during the transition */
+    @Presubmit @Test fun entireScreenCovered() = flicker.entireScreenCovered()
+
+    /**
+     * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
+     */
+    @Presubmit
+    @Test
+    fun navBarLayerIsVisibleAtStartAndEnd() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    /**
+     * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the
+     * transition
+     */
+    @Presubmit
+    @Test
+    fun navBarLayerPositionAtStartAndEnd() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerPositionAtStartAndEnd()
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition
+     *
+     * Note: Phones only
+     */
+    @Presubmit
+    @Test
+    fun navBarWindowIsAlwaysVisible() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarWindowIsAlwaysVisible()
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible during the whole transition
+     */
+    @Presubmit
+    @Test
+    fun taskBarLayerIsVisibleAtStartAndEnd() {
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.taskBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition
+     *
+     * Note: Large screen only
+     */
+    @Presubmit
+    @Test
+    fun taskBarWindowIsAlwaysVisible() {
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.taskBarWindowIsAlwaysVisible()
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible during the whole
+     * transition
+     */
+    @Presubmit
+    @Test
+    fun statusBarLayerIsVisibleAtStartAndEnd() = flicker.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /**
+     * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
+     * transition
+     */
+    @Presubmit
+    @Test
+    fun statusBarLayerPositionAtStartAndEnd() = flicker.statusBarLayerPositionAtStartAndEnd()
+
+    /**
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
+     * transition
+     */
+    @Presubmit @Test fun statusBarWindowIsAlwaysVisible() = flicker.statusBarWindowIsAlwaysVisible()
+
+    /**
+     * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
+     * entries.
+     */
+    @Presubmit
+    @Test
+    fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
+    }
+
+    /**
+     * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive
+     * entries.
+     */
+    @Presubmit
+    @Test
+    fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+        flicker.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
index 11c5951..6178156 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
@@ -18,15 +18,18 @@
 
 import android.content.Context
 import android.system.helpers.CommandsHelper
+import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import android.tools.device.flicker.legacy.IFlickerTestData
-import com.android.server.wm.flicker.helpers.LetterboxAppHelper
-import com.android.server.wm.flicker.helpers.setRotation
 import com.android.wm.shell.flicker.BaseTest
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.layerKeepVisible
+import org.junit.After
 import org.junit.Assume
 import org.junit.Before
 import org.junit.runners.Parameterized
@@ -35,7 +38,7 @@
     protected val context: Context = instrumentation.context
     protected val letterboxApp = LetterboxAppHelper(instrumentation)
     lateinit var cmdHelper: CommandsHelper
-    lateinit var letterboxStyle: HashMap<String, String>
+    private lateinit var letterboxStyle: HashMap<String, String>
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -45,12 +48,22 @@
                 letterboxApp.launchViaIntent(wmHelper)
                 setEndRotation()
             }
+            teardown {
+                letterboxApp.exit(wmHelper)
+            }
         }
 
     @Before
     fun before() {
         cmdHelper = CommandsHelper.getInstance(instrumentation)
         Assume.assumeTrue(tapl.isTablet && isIgnoreOrientationRequest())
+        letterboxStyle = mapLetterboxStyle()
+        setLetterboxEducationEnabled(false)
+    }
+
+    @After
+    fun after() {
+        resetLetterboxEducationEnabled()
     }
 
     private fun mapLetterboxStyle(): HashMap<String, String> {
@@ -67,6 +80,22 @@
         return map
     }
 
+    private fun getLetterboxStyle(): HashMap<String, String> {
+        if (!::letterboxStyle.isInitialized) {
+            letterboxStyle = mapLetterboxStyle()
+        }
+        return letterboxStyle
+    }
+
+    private fun resetLetterboxEducationEnabled() {
+        val enabled = getLetterboxStyle().getValue("Is education enabled")
+        cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled")
+    }
+
+    private fun setLetterboxEducationEnabled(enabled: Boolean) {
+        cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled")
+    }
+
     private fun isIgnoreOrientationRequest(): Boolean {
         val res = cmdHelper.executeShellCommand("wm get-ignore-orientation-request")
         return res != null && res.contains("true")
@@ -89,10 +118,7 @@
 
     /** Only run on tests with config_letterboxActivityCornersRadius != 0 in devices */
     private fun assumeLetterboxRoundedCornersEnabled() {
-        if (!::letterboxStyle.isInitialized) {
-            letterboxStyle = mapLetterboxStyle()
-        }
-        Assume.assumeTrue(letterboxStyle.getValue("Corner radius") != "0")
+        Assume.assumeTrue(getLetterboxStyle().getValue("Corner radius") != "0")
     }
 
     fun assertLetterboxAppVisibleAtStartAndEnd() {
@@ -100,12 +126,20 @@
         flicker.appWindowIsVisibleAtEnd(letterboxApp)
     }
 
+    fun assertAppLetterboxedAtEnd() =
+            flicker.assertLayersEnd { isVisible(ComponentNameMatcher.LETTERBOX) }
+
+    fun assertAppLetterboxedAtStart() =
+            flicker.assertLayersStart { isVisible(ComponentNameMatcher.LETTERBOX) }
+
+    fun assertLetterboxAppLayerKeepVisible() = flicker.layerKeepVisible(letterboxApp)
+
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.rotationTests] for configuring screen orientation and navigation
-         * modes.
+         * See [FlickerTestFactory.rotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
index f212a4e..c2141a3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -70,6 +70,10 @@
     @Test
     fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtEndHasRoundedCorners()
 
+    @Postsubmit
+    @Test
+    fun appIsLetterboxedAtEnd() = assertAppLetterboxedAtEnd()
+
     /**
      * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
      * flicker, and disappears before the transition is complete
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
index 8e75439..b0e1a42 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
@@ -53,25 +53,32 @@
         get() = {
             super.transition(this)
             transitions { letterboxApp.clickRestart(wmHelper) }
-            teardown { letterboxApp.exit(wmHelper) }
         }
 
     @Postsubmit @Test fun appVisibleAtStartAndEnd() = assertLetterboxAppVisibleAtStartAndEnd()
 
     @Postsubmit
     @Test
-    fun appLayerVisibilityChanges() {
-        flicker.assertLayers {
-            this.isVisible(letterboxApp)
+    fun appWindowVisibilityChanges() {
+        flicker.assertWm {
+            this.isAppWindowVisible(letterboxApp)
                 .then()
-                .isInvisible(letterboxApp)
+                .isAppWindowInvisible(letterboxApp) // animatingExit true
                 .then()
-                .isVisible(letterboxApp)
+                .isAppWindowVisible(letterboxApp) // Activity finish relaunching
         }
     }
 
     @Postsubmit
     @Test
+    fun appLayerKeepVisible() = assertLetterboxAppLayerKeepVisible()
+
+    @Postsubmit
+    @Test
+    fun appIsLetterboxedAtStart() = assertAppLetterboxedAtStart()
+
+    @Postsubmit
+    @Test
     fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtStartHasRoundedCorners()
 
     /** Checks that the visible region of [letterboxApp] is still within display bounds */
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 93ee699..a4ac261 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -54,7 +54,6 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 238367575)
 class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
     /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
@@ -71,7 +70,7 @@
             transitions { tapl.goHome() }
         }
 
-    @FlakyTest(bugId = 256863309)
+    @Presubmit
     @Test
     override fun pipLayerReduces() {
         flicker.assertLayers {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index d53eac0..b7e73ad 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -26,6 +26,7 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
@@ -34,6 +35,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
 import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
+import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -49,29 +51,19 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CopyContentInSplit(flicker: FlickerTest) : SplitScreenBase(flicker) {
-    private val textEditApp = SplitScreenUtils.getIme(instrumentation)
-    private val MagnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
-    private val PopupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
-
+class CopyContentInSplit(override val flicker: FlickerTest) :
+    CopyContentInSplitBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) }
-            transitions {
-                SplitScreenUtils.copyContentInSplit(
-                    instrumentation,
-                    device,
-                    primaryApp,
-                    textEditApp
-                )
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() {
+    override fun cujCompleted() {
         flicker.appWindowIsVisibleAtStart(primaryApp)
         flicker.appWindowIsVisibleAtStart(textEditApp)
         flicker.splitScreenDividerIsVisibleAtStart()
@@ -128,8 +120,8 @@
                         ComponentNameMatcher.SNAPSHOT,
                         ComponentNameMatcher.IME_SNAPSHOT,
                         EdgeExtensionComponentMatcher(),
-                        MagnifierLayer,
-                        PopupWindowLayer
+                        magnifierLayer,
+                        popupWindowLayer
                     )
             )
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 1b55f39..3fd6d17 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -17,22 +17,21 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.layerBecomesInvisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
-import com.android.wm.shell.flicker.splitScreenDismissed
 import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
+import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByDividerBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -48,37 +47,16 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByDivider(flicker: FlickerTest) : SplitScreenBase(flicker) {
+class DismissSplitScreenByDivider(override val flicker: FlickerTest) :
+    DismissSplitScreenByDividerBenchmark(flicker), ICommonAssertions {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
-            transitions {
-                if (tapl.isTablet) {
-                    SplitScreenUtils.dragDividerToDismissSplit(
-                        device,
-                        wmHelper,
-                        dragToRight = false,
-                        dragToBottom = true
-                    )
-                } else {
-                    SplitScreenUtils.dragDividerToDismissSplit(
-                        device,
-                        wmHelper,
-                        dragToRight = true,
-                        dragToBottom = true
-                    )
-                }
-                wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify()
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
-
     @Presubmit
     @Test
     fun splitScreenDividerBecomesInvisible() = flicker.splitScreenDividerBecomesInvisible()
@@ -176,12 +154,4 @@
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 2e81b30..e05b221 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -17,18 +17,18 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
 import com.android.wm.shell.flicker.layerBecomesInvisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
-import com.android.wm.shell.flicker.splitScreenDismissed
 import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
+import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByGoHomeBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -44,21 +44,14 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByGoHome(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
+class DismissSplitScreenByGoHome(override val flicker: FlickerTest) :
+    DismissSplitScreenByGoHomeBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
-            transitions {
-                tapl.goHome()
-                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 5180791..0b0a3da 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -24,6 +24,7 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
@@ -32,8 +33,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
 import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
-import org.junit.Assume
-import org.junit.Before
+import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -49,24 +49,19 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
+class DragDividerToResize(override val flicker: FlickerTest) :
+    DragDividerToResizeBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
-            transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @Before
-    fun before() {
-        Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart)
-    }
-
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() {
+    override fun cujCompleted() {
         flicker.appWindowIsVisibleAtStart(primaryApp)
         flicker.appWindowIsVisibleAtStart(secondaryApp)
         flicker.splitScreenDividerIsVisibleAtStart()
@@ -74,9 +69,6 @@
         flicker.appWindowIsVisibleAtEnd(primaryApp)
         flicker.appWindowIsVisibleAtEnd(secondaryApp)
         flicker.splitScreenDividerIsVisibleAtEnd()
-
-        // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
-        // robust enough to get the correct end state.
     }
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 69da1e2..e558686 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
@@ -26,6 +25,7 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -34,9 +34,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
-import org.junit.Before
+import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromAllAppsBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -53,40 +51,15 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromAllApps(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
-    @Before
-    fun before() {
-        Assume.assumeTrue(tapl.isTablet)
-    }
-
+class EnterSplitScreenByDragFromAllApps(override val flicker: FlickerTest) :
+    EnterSplitScreenByDragFromAllAppsBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                tapl.goHome()
-                primaryApp.launchViaIntent(wmHelper)
-            }
-            transitions {
-                tapl.launchedAppState.taskbar
-                    .openAllApps()
-                    .getAppIcon(secondaryApp.appName)
-                    .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(
-            primaryApp,
-            secondaryApp,
-            fromOtherApp = false,
-            appExistAtStart = false
-        )
-
     @FlakyTest(bugId = 245472831)
     @Test
     fun splitScreenDividerBecomesVisible() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 1773846..ab8ecc5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
@@ -26,6 +25,7 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.layerBecomesVisible
@@ -33,9 +33,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
-import org.junit.Before
+import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromNotificationBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -52,39 +50,16 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromNotification(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
-    private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
-
-    @Before
-    fun before() {
-        Assume.assumeTrue(tapl.isTablet)
-    }
-
+class EnterSplitScreenByDragFromNotification(override val flicker: FlickerTest) :
+    EnterSplitScreenByDragFromNotificationBenchmark(flicker), ICommonAssertions {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                // Send a notification
-                sendNotificationApp.launchViaIntent(wmHelper)
-                sendNotificationApp.postNotification(wmHelper)
-                tapl.goHome()
-                primaryApp.launchViaIntent(wmHelper)
-            }
-            transitions {
-                SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
-            }
-            teardown { sendNotificationApp.exit(wmHelper) }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false)
-
     @FlakyTest(bugId = 245472831)
     @Test
     fun splitScreenDividerBecomesVisible() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
index c1977e9..516ca97 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -25,15 +24,14 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
-import org.junit.Before
+import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromShortcutBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -49,42 +47,16 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromShortcut(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
-    @Before
-    fun before() {
-        Assume.assumeTrue(tapl.isTablet)
-    }
+class EnterSplitScreenByDragFromShortcut(override val flicker: FlickerTest) :
+    EnterSplitScreenByDragFromShortcutBenchmark(flicker), ICommonAssertions {
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                tapl.goHome()
-                SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
-                primaryApp.launchViaIntent(wmHelper)
-            }
-            transitions {
-                tapl.launchedAppState.taskbar
-                    .getAppIcon(secondaryApp.appName)
-                    .openDeepShortcutMenu()
-                    .getMenuItem("Split Screen Secondary Activity")
-                    .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(
-            primaryApp,
-            secondaryApp,
-            fromOtherApp = false,
-            appExistAtStart = false
-        )
-
     @Presubmit
     @Test
     fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 3bea66e..4af7e24 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
@@ -26,6 +25,7 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -34,9 +34,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
-import org.junit.Before
+import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromTaskbarBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -53,41 +51,16 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromTaskbar(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
-    @Before
-    fun before() {
-        Assume.assumeTrue(tapl.isTablet)
-    }
-
+class EnterSplitScreenByDragFromTaskbar(override val flicker: FlickerTest) :
+    EnterSplitScreenByDragFromTaskbarBenchmark(flicker), ICommonAssertions {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                tapl.goHome()
-                SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
-                primaryApp.launchViaIntent(wmHelper)
-            }
-            transitions {
-                tapl.launchedAppState.taskbar
-                    .getAppIcon(secondaryApp.appName)
-                    .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(
-            primaryApp,
-            secondaryApp,
-            fromOtherApp = false,
-            appExistAtStart = false
-        )
-
     @FlakyTest(bugId = 245472831)
     @Test
     fun splitScreenDividerBecomesVisible() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index c453877..faad9e8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -17,20 +17,20 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenFromOverviewBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -46,31 +46,15 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenFromOverview(flicker: FlickerTest) : SplitScreenBase(flicker) {
+class EnterSplitScreenFromOverview(override val flicker: FlickerTest) :
+    EnterSplitScreenFromOverviewBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                primaryApp.launchViaIntent(wmHelper)
-                secondaryApp.launchViaIntent(wmHelper)
-                tapl.goHome()
-                wmHelper
-                    .StateSyncBuilder()
-                    .withAppTransitionIdle()
-                    .withHomeActivityVisible()
-                    .waitForAndVerify()
-            }
-            transitions {
-                SplitScreenUtils.splitFromOverview(tapl, device)
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     @Presubmit
     @Test
     fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
index 7abdc06..195b73a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
@@ -20,25 +20,33 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.wm.shell.flicker.BaseTest
+import com.android.wm.shell.flicker.BaseBenchmarkTest
 
-abstract class SplitScreenBase(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class SplitScreenBase(flicker: FlickerTest) : BaseBenchmarkTest(flicker) {
     protected val context: Context = instrumentation.context
     protected val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
     protected val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
 
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            setup {
-                tapl.setEnableRotation(true)
-                setRotation(flicker.scenario.startRotation)
-                tapl.setExpectedRotation(flicker.scenario.startRotation.value)
-                tapl.workspace.switchToOverview().dismissAllTasks()
-            }
-            teardown {
-                primaryApp.exit(wmHelper)
-                secondaryApp.exit(wmHelper)
-            }
+    protected open val defaultSetup: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setEnableRotation(true)
+            setRotation(flicker.scenario.startRotation)
+            tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+            tapl.workspace.switchToOverview().dismissAllTasks()
         }
+    }
+
+    protected open val defaultTeardown: FlickerBuilder.() -> Unit = {
+        teardown {
+            primaryApp.exit(wmHelper)
+            secondaryApp.exit(wmHelper)
+        }
+    }
+
+    protected open val withoutTracing: FlickerBuilder.() -> Unit = {
+        withoutLayerTracing()
+        withoutWindowManagerTracing()
+        withoutTransitionTracing()
+        withoutTransactionsTracing()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index fbb7c71..8cf871f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -20,14 +20,12 @@
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
-import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.helpers.WindowUtils
-import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
@@ -36,6 +34,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
+import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchAppByDoubleTapDividerBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -51,98 +50,19 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchAppByDoubleTapDivider(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
+class SwitchAppByDoubleTapDivider(override val flicker: FlickerTest) :
+    SwitchAppByDoubleTapDividerBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
-            transitions {
-                SplitScreenUtils.doubleTapDividerToSwitch(device)
-                wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
-
-                waitForLayersToSwitch(wmHelper)
-                waitForWindowsToSwitch(wmHelper)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
-        wmHelper
-            .StateSyncBuilder()
-            .add("appWindowsSwitched") {
-                val primaryAppWindow =
-                    it.wmState.visibleWindows.firstOrNull { window ->
-                        primaryApp.windowMatchesAnyOf(window)
-                    }
-                        ?: return@add false
-                val secondaryAppWindow =
-                    it.wmState.visibleWindows.firstOrNull { window ->
-                        secondaryApp.windowMatchesAnyOf(window)
-                    }
-                        ?: return@add false
-
-                if (isLandscape(flicker.scenario.endRotation)) {
-                    return@add if (flicker.scenario.isTablet) {
-                        secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
-                    } else {
-                        primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
-                    }
-                } else {
-                    return@add if (flicker.scenario.isTablet) {
-                        primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
-                    } else {
-                        primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
-                    }
-                }
-            }
-            .waitForAndVerify()
-    }
-
-    private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) {
-        wmHelper
-            .StateSyncBuilder()
-            .add("appLayersSwitched") {
-                val primaryAppLayer =
-                    it.layerState.visibleLayers.firstOrNull { window ->
-                        primaryApp.layerMatchesAnyOf(window)
-                    }
-                        ?: return@add false
-                val secondaryAppLayer =
-                    it.layerState.visibleLayers.firstOrNull { window ->
-                        secondaryApp.layerMatchesAnyOf(window)
-                    }
-                        ?: return@add false
-
-                val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false
-                val secondaryVisibleRegion =
-                    secondaryAppLayer.visibleRegion?.bounds ?: return@add false
-
-                if (isLandscape(flicker.scenario.endRotation)) {
-                    return@add if (flicker.scenario.isTablet) {
-                        secondaryVisibleRegion.right <= primaryVisibleRegion.left
-                    } else {
-                        primaryVisibleRegion.right <= secondaryVisibleRegion.left
-                    }
-                } else {
-                    return@add if (flicker.scenario.isTablet) {
-                        primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
-                    } else {
-                        primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
-                    }
-                }
-            }
-            .waitForAndVerify()
-    }
-
-    private fun isLandscape(rotation: Rotation): Boolean {
-        val displayBounds = WindowUtils.getDisplayBounds(rotation)
-        return displayBounds.width > displayBounds.height
-    }
-
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() {
+    override fun cujCompleted() {
         flicker.appWindowIsVisibleAtStart(primaryApp)
         flicker.appWindowIsVisibleAtStart(secondaryApp)
         flicker.splitScreenDividerIsVisibleAtStart()
@@ -150,9 +70,6 @@
         flicker.appWindowIsVisibleAtEnd(primaryApp)
         flicker.appWindowIsVisibleAtEnd(secondaryApp)
         flicker.splitScreenDividerIsVisibleAtEnd()
-
-        // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
-        // robust enough to get the correct end state.
     }
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index d675bfb..078d95d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -25,11 +24,12 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromAnotherAppBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -45,29 +45,15 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromAnotherApp(flicker: FlickerTest) : SplitScreenBase(flicker) {
-    val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
-
+class SwitchBackToSplitFromAnotherApp(override val flicker: FlickerTest) :
+    SwitchBackToSplitFromAnotherAppBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
-
-                thirdApp.launchViaIntent(wmHelper)
-                wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
-            }
-            transitions {
-                tapl.launchedAppState.quickSwitchToPreviousApp()
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     @Presubmit
     @Test
     fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 9f4cb8c..7c84243 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -25,11 +24,12 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromHomeBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -45,28 +45,15 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromHome(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
+class SwitchBackToSplitFromHome(override val flicker: FlickerTest) :
+    SwitchBackToSplitFromHomeBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
-
-                tapl.goHome()
-                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
-            }
-            transitions {
-                tapl.workspace.quickSwitchToPreviousApp()
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     @Presubmit
     @Test
     fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index a33d8ca..7c46d3e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -25,11 +24,12 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromRecentBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -45,28 +45,15 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromRecent(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
+class SwitchBackToSplitFromRecent(override val flicker: FlickerTest) :
+    SwitchBackToSplitFromRecentBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
-
-                tapl.goHome()
-                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
-            }
-            transitions {
-                tapl.workspace.switchToOverview().currentTask.open()
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
-    @IwTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     @Presubmit
     @Test
     fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
index 4c96b3a..3b2da8d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -24,6 +24,7 @@
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
 import com.android.wm.shell.flicker.appWindowBecomesVisible
@@ -36,6 +37,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider
 import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
+import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -51,32 +53,19 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBetweenSplitPairs(flicker: FlickerTest) : SplitScreenBase(flicker) {
-    private val thirdApp = SplitScreenUtils.getIme(instrumentation)
-    private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
-
+class SwitchBetweenSplitPairs(override val flicker: FlickerTest) :
+    SwitchBetweenSplitPairsBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            super.transition(this)
-            setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
-                SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
-            }
-            transitions {
-                tapl.launchedAppState.quickSwitchToPreviousApp()
-                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-            }
-            teardown {
-                thirdApp.exit(wmHelper)
-                fourthApp.exit(wmHelper)
-            }
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
         }
 
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
-    fun cujCompleted() {
+    override fun cujCompleted() {
         flicker.appWindowIsVisibleAtStart(thirdApp)
         flicker.appWindowIsVisibleAtStart(fourthApp)
         flicker.splitScreenDividerIsVisibleAtStart()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
new file mode 100644
index 0000000..a189a3f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class CopyContentInSplitBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val textEditApp = SplitScreenUtils.getIme(instrumentation)
+    protected val magnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
+    protected val popupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) }
+            transitions {
+                SplitScreenUtils.copyContentInSplit(
+                    instrumentation,
+                    device,
+                    primaryApp,
+                    textEditApp
+                )
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    open fun cujCompleted() {
+        // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
new file mode 100644
index 0000000..55ab7b3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenDismissed
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class DismissSplitScreenByDividerBenchmark(flicker: FlickerTest) : SplitScreenBase(flicker) {
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+            transitions {
+                if (tapl.isTablet) {
+                    SplitScreenUtils.dragDividerToDismissSplit(
+                        device,
+                        wmHelper,
+                        dragToRight = false,
+                        dragToBottom = true
+                    )
+                } else {
+                    SplitScreenUtils.dragDividerToDismissSplit(
+                        device,
+                        wmHelper,
+                        dragToRight = true,
+                        dragToBottom = true
+                    )
+                }
+                wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify()
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
new file mode 100644
index 0000000..c4cfd1a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenDismissed
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class DismissSplitScreenByGoHomeBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+            transitions {
+                tapl.goHome()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
new file mode 100644
index 0000000..146287c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class DragDividerToResizeBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+            transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @Before
+    fun before() {
+        Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart)
+    }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    open fun cujCompleted() {
+        // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
+        // robust enough to get the correct end state.
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
new file mode 100644
index 0000000..cc71502
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                tapl.goHome()
+                primaryApp.launchViaIntent(wmHelper)
+            }
+            transitions {
+                tapl.launchedAppState.taskbar
+                    .openAllApps()
+                    .getAppIcon(secondaryApp.appName)
+                    .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @Before
+    fun before() {
+        Assume.assumeTrue(tapl.isTablet)
+    }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() =
+        flicker.splitScreenEntered(
+            primaryApp,
+            secondaryApp,
+            fromOtherApp = false,
+            appExistAtStart = false
+        )
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
new file mode 100644
index 0000000..de78f09
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterSplitScreenByDragFromNotificationBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                // Send a notification
+                sendNotificationApp.launchViaIntent(wmHelper)
+                sendNotificationApp.postNotification(wmHelper)
+                tapl.goHome()
+                primaryApp.launchViaIntent(wmHelper)
+            }
+            transitions {
+                SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
+            }
+            teardown { sendNotificationApp.exit(wmHelper) }
+        }
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() =
+        flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false)
+
+    @Before
+    fun before() {
+        Assume.assumeTrue(tapl.isTablet)
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
new file mode 100644
index 0000000..a29eb40
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterSplitScreenByDragFromShortcutBenchmark(flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    @Before
+    fun before() {
+        Assume.assumeTrue(tapl.isTablet)
+    }
+
+    protected val thisTransition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.goHome()
+            SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+            primaryApp.launchViaIntent(wmHelper)
+        }
+        transitions {
+            tapl.launchedAppState.taskbar
+                .getAppIcon(secondaryApp.appName)
+                .openDeepShortcutMenu()
+                .getMenuItem("Split Screen Secondary Activity")
+                .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+            SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+        }
+    }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() =
+        flicker.splitScreenEntered(
+            primaryApp,
+            secondaryApp,
+            fromOtherApp = false,
+            appExistAtStart = false
+        )
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
new file mode 100644
index 0000000..b2395ca
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                tapl.goHome()
+                SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+                primaryApp.launchViaIntent(wmHelper)
+            }
+            transitions {
+                tapl.launchedAppState.taskbar
+                    .getAppIcon(secondaryApp.appName)
+                    .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() =
+        flicker.splitScreenEntered(
+            primaryApp,
+            secondaryApp,
+            fromOtherApp = false,
+            appExistAtStart = false
+        )
+
+    @Before
+    fun before() {
+        Assume.assumeTrue(tapl.isTablet)
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
new file mode 100644
index 0000000..e1d85d0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterSplitScreenFromOverviewBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                primaryApp.launchViaIntent(wmHelper)
+                secondaryApp.launchViaIntent(wmHelper)
+                tapl.goHome()
+                wmHelper
+                    .StateSyncBuilder()
+                    .withAppTransitionIdle()
+                    .withHomeActivityVisible()
+                    .waitForAndVerify()
+            }
+            transitions {
+                SplitScreenUtils.splitFromOverview(tapl, device)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
new file mode 100644
index 0000000..ba8c460
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.helpers.WindowUtils
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+            transitions {
+                SplitScreenUtils.doubleTapDividerToSwitch(device)
+                wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+                waitForLayersToSwitch(wmHelper)
+                waitForWindowsToSwitch(wmHelper)
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
+        wmHelper
+            .StateSyncBuilder()
+            .add("appWindowsSwitched") {
+                val primaryAppWindow =
+                    it.wmState.visibleWindows.firstOrNull { window ->
+                        primaryApp.windowMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+                val secondaryAppWindow =
+                    it.wmState.visibleWindows.firstOrNull { window ->
+                        secondaryApp.windowMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+
+                if (isLandscape(flicker.scenario.endRotation)) {
+                    return@add if (flicker.scenario.isTablet) {
+                        secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
+                    } else {
+                        primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
+                    }
+                } else {
+                    return@add if (flicker.scenario.isTablet) {
+                        primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+                    } else {
+                        primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+                    }
+                }
+            }
+            .waitForAndVerify()
+    }
+
+    private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) {
+        wmHelper
+            .StateSyncBuilder()
+            .add("appLayersSwitched") {
+                val primaryAppLayer =
+                    it.layerState.visibleLayers.firstOrNull { window ->
+                        primaryApp.layerMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+                val secondaryAppLayer =
+                    it.layerState.visibleLayers.firstOrNull { window ->
+                        secondaryApp.layerMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+
+                val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false
+                val secondaryVisibleRegion =
+                    secondaryAppLayer.visibleRegion?.bounds ?: return@add false
+
+                if (isLandscape(flicker.scenario.endRotation)) {
+                    return@add if (flicker.scenario.isTablet) {
+                        secondaryVisibleRegion.right <= primaryVisibleRegion.left
+                    } else {
+                        primaryVisibleRegion.right <= secondaryVisibleRegion.left
+                    }
+                } else {
+                    return@add if (flicker.scenario.isTablet) {
+                        primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+                    } else {
+                        primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+                    }
+                }
+            }
+            .waitForAndVerify()
+    }
+
+    private fun isLandscape(rotation: Rotation): Boolean {
+        val displayBounds = WindowUtils.getDisplayBounds(rotation)
+        return displayBounds.width > displayBounds.height
+    }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    open fun cujCompleted() {
+        // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
+        // robust enough to get the correct end state.
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
new file mode 100644
index 0000000..bbb2edc
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
+
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+                thirdApp.launchViaIntent(wmHelper)
+                wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
+            }
+            transitions {
+                tapl.launchedAppState.quickSwitchToPreviousApp()
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
new file mode 100644
index 0000000..fa38293
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class SwitchBackToSplitFromHomeBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+                tapl.goHome()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+            }
+            transitions {
+                tapl.workspace.quickSwitchToPreviousApp()
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
new file mode 100644
index 0000000..1064bd9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class SwitchBackToSplitFromRecentBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+                tapl.goHome()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+            }
+            transitions {
+                tapl.workspace.switchToOverview().currentTask.open()
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            withoutTracing(this)
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
new file mode 100644
index 0000000..8f4393f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class SwitchBetweenSplitPairsBenchmark(override val flicker: FlickerTest) :
+    SplitScreenBase(flicker) {
+    protected val thirdApp = SplitScreenUtils.getIme(instrumentation)
+    protected val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
+
+    protected val thisTransition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
+            }
+            transitions {
+                tapl.launchedAppState.quickSwitchToPreviousApp()
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+            teardown {
+                thirdApp.exit(wmHelper)
+                fourthApp.exit(wmHelper)
+            }
+        }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            defaultSetup(this)
+            defaultTeardown(this)
+            thisTransition(this)
+        }
+
+    @IwTest(focusArea = "sysui") @Presubmit @Test open fun cujCompleted() {}
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 57a6981..ad4d97f 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -47,7 +47,7 @@
         "truth-prebuilt",
         "testables",
         "platform-test-annotations",
-        "frameworks-base-testutils",
+        "servicestests-utils",
     ],
 
     libs: [
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
index de967bf..afec1ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
@@ -33,6 +33,7 @@
 import android.content.res.Resources;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -42,6 +43,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.bubbles.BubbleInfo;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -77,7 +79,7 @@
         Intent target = new Intent(mContext, BubblesTestActivity.class);
         Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
                 PendingIntent.getActivity(mContext, 0, target, PendingIntent.FLAG_MUTABLE),
-                        Icon.createWithResource(mContext, R.drawable.bubble_ic_create_bubble))
+                Icon.createWithResource(mContext, R.drawable.bubble_ic_create_bubble))
                 .build();
         when(mSbn.getNotification()).thenReturn(mNotif);
         when(mNotif.getBubbleMetadata()).thenReturn(metadata);
@@ -179,6 +181,34 @@
         assertThat(bubble.isConversation()).isFalse();
     }
 
+    @Test
+    public void testBubbleAsBubbleBarBubble_withShortcut() {
+        Bubble bubble = createBubbleWithShortcut();
+        BubbleInfo bubbleInfo = bubble.asBubbleBarBubble();
+
+        assertThat(bubble.getShortcutInfo()).isNotNull();
+        assertThat(bubbleInfo.getShortcutId()).isNotNull();
+        assertThat(bubbleInfo.getShortcutId()).isEqualTo(bubble.getShortcutId());
+        assertThat(bubbleInfo.getKey()).isEqualTo(bubble.getKey());
+        assertThat(bubbleInfo.getUserId()).isEqualTo(bubble.getUser().getIdentifier());
+        assertThat(bubbleInfo.getPackageName()).isEqualTo(bubble.getPackageName());
+    }
+
+    @Test
+    public void testBubbleAsBubbleBarBubble_withoutShortcut() {
+        Intent intent = new Intent(mContext, BubblesTestActivity.class);
+        intent.setPackage(mContext.getPackageName());
+        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1 /* userId */),
+                null /* icon */, mMainExecutor);
+        BubbleInfo bubbleInfo = bubble.asBubbleBarBubble();
+
+        assertThat(bubble.getShortcutInfo()).isNull();
+        assertThat(bubbleInfo.getShortcutId()).isNull();
+        assertThat(bubbleInfo.getKey()).isEqualTo(bubble.getKey());
+        assertThat(bubbleInfo.getUserId()).isEqualTo(bubble.getUser().getIdentifier());
+        assertThat(bubbleInfo.getPackageName()).isEqualTo(bubble.getPackageName());
+    }
+
     private Bubble createBubbleWithShortcut() {
         ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext)
                 .setId("mockShortcutId")
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
index 1347e06..60ee918 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
@@ -112,9 +112,9 @@
     @Test
     public void testOnTaskProfileLocked() {
         ActivityManager.RunningTaskInfo info = mock(ActivityManager.RunningTaskInfo.class);
-        mImpl.onTaskProfileLocked(info);
-        verify(mCallback).onTaskProfileLocked(eq(info));
-        verify(mOtherCallback).onTaskProfileLocked(eq(info));
+        mImpl.onTaskProfileLocked(info, 0);
+        verify(mCallback).onTaskProfileLocked(eq(info), eq(0));
+        verify(mOtherCallback).onTaskProfileLocked(eq(info), eq(0));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 63de74f..d6387ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_NONE;
@@ -82,6 +83,8 @@
 @RunWith(AndroidTestingRunner.class)
 public class DesktopModeControllerTest extends ShellTestCase {
 
+    private static final int SECOND_DISPLAY = 2;
+
     @Mock
     private ShellController mShellController;
     @Mock
@@ -248,22 +251,22 @@
     public void testShowDesktopApps_allAppsInvisible_bringsToFront() {
         // Set up two active tasks on desktop, task2 is on top of task1.
         RunningTaskInfo freeformTask1 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(freeformTask1.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask1.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask1.taskId);
         mDesktopModeTaskRepository.updateVisibleFreeformTasks(
-                freeformTask1.taskId, false /* visible */);
+                DEFAULT_DISPLAY, freeformTask1.taskId, false /* visible */);
         RunningTaskInfo freeformTask2 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(freeformTask2.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask2.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask2.taskId);
         mDesktopModeTaskRepository.updateVisibleFreeformTasks(
-                freeformTask2.taskId, false /* visible */);
+                DEFAULT_DISPLAY, freeformTask2.taskId, false /* visible */);
         when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn(
                 freeformTask1);
         when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn(
                 freeformTask2);
 
         // Run show desktop apps logic
-        mController.showDesktopApps();
+        mController.showDesktopApps(DEFAULT_DISPLAY);
 
         final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
         // Check wct has reorder calls
@@ -283,17 +286,19 @@
     @Test
     public void testShowDesktopApps_appsAlreadyVisible_bringsToFront() {
         final RunningTaskInfo task1 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
+                true /* visible */);
         when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
         final RunningTaskInfo task2 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
+                true /* visible */);
         when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
 
-        mController.showDesktopApps();
+        mController.showDesktopApps(DEFAULT_DISPLAY);
 
         final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
         // Check wct has reorder calls
@@ -312,17 +317,19 @@
     @Test
     public void testShowDesktopApps_someAppsInvisible_reordersAll() {
         final RunningTaskInfo task1 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, false /* visible */);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
+                false /* visible */);
         when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
         final RunningTaskInfo task2 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
+                true /* visible */);
         when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
 
-        mController.showDesktopApps();
+        mController.showDesktopApps(DEFAULT_DISPLAY);
 
         final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
         // Both tasks should be reordered to top, even if one was already visible.
@@ -336,38 +343,87 @@
     }
 
     @Test
+    public void testShowDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay() {
+        RunningTaskInfo taskDefaultDisplay = createFreeformTask(DEFAULT_DISPLAY);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+                DEFAULT_DISPLAY, taskDefaultDisplay.taskId, false /* visible */);
+        when(mShellTaskOrganizer.getRunningTaskInfo(taskDefaultDisplay.taskId)).thenReturn(
+                taskDefaultDisplay);
+
+        RunningTaskInfo taskSecondDisplay = createFreeformTask(SECOND_DISPLAY);
+        mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+                SECOND_DISPLAY, taskSecondDisplay.taskId, false /* visible */);
+        when(mShellTaskOrganizer.getRunningTaskInfo(taskSecondDisplay.taskId)).thenReturn(
+                taskSecondDisplay);
+
+        mController.showDesktopApps(DEFAULT_DISPLAY);
+
+        WindowContainerTransaction wct = getBringAppsToFrontTransaction();
+        assertThat(wct.getHierarchyOps()).hasSize(1);
+        HierarchyOp op = wct.getHierarchyOps().get(0);
+        assertThat(op.getContainer()).isEqualTo(taskDefaultDisplay.token.asBinder());
+    }
+
+    @Test
     public void testGetVisibleTaskCount_noTasks_returnsZero() {
-        assertThat(mController.getVisibleTaskCount()).isEqualTo(0);
+        assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0);
     }
 
     @Test
     public void testGetVisibleTaskCount_twoTasks_bothVisible_returnsTwo() {
         RunningTaskInfo task1 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
+                true /* visible */);
 
         RunningTaskInfo task2 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
+                true /* visible */);
 
-        assertThat(mController.getVisibleTaskCount()).isEqualTo(2);
+        assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2);
     }
 
     @Test
     public void testGetVisibleTaskCount_twoTasks_oneVisible_returnsOne() {
         RunningTaskInfo task1 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
+                true /* visible */);
 
         RunningTaskInfo task2 = createFreeformTask();
-        mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
         mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
-        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, false /* visible */);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
+                false /* visible */);
 
-        assertThat(mController.getVisibleTaskCount()).isEqualTo(1);
+        assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1);
+    }
+
+    @Test
+    public void testGetVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
+        RunningTaskInfo taskDefaultDisplay = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY,
+                taskDefaultDisplay.taskId,
+                true /* visible */);
+
+        RunningTaskInfo taskSecondDisplay = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(SECOND_DISPLAY,
+                taskSecondDisplay.taskId,
+                true /* visible */);
+
+        assertThat(mController.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 45cb3a0..3bc2f0e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -17,10 +17,12 @@
 package com.android.wm.shell.desktopmode
 
 import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestShellExecutor
 import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.fail
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -41,8 +43,8 @@
         val listener = TestListener()
         repo.addActiveTaskListener(listener)
 
-        repo.addActiveTask(1)
-        assertThat(listener.activeTaskChangedCalls).isEqualTo(1)
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+        assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(1)
         assertThat(repo.isActiveTask(1)).isTrue()
     }
 
@@ -51,9 +53,9 @@
         val listener = TestListener()
         repo.addActiveTaskListener(listener)
 
-        repo.addActiveTask(1)
-        repo.addActiveTask(1)
-        assertThat(listener.activeTaskChangedCalls).isEqualTo(1)
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+        assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(1)
     }
 
     @Test
@@ -61,9 +63,22 @@
         val listener = TestListener()
         repo.addActiveTaskListener(listener)
 
-        repo.addActiveTask(1)
-        repo.addActiveTask(2)
-        assertThat(listener.activeTaskChangedCalls).isEqualTo(2)
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 2)
+        assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2)
+    }
+
+    @Test
+    fun addActiveTask_multipleDisplays_notifiesCorrectListener() {
+        val listener = TestListener()
+        repo.addActiveTaskListener(listener)
+
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 2)
+        repo.addActiveTask(SECOND_DISPLAY, taskId = 3)
+
+        assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2)
+        assertThat(listener.activeChangesOnSecondaryDisplay).isEqualTo(1)
     }
 
     @Test
@@ -71,10 +86,10 @@
         val listener = TestListener()
         repo.addActiveTaskListener(listener)
 
-        repo.addActiveTask(1)
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
         repo.removeActiveTask(1)
         // Notify once for add and once for remove
-        assertThat(listener.activeTaskChangedCalls).isEqualTo(2)
+        assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2)
         assertThat(repo.isActiveTask(1)).isFalse()
     }
 
@@ -83,7 +98,17 @@
         val listener = TestListener()
         repo.addActiveTaskListener(listener)
         repo.removeActiveTask(99)
-        assertThat(listener.activeTaskChangedCalls).isEqualTo(0)
+        assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(0)
+    }
+
+    @Test
+    fun remoteActiveTask_listenerForOtherDisplayNotNotified() {
+        val listener = TestListener()
+        repo.addActiveTaskListener(listener)
+        repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+        repo.removeActiveTask(1)
+        assertThat(listener.activeChangesOnSecondaryDisplay).isEqualTo(0)
+        assertThat(repo.isActiveTask(1)).isFalse()
     }
 
     @Test
@@ -93,14 +118,27 @@
 
     @Test
     fun addListener_notifiesVisibleFreeformTask() {
-        repo.updateVisibleFreeformTasks(1, true)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
         val listener = TestVisibilityListener()
         val executor = TestShellExecutor()
         repo.addVisibleTasksListener(listener, executor)
         executor.flushAll()
 
-        assertThat(listener.hasVisibleFreeformTasks).isTrue()
-        assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(1)
+        assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+        assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+    }
+
+    @Test
+    fun addListener_tasksOnDifferentDisplay_doesNotNotify() {
+        repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true)
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+        executor.flushAll()
+
+        assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
+        // One call as adding listener notifies it
+        assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(0)
     }
 
     @Test
@@ -108,13 +146,61 @@
         val listener = TestVisibilityListener()
         val executor = TestShellExecutor()
         repo.addVisibleTasksListener(listener, executor)
-        repo.updateVisibleFreeformTasks(1, true)
-        repo.updateVisibleFreeformTasks(2, true)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
         executor.flushAll()
 
-        assertThat(listener.hasVisibleFreeformTasks).isTrue()
-        // Equal to 2 because adding the listener notifies the current state
-        assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(2)
+        assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+        assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+    }
+
+    @Test
+    fun updateVisibleFreeformTasks_addVisibleTaskNotifiesListenerForThatDisplay() {
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+        executor.flushAll()
+
+        assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+        assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+        assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isFalse()
+        assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(0)
+
+        repo.updateVisibleFreeformTasks(displayId = 1, taskId = 2, visible = true)
+        executor.flushAll()
+
+        // Listener for secondary display is notified
+        assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isTrue()
+        assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
+        // No changes to listener for default display
+        assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+    }
+
+    @Test
+    fun updateVisibleFreeformTasks_taskOnDefaultBecomesVisibleOnSecondDisplay_listenersNotified() {
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+        executor.flushAll()
+        assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+
+        // Mark task 1 visible on secondary display
+        repo.updateVisibleFreeformTasks(displayId = 1, taskId = 1, visible = true)
+        executor.flushAll()
+
+        // Default display should have 2 calls
+        // 1 - visible task added
+        // 2 - visible task removed
+        assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
+        assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
+
+        // Secondary display should have 1 call for visible task added
+        assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
+        assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isTrue()
     }
 
     @Test
@@ -122,52 +208,83 @@
         val listener = TestVisibilityListener()
         val executor = TestShellExecutor()
         repo.addVisibleTasksListener(listener, executor)
-        repo.updateVisibleFreeformTasks(1, true)
-        repo.updateVisibleFreeformTasks(2, true)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
         executor.flushAll()
 
-        assertThat(listener.hasVisibleFreeformTasks).isTrue()
-        repo.updateVisibleFreeformTasks(1, false)
+        assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue()
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
         executor.flushAll()
 
-        // Equal to 2 because adding the listener notifies the current state
-        assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(2)
+        assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
 
-        repo.updateVisibleFreeformTasks(2, false)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false)
         executor.flushAll()
 
-        assertThat(listener.hasVisibleFreeformTasks).isFalse()
-        assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(3)
+        assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse()
+        assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
     }
 
     @Test
     fun getVisibleTaskCount() {
         // No tasks, count is 0
-        assertThat(repo.getVisibleTaskCount()).isEqualTo(0)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
 
         // New task increments count to 1
-        repo.updateVisibleFreeformTasks(taskId = 1, visible = true)
-        assertThat(repo.getVisibleTaskCount()).isEqualTo(1)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
 
         // Visibility update to same task does not increase count
-        repo.updateVisibleFreeformTasks(taskId = 1, visible = true)
-        assertThat(repo.getVisibleTaskCount()).isEqualTo(1)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
 
         // Second task visible increments count
-        repo.updateVisibleFreeformTasks(taskId = 2, visible = true)
-        assertThat(repo.getVisibleTaskCount()).isEqualTo(2)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
 
         // Hiding a task decrements count
-        repo.updateVisibleFreeformTasks(taskId = 1, visible = false)
-        assertThat(repo.getVisibleTaskCount()).isEqualTo(1)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
 
         // Hiding all tasks leaves count at 0
-        repo.updateVisibleFreeformTasks(taskId = 2, visible = false)
-        assertThat(repo.getVisibleTaskCount()).isEqualTo(0)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false)
+        assertThat(repo.getVisibleTaskCount(displayId = 9)).isEqualTo(0)
 
         // Hiding a not existing task, count remains at 0
-        repo.updateVisibleFreeformTasks(taskId = 999, visible = false)
-        assertThat(repo.getVisibleTaskCount()).isEqualTo(0)
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 999, visible = false)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+    }
+
+    @Test
+    fun getVisibleTaskCount_multipleDisplays() {
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
+
+        // New task on default display increments count for that display only
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
+
+        // New task on secondary display, increments count for that display only
+        repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 2, visible = true)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+
+        // Marking task visible on another display, updates counts for both displays
+        repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
+
+        // Marking task that is on secondary display, hidden on default display, does not affect
+        // secondary display
+        repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
+
+        // Hiding a task on that display, decrements count
+        repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = false)
+        assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+        assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
     }
 
     @Test
@@ -197,19 +314,40 @@
     }
 
     class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
-        var activeTaskChangedCalls = 0
-        override fun onActiveTasksChanged() {
-            activeTaskChangedCalls++
+        var activeChangesOnDefaultDisplay = 0
+        var activeChangesOnSecondaryDisplay = 0
+        override fun onActiveTasksChanged(displayId: Int) {
+            when (displayId) {
+                DEFAULT_DISPLAY -> activeChangesOnDefaultDisplay++
+                SECOND_DISPLAY -> activeChangesOnSecondaryDisplay++
+                else -> fail("Active task listener received unexpected display id: $displayId")
+            }
         }
     }
 
     class TestVisibilityListener : DesktopModeTaskRepository.VisibleTasksListener {
-        var hasVisibleFreeformTasks = false
-        var visibleFreeformTaskChangedCalls = 0
+        var hasVisibleTasksOnDefaultDisplay = false
+        var hasVisibleTasksOnSecondaryDisplay = false
 
-        override fun onVisibilityChanged(hasVisibleTasks: Boolean) {
-            hasVisibleFreeformTasks = hasVisibleTasks
-            visibleFreeformTaskChangedCalls++
+        var visibleChangesOnDefaultDisplay = 0
+        var visibleChangesOnSecondaryDisplay = 0
+
+        override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
+            when (displayId) {
+                DEFAULT_DISPLAY -> {
+                    hasVisibleTasksOnDefaultDisplay = hasVisibleFreeformTasks
+                    visibleChangesOnDefaultDisplay++
+                }
+                SECOND_DISPLAY -> {
+                    hasVisibleTasksOnSecondaryDisplay = hasVisibleFreeformTasks
+                    visibleChangesOnSecondaryDisplay++
+                }
+                else -> fail("Visible task listener received unexpected display id: $displayId")
+            }
         }
     }
+
+    companion object {
+        const val SECOND_DISPLAY = 1
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index c9bd695..1335ebf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -25,11 +25,13 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
 import android.os.Binder
 import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
 import android.view.WindowManager
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.view.WindowManager.TRANSIT_NONE
 import android.view.WindowManager.TRANSIT_OPEN
 import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.window.DisplayAreaInfo
 import android.window.TransitionRequestInfo
 import android.window.WindowContainerTransaction
 import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
@@ -84,10 +86,10 @@
     @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
     @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
 
-    lateinit var mockitoSession: StaticMockitoSession
-    lateinit var controller: DesktopTasksController
-    lateinit var shellInit: ShellInit
-    lateinit var desktopModeTaskRepository: DesktopModeTaskRepository
+    private lateinit var mockitoSession: StaticMockitoSession
+    private lateinit var controller: DesktopTasksController
+    private lateinit var shellInit: ShellInit
+    private lateinit var desktopModeTaskRepository: DesktopModeTaskRepository
 
     // Mock running tasks are registered here so we can get the list from mock shell task organizer
     private val runningTasks = mutableListOf<RunningTaskInfo>()
@@ -155,7 +157,7 @@
         markTaskHidden(task1)
         markTaskHidden(task2)
 
-        controller.showDesktopApps()
+        controller.showDesktopApps(DEFAULT_DISPLAY)
 
         val wct = getLatestWct(expectTransition = TRANSIT_NONE)
         assertThat(wct.hierarchyOps).hasSize(3)
@@ -173,7 +175,7 @@
         markTaskVisible(task1)
         markTaskVisible(task2)
 
-        controller.showDesktopApps()
+        controller.showDesktopApps(DEFAULT_DISPLAY)
 
         val wct = getLatestWct(expectTransition = TRANSIT_NONE)
         assertThat(wct.hierarchyOps).hasSize(3)
@@ -191,7 +193,7 @@
         markTaskHidden(task1)
         markTaskVisible(task2)
 
-        controller.showDesktopApps()
+        controller.showDesktopApps(DEFAULT_DISPLAY)
 
         val wct = getLatestWct(expectTransition = TRANSIT_NONE)
         assertThat(wct.hierarchyOps).hasSize(3)
@@ -205,7 +207,7 @@
     fun showDesktopApps_noActiveTasks_reorderHomeToTop() {
         val homeTask = setUpHomeTask()
 
-        controller.showDesktopApps()
+        controller.showDesktopApps(DEFAULT_DISPLAY)
 
         val wct = getLatestWct(expectTransition = TRANSIT_NONE)
         assertThat(wct.hierarchyOps).hasSize(1)
@@ -213,8 +215,26 @@
     }
 
     @Test
+    fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay() {
+        val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
+        val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
+        setUpHomeTask(SECOND_DISPLAY)
+        val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY)
+        markTaskHidden(taskDefaultDisplay)
+        markTaskHidden(taskSecondDisplay)
+
+        controller.showDesktopApps(DEFAULT_DISPLAY)
+
+        val wct = getLatestWct(expectTransition = TRANSIT_NONE)
+        assertThat(wct.hierarchyOps).hasSize(2)
+        // Expect order to be from bottom: home, task
+        wct.assertReorderAt(index = 0, homeTaskDefaultDisplay)
+        wct.assertReorderAt(index = 1, taskDefaultDisplay)
+    }
+
+    @Test
     fun getVisibleTaskCount_noTasks_returnsZero() {
-        assertThat(controller.getVisibleTaskCount()).isEqualTo(0)
+        assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
     }
 
     @Test
@@ -222,7 +242,7 @@
         setUpHomeTask()
         setUpFreeformTask().also(::markTaskVisible)
         setUpFreeformTask().also(::markTaskVisible)
-        assertThat(controller.getVisibleTaskCount()).isEqualTo(2)
+        assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
     }
 
     @Test
@@ -230,7 +250,15 @@
         setUpHomeTask()
         setUpFreeformTask().also(::markTaskVisible)
         setUpFreeformTask().also(::markTaskHidden)
-        assertThat(controller.getVisibleTaskCount()).isEqualTo(1)
+        assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+    }
+
+    @Test
+    fun getVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
+        setUpHomeTask()
+        setUpFreeformTask(DEFAULT_DISPLAY).also(::markTaskVisible)
+        setUpFreeformTask(SECOND_DISPLAY).also(::markTaskVisible)
+        assertThat(controller.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
     }
 
     @Test
@@ -258,6 +286,7 @@
         controller.moveToDesktop(fullscreenTask)
 
         with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+            // Operations should include home task, freeform task
             assertThat(hierarchyOps).hasSize(3)
             assertReorderSequence(homeTask, freeformTask, fullscreenTask)
             assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode)
@@ -266,6 +295,28 @@
     }
 
     @Test
+    fun moveToDesktop_onlyFreeformTasksFromCurrentDisplayBroughtToFront() {
+        setUpHomeTask(displayId = DEFAULT_DISPLAY)
+        val freeformTaskDefault = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+        val fullscreenTaskDefault = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+        markTaskHidden(freeformTaskDefault)
+
+        val homeTaskSecond = setUpHomeTask(displayId = SECOND_DISPLAY)
+        val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
+        markTaskHidden(freeformTaskSecond)
+
+        controller.moveToDesktop(fullscreenTaskDefault)
+
+        with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+            // Check that hierarchy operations do not include tasks from second display
+            assertThat(hierarchyOps.map { it.container })
+                .doesNotContain(homeTaskSecond.token.asBinder())
+            assertThat(hierarchyOps.map { it.container })
+                .doesNotContain(freeformTaskSecond.token.asBinder())
+        }
+    }
+
+    @Test
     fun moveToFullscreen() {
         val task = setUpFreeformTask()
         controller.moveToFullscreen(task)
@@ -281,6 +332,70 @@
     }
 
     @Test
+    fun moveToFullscreen_secondDisplayTaskHasFreeform_secondDisplayNotAffected() {
+        val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+        val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY)
+
+        controller.moveToFullscreen(taskDefaultDisplay)
+
+        with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+            assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder())
+            assertThat(changes.keys).doesNotContain(taskSecondDisplay.token.asBinder())
+        }
+    }
+
+    @Test
+    fun moveToNextDisplay_noOtherDisplays() {
+        whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY))
+        val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+        controller.moveToNextDisplay(task.taskId)
+        verifyWCTNotExecuted()
+    }
+
+    @Test
+    fun moveToNextDisplay_moveFromFirstToSecondDisplay() {
+        // Set up two display ids
+        whenever(rootTaskDisplayAreaOrganizer.displayIds)
+                .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+        // Create a mock for the target display area: second display
+        val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
+        whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+                .thenReturn(secondDisplayArea)
+
+        val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+        controller.moveToNextDisplay(task.taskId)
+        with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+            assertThat(hierarchyOps).hasSize(1)
+            assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
+            assertThat(hierarchyOps[0].isReparent).isTrue()
+            assertThat(hierarchyOps[0].newParent).isEqualTo(secondDisplayArea.token.asBinder())
+            assertThat(hierarchyOps[0].toTop).isTrue()
+        }
+    }
+
+    @Test
+    fun moveToNextDisplay_moveFromSecondToFirstDisplay() {
+        // Set up two display ids
+        whenever(rootTaskDisplayAreaOrganizer.displayIds)
+            .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+        // Create a mock for the target display area: default display
+        val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+        whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+                .thenReturn(defaultDisplayArea)
+
+        val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
+        controller.moveToNextDisplay(task.taskId)
+
+        with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+            assertThat(hierarchyOps).hasSize(1)
+            assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
+            assertThat(hierarchyOps[0].isReparent).isTrue()
+            assertThat(hierarchyOps[0].newParent).isEqualTo(defaultDisplayArea.token.asBinder())
+            assertThat(hierarchyOps[0].toTop).isTrue()
+        }
+    }
+
+    @Test
     fun getTaskWindowingMode() {
         val fullscreenTask = setUpFullscreenTask()
         val freeformTask = setUpFreeformTask()
@@ -324,6 +439,18 @@
     }
 
     @Test
+    fun handleRequest_fullscreenTask_freeformTaskOnOtherDisplay_returnNull() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+        val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY)
+        createFreeformTask(displayId = SECOND_DISPLAY)
+
+        val result =
+            controller.handleRequest(Binder(), createTransition(fullscreenTaskDefaultDisplay))
+        assertThat(result).isNull()
+    }
+
+    @Test
     fun handleRequest_freeformTask_freeformVisible_returnNull() {
         assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
@@ -362,6 +489,18 @@
     }
 
     @Test
+    fun handleRequest_freeformTask_freeformOnOtherDisplayOnly_returnSwitchToFullscreenWCT() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+        val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
+        createFreeformTask(displayId = SECOND_DISPLAY)
+
+        val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
+        assertThat(result?.changes?.get(taskDefaultDisplay.token.asBinder())?.windowingMode)
+            .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+    }
+
+    @Test
     fun handleRequest_notOpenOrToFrontTransition_returnNull() {
         assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
@@ -400,35 +539,43 @@
         assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
     }
 
-    private fun setUpFreeformTask(): RunningTaskInfo {
-        val task = createFreeformTask()
+    private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+        val task = createFreeformTask(displayId)
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
-        desktopModeTaskRepository.addActiveTask(task.taskId)
+        desktopModeTaskRepository.addActiveTask(displayId, task.taskId)
         desktopModeTaskRepository.addOrMoveFreeformTaskToTop(task.taskId)
         runningTasks.add(task)
         return task
     }
 
-    private fun setUpHomeTask(): RunningTaskInfo {
-        val task = createHomeTask()
+    private fun setUpHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+        val task = createHomeTask(displayId)
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
         runningTasks.add(task)
         return task
     }
 
-    private fun setUpFullscreenTask(): RunningTaskInfo {
-        val task = createFullscreenTask()
+    private fun setUpFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+        val task = createFullscreenTask(displayId)
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
         runningTasks.add(task)
         return task
     }
 
     private fun markTaskVisible(task: RunningTaskInfo) {
-        desktopModeTaskRepository.updateVisibleFreeformTasks(task.taskId, visible = true)
+        desktopModeTaskRepository.updateVisibleFreeformTasks(
+            task.displayId,
+            task.taskId,
+            visible = true
+        )
     }
 
     private fun markTaskHidden(task: RunningTaskInfo) {
-        desktopModeTaskRepository.updateVisibleFreeformTasks(task.taskId, visible = false)
+        desktopModeTaskRepository.updateVisibleFreeformTasks(
+            task.displayId,
+            task.taskId,
+            visible = false
+        )
     }
 
     private fun getLatestWct(
@@ -457,6 +604,10 @@
     ): TransitionRequestInfo {
         return TransitionRequestInfo(type, task, null /* remoteTransition */)
     }
+
+    companion object {
+        const val SECOND_DISPLAY = 2
+    }
 }
 
 private fun WindowContainerTransaction.assertReorderAt(index: Int, task: RunningTaskInfo) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index dc91d75..cf1ff32 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -21,14 +21,17 @@
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.view.Display.DEFAULT_DISPLAY
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 
 class DesktopTestHelpers {
     companion object {
         /** Create a task that has windowing mode set to [WINDOWING_MODE_FREEFORM] */
         @JvmStatic
-        fun createFreeformTask(): RunningTaskInfo {
+        @JvmOverloads
+        fun createFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
             return TestRunningTaskInfoBuilder()
+                    .setDisplayId(displayId)
                     .setToken(MockToken().token())
                     .setActivityType(ACTIVITY_TYPE_STANDARD)
                     .setWindowingMode(WINDOWING_MODE_FREEFORM)
@@ -38,8 +41,10 @@
 
         /** Create a task that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */
         @JvmStatic
-        fun createFullscreenTask(): RunningTaskInfo {
+        @JvmOverloads
+        fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
             return TestRunningTaskInfoBuilder()
+                    .setDisplayId(displayId)
                     .setToken(MockToken().token())
                     .setActivityType(ACTIVITY_TYPE_STANDARD)
                     .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
@@ -49,8 +54,10 @@
 
         /** Create a new home task */
         @JvmStatic
-        fun createHomeTask(): RunningTaskInfo {
+        @JvmOverloads
+        fun createHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
             return TestRunningTaskInfoBuilder()
+                    .setDisplayId(displayId)
                     .setToken(MockToken().token())
                     .setActivityType(ACTIVITY_TYPE_HOME)
                     .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
index 6199e0b..8592dea 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
@@ -94,7 +94,7 @@
         WindowContainerTransaction wct = new WindowContainerTransaction();
         doReturn(mToken).when(mTransitions)
                 .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
-        mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct);
+        mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
 
         TransitionInfo.Change change =
                 createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
@@ -115,7 +115,7 @@
         WindowContainerTransaction wct = new WindowContainerTransaction();
         doReturn(mToken).when(mTransitions)
                 .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
-        mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct);
+        mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
 
         TransitionInfo.Change change =
                 createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index 4fad054..265b10d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -100,7 +100,7 @@
         doReturn(mToken).when(mTransitions)
                 .startTransition(transitionType, wct, mExitDesktopTaskTransitionHandler);
 
-        mExitDesktopTaskTransitionHandler.startTransition(transitionType, wct);
+        mExitDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
 
         TransitionInfo.Change change =
                 createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FULLSCREEN);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index 523cb66..54f36f6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -48,6 +48,7 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
@@ -71,6 +72,8 @@
     @Mock
     private ShellController mShellController;
     @Mock
+    private ShellCommandHandler mShellCommandHandler;
+    @Mock
     private DisplayController mDisplayController;
     @Mock
     private UiEventLogger mUiEventLogger;
@@ -89,7 +92,8 @@
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
         mController = new DragAndDropController(mContext, mShellInit, mShellController,
-                mDisplayController, mUiEventLogger, mIconProvider, mMainExecutor);
+                mShellCommandHandler, mDisplayController, mUiEventLogger, mIconProvider,
+                mMainExecutor);
         mController.onInit();
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 6995d10..04f2c99 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -268,7 +268,7 @@
     }
 
     @Test
-    public void saveReentryState_userHasResized_savesSize() {
+    public void saveReentryState_nonEmptyUserResizeBounds_savesSize() {
         final Rect bounds = new Rect(0, 0, 10, 10);
         final Rect resizedBounds = new Rect(0, 0, 30, 30);
         when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f);
@@ -281,6 +281,19 @@
     }
 
     @Test
+    public void saveReentryState_emptyUserResizeBounds_savesSize() {
+        final Rect bounds = new Rect(0, 0, 10, 10);
+        final Rect resizedBounds = new Rect(0, 0, 0, 0);
+        when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f);
+        when(mMockPipTouchHandler.getUserResizeBounds()).thenReturn(resizedBounds);
+        when(mMockPipBoundsState.hasUserResizedPip()).thenReturn(true);
+
+        mPipController.saveReentryState(bounds);
+
+        verify(mMockPipBoundsState).saveReentryState(new Size(10, 10), 1.0f);
+    }
+
+    @Test
     public void onDisplayConfigurationChanged_inPip_movePip() {
         final int displayId = 1;
         final Rect bounds = new Rect(0, 0, 10, 10);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index e6219d1..d27064d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -152,10 +152,15 @@
         when(mStageCoordinator.isSplitActive()).thenReturn(true);
 
         final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        final WindowContainerTransaction wct = spy(new WindowContainerTransaction());
 
         mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
-        verify(mSideStage).addTask(eq(task), eq(wct));
+        verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
+                eq(SPLIT_POSITION_BOTTOM_OR_RIGHT));
+        verify(mMainStage).reparentTopTask(eq(wct));
+        verify(mMainStage).evictAllChildren(eq(wct));
+        verify(mSideStage).evictAllChildren(eq(wct));
+        verify(mSplitLayout).resetDividerPosition();
         assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
         assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
     }
@@ -171,14 +176,11 @@
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
         mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
-        verify(mMainStage).addTask(eq(task), eq(wct));
+        verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
+                eq(SPLIT_POSITION_BOTTOM_OR_RIGHT));
+        verify(mMainStage).evictAllChildren(eq(wct));
         assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
         assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition());
-
-        mStageCoordinator.moveToStage(task, SPLIT_POSITION_TOP_OR_LEFT, wct);
-        verify(mSideStage).addTask(eq(task), eq(wct));
-        assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition());
-        assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index bf62acf..8115a5d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -265,17 +265,17 @@
         mStartingSurfaceDrawer.mWindowRecords.addRecord(taskId,
                 new StartingSurfaceDrawer.StartingWindowRecord() {
                     @Override
-                    public void removeIfPossible(StartingWindowRemovalInfo info,
+                    public boolean removeIfPossible(StartingWindowRemovalInfo info,
                             boolean immediately) {
-
+                        return true;
                     }
                 });
         mStartingSurfaceDrawer.mWindowlessRecords.addRecord(taskId,
                 new StartingSurfaceDrawer.StartingWindowRecord() {
                     @Override
-                    public void removeIfPossible(StartingWindowRemovalInfo info,
+                    public boolean removeIfPossible(StartingWindowRemovalInfo info,
                             boolean immediately) {
-
+                        return true;
                     }
                 });
         mStartingSurfaceDrawer.clearAllWindows();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index 4559095..9d56686 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -179,4 +179,23 @@
                 mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE);
         assertThat(pendingBounds2).isNull();
     }
+
+    @Test
+    public void testSetTaskVisibility_taskRemoved_noNPE() {
+        mTaskViewTransitions.removeTaskView(mTaskViewTaskController);
+
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+        mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, false);
+    }
+
+    @Test
+    public void testSetTaskBounds_taskRemoved_noNPE() {
+        mTaskViewTransitions.removeTaskView(mTaskViewTaskController);
+
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+        mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
+                new Rect(0, 0, 100, 100));
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 8eb5c6a..963632b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.transition;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -50,6 +51,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.after;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.inOrder;
@@ -58,14 +60,19 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.ActivityManager.RunningTaskInfo;
+import android.app.IApplicationThread;
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.util.ArraySet;
 import android.util.Pair;
+import android.view.IRecentsAnimationRunner;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
@@ -86,6 +93,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.server.testutils.StubTransaction;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.TransitionInfoBuilder;
@@ -93,6 +101,7 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.sysui.ShellSharedConstants;
@@ -100,6 +109,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Answers;
 import org.mockito.InOrder;
 
 import java.util.ArrayList;
@@ -162,8 +172,8 @@
         verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
         TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
-        transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
-                mock(SurfaceControl.Transaction.class));
+        transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+                new StubTransaction());
         assertEquals(1, mDefaultHandler.activeCount());
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
@@ -212,8 +222,8 @@
         transitions.requestStartTransition(transitToken,
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
         verify(mOrganizer, times(1)).startTransition(eq(transitToken), isNull());
-        transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class),
-                mock(SurfaceControl.Transaction.class));
+        transitions.onTransitionReady(transitToken, open, new StubTransaction(),
+                new StubTransaction());
         assertEquals(1, mDefaultHandler.activeCount());
         assertEquals(0, testHandler.activeCount());
         mDefaultHandler.finishAll();
@@ -228,8 +238,8 @@
                 new TransitionRequestInfo(TRANSIT_OPEN, mwTaskInfo, null /* remote */));
         verify(mOrganizer, times(1)).startTransition(
                 eq(transitToken), eq(handlerWCT));
-        transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class),
-                mock(SurfaceControl.Transaction.class));
+        transitions.onTransitionReady(transitToken, open, new StubTransaction(),
+                new StubTransaction());
         assertEquals(1, mDefaultHandler.activeCount());
         assertEquals(0, testHandler.activeCount());
         mDefaultHandler.finishAll();
@@ -246,8 +256,8 @@
                 eq(transitToken), eq(handlerWCT));
         TransitionInfo change = new TransitionInfoBuilder(TRANSIT_CHANGE)
                 .addChange(TRANSIT_CHANGE).build();
-        transitions.onTransitionReady(transitToken, change, mock(SurfaceControl.Transaction.class),
-                mock(SurfaceControl.Transaction.class));
+        transitions.onTransitionReady(transitToken, change, new StubTransaction(),
+                new StubTransaction());
         assertEquals(0, mDefaultHandler.activeCount());
         assertEquals(1, testHandler.activeCount());
         assertEquals(0, topHandler.activeCount());
@@ -284,8 +294,8 @@
         verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
         TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
-        transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
-                mock(SurfaceControl.Transaction.class));
+        transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+                new StubTransaction());
         assertEquals(0, mDefaultHandler.activeCount());
         assertTrue(remoteCalled[0]);
         mDefaultHandler.finishAll();
@@ -434,8 +444,8 @@
         verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
         TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
-        transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
-                mock(SurfaceControl.Transaction.class));
+        transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+                new StubTransaction());
         assertEquals(0, mDefaultHandler.activeCount());
         assertTrue(remoteCalled[0]);
         mDefaultHandler.finishAll();
@@ -484,10 +494,10 @@
         oneShot.setTransition(transitToken);
         IBinder anotherToken = new Binder();
         assertFalse(oneShot.startAnimation(anotherToken, new TransitionInfo(transitType, 0),
-                mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class),
+                new StubTransaction(), new StubTransaction(),
                 testFinish));
         assertTrue(oneShot.startAnimation(transitToken, new TransitionInfo(transitType, 0),
-                mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class),
+                new StubTransaction(), new StubTransaction(),
                 testFinish));
     }
 
@@ -501,8 +511,8 @@
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
         TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
-        transitions.onTransitionReady(transitToken1, info1, mock(SurfaceControl.Transaction.class),
-                mock(SurfaceControl.Transaction.class));
+        transitions.onTransitionReady(transitToken1, info1, new StubTransaction(),
+                new StubTransaction());
         assertEquals(1, mDefaultHandler.activeCount());
 
         IBinder transitToken2 = new Binder();
@@ -510,8 +520,8 @@
                 new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
         TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
-        transitions.onTransitionReady(transitToken2, info2, mock(SurfaceControl.Transaction.class),
-                mock(SurfaceControl.Transaction.class));
+        transitions.onTransitionReady(transitToken2, info2, new StubTransaction(),
+                new StubTransaction());
         // default handler doesn't merge by default, so it shouldn't increment active count.
         assertEquals(1, mDefaultHandler.activeCount());
         assertEquals(0, mDefaultHandler.mergeCount());
@@ -542,8 +552,8 @@
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
         TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
-        transitions.onTransitionReady(transitToken1, info1, mock(SurfaceControl.Transaction.class),
-                mock(SurfaceControl.Transaction.class));
+        transitions.onTransitionReady(transitToken1, info1, new StubTransaction(),
+                new StubTransaction());
         assertEquals(1, mDefaultHandler.activeCount());
 
         IBinder transitToken2 = new Binder();
@@ -551,8 +561,8 @@
                 new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
         TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
-        transitions.onTransitionReady(transitToken2, info2, mock(SurfaceControl.Transaction.class),
-                mock(SurfaceControl.Transaction.class));
+        transitions.onTransitionReady(transitToken2, info2, new StubTransaction(),
+                new StubTransaction());
         // it should still only have 1 active, but then show 1 merged
         assertEquals(1, mDefaultHandler.activeCount());
         assertEquals(1, mDefaultHandler.mergeCount());
@@ -611,8 +621,8 @@
                     new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
             TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                     .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
-            transitions.onTransitionReady(token, info, mock(SurfaceControl.Transaction.class),
-                    mock(SurfaceControl.Transaction.class));
+            transitions.onTransitionReady(token, info, new StubTransaction(),
+                    new StubTransaction());
             return token;
         };
 
@@ -678,8 +688,8 @@
         // queued), so continue the transition lifecycle for that.
         TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
-        transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
-                mock(SurfaceControl.Transaction.class));
+        transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+                new StubTransaction());
         // At this point, if things are not working, we'd get an NPE due to attempting to merge
         // into the shellInit transition which hasn't started yet.
         assertEquals(1, mDefaultHandler.activeCount());
@@ -791,8 +801,8 @@
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
         TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
-        transitions.onTransitionReady(transitToken1, info1, mock(SurfaceControl.Transaction.class),
-                mock(SurfaceControl.Transaction.class));
+        transitions.onTransitionReady(transitToken1, info1, new StubTransaction(),
+                new StubTransaction());
         assertEquals(1, mDefaultHandler.activeCount());
 
         transitions.runOnIdle(runnable2);
@@ -806,8 +816,8 @@
                 new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
         TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
-        transitions.onTransitionReady(transitToken2, info2, mock(SurfaceControl.Transaction.class),
-                mock(SurfaceControl.Transaction.class));
+        transitions.onTransitionReady(transitToken2, info2, new StubTransaction(),
+                new StubTransaction());
         assertEquals(1, mDefaultHandler.activeCount());
 
         mDefaultHandler.finishAll();
@@ -858,8 +868,8 @@
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
         TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
-        SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
-        SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction startT = new StubTransaction();
+        SurfaceControl.Transaction finishT = new StubTransaction();
         transitions.onTransitionReady(transitToken, info, startT, finishT);
 
         InOrder observerOrder = inOrder(observer);
@@ -883,8 +893,8 @@
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
         TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
-        SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
-        SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction startT1 = new StubTransaction();
+        SurfaceControl.Transaction finishT1 = new StubTransaction();
         transitions.onTransitionReady(transitToken1, info1, startT1, finishT1);
         verify(observer).onTransitionReady(transitToken1, info1, startT1, finishT1);
 
@@ -893,8 +903,8 @@
                 new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
         TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
-        SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
-        SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction startT2 = new StubTransaction();
+        SurfaceControl.Transaction finishT2 = new StubTransaction();
         transitions.onTransitionReady(transitToken2, info2, startT2, finishT2);
         verify(observer, times(1)).onTransitionReady(transitToken2, info2, startT2, finishT2);
         verify(observer, times(0)).onTransitionStarting(transitToken2);
@@ -927,8 +937,8 @@
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
         TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
-        SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
-        SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction startT1 = new StubTransaction();
+        SurfaceControl.Transaction finishT1 = new StubTransaction();
         transitions.onTransitionReady(transitToken1, info1, startT1, finishT1);
 
         IBinder transitToken2 = new Binder();
@@ -936,8 +946,8 @@
                 new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
         TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
-        SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
-        SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction startT2 = new StubTransaction();
+        SurfaceControl.Transaction finishT2 = new StubTransaction();
         transitions.onTransitionReady(transitToken2, info2, startT2, finishT2);
 
         InOrder observerOrder = inOrder(observer);
@@ -999,8 +1009,8 @@
                 new TransitionRequestInfo(TRANSIT_CHANGE, mwTaskInfo, null /* remote */));
         TransitionInfo change = new TransitionInfoBuilder(TRANSIT_CHANGE)
                 .addChange(TRANSIT_CHANGE).build();
-        SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
-        SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction startT1 = new StubTransaction();
+        SurfaceControl.Transaction finishT1 = new StubTransaction();
         transitions.onTransitionReady(transitToken1, change, startT1, finishT1);
 
         // Request the second transition that should be handled by the default handler
@@ -1009,8 +1019,8 @@
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
         transitions.requestStartTransition(transitToken2,
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
-        SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
-        SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction startT2 = new StubTransaction();
+        SurfaceControl.Transaction finishT2 = new StubTransaction();
         transitions.onTransitionReady(transitToken2, open, startT2, finishT2);
         verify(observer).onTransitionReady(transitToken2, open, startT2, finishT2);
         verify(observer, times(0)).onTransitionStarting(transitToken2);
@@ -1019,8 +1029,8 @@
         IBinder transitToken3 = new Binder();
         transitions.requestStartTransition(transitToken3,
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
-        SurfaceControl.Transaction startT3 = mock(SurfaceControl.Transaction.class);
-        SurfaceControl.Transaction finishT3 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction startT3 = new StubTransaction();
+        SurfaceControl.Transaction finishT3 = new StubTransaction();
         transitions.onTransitionReady(transitToken3, open, startT3, finishT3);
         verify(observer, times(0)).onTransitionStarting(transitToken2);
         verify(observer).onTransitionReady(transitToken3, open, startT3, finishT3);
@@ -1045,6 +1055,104 @@
     }
 
     @Test
+    public void testTransitSleep_squashesRecents() {
+        ShellInit shellInit = new ShellInit(mMainExecutor);
+        final Transitions transitions =
+                new Transitions(mContext, shellInit, mock(ShellController.class), mOrganizer,
+                        mTransactionPool, createTestDisplayController(), mMainExecutor,
+                        mMainHandler, mAnimExecutor);
+        final RecentsTransitionHandler recentsHandler =
+                new RecentsTransitionHandler(shellInit, transitions, null);
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+        shellInit.init();
+
+        Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
+        transitions.registerObserver(observer);
+
+        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS);
+        RunningTaskInfo task2 = createTaskInfo(2);
+
+        // Start an open transition for the purpose of occupying the ready queue
+        final IBinder transitOpen1 = new Binder("transitOpen1");
+        final TransitionInfo infoOpen1 =
+                new TransitionInfoBuilder(TRANSIT_OPEN)
+                        .addChange(TRANSIT_OPEN, task1)
+                        .build();
+        mMainExecutor.execute(() -> {
+            transitions.requestStartTransition(transitOpen1, new TransitionRequestInfo(
+                        TRANSIT_OPEN, task1 /* trigger */, null /* remote */));
+            onTransitionReady(transitions, transitOpen1, infoOpen1);
+        });
+
+        // First transition on the queue should start immediately.
+        mMainExecutor.flushAll();
+        verify(observer).onTransitionReady(eq(transitOpen1), any(), any(), any());
+        verify(observer).onTransitionStarting(eq(transitOpen1));
+
+        // Start recents
+        final IRecentsAnimationRunner recentsListener =
+                mock(IRecentsAnimationRunner.class, Answers.RETURNS_DEEP_STUBS);
+        final IBinder transitRecents = recentsHandler.startRecentsTransition(
+                mock(PendingIntent.class) /* intent */,
+                mock(Intent.class) /* fillIn */,
+                new Bundle() /* options */,
+                mock(IApplicationThread.class) /* appThread */,
+                recentsListener);
+        final TransitionInfo infoRecents =
+                new TransitionInfoBuilder(TRANSIT_TO_FRONT)
+                        .addChange(TRANSIT_TO_FRONT, task1)
+                        .addChange(TRANSIT_CLOSE, task2)
+                        .build();
+        onTransitionReady(transitions, transitRecents, infoRecents);
+
+        // Start another open transition during recents
+        final IBinder transitOpen2 = new Binder("transitOpen2");
+        final TransitionInfo infoOpen2 =
+                new TransitionInfoBuilder(TRANSIT_OPEN)
+                        .addChange(TRANSIT_OPEN, task2)
+                        .addChange(TRANSIT_TO_BACK, task1)
+                        .build();
+        mMainExecutor.execute(() -> {
+            transitions.requestStartTransition(transitOpen2,  new TransitionRequestInfo(
+                        TRANSIT_OPEN, task2 /* trigger */, null /* remote */));
+            onTransitionReady(transitions, transitOpen2, infoOpen2);
+        });
+
+        // Finish testOpen1 to start processing the other transitions
+        mMainExecutor.execute(() -> {
+            mDefaultHandler.finishOne();
+        });
+        mMainExecutor.flushAll();
+
+        // Recents transition SHOULD start, and merge the open transition, which should NOT start.
+        verify(observer).onTransitionFinished(eq(transitOpen1), eq(false) /* aborted */);
+        verify(observer).onTransitionReady(eq(transitRecents), any(), any(), any());
+        verify(observer).onTransitionStarting(eq(transitRecents));
+        verify(observer).onTransitionReady(eq(transitOpen2), any(), any(), any());
+        verify(observer).onTransitionMerged(eq(transitOpen2), eq(transitRecents));
+        // verify(observer).onTransitionFinished(eq(transitOpen2), eq(true) /* aborted */);
+
+        // Go to sleep
+        final IBinder transitSleep = new Binder("transitSleep");
+        final TransitionInfo infoSleep = new TransitionInfoBuilder(TRANSIT_SLEEP).build();
+        mMainExecutor.execute(() -> {
+            transitions.requestStartTransition(transitSleep, new TransitionRequestInfo(
+                        TRANSIT_SLEEP, null /* trigger */, null /* remote */));
+            onTransitionReady(transitions, transitSleep, infoSleep);
+        });
+        mMainExecutor.flushAll();
+
+        // Recents transition should finish itself when it sees the sleep transition coming.
+        verify(observer).onTransitionFinished(eq(transitRecents), eq(false));
+        verify(observer).onTransitionFinished(eq(transitSleep), eq(false));
+    }
+
+    private void onTransitionReady(Transitions transitions, IBinder token, TransitionInfo info) {
+        transitions.onTransitionReady(token, info, new StubTransaction(),
+                new StubTransaction());
+    }
+
+    @Test
     public void testEmptyTransitionStillReportsKeyguardGoingAway() {
         Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -1056,8 +1164,8 @@
         // Make a no-op transition
         TransitionInfo info = new TransitionInfoBuilder(
                 TRANSIT_OPEN, TRANSIT_FLAG_KEYGUARD_GOING_AWAY, true /* noOp */).build();
-        transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
-                mock(SurfaceControl.Transaction.class));
+        transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+                new StubTransaction());
 
         // If keyguard-going-away flag set, then it shouldn't be aborted.
         assertEquals(1, mDefaultHandler.activeCount());
@@ -1397,7 +1505,7 @@
 
     private static void onTransitionReady(Transitions transitions, IBinder token) {
         transitions.onTransitionReady(token, createTransitionInfo(),
-                mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class));
+                new StubTransaction(), new StubTransaction());
     }
 
     private static TransitionInfo createTransitionInfo() {
@@ -1414,15 +1522,15 @@
     private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode, int activityType) {
         RunningTaskInfo taskInfo = new RunningTaskInfo();
         taskInfo.taskId = taskId;
+        taskInfo.topActivityType = activityType;
         taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
         taskInfo.configuration.windowConfiguration.setActivityType(activityType);
+        taskInfo.token = mock(WindowContainerToken.class);
         return taskInfo;
     }
 
     private static RunningTaskInfo createTaskInfo(int taskId) {
-        RunningTaskInfo taskInfo = new RunningTaskInfo();
-        taskInfo.taskId = taskId;
-        return taskInfo;
+        return createTaskInfo(taskId, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
     }
 
     private DisplayController createTestDisplayController() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
new file mode 100644
index 0000000..348b365
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager
+import android.graphics.PointF
+import android.graphics.Rect
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.window.WindowContainerToken
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.Mockito.any
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [DragPositioningCallbackUtility].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DragPositioningCallbackUtilityTest
+ */
+@RunWith(AndroidTestingRunner::class)
+class DragPositioningCallbackUtilityTest {
+    @Mock
+    private lateinit var mockWindowDecoration: WindowDecoration<*>
+    @Mock
+    private lateinit var taskToken: WindowContainerToken
+    @Mock
+    private lateinit var taskBinder: IBinder
+    @Mock
+    private lateinit var mockDisplayController: DisplayController
+    @Mock
+    private lateinit var mockDisplayLayout: DisplayLayout
+    @Mock
+    private lateinit var mockDisplay: Display
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(taskToken.asBinder()).thenReturn(taskBinder)
+        whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+        whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+        whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+            (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+        }
+
+        mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
+            taskId = TASK_ID
+            token = taskToken
+            minWidth = MIN_WIDTH
+            minHeight = MIN_HEIGHT
+            defaultMinSize = DEFAULT_MIN
+            displayId = DISPLAY_ID
+            configuration.windowConfiguration.bounds = STARTING_BOUNDS
+        }
+        mockWindowDecoration.mDisplay = mockDisplay
+        whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+    }
+
+    @Test
+    fun testChangeBoundsDoesNotChangeHeightWhenLessThanMin() {
+        val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+        val repositionTaskBounds = Rect()
+
+        // Resize to width of 95px and height of 5px with min width of 10px
+        val newX = STARTING_BOUNDS.right.toFloat() - 5
+        val newY = STARTING_BOUNDS.top.toFloat() + 95
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+            false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+            mockDisplayController, mockWindowDecoration)
+
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 5)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+    }
+
+    @Test
+    fun testChangeBoundsDoesNotChangeWidthWhenLessThanMin() {
+        val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+        val repositionTaskBounds = Rect()
+
+        // Resize to height of 95px and width of 5px with min width of 10px
+        val newX = STARTING_BOUNDS.right.toFloat() - 95
+        val newY = STARTING_BOUNDS.top.toFloat() + 5
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+            false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+            mockDisplayController, mockWindowDecoration)
+
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top + 5)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+    }
+
+    @Test
+    fun testChangeBoundsDoesNotChangeHeightWhenNegative() {
+        val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+        val repositionTaskBounds = Rect()
+
+        // Resize to width of 95px and width of -5px with minimum of 10px
+        val newX = STARTING_BOUNDS.right.toFloat() - 5
+        val newY = STARTING_BOUNDS.top.toFloat() + 105
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+            false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+            mockDisplayController, mockWindowDecoration)
+
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 5)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+    }
+
+    @Test
+    fun testChangeBoundsRunsWhenResizeBoundsValid() {
+        val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+        val repositionTaskBounds = Rect()
+
+        // Shrink to height 20px and width 20px with both min height/width equal to 10px
+        val newX = STARTING_BOUNDS.right.toFloat() - 80
+        val newY = STARTING_BOUNDS.top.toFloat() + 80
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+                false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+                mockDisplayController, mockWindowDecoration)
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top + 80)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 80)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+    }
+
+    @Test
+    fun testChangeBoundsDoesNotRunWithNegativeHeightAndWidth() {
+        val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+        val repositionTaskBounds = Rect()
+        // Shrink to height -5px and width -5px with both min height/width equal to 10px
+        val newX = STARTING_BOUNDS.right.toFloat() - 105
+        val newY = STARTING_BOUNDS.top.toFloat() + 105
+
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+            false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+            mockDisplayController, mockWindowDecoration)
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+    }
+
+    @Test
+    fun testChangeBounds_toDisallowedBounds_freezesAtLimit() {
+        var hasMoved = false
+        val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(),
+            STARTING_BOUNDS.bottom.toFloat())
+        val repositionTaskBounds = Rect()
+        // Initial resize to width and height 110px.
+        var newX = STARTING_BOUNDS.right.toFloat() + 10
+        var newY = STARTING_BOUNDS.bottom.toFloat() + 10
+        var delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+        assertTrue(DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+            hasMoved, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+            mockDisplayController, mockWindowDecoration))
+        hasMoved = true
+        // Resize width to 120px, height to disallowed area which should not result in a change.
+        newX += 10
+        newY = DISALLOWED_RESIZE_AREA.top.toFloat()
+        delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+        assertTrue(DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+            hasMoved, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+            mockDisplayController, mockWindowDecoration))
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+        assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right + 20)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom + 10)
+    }
+
+    companion object {
+        private const val TASK_ID = 5
+        private const val MIN_WIDTH = 10
+        private const val MIN_HEIGHT = 10
+        private const val DENSITY_DPI = 20
+        private const val DEFAULT_MIN = 40
+        private const val DISPLAY_ID = 1
+        private const val NAVBAR_HEIGHT = 50
+        private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
+        private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+        private val DISALLOWED_RESIZE_AREA = Rect(
+            DISPLAY_BOUNDS.left,
+            DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
+            DISPLAY_BOUNDS.right,
+            DISPLAY_BOUNDS.bottom)
+        private val STABLE_BOUNDS = Rect(
+            DISPLAY_BOUNDS.left,
+            DISPLAY_BOUNDS.top,
+            DISPLAY_BOUNDS.right,
+            DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
+        )
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 84ccdde..5bea8f2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -14,7 +14,6 @@
 import com.android.wm.shell.common.DisplayLayout
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
@@ -22,7 +21,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.Mockito.any
 import org.mockito.Mockito.argThat
 import org.mockito.Mockito.never
@@ -72,10 +71,10 @@
                 mockDragStartListener
             )
 
-        `when`(taskToken.asBinder()).thenReturn(taskBinder)
-        `when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
-        `when`(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
-        `when`(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+        whenever(taskToken.asBinder()).thenReturn(taskBinder)
+        whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+        whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+        whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
             (i.arguments.first() as Rect).set(STABLE_BOUNDS)
         }
 
@@ -89,7 +88,7 @@
             configuration.windowConfiguration.bounds = STARTING_BOUNDS
         }
         mockWindowDecoration.mDisplay = mockDisplay
-        `when`(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+        whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
     }
 
     @Test
@@ -237,293 +236,6 @@
         })
     }
 
-    @Test
-    fun testDragResize_resize_setBoundsDoesNotChangeHeightWhenLessThanMin() {
-        taskPositioner.onDragPositioningStart(
-                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
-                STARTING_BOUNDS.right.toFloat(),
-                STARTING_BOUNDS.top.toFloat()
-        )
-
-        // Resize to width of 95px and height of 5px with min width of 10px
-        val newX = STARTING_BOUNDS.right.toFloat() - 5
-        val newY = STARTING_BOUNDS.top.toFloat() + 95
-        taskPositioner.onDragPositioningMove(
-                newX,
-                newY
-        )
-
-        taskPositioner.onDragPositioningEnd(newX, newY)
-
-        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder &&
-                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
-                                != 0) && change.configuration.windowConfiguration.bounds.top ==
-                        STARTING_BOUNDS.top &&
-                        change.configuration.windowConfiguration.bounds.bottom ==
-                        STARTING_BOUNDS.bottom
-            }
-        })
-    }
-
-    @Test
-    fun testDragResize_resize_setBoundsDoesNotChangeWidthWhenLessThanMin() {
-        taskPositioner.onDragPositioningStart(
-                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
-                STARTING_BOUNDS.right.toFloat(),
-                STARTING_BOUNDS.top.toFloat()
-        )
-
-        // Resize to height of 95px and width of 5px with min width of 10px
-        val newX = STARTING_BOUNDS.right.toFloat() - 95
-        val newY = STARTING_BOUNDS.top.toFloat() + 5
-        taskPositioner.onDragPositioningMove(
-                newX,
-                newY
-        )
-
-        taskPositioner.onDragPositioningEnd(newX, newY)
-
-        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder &&
-                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
-                                != 0) && change.configuration.windowConfiguration.bounds.right ==
-                        STARTING_BOUNDS.right &&
-                        change.configuration.windowConfiguration.bounds.left ==
-                        STARTING_BOUNDS.left
-            }
-        })
-    }
-
-    @Test
-    fun testDragResize_resize_setBoundsDoesNotChangeHeightWhenNegative() {
-        taskPositioner.onDragPositioningStart(
-                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
-                STARTING_BOUNDS.right.toFloat(),
-                STARTING_BOUNDS.top.toFloat()
-        )
-
-        // Resize to height of -5px and width of 95px
-        val newX = STARTING_BOUNDS.right.toFloat() - 5
-        val newY = STARTING_BOUNDS.top.toFloat() + 105
-        taskPositioner.onDragPositioningMove(
-                newX,
-                newY
-        )
-
-        taskPositioner.onDragPositioningEnd(newX, newY)
-
-        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder &&
-                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
-                                != 0) && change.configuration.windowConfiguration.bounds.top ==
-                        STARTING_BOUNDS.top &&
-                        change.configuration.windowConfiguration.bounds.bottom ==
-                        STARTING_BOUNDS.bottom
-            }
-        })
-    }
-
-    @Test
-    fun testDragResize_resize_setBoundsDoesNotChangeWidthWhenNegative() {
-        taskPositioner.onDragPositioningStart(
-                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
-                STARTING_BOUNDS.right.toFloat(),
-                STARTING_BOUNDS.top.toFloat()
-        )
-
-        // Resize to width of -5px and height of 95px
-        val newX = STARTING_BOUNDS.right.toFloat() - 105
-        val newY = STARTING_BOUNDS.top.toFloat() + 5
-        taskPositioner.onDragPositioningMove(
-                newX,
-                newY
-        )
-
-        taskPositioner.onDragPositioningEnd(newX, newY)
-
-        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder &&
-                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
-                                != 0) && change.configuration.windowConfiguration.bounds.right ==
-                        STARTING_BOUNDS.right &&
-                        change.configuration.windowConfiguration.bounds.left ==
-                        STARTING_BOUNDS.left
-            }
-        })
-    }
-
-    @Test
-    fun testDragResize_resize_setBoundsRunsWhenResizeBoundsValid() {
-        taskPositioner.onDragPositioningStart(
-                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
-                STARTING_BOUNDS.right.toFloat(),
-                STARTING_BOUNDS.top.toFloat()
-        )
-
-        // Shrink to height 20px and width 20px with both min height/width equal to 10px
-        val newX = STARTING_BOUNDS.right.toFloat() - 80
-        val newY = STARTING_BOUNDS.top.toFloat() + 80
-        taskPositioner.onDragPositioningMove(
-                newX,
-                newY
-        )
-
-        taskPositioner.onDragPositioningEnd(newX, newY)
-
-        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder &&
-                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
-            }
-        })
-    }
-
-    @Test
-    fun testDragResize_resize_setBoundsDoesNotRunWithNegativeHeightAndWidth() {
-        taskPositioner.onDragPositioningStart(
-                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
-                STARTING_BOUNDS.right.toFloat(),
-                STARTING_BOUNDS.top.toFloat()
-        )
-
-        // Shrink to height 5px and width 5px with both min height/width equal to 10px
-        val newX = STARTING_BOUNDS.right.toFloat() - 95
-        val newY = STARTING_BOUNDS.top.toFloat() + 95
-        taskPositioner.onDragPositioningMove(
-                newX,
-                newY
-        )
-
-        taskPositioner.onDragPositioningEnd(newX, newY)
-
-        verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder &&
-                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
-            }
-        })
-    }
-
-    @Test
-    fun testDragResize_resize_useDefaultMinWhenMinWidthInvalid() {
-        mockWindowDecoration.mTaskInfo.minWidth = -1
-
-        taskPositioner.onDragPositioningStart(
-                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
-                STARTING_BOUNDS.right.toFloat(),
-                STARTING_BOUNDS.top.toFloat()
-        )
-
-        // Shrink to width and height of 3px with invalid minWidth = -1 and defaultMinSize = 5px
-        val newX = STARTING_BOUNDS.right.toFloat() - 97
-        val newY = STARTING_BOUNDS.top.toFloat() + 97
-        taskPositioner.onDragPositioningMove(
-                newX,
-                newY
-        )
-
-        taskPositioner.onDragPositioningEnd(newX, newY)
-
-        verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder &&
-                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
-            }
-        })
-    }
-
-    @Test
-    fun testDragResize_resize_useMinWidthWhenValid() {
-        taskPositioner.onDragPositioningStart(
-                CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
-                STARTING_BOUNDS.right.toFloat(),
-                STARTING_BOUNDS.top.toFloat()
-        )
-
-        // Shrink to width and height of 7px with valid minWidth = 10px and defaultMinSize = 5px
-        val newX = STARTING_BOUNDS.right.toFloat() - 93
-        val newY = STARTING_BOUNDS.top.toFloat() + 93
-        taskPositioner.onDragPositioningMove(
-                newX,
-                newY
-        )
-
-        taskPositioner.onDragPositioningEnd(newX, newY)
-
-        verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder &&
-                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
-            }
-        })
-    }
-
-    fun testDragResize_toDisallowedBounds_freezesAtLimit() {
-        taskPositioner.onDragPositioningStart(
-                CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, // Resize right-bottom corner
-                STARTING_BOUNDS.right.toFloat(),
-                STARTING_BOUNDS.bottom.toFloat()
-        )
-
-        // Resize the task by 10px to the right and bottom, a valid destination
-        val newBounds = Rect(
-                STARTING_BOUNDS.left,
-                STARTING_BOUNDS.top,
-                STARTING_BOUNDS.right + 10,
-                STARTING_BOUNDS.bottom + 10)
-        taskPositioner.onDragPositioningMove(
-                newBounds.right.toFloat(),
-                newBounds.bottom.toFloat()
-        )
-
-        // Resize the task by another 10px to the right (allowed) and to just in the disallowed
-        // area of the Y coordinate.
-        val newBounds2 = Rect(
-                newBounds.left,
-                newBounds.top,
-                newBounds.right + 10,
-                DISALLOWED_RESIZE_AREA.top
-        )
-        taskPositioner.onDragPositioningMove(
-                newBounds2.right.toFloat(),
-                newBounds2.bottom.toFloat()
-        )
-
-        taskPositioner.onDragPositioningEnd(newBounds2.right.toFloat(), newBounds2.bottom.toFloat())
-
-        // The first resize falls in the allowed area, verify there's a change for it.
-        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder && change.ofBounds(newBounds)
-            }
-        })
-        // The second resize falls in the disallowed area, verify there's no change for it.
-        verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder && change.ofBounds(newBounds2)
-            }
-        })
-        // Instead, there should be a change for its allowed portion (the X movement) with the Y
-        // staying frozen in the last valid resize position.
-        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder && change.ofBounds(
-                        Rect(
-                                newBounds2.left,
-                                newBounds2.top,
-                                newBounds2.right,
-                                newBounds.bottom // Stayed at the first resize destination.
-                        )
-                )
-            }
-        })
-    }
-
     private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean {
         return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) &&
                 bounds == configuration.windowConfiguration.bounds
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index bf365ca..498082b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -34,7 +34,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.Mockito.any
 import org.mockito.Mockito.argThat
 import org.mockito.Mockito.never
@@ -85,10 +85,10 @@
                 mockDragStartListener
             )
 
-        `when`(taskToken.asBinder()).thenReturn(taskBinder)
-        `when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
-        `when`(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
-        `when`(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+        whenever(taskToken.asBinder()).thenReturn(taskBinder)
+        whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+        whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+        whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
             (i.arguments.first() as Rect).set(STABLE_BOUNDS)
         }
 
@@ -102,7 +102,7 @@
             configuration.windowConfiguration.bounds = STARTING_BOUNDS
         }
         mockDesktopWindowDecoration.mDisplay = mockDisplay
-        `when`(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+        whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
     }
 
     @Test
diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp
index 0d39f0e..974a5d0 100644
--- a/libs/hwui/Tonemapper.cpp
+++ b/libs/hwui/Tonemapper.cpp
@@ -97,7 +97,6 @@
                 .inputDataspace = sourceDataspace,
                 .outputDataspace = destinationDataspace,
                 .undoPremultipliedAlpha = source.alphaType() == kPremul_SkAlphaType,
-                .fakeInputDataspace = destinationDataspace,
                 .type = shaders::LinearEffect::SkSLType::ColorFilter};
         constexpr float kMaxDisplayBrightnessNits = 1000.f;
         constexpr float kCurrentDisplayBrightnessNits = 500.f;
diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp
index bfe4eaf..613f52b 100644
--- a/libs/hwui/effects/GainmapRenderer.cpp
+++ b/libs/hwui/effects/GainmapRenderer.cpp
@@ -38,7 +38,7 @@
 
 using namespace renderthread;
 
-static float getTargetHdrSdrRatio(const SkColorSpace* destColorspace) {
+float getTargetHdrSdrRatio(const SkColorSpace* destColorspace) {
     // We should always have a known destination colorspace. If we don't we must be in some
     // legacy mode where we're lost and also definitely not going to HDR
     if (destColorspace == nullptr) {
diff --git a/libs/hwui/effects/GainmapRenderer.h b/libs/hwui/effects/GainmapRenderer.h
index 4ed2445..0ab03f0 100644
--- a/libs/hwui/effects/GainmapRenderer.h
+++ b/libs/hwui/effects/GainmapRenderer.h
@@ -25,6 +25,8 @@
 
 namespace android::uirenderer {
 
+float getTargetHdrSdrRatio(const SkColorSpace* destColorspace);
+
 void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkRect& src,
                        const SkRect& dst, const SkSamplingOptions& sampling, const SkPaint* paint,
                        SkCanvas::SrcRectConstraint constraint,
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index a4960ea..c58ba68 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -26,6 +26,7 @@
 #include "SkM44.h"
 #include "include/gpu/GpuTypes.h" // from Skia
 #include "utils/GLUtils.h"
+#include <effects/GainmapRenderer.h>
 
 namespace android {
 namespace uirenderer {
@@ -129,6 +130,7 @@
     info.height = fboSize.height();
     mat4.getColMajor(&info.transform[0]);
     info.color_space_ptr = canvas->imageInfo().colorSpace();
+    info.currentHdrSdrRatio = getTargetHdrSdrRatio(info.color_space_ptr);
 
     // ensure that the framebuffer that the webview will render into is bound before we clear
     // the stencil and/or draw the functor.
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
index e6ef95b..e299d12 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
@@ -30,6 +30,7 @@
 #include "renderthread/VulkanManager.h"
 #include "thread/ThreadBase.h"
 #include "utils/TimeUtils.h"
+#include "effects/GainmapRenderer.h"
 
 namespace android {
 namespace uirenderer {
@@ -73,6 +74,7 @@
             .clip_right = mClip.fRight,
             .clip_bottom = mClip.fBottom,
             .is_layer = !vulkan_info.fFromSwapchainOrAndroidWindow,
+            .currentHdrSdrRatio = getTargetHdrSdrRatio(mImageInfo.colorSpace()),
     };
     mat4.getColMajor(&params.transform[0]);
     params.secondary_command_buffer = vulkan_info.fSecondaryCommandBuffer;
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index e168a7b..adf3c06 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -32,6 +32,7 @@
 #include "renderthread/EglManager.h"
 #include "thread/ThreadBase.h"
 #include "utils/TimeUtils.h"
+#include "effects/GainmapRenderer.h"
 
 #include <SkBlendMode.h>
 
@@ -139,6 +140,7 @@
         info.height = mFBInfo.height();
         mat4.getColMajor(&info.transform[0]);
         info.color_space_ptr = canvas->imageInfo().colorSpace();
+        info.currentHdrSdrRatio = getTargetHdrSdrRatio(info.color_space_ptr);
 
         glViewport(0, 0, info.width, info.height);
 
diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h
index 501b8df..7888c87 100644
--- a/libs/hwui/private/hwui/DrawGlInfo.h
+++ b/libs/hwui/private/hwui/DrawGlInfo.h
@@ -86,6 +86,11 @@
         // commands are issued.
         kStatusDrew = 0x4
     };
+
+    // The current HDR/SDR ratio that we are rendering to. The transform to SDR will already
+    // be baked into the color_space_ptr, so this is just to indicate the amount of extended
+    // range is available if desired
+    float currentHdrSdrRatio;
 };  // struct DrawGlInfo
 
 }  // namespace uirenderer
diff --git a/libs/hwui/private/hwui/DrawVkInfo.h b/libs/hwui/private/hwui/DrawVkInfo.h
index 5c59657..8f7063d 100644
--- a/libs/hwui/private/hwui/DrawVkInfo.h
+++ b/libs/hwui/private/hwui/DrawVkInfo.h
@@ -71,6 +71,11 @@
 
   // Input: Whether destination surface is offscreen surface.
   bool is_layer;
+
+  // The current HDR/SDR ratio that we are rendering to. The transform to SDR will already
+  // be baked into the color_space_ptr, so this is just to indicate the amount of extended
+  // range is available if desired
+  float currentHdrSdrRatio;
 };
 
 }  // namespace uirenderer
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 7e9d44f..c00a270 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -29,6 +29,7 @@
 #include "Layer.h"
 #include "Properties.h"
 #include "RenderThread.h"
+#include "VulkanManager.h"
 #include "pipeline/skia/ATraceMemoryDump.h"
 #include "pipeline/skia/ShaderCache.h"
 #include "pipeline/skia/SkiaMemoryTracer.h"
@@ -182,8 +183,14 @@
     }
     log.appendFormat("Contexts: %zu (stopped = %zu)\n", mCanvasContexts.size(), stoppedContexts);
 
+    auto vkInstance = VulkanManager::peekInstance();
     if (!mGrContext) {
-        log.appendFormat("No GPU context.\n");
+        if (!vkInstance) {
+            log.appendFormat("No GPU context.\n");
+        } else {
+            log.appendFormat("No GrContext; however %d remaining Vulkan refs",
+                             vkInstance->getStrongCount() - 1);
+        }
         return;
     }
     std::vector<skiapipeline::ResourcePair> cpuResourceMap = {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 96bfc10..4cffc6c 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -42,7 +42,7 @@
 namespace uirenderer {
 namespace renderthread {
 
-static std::array<std::string_view, 18> sEnableExtensions{
+static std::array<std::string_view, 19> sEnableExtensions{
         VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
         VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
         VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
@@ -56,6 +56,7 @@
         VK_KHR_SURFACE_EXTENSION_NAME,
         VK_KHR_SWAPCHAIN_EXTENSION_NAME,
         VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME,
+        VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME,
         VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME,
         VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
         VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
@@ -106,11 +107,11 @@
 #define GET_INST_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(mInstance, "vk" #F)
 #define GET_DEV_PROC(F) m##F = (PFN_vk##F)vkGetDeviceProcAddr(mDevice, "vk" #F)
 
-sp<VulkanManager> VulkanManager::getInstance() {
-    // cache a weakptr to the context to enable a second thread to share the same vulkan state
-    static wp<VulkanManager> sWeakInstance = nullptr;
-    static std::mutex sLock;
+// cache a weakptr to the context to enable a second thread to share the same vulkan state
+static wp<VulkanManager> sWeakInstance = nullptr;
+static std::mutex sLock;
 
+sp<VulkanManager> VulkanManager::getInstance() {
     std::lock_guard _lock{sLock};
     sp<VulkanManager> vulkanManager = sWeakInstance.promote();
     if (!vulkanManager.get()) {
@@ -121,6 +122,11 @@
     return vulkanManager;
 }
 
+sp<VulkanManager> VulkanManager::peekInstance() {
+    std::lock_guard _lock{sLock};
+    return sWeakInstance.promote();
+}
+
 VulkanManager::~VulkanManager() {
     if (mDevice != VK_NULL_HANDLE) {
         mDeviceWaitIdle(mDevice);
@@ -403,9 +409,13 @@
     }
 }
 
-sk_sp<GrDirectContext> VulkanManager::createContext(const GrContextOptions& options,
-                                                    ContextType contextType) {
+static void onGrContextReleased(void* context) {
+    VulkanManager* manager = (VulkanManager*)context;
+    manager->decStrong((void*)onGrContextReleased);
+}
 
+sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options,
+                                                    ContextType contextType) {
     GrVkBackendContext backendContext;
     backendContext.fInstance = mInstance;
     backendContext.fPhysicalDevice = mPhysicalDevice;
@@ -417,6 +427,11 @@
     backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2;
     backendContext.fGetProc = sSkiaGetProp;
 
+    LOG_ALWAYS_FATAL_IF(options.fContextDeleteProc != nullptr, "Conflicting fContextDeleteProcs!");
+    this->incStrong((void*)onGrContextReleased);
+    options.fContextDeleteContext = this;
+    options.fContextDeleteProc = onGrContextReleased;
+
     return GrDirectContext::MakeVulkan(backendContext, options);
 }
 
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index c5196ee..00a40c0 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -66,6 +66,7 @@
 class VulkanManager final : public RefBase {
 public:
     static sp<VulkanManager> getInstance();
+    static sp<VulkanManager> peekInstance();
 
     // Sets up the vulkan context that is shared amonst all clients of the VulkanManager. This must
     // be call once before use of the VulkanManager. Multiple calls after the first will simiply
@@ -109,7 +110,7 @@
     };
 
     // returns a Skia graphic context used to draw content on the specified thread
-    sk_sp<GrDirectContext> createContext(const GrContextOptions& options,
+    sk_sp<GrDirectContext> createContext(GrContextOptions& options,
                                          ContextType contextType = ContextType::kRenderThread);
 
     uint32_t getDriverVersion() const { return mDriverVersion; }
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index bffe137..913af8a 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -284,7 +284,9 @@
         case HAL_DATASPACE_TRANSFER_GAMMA2_8:
             return SkColorSpace::MakeRGB({2.8f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut);
         case HAL_DATASPACE_TRANSFER_ST2084:
-            return SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut);
+            return SkColorSpace::MakeRGB({-2.0, -1.555223, 1.860454, 32 / 2523.0, 2413 / 128.0,
+                                          -2392 / 128.0, 8192 / 1305.0},
+                                         gamut);
         case HAL_DATASPACE_TRANSFER_SMPTE_170M:
             return SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut);
         case HAL_DATASPACE_TRANSFER_UNSPECIFIED:
@@ -427,10 +429,10 @@
 }
 
 // Skia skcms' default HLG maps encoded [0, 1] to linear [1, 12] in order to follow ARIB
-// but LinearEffect expects a decoded [0, 1] range instead to follow Rec 2100.
+// but LinearEffect expects to map 1.0 == 203 nits
 std::optional<skcms_TransferFunction> GetHLGScaleTransferFunction() {
     skcms_TransferFunction hlgFn;
-    if (skcms_TransferFunction_makeScaledHLGish(&hlgFn, 1.f / 12.f, 2.f, 2.f, 1.f / 0.17883277f,
+    if (skcms_TransferFunction_makeScaledHLGish(&hlgFn, 0.314509843, 2.f, 2.f, 1.f / 0.17883277f,
                                                 0.28466892f, 0.55991073f)) {
         return std::make_optional<skcms_TransferFunction>(hlgFn);
     }
diff --git a/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
index 6092ac5..82fc33e 100644
--- a/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
@@ -15,8 +15,8 @@
  */
 package android.media.soundtrigger_middleware;
 
-import android.media.soundtrigger.RecognitionEvent;
-import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
 
 /**
  * Main interface for a client to get notifications of events coming from this module.
@@ -31,7 +31,7 @@
      * In case of abortion, the caller may retry after the next onRecognitionAvailabilityChange()
      * callback.
      */
-    void onRecognition(int modelHandle, in RecognitionEvent event, int captureSession);
+    void onRecognition(int modelHandle, in RecognitionEventSys event, int captureSession);
      /**
       * Invoked whenever a phrase recognition event is triggered (typically, on recognition, but
       * also in case of external aborting of a recognition or a forced recognition event - see the
@@ -39,7 +39,7 @@
       * In case of abortion, the caller may retry after the next onRecognitionAvailabilityChange()
       * callback.
       */
-    void onPhraseRecognition(int modelHandle, in PhraseRecognitionEvent event, int captureSession);
+    void onPhraseRecognition(int modelHandle, in PhraseRecognitionEventSys event, int captureSession);
     /**
      * Notifies the client that some start/load operations that have previously failed for resource
      * reasons (threw a ServiceSpecificException(RESOURCE_CONTENTION) or have been preempted) may
diff --git a/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl
new file mode 100644
index 0000000..6c912ed
--- /dev/null
+++ b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger.PhraseRecognitionEvent;
+
+/**
+ * Wrapper to android.media.soundtrigger.RecognitionEvent providing additional fields used by the
+ * framework.
+ */
+parcelable PhraseRecognitionEventSys {
+
+    PhraseRecognitionEvent phraseRecognitionEvent;
+    /**
+     * Timestamp of when the trigger event from SoundTriggerHal was received by the
+     * framework.
+     *
+     * <p>same units and timebase as {@link SystemClock#elapsedRealtime()}.
+     * The value will be -1 if the event was not generated from the HAL.
+     */
+    // @ElapsedRealtimeLong
+    long halEventReceivedMillis = -1;
+}
diff --git a/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl b/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl
new file mode 100644
index 0000000..84e327d
--- /dev/null
+++ b/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger.RecognitionEvent;
+
+/**
+ * Wrapper to android.media.soundtrigger.RecognitionEvent providing additional fields used by the
+ * framework.
+ */
+parcelable RecognitionEventSys {
+
+    RecognitionEvent recognitionEvent;
+    /**
+     * Timestamp of when the trigger event from SoundTriggerHal was received by the
+     * framework.
+     *
+     * <p>same units and timebase as {@link SystemClock#elapsedRealtime()}.
+     * The value will be -1 if the event was not generated from the HAL.
+     */
+    // @ElapsedRealtimeLong
+    long halEventReceivedMillis = -1;
+}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index e73cf87..3123ee6 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1237,6 +1237,9 @@
     public static final Set<Integer> DEVICE_IN_ALL_SCO_SET;
     /** @hide */
     public static final Set<Integer> DEVICE_IN_ALL_USB_SET;
+    /** @hide */
+    public static final Set<Integer> DEVICE_IN_ALL_BLE_SET;
+
     static {
         DEVICE_IN_ALL_SET = new HashSet<>();
         DEVICE_IN_ALL_SET.add(DEVICE_IN_COMMUNICATION);
@@ -1276,6 +1279,66 @@
         DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_ACCESSORY);
         DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_DEVICE);
         DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_HEADSET);
+
+        DEVICE_IN_ALL_BLE_SET = new HashSet<>();
+        DEVICE_IN_ALL_BLE_SET.add(DEVICE_IN_BLE_HEADSET);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothDevice(int deviceType) {
+        return isBluetoothA2dpOutDevice(deviceType)
+                || isBluetoothScoDevice(deviceType)
+                || isBluetoothLeDevice(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothOutDevice(int deviceType) {
+        return isBluetoothA2dpOutDevice(deviceType)
+                || isBluetoothScoOutDevice(deviceType)
+                || isBluetoothLeOutDevice(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothInDevice(int deviceType) {
+        return isBluetoothScoInDevice(deviceType)
+                || isBluetoothLeInDevice(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothA2dpOutDevice(int deviceType) {
+        return DEVICE_OUT_ALL_A2DP_SET.contains(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothScoOutDevice(int deviceType) {
+        return DEVICE_OUT_ALL_SCO_SET.contains(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothScoInDevice(int deviceType) {
+        return DEVICE_IN_ALL_SCO_SET.contains(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothScoDevice(int deviceType) {
+        return isBluetoothScoOutDevice(deviceType)
+                || isBluetoothScoInDevice(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothLeOutDevice(int deviceType) {
+        return DEVICE_OUT_ALL_BLE_SET.contains(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothLeInDevice(int deviceType) {
+        return DEVICE_IN_ALL_BLE_SET.contains(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothLeDevice(int deviceType) {
+        return isBluetoothLeOutDevice(deviceType)
+                || isBluetoothLeInDevice(deviceType);
     }
 
     /** @hide */
diff --git a/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
index 80bc5c0..3dfc587 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
@@ -231,9 +231,17 @@
          */
         public void setModelCallback(@NonNull @CallbackExecutor Executor executor, @NonNull
                 ModelCallback callback) {
+            Objects.requireNonNull(callback);
+            Objects.requireNonNull(executor);
             synchronized (SoundTriggerInstrumentation.this.mLock) {
-                mModelCallback = Objects.requireNonNull(callback);
-                mModelExecutor = Objects.requireNonNull(executor);
+                if (mModelCallback == null) {
+                    for (var droppedConsumer : mDroppedConsumerList) {
+                        executor.execute(() -> droppedConsumer.accept(callback));
+                    }
+                    mDroppedConsumerList.clear();
+                }
+                mModelCallback = callback;
+                mModelExecutor = executor;
             }
         }
 
@@ -267,9 +275,11 @@
 
         private void wrap(Consumer<ModelCallback> consumer) {
             synchronized (SoundTriggerInstrumentation.this.mLock) {
-                if (mModelCallback != null && mModelExecutor != null) {
+                if (mModelCallback != null) {
                     final ModelCallback callback = mModelCallback;
                     mModelExecutor.execute(() -> consumer.accept(callback));
+                } else {
+                    mDroppedConsumerList.add(consumer);
                 }
             }
         }
@@ -282,6 +292,8 @@
         private ModelCallback mModelCallback = null;
         @GuardedBy("SoundTriggerInstrumentation.this.mLock")
         private Executor mModelExecutor = null;
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private final List<Consumer<ModelCallback>> mDroppedConsumerList = new ArrayList<>();
     }
 
     /**
@@ -374,9 +386,18 @@
          */
         public void setRecognitionCallback(@NonNull @CallbackExecutor Executor executor,
                 @NonNull RecognitionCallback callback) {
+            Objects.requireNonNull(callback);
+            Objects.requireNonNull(executor);
             synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (mRecognitionCallback == null) {
+                    for (var droppedConsumer : mDroppedConsumerList) {
+                        executor.execute(() -> droppedConsumer.accept(callback));
+                    }
+                    mDroppedConsumerList.clear();
+                }
                 mRecognitionCallback = callback;
                 mRecognitionExecutor = executor;
+
             }
         }
 
@@ -401,9 +422,11 @@
 
         private void wrap(Consumer<RecognitionCallback> consumer) {
             synchronized (SoundTriggerInstrumentation.this.mLock) {
-                if (mRecognitionCallback != null && mRecognitionExecutor != null) {
+                if (mRecognitionCallback != null) {
                     final RecognitionCallback callback = mRecognitionCallback;
                     mRecognitionExecutor.execute(() -> consumer.accept(callback));
+                } else {
+                    mDroppedConsumerList.add(consumer);
                 }
             }
         }
@@ -416,6 +439,8 @@
         private Executor mRecognitionExecutor = null;
         @GuardedBy("SoundTriggerInstrumentation.this.mLock")
         private RecognitionCallback mRecognitionCallback = null;
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private final List<Consumer<RecognitionCallback>> mDroppedConsumerList = new ArrayList<>();
     }
 
     // Implementation of injection interface passed to the HAL.
diff --git a/native/android/activity_manager.cpp b/native/android/activity_manager.cpp
index 155a355..bc6a84f 100644
--- a/native/android/activity_manager.cpp
+++ b/native/android/activity_manager.cpp
@@ -45,7 +45,7 @@
     void onUidIdle(uid_t uid, bool disabled) override;
     void onUidStateChanged(uid_t uid, int32_t procState, int64_t procStateSeq,
                            int32_t capability) override;
-    void onUidProcAdjChanged(uid_t uid) override;
+    void onUidProcAdjChanged(uid_t uid, int32_t adj) override;
 
     // IBinder::DeathRecipient implementation
     void binderDied(const wp<IBinder>& who) override;
@@ -121,7 +121,7 @@
 
 void UidObserver::onUidIdle(uid_t uid __unused, bool disabled __unused) {}
 
-void UidObserver::onUidProcAdjChanged(uid_t uid __unused) {}
+void UidObserver::onUidProcAdjChanged(uid_t uid __unused, int32_t adj __unused) {}
 
 void UidObserver::onUidStateChanged(uid_t uid, int32_t procState,
                                     int64_t procStateSeq __unused,
diff --git a/native/android/input.cpp b/native/android/input.cpp
index 1bff97d..64e8efe 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -87,11 +87,8 @@
 
 const AInputEvent* AKeyEvent_fromJava(JNIEnv* env, jobject keyEvent) {
     std::unique_ptr<KeyEvent> event = std::make_unique<KeyEvent>();
-    android::status_t ret = android::android_view_KeyEvent_toNative(env, keyEvent, event.get());
-    if (ret == android::OK) {
-        return event.release();
-    }
-    return nullptr;
+    *event = android::android_view_KeyEvent_toNative(env, keyEvent);
+    return event.release();
 }
 
 int64_t AKeyEvent_getEventTime(const AInputEvent* key_event) {
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index 5f067e9..b8887390 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -60,6 +60,7 @@
     @NonNull private Intent mIntent;
     @NonNull private URL mUrl;
     @TelephonyManager.PremiumCapability protected int mCapability;
+    private boolean mIsUserTriggeredFinish;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -71,6 +72,7 @@
                 SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
         String url = mIntent.getStringExtra(SlicePurchaseController.EXTRA_PURCHASE_URL);
         mApplicationContext = getApplicationContext();
+        mIsUserTriggeredFinish = true;
         logd("onCreate: subId=" + subId + ", capability="
                 + TelephonyManager.convertPremiumCapabilityToString(mCapability) + ", url=" + url);
 
@@ -153,12 +155,20 @@
 
     @Override
     protected void onDestroy() {
-        logd("onDestroy: User canceled the purchase by closing the application.");
-        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
-                mIntent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
+        if (mIsUserTriggeredFinish) {
+            logd("onDestroy: User canceled the purchase by closing the application.");
+            SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+                    mIntent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
+        }
         super.onDestroy();
     }
 
+    @Override
+    public void finishAndRemoveTask() {
+        mIsUserTriggeredFinish = false;
+        super.finishAndRemoveTask();
+    }
+
     private void setupWebView() {
         // Create WebView
         mWebView = new WebView(this);
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
index 6bfcd82..8c35da1 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
@@ -58,6 +58,7 @@
             android:textSize="14sp"
             android:layout_marginTop="2dp"
             style="@style/TextAppearance"
+            android:focusable="true"
             android:textColor="?android:attr/textColorSecondary"/>
 
     </LinearLayout>
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 74072e9..2502bbf 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -113,17 +113,11 @@
     <!-- Back button for the helper consent dialog [CHAR LIMIT=30] -->
     <string name="consent_back">Back</string>
 
-    <!-- Action when permission list view is expanded CHAR LIMIT=30] -->
-    <string name="permission_expanded">Expanded</string>
+    <!-- Expand permission in the list CHAR LIMIT=30] -->
+    <string name="permission_expand">Expand <xliff:g id="permission_type" example="Notification">%1$s</xliff:g></string>
 
-    <!-- Expand action permission list CHAR LIMIT=30] -->
-    <string name="permission_expand">Expand</string>
-
-    <!-- Action when permission list view is collapsed CHAR LIMIT=30] -->
-    <string name="permission_collapsed">Collapsed</string>
-
-    <!-- Collapse action permission list CHAR LIMIT=30] -->
-    <string name="permission_collapse">Collapse</string>
+    <!-- Collapse permission int the list CHAR LIMIT=30] -->
+    <string name="permission_collapse">Collapse <xliff:g id="permission_type" example="Notification">%1$s</xliff:g></string>
 
     <!-- ================== System data transfer ==================== -->
     <!-- Title of the permission sync confirmation dialog. [CHAR LIMIT=NONE] -->
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
index b86ef64..7ed1816 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
@@ -123,12 +123,11 @@
             viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more);
         }
 
-        setAccessibility(view, viewType,
-                AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_expand);
-
         // Add expand buttons if the permissions are more than PERMISSION_SIZE in this list also
         // make the summary invisible by default.
         if (mPermissions.size() > PERMISSION_SIZE) {
+            setAccessibility(view, viewType,
+                    AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_expand, 0);
 
             viewHolder.mPermissionSummary.setVisibility(View.GONE);
 
@@ -137,18 +136,16 @@
                     viewHolder.mExpandButton.setImageResource(R.drawable.btn_expand_less);
                     viewHolder.mPermissionSummary.setVisibility(View.VISIBLE);
                     viewHolder.mExpandButton.setTag(R.drawable.btn_expand_less);
-                    view.setContentDescription(mContext.getString(R.string.permission_expanded));
                     setAccessibility(view, viewType,
-                            AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_collapse);
-                    viewHolder.mPermissionSummary.setFocusable(true);
+                            AccessibilityNodeInfo.ACTION_CLICK,
+                            R.string.permission_collapse, R.string.permission_expand);
                 } else {
                     viewHolder.mExpandButton.setImageResource(R.drawable.btn_expand_more);
                     viewHolder.mPermissionSummary.setVisibility(View.GONE);
                     viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more);
-                    view.setContentDescription(mContext.getString(R.string.permission_collapsed));
                     setAccessibility(view, viewType,
-                            AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_expanded);
-                    viewHolder.mPermissionSummary.setFocusable(false);
+                            AccessibilityNodeInfo.ACTION_CLICK,
+                            R.string.permission_expand, R.string.permission_collapse);
                 }
             });
         } else {
@@ -200,14 +197,20 @@
         }
     }
 
-    private void setAccessibility(View view, int viewType, int action, int resourceId) {
-        final String actionString = mContext.getString(resourceId);
+    private void setAccessibility(View view, int viewType, int action, int statusResourceId,
+            int actionResourceId) {
         final String permission = mContext.getString(sTitleMap.get(viewType));
+
+        if (actionResourceId != 0) {
+            view.announceForAccessibility(
+                    getHtmlFromResources(mContext, actionResourceId, permission));
+        }
+
         view.setAccessibilityDelegate(new View.AccessibilityDelegate() {
             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
                 super.onInitializeAccessibilityNodeInfo(host, info);
                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(action,
-                        actionString + permission));
+                        getHtmlFromResources(mContext, statusResourceId, permission)));
             }
         });
     }
diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml
index 8724d69..4161601 100644
--- a/packages/CredentialManager/AndroidManifest.xml
+++ b/packages/CredentialManager/AndroidManifest.xml
@@ -42,15 +42,6 @@
         android:excludeFromRecents="true"
         android:theme="@style/Theme.CredentialSelector">
     </activity>
-
-    <receiver
-        android:name=".CredentialProviderReceiver"
-        android:exported="true"
-        android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR">
-        <intent-filter>
-            <action android:name="android.credentials.ui.action.CREDMAN_ENABLED_PROVIDERS_UPDATED"/>
-        </intent-filter>
-    </receiver>
   </application>
 
 </manifest>
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index a3b2752..6df0778 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -24,8 +24,8 @@
   <string name="string_cancel">Cancel</string>
   <!-- This is a label for a button that takes user to the next screen. [CHAR LIMIT=20] -->
   <string name="string_continue">Continue</string>
-  <!-- This is a label for a button that links to different places where the user can save their passkeys. [CHAR LIMIT=20] -->
-  <string name="string_more_options">More options</string>
+  <!-- This is a label for a button that leads to a holistic view of all different options where the user can save their new app credential. [CHAR LIMIT=20] -->
+  <string name="string_more_options">Save another way</string>
   <!-- This is a label for a button that links to additional information about passkeys. [CHAR LIMIT=20] -->
   <string name="string_learn_more">Learn more</string>
   <!-- This is a label for content description for show password icon button. -->
@@ -78,14 +78,20 @@
 
   <!-- This text is followed by a list of one or more options. [CHAR LIMIT=200] -->
   <string name="save_credential_to_title">Save <xliff:g id="credentialTypes" example="passkey">%1$s</xliff:g> to</string>
-  <!-- This appears as the title of the modal bottom sheet for users to choose to create a passkey on another device. [CHAR LIMIT=200] -->
-  <string name="create_passkey_in_other_device_title">Create passkey in another device?</string>
+  <!-- This appears as the title of the modal bottom sheet for users to confirm to create a passkey on another device. [CHAR LIMIT=200] -->
+  <string name="create_passkey_in_other_device_title">Create passkey on another device?</string>
+  <!-- This appears as the title of the modal bottom sheet for users to confirm to save a password on another device. [CHAR LIMIT=200] -->
+  <string name="save_password_on_other_device_title">Save password on another device?</string>
+  <!-- This appears as the title of the modal bottom sheet for users to confirm to save a sign-in credential on another device. [CHAR LIMIT=200] -->
+  <string name="save_sign_in_on_other_device_title">Save sign-in on another device?</string>
   <!-- This appears as the title of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] -->
   <string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g> for all your sign-ins?</string>
   <!-- This appears as the description body of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=300] -->
   <string name="use_provider_for_all_description">This password manager for <xliff:g id="username" example="becket@gmail.com">%1$s</xliff:g> will store your passwords and passkeys to help you easily sign in</string>
   <!-- This is a label for a button that sets this password manager as the default. [CHAR LIMIT=20] -->
   <string name="set_as_default">Set as default</string>
+  <!-- This is a button text to navigate the user to their system settings. [CHAR LIMIT=30] -->
+  <string name="settings">Settings</string>
   <!-- This is a label for a button that makes this password manager be used just in this specific case. [CHAR LIMIT=20] -->
   <string name="use_once">Use once</string>
   <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are passwords and passkeys. [CHAR LIMIT=80] -->
@@ -114,6 +120,8 @@
   <!-- Strings for the get flow. -->
   <!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
   <string name="get_dialog_title_use_passkey_for">Use your saved passkey for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
+  <!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved password to sign in to the app. [CHAR LIMIT=200] -->
+  <string name="get_dialog_title_use_password_for">Use your saved password for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
   <!-- This appears as the title of the dialog asking for user confirmation to use the single user credential (previously saved or to be created) to sign in to the app. [CHAR LIMIT=200] -->
   <string name="get_dialog_title_use_sign_in_for">Use your sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
   <!-- This appears as the title of the dialog asking for user to make a choice from various available user credentials (previously saved or to be created) to sign in to the app. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index a9bee03..693e767 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -107,7 +107,6 @@
 
         initialUiState = when (requestInfo?.type) {
             RequestInfo.TYPE_CREATE -> {
-                val defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId()
                 val isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
                 val providerEnableListUiState = getCreateProviderEnableListInitialUiState()
                 val providerDisableListUiState = getCreateProviderDisableListInitialUiState()
@@ -119,7 +118,8 @@
                         disabledProviders = providerDisableListUiState,
                         defaultProviderIdPreferredByApp =
                         requestDisplayInfoUiState.appPreferredDefaultProviderId,
-                        defaultProviderIdSetByUser = defaultProviderIdSetByUser,
+                        defaultProviderIdsSetByUser =
+                        requestDisplayInfoUiState.userSetDefaultProviderIds,
                         requestDisplayInfo = requestDisplayInfoUiState,
                         isOnPasskeyIntroStateAlready = false,
                         isPasskeyFirstUse = isPasskeyFirstUse,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt
deleted file mode 100644
index ee8cffe..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.util.Log
-import com.android.credentialmanager.common.Constants
-
-
-class CredentialProviderReceiver : BroadcastReceiver() {
-
-    override fun onReceive(context: Context?, intent: Intent?) {
-        Log.d(Constants.LOG_TAG, "Received intent in CredentialProviderReceiver")
-
-        val sharedPreferences = context?.getSharedPreferences(context?.packageName,
-                Context.MODE_PRIVATE)
-        sharedPreferences?.edit()?.remove(UserConfigRepo.DEFAULT_PROVIDER)?.commit()
-    }
-}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 6549b2d..54a8678d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -25,6 +25,7 @@
 import android.os.ResultReceiver
 import android.util.Log
 import androidx.activity.ComponentActivity
+import androidx.activity.OnBackPressedCallback
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.compose.setContent
 import androidx.activity.viewModels
@@ -48,9 +49,12 @@
 class CredentialSelectorActivity : ComponentActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+        Log.d(Constants.LOG_TAG, "Creating new CredentialSelectorActivity")
         overrideActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN,
             0, 0)
-        Log.d(Constants.LOG_TAG, "Creating new CredentialSelectorActivity")
+        overrideActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE,
+            0, 0)
+
         try {
             val (isCancellationRequest, shouldShowCancellationUi, _) =
                 maybeCancelUIUponRequest(intent)
@@ -59,6 +63,18 @@
             }
             val userConfigRepo = UserConfigRepo(this)
             val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
+
+            val backPressedCallback = object : OnBackPressedCallback(
+                true // default to enabled
+            ) {
+                override fun handleOnBackPressed() {
+                    credManRepo.onUserCancel()
+                    Log.d(Constants.LOG_TAG, "Activity back triggered: finish the activity.")
+                    this@CredentialSelectorActivity.finish()
+                }
+            }
+            onBackPressedDispatcher.addCallback(this, backPressedCallback)
+
             setContent {
                 PlatformTheme {
                     CredentialManagerBottomSheet(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 8b74d76..de67989 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -255,7 +255,8 @@
             disabledProviders = prevUiState.disabledProviders,
             defaultProviderIdPreferredByApp =
             prevUiState.requestDisplayInfo.appPreferredDefaultProviderId,
-            defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId(),
+            defaultProviderIdsSetByUser =
+            prevUiState.requestDisplayInfo.userSetDefaultProviderIds,
             requestDisplayInfo = prevUiState.requestDisplayInfo,
             isOnPasskeyIntroStateAlready = true,
             isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
@@ -269,28 +270,10 @@
         userConfigRepo.setIsPasskeyFirstUse(false)
     }
 
-    fun createFlowOnMoreOptionsSelectedOnProviderSelection() {
-        uiState = uiState.copy(
-            createCredentialUiState = uiState.createCredentialUiState?.copy(
-                currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
-                isFromProviderSelection = true
-            )
-        )
-    }
-
     fun createFlowOnMoreOptionsSelectedOnCreationSelection() {
         uiState = uiState.copy(
             createCredentialUiState = uiState.createCredentialUiState?.copy(
                 currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
-                isFromProviderSelection = false
-            )
-        )
-    }
-
-    fun createFlowOnBackProviderSelectionButtonSelected() {
-        uiState = uiState.copy(
-            createCredentialUiState = uiState.createCredentialUiState?.copy(
-                currentScreenState = CreateScreenState.PROVIDER_SELECTION,
             )
         )
     }
@@ -315,7 +298,10 @@
         uiState = uiState.copy(
             createCredentialUiState = uiState.createCredentialUiState?.copy(
                 currentScreenState =
-                if (activeEntry.activeProvider.id == userConfigRepo.getDefaultProviderId() ||
+                if (uiState.createCredentialUiState?.requestDisplayInfo?.userSetDefaultProviderIds
+                        ?.contains(activeEntry.activeProvider.id) ?: true ||
+                    !(uiState.createCredentialUiState?.foundCandidateFromUserDefaultProvider
+                    ?: false) ||
                     !TextUtils.isEmpty(uiState.createCredentialUiState?.requestDisplayInfo
                         ?.appPreferredDefaultProviderId))
                     CreateScreenState.CREATION_OPTION_SELECTION
@@ -325,18 +311,7 @@
         )
     }
 
-    fun createFlowOnEntrySelectedFromFirstUseScreen(activeEntry: ActiveEntry) {
-        val providerId = activeEntry.activeProvider.id
-        createFlowOnDefaultChanged(providerId)
-        uiState = uiState.copy(
-            createCredentialUiState = uiState.createCredentialUiState?.copy(
-                currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
-                activeEntry = activeEntry
-            )
-        )
-    }
-
-    fun createFlowOnDisabledProvidersSelected() {
+    fun createFlowOnLaunchSettings() {
         credManRepo.onSettingLaunchCancel()
         uiState = uiState.copy(dialogState = DialogState.CANCELED_FOR_SETTINGS)
     }
@@ -349,16 +324,6 @@
         )
     }
 
-    fun createFlowOnChangeDefaultSelected() {
-        uiState = uiState.copy(
-            createCredentialUiState = uiState.createCredentialUiState?.copy(
-                currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
-            )
-        )
-        val providerId = uiState.createCredentialUiState?.activeEntry?.activeProvider?.id
-        createFlowOnDefaultChanged(providerId)
-    }
-
     fun createFlowOnUseOnceSelected() {
         uiState = uiState.copy(
             createCredentialUiState = uiState.createCredentialUiState?.copy(
@@ -367,17 +332,6 @@
         )
     }
 
-    fun createFlowOnDefaultChanged(providerId: String?) {
-        if (providerId != null) {
-            Log.d(
-                Constants.LOG_TAG, "Default provider changed to: " +
-                " {provider=$providerId")
-            userConfigRepo.setDefaultProvider(providerId)
-        } else {
-            Log.w(Constants.LOG_TAG, "Null provider is being changed")
-        }
-    }
-
     fun createFlowOnEntrySelected(selectedEntry: BaseEntry) {
         val providerId = selectedEntry.providerId
         val entryKey = selectedEntry.entryKey
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 57035d4..a35310c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -61,6 +61,7 @@
 import androidx.credentials.provider.PublicKeyCredentialEntry
 import androidx.credentials.provider.RemoteEntry
 import org.json.JSONObject
+import java.time.Instant
 
 // TODO: remove all !! checks
 fun getAppLabel(
@@ -420,7 +421,7 @@
                     id = it.providerFlattenedComponentName,
                     displayName = providerLabel,
                     icon = providerIcon,
-                    createOptions = toCreationOptionInfoList(
+                    sortedCreateOptions = toSortedCreationOptionInfoList(
                         it.providerFlattenedComponentName, it.saveEntries, context
                     ),
                     remoteEntry = toRemoteInfo(it.providerFlattenedComponentName, it.remoteEntry),
@@ -481,8 +482,10 @@
                     CredentialType.PASSWORD,
                     appLabel,
                     context.getDrawable(R.drawable.ic_password_24) ?: return null,
-                    preferImmediatelyAvailableCredentials = false,
+                    preferImmediatelyAvailableCredentials =
+                    createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
                     appPreferredDefaultProviderId = appPreferredDefaultProviderId,
+                    userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
                 )
                 is CreatePublicKeyCredentialRequest -> {
                     newRequestDisplayInfoFromPasskeyJson(
@@ -492,6 +495,7 @@
                         preferImmediatelyAvailableCredentials =
                         createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
                         appPreferredDefaultProviderId = appPreferredDefaultProviderId,
+                        userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
                     )
                 }
                 is CreateCustomCredentialRequest -> {
@@ -506,8 +510,10 @@
                         appName = appLabel,
                         typeIcon = displayInfo.credentialTypeIcon?.loadDrawable(context)
                             ?: context.getDrawable(R.drawable.ic_other_sign_in_24) ?: return null,
-                        preferImmediatelyAvailableCredentials = false,
+                        preferImmediatelyAvailableCredentials =
+                        createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
                         appPreferredDefaultProviderId = appPreferredDefaultProviderId,
+                        userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
                     )
                 }
                 else -> null
@@ -518,13 +524,13 @@
             enabledProviders: List<EnabledProviderInfo>,
             disabledProviders: List<DisabledProviderInfo>?,
             defaultProviderIdPreferredByApp: String?,
-            defaultProviderIdSetByUser: String?,
+            defaultProviderIdsSetByUser: Set<String>,
             requestDisplayInfo: RequestDisplayInfo,
             isOnPasskeyIntroStateAlready: Boolean,
             isPasskeyFirstUse: Boolean,
         ): CreateCredentialUiState? {
-            var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
             var remoteEntry: RemoteInfo? = null
+            var remoteEntryProvider: EnabledProviderInfo? = null
             var defaultProviderPreferredByApp: EnabledProviderInfo? = null
             var defaultProviderSetByUser: EnabledProviderInfo? = null
             var createOptionsPairs:
@@ -535,14 +541,24 @@
                         defaultProviderPreferredByApp = enabledProvider
                     }
                 }
-                if (defaultProviderIdSetByUser != null) {
-                    if (enabledProvider.id == defaultProviderIdSetByUser) {
+                if (enabledProvider.sortedCreateOptions.isNotEmpty() &&
+                    defaultProviderIdsSetByUser.contains(enabledProvider.id)) {
+                    if (defaultProviderSetByUser == null) {
                         defaultProviderSetByUser = enabledProvider
+                    } else {
+                        val newLastUsedTime = enabledProvider.sortedCreateOptions.firstOrNull()
+                          ?.lastUsedTime
+                        val curLastUsedTime = defaultProviderSetByUser?.sortedCreateOptions
+                        ?.firstOrNull()?.lastUsedTime ?: Instant.MIN
+                        if (newLastUsedTime != null) {
+                            if (curLastUsedTime == null || newLastUsedTime > curLastUsedTime) {
+                                defaultProviderSetByUser = enabledProvider
+                            }
+                        }
                     }
                 }
-                if (enabledProvider.createOptions.isNotEmpty()) {
-                    lastSeenProviderWithNonEmptyCreateOptions = enabledProvider
-                    enabledProvider.createOptions.forEach {
+                if (enabledProvider.sortedCreateOptions.isNotEmpty()) {
+                    enabledProvider.sortedCreateOptions.forEach {
                         createOptionsPairs.add(Pair(it, enabledProvider))
                     }
                 }
@@ -554,6 +570,7 @@
                         return null
                     }
                     remoteEntry = currRemoteEntry
+                    remoteEntryProvider = enabledProvider
                 }
             }
             val defaultProvider = defaultProviderPreferredByApp ?: defaultProviderSetByUser
@@ -561,27 +578,26 @@
                 createOptionSize = createOptionsPairs.size,
                 isOnPasskeyIntroStateAlready = isOnPasskeyIntroStateAlready,
                 requestDisplayInfo = requestDisplayInfo,
-                defaultProvider = defaultProvider,
                 remoteEntry = remoteEntry,
                 isPasskeyFirstUse = isPasskeyFirstUse
             ) ?: return null
+            val sortedCreateOptionsPairs = createOptionsPairs.sortedWith(
+                compareByDescending { it.first.lastUsedTime }
+            )
             return CreateCredentialUiState(
                 enabledProviders = enabledProviders,
                 disabledProviders = disabledProviders,
                 currentScreenState = initialScreenState,
                 requestDisplayInfo = requestDisplayInfo,
-                sortedCreateOptionsPairs = createOptionsPairs.sortedWith(
-                    compareByDescending { it.first.lastUsedTime }
-                ),
-                hasDefaultProvider = defaultProvider != null,
+                sortedCreateOptionsPairs = sortedCreateOptionsPairs,
                 activeEntry = toActiveEntry(
-                    /*defaultProvider=*/defaultProvider,
-                    /*createOptionSize=*/createOptionsPairs.size,
-                    /*lastSeenProviderWithNonEmptyCreateOptions=*/
-                    lastSeenProviderWithNonEmptyCreateOptions,
-                    /*remoteEntry=*/remoteEntry
+                    defaultProvider = defaultProvider,
+                    sortedCreateOptionsPairs = sortedCreateOptionsPairs,
+                    remoteEntry = remoteEntry,
+                    remoteEntryProvider = remoteEntryProvider,
                 ),
                 remoteEntry = remoteEntry,
+                foundCandidateFromUserDefaultProvider = defaultProviderSetByUser != null,
             )
         }
 
@@ -589,59 +605,43 @@
             createOptionSize: Int,
             isOnPasskeyIntroStateAlready: Boolean,
             requestDisplayInfo: RequestDisplayInfo,
-            defaultProvider: EnabledProviderInfo?,
             remoteEntry: RemoteInfo?,
             isPasskeyFirstUse: Boolean,
         ): CreateScreenState? {
-            return if (isPasskeyFirstUse && requestDisplayInfo.type ==
-                CredentialType.PASSKEY && !isOnPasskeyIntroStateAlready) {
+            return if (isPasskeyFirstUse && requestDisplayInfo.type == CredentialType.PASSKEY &&
+                !isOnPasskeyIntroStateAlready) {
                 CreateScreenState.PASSKEY_INTRO
-            } else if ((defaultProvider == null || defaultProvider.createOptions.isEmpty()) &&
-                createOptionSize > 1) {
-                CreateScreenState.PROVIDER_SELECTION
-            } else if (((defaultProvider == null || defaultProvider.createOptions.isEmpty()) &&
-                    createOptionSize == 1) || (defaultProvider != null &&
-                    defaultProvider.createOptions.isNotEmpty())) {
-                CreateScreenState.CREATION_OPTION_SELECTION
             } else if (createOptionSize == 0 && remoteEntry != null) {
                 CreateScreenState.EXTERNAL_ONLY_SELECTION
             } else {
-                Log.d(
-                    Constants.LOG_TAG,
-                    "Unexpected failure: the screen state failed to instantiate" +
-                        " because the provider list is empty."
-                )
-                null
+                CreateScreenState.CREATION_OPTION_SELECTION
             }
         }
 
         private fun toActiveEntry(
             defaultProvider: EnabledProviderInfo?,
-            createOptionSize: Int,
-            lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo?,
+            sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
             remoteEntry: RemoteInfo?,
+            remoteEntryProvider: EnabledProviderInfo?,
         ): ActiveEntry? {
             return if (
-                defaultProvider != null && defaultProvider.createOptions.isEmpty() &&
-                remoteEntry != null
+                sortedCreateOptionsPairs.isEmpty() && remoteEntry != null &&
+                remoteEntryProvider != null
             ) {
-                ActiveEntry(defaultProvider, remoteEntry)
-            } else if (
-                defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
-            ) {
-                ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
-            } else if (createOptionSize == 1) {
-                ActiveEntry(
-                    lastSeenProviderWithNonEmptyCreateOptions!!,
-                    lastSeenProviderWithNonEmptyCreateOptions.createOptions.first()
-                )
+                ActiveEntry(remoteEntryProvider, remoteEntry)
+            } else if (defaultProvider != null &&
+                defaultProvider.sortedCreateOptions.isNotEmpty()) {
+                ActiveEntry(defaultProvider, defaultProvider.sortedCreateOptions.first())
+            } else if (sortedCreateOptionsPairs.isNotEmpty()) {
+                val (topEntry, topEntryProvider) = sortedCreateOptionsPairs.first()
+                ActiveEntry(topEntryProvider, topEntry)
             } else null
         }
 
         /**
          * Note: caller required handle empty list due to parsing error.
          */
-        private fun toCreationOptionInfoList(
+        private fun toSortedCreationOptionInfoList(
             providerId: String,
             creationEntries: List<Entry>,
             context: Context,
@@ -664,7 +664,9 @@
                     footerDescription = createEntry.description?.toString()
                 ))
             }
-            return result
+            return result.sortedWith(
+                compareByDescending { it.lastUsedTime }
+            )
         }
 
         private fun toRemoteInfo(
@@ -690,6 +692,7 @@
             context: Context,
             preferImmediatelyAvailableCredentials: Boolean,
             appPreferredDefaultProviderId: String?,
+            userSetDefaultProviderIds: Set<String>,
         ): RequestDisplayInfo? {
             val json = JSONObject(requestJson)
             var passkeyUsername = ""
@@ -711,6 +714,7 @@
                 context.getDrawable(R.drawable.ic_passkey_24) ?: return null,
                 preferImmediatelyAvailableCredentials,
                 appPreferredDefaultProviderId,
+                userSetDefaultProviderIds,
             )
         }
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
index a17f2c8..bfcca49 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
@@ -23,15 +23,6 @@
     val sharedPreferences: SharedPreferences = context.getSharedPreferences(
         context.packageName, Context.MODE_PRIVATE)
 
-    fun setDefaultProvider(
-        providerId: String
-    ) {
-        sharedPreferences.edit().apply {
-            putString(DEFAULT_PROVIDER, providerId)
-            apply()
-        }
-    }
-
     fun setIsPasskeyFirstUse(
         isFirstUse: Boolean
     ) {
@@ -41,16 +32,11 @@
         }
     }
 
-    fun getDefaultProviderId(): String? {
-        return sharedPreferences.getString(DEFAULT_PROVIDER, null)
-    }
-
     fun getIsPasskeyFirstUse(): Boolean {
         return sharedPreferences.getBoolean(IS_PASSKEY_FIRST_USE, true)
     }
 
     companion object {
-        const val DEFAULT_PROVIDER = "default_provider"
         // This first use value only applies to passkeys, not related with if generally
         // credential manager is first use or not
         const val IS_PASSKEY_FIRST_USE = "is_passkey_first_use"
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 9d871ed..d16120f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -88,27 +88,11 @@
                                 onLearnMore = viewModel::createFlowOnLearnMore,
                                 onLog = { viewModel.logUiEvent(it) },
                         )
-                        CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
-                                requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
-                                disabledProviderList = createCredentialUiState
-                                        .disabledProviders,
-                                sortedCreateOptionsPairs =
-                                createCredentialUiState.sortedCreateOptionsPairs,
-                                hasRemoteEntry = createCredentialUiState.remoteEntry != null,
-                                onOptionSelected =
-                                viewModel::createFlowOnEntrySelectedFromFirstUseScreen,
-                                onDisabledProvidersSelected =
-                                viewModel::createFlowOnDisabledProvidersSelected,
-                                onMoreOptionsSelected =
-                                viewModel::createFlowOnMoreOptionsSelectedOnProviderSelection,
-                                onLog = { viewModel.logUiEvent(it) },
-                        )
                         CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
                                 requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
                                 enabledProviderList = createCredentialUiState.enabledProviders,
                                 providerInfo = createCredentialUiState
                                         .activeEntry?.activeProvider!!,
-                                hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
                                 createOptionInfo =
                                 createCredentialUiState.activeEntry.activeEntryInfo
                                         as CreateOptionInfo,
@@ -121,21 +105,15 @@
                         CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
                                 requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
                                 enabledProviderList = createCredentialUiState.enabledProviders,
-                                disabledProviderList = createCredentialUiState
-                                        .disabledProviders,
+                                disabledProviderList = createCredentialUiState.disabledProviders,
                                 sortedCreateOptionsPairs =
                                 createCredentialUiState.sortedCreateOptionsPairs,
-                                hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
-                                isFromProviderSelection =
-                                createCredentialUiState.isFromProviderSelection!!,
-                                onBackProviderSelectionButtonSelected =
-                                viewModel::createFlowOnBackProviderSelectionButtonSelected,
                                 onBackCreationSelectionButtonSelected =
                                 viewModel::createFlowOnBackCreationSelectionButtonSelected,
                                 onOptionSelected =
                                 viewModel::createFlowOnEntrySelectedFromMoreOptionScreen,
                                 onDisabledProvidersSelected =
-                                viewModel::createFlowOnDisabledProvidersSelected,
+                                viewModel::createFlowOnLaunchSettings,
                                 onRemoteEntrySelected = viewModel::createFlowOnEntrySelected,
                                 onLog = { viewModel.logUiEvent(it) },
                         )
@@ -144,11 +122,11 @@
                                 viewModel.onIllegalUiState("Expect active entry to be non-null" +
                                         " upon default provider dialog.")
                             } else {
-                                DefaultProviderConfirmationCard(
+                                NonDefaultUsageConfirmationCard(
                                         selectedEntry = createCredentialUiState.activeEntry,
                                         onIllegalScreenState = viewModel::onIllegalUiState,
-                                        onChangeDefaultSelected =
-                                        viewModel::createFlowOnChangeDefaultSelected,
+                                        onLaunchSettings =
+                                        viewModel::createFlowOnLaunchSettings,
                                         onUseOnceSelected = viewModel::createFlowOnUseOnceSelected,
                                         onLog = { viewModel.logUiEvent(it) },
                                 )
@@ -263,94 +241,11 @@
 }
 
 @Composable
-fun ProviderSelectionCard(
-    requestDisplayInfo: RequestDisplayInfo,
-    disabledProviderList: List<DisabledProviderInfo>?,
-    sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
-    hasRemoteEntry: Boolean,
-    onOptionSelected: (ActiveEntry) -> Unit,
-    onDisabledProvidersSelected: () -> Unit,
-    onMoreOptionsSelected: () -> Unit,
-    onLog: @Composable (UiEventEnum) -> Unit,
-) {
-    SheetContainerCard {
-        item { HeadlineIcon(bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()) }
-        item { Divider(thickness = 16.dp, color = Color.Transparent) }
-        item {
-            HeadlineText(
-                text = stringResource(
-                    R.string.choose_provider_title,
-                    when (requestDisplayInfo.type) {
-                        CredentialType.PASSKEY ->
-                            stringResource(R.string.passkeys)
-                        CredentialType.PASSWORD ->
-                            stringResource(R.string.passwords)
-                        CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
-                    }
-                )
-            )
-        }
-        item { Divider(thickness = 24.dp, color = Color.Transparent) }
-
-        item {
-            Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
-                BodyMediumText(text = stringResource(R.string.choose_provider_body))
-            }
-        }
-        item { Divider(thickness = 16.dp, color = Color.Transparent) }
-        item {
-            CredentialContainerCard {
-                Column(
-                    verticalArrangement = Arrangement.spacedBy(2.dp)
-                ) {
-                    sortedCreateOptionsPairs.forEach { entry ->
-                        MoreOptionsInfoRow(
-                            requestDisplayInfo = requestDisplayInfo,
-                            providerInfo = entry.second,
-                            createOptionInfo = entry.first,
-                            onOptionSelected = {
-                                onOptionSelected(
-                                    ActiveEntry(
-                                        entry.second,
-                                        entry.first
-                                    )
-                                )
-                            }
-                        )
-                    }
-                    MoreOptionsDisabledProvidersRow(
-                        disabledProviders = disabledProviderList,
-                        onDisabledProvidersSelected = onDisabledProvidersSelected,
-                    )
-                }
-            }
-        }
-        if (hasRemoteEntry) {
-            item { Divider(thickness = 24.dp, color = Color.Transparent) }
-            item {
-                CtaButtonRow(
-                    leftButton = {
-                        ActionButton(
-                            stringResource(R.string.string_more_options),
-                            onMoreOptionsSelected
-                        )
-                    }
-                )
-            }
-        }
-    }
-    onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_PROVIDER_SELECTION)
-}
-
-@Composable
 fun MoreOptionsSelectionCard(
         requestDisplayInfo: RequestDisplayInfo,
         enabledProviderList: List<EnabledProviderInfo>,
         disabledProviderList: List<DisabledProviderInfo>?,
         sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
-        hasDefaultProvider: Boolean,
-        isFromProviderSelection: Boolean,
-        onBackProviderSelectionButtonSelected: () -> Unit,
         onBackCreationSelectionButtonSelected: () -> Unit,
         onOptionSelected: (ActiveEntry) -> Unit,
         onDisabledProvidersSelected: () -> Unit,
@@ -369,9 +264,7 @@
                     CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
                 }
             ),
-            onNavigationIconClicked =
-            if (isFromProviderSelection) onBackProviderSelectionButtonSelected
-            else onBackCreationSelectionButtonSelected,
+            onNavigationIconClicked = onBackCreationSelectionButtonSelected,
             bottomPadding = 16.dp,
         )
     }) {
@@ -379,30 +272,26 @@
         item {
             CredentialContainerCard {
                 Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
-                    // Only in the flows with default provider(not first time use) we can show the
-                    // createOptions here, or they will be shown on ProviderSelectionCard
-                    if (hasDefaultProvider) {
-                        sortedCreateOptionsPairs.forEach { entry ->
-                            MoreOptionsInfoRow(
-                                requestDisplayInfo = requestDisplayInfo,
-                                providerInfo = entry.second,
-                                createOptionInfo = entry.first,
-                                onOptionSelected = {
-                                    onOptionSelected(
-                                        ActiveEntry(
-                                            entry.second,
-                                            entry.first
-                                        )
+                    sortedCreateOptionsPairs.forEach { entry ->
+                        MoreOptionsInfoRow(
+                            requestDisplayInfo = requestDisplayInfo,
+                            providerInfo = entry.second,
+                            createOptionInfo = entry.first,
+                            onOptionSelected = {
+                                onOptionSelected(
+                                    ActiveEntry(
+                                        entry.second,
+                                        entry.first
                                     )
-                                }
-                            )
-                        }
-                        MoreOptionsDisabledProvidersRow(
-                            disabledProviders = disabledProviderList,
-                            onDisabledProvidersSelected =
-                            onDisabledProvidersSelected,
+                                )
+                            }
                         )
                     }
+                    MoreOptionsDisabledProvidersRow(
+                        disabledProviders = disabledProviderList,
+                        onDisabledProvidersSelected =
+                        onDisabledProvidersSelected,
+                    )
                     enabledProviderList.forEach {
                         if (it.remoteEntry != null) {
                             RemoteEntryRow(
@@ -420,10 +309,10 @@
 }
 
 @Composable
-fun DefaultProviderConfirmationCard(
+fun NonDefaultUsageConfirmationCard(
         selectedEntry: ActiveEntry,
         onIllegalScreenState: (String) -> Unit,
-        onChangeDefaultSelected: () -> Unit,
+        onLaunchSettings: () -> Unit,
         onUseOnceSelected: () -> Unit,
         onLog: @Composable (UiEventEnum) -> Unit,
 ) {
@@ -454,14 +343,14 @@
             CtaButtonRow(
                 leftButton = {
                     ActionButton(
-                        stringResource(R.string.use_once),
-                        onClick = onUseOnceSelected
+                        stringResource(R.string.settings),
+                        onClick = onLaunchSettings,
                     )
                 },
                 rightButton = {
                     ConfirmButton(
-                        stringResource(R.string.set_as_default),
-                        onClick = onChangeDefaultSelected
+                        stringResource(R.string.use_once),
+                        onClick = onUseOnceSelected,
                     )
                 },
             )
@@ -479,7 +368,6 @@
         onOptionSelected: (BaseEntry) -> Unit,
         onConfirm: () -> Unit,
         onMoreOptionsSelected: () -> Unit,
-        hasDefaultProvider: Boolean,
         onLog: @Composable (UiEventEnum) -> Unit,
 ) {
     SheetContainerCard {
@@ -527,16 +415,9 @@
             if (enabledProvider.remoteEntry != null) {
                 remoteEntry = enabledProvider.remoteEntry
             }
-            createOptionsSize += enabledProvider.createOptions.size
+            createOptionsSize += enabledProvider.sortedCreateOptions.size
         }
-        val shouldShowMoreOptionsButton = if (!hasDefaultProvider) {
-            // User has already been presented with all options on the default provider
-            // selection screen. Don't show them again. Therefore, only show the more option
-            // button if remote option is present.
-            remoteEntry != null
-        } else {
-            createOptionsSize > 1 || remoteEntry != null
-        }
+        val shouldShowMoreOptionsButton = createOptionsSize > 1 || remoteEntry != null
         item {
             CtaButtonRow(
                 leftButton = if (shouldShowMoreOptionsButton) {
@@ -584,7 +465,17 @@
     SheetContainerCard {
         item { HeadlineIcon(imageVector = Icons.Outlined.QrCodeScanner) }
         item { Divider(thickness = 16.dp, color = Color.Transparent) }
-        item { HeadlineText(text = stringResource(R.string.create_passkey_in_other_device_title)) }
+        item {
+            HeadlineText(
+                text = stringResource(
+                    when (requestDisplayInfo.type) {
+                        CredentialType.PASSKEY -> R.string.create_passkey_in_other_device_title
+                        CredentialType.PASSWORD -> R.string.save_password_on_other_device_title
+                        else -> R.string.save_sign_in_on_other_device_title
+                    }
+                )
+            )
+        }
         item { Divider(thickness = 24.dp, color = Color.Transparent) }
         item {
             CredentialContainerCard {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 225dbf2..fe1ce1b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -29,12 +29,9 @@
   val currentScreenState: CreateScreenState,
   val requestDisplayInfo: RequestDisplayInfo,
   val sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
-  // Should not change with the real time update of default provider, only determine whether
-  // we're showing provider selection page at the beginning
-  val hasDefaultProvider: Boolean,
   val activeEntry: ActiveEntry? = null,
   val remoteEntry: RemoteInfo? = null,
-  val isFromProviderSelection: Boolean? = null,
+  val foundCandidateFromUserDefaultProvider: Boolean,
 )
 
 internal fun hasContentToDisplay(state: CreateCredentialUiState): Boolean {
@@ -50,11 +47,12 @@
 )
 
 class EnabledProviderInfo(
-  icon: Drawable,
-  id: String,
-  displayName: String,
-  var createOptions: List<CreateOptionInfo>,
-  var remoteEntry: RemoteInfo?,
+    icon: Drawable,
+    id: String,
+    displayName: String,
+    // Sorted by last used time
+    var sortedCreateOptions: List<CreateOptionInfo>,
+    var remoteEntry: RemoteInfo?,
 ) : ProviderInfo(icon, id, displayName)
 
 class DisabledProviderInfo(
@@ -108,6 +106,7 @@
   val typeIcon: Drawable,
   val preferImmediatelyAvailableCredentials: Boolean,
   val appPreferredDefaultProviderId: String?,
+  val userSetDefaultProviderIds: Set<String>,
 )
 
 /**
@@ -123,7 +122,6 @@
 enum class CreateScreenState {
   PASSKEY_INTRO,
   MORE_ABOUT_PASSKEYS_INTRO,
-  PROVIDER_SELECTION,
   CREATION_OPTION_SELECTION,
   MORE_OPTIONS_SELECTION,
   DEFAULT_PROVIDER_CONFIRMATION,
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
index 4c5875b..7b17cbd 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
@@ -16,14 +16,14 @@
 
 package com.android.packageinstaller;
 
+import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
+
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
 
 import androidx.annotation.Nullable;
 
-import java.io.File;
-
 /**
  * Trampoline activity. Calls PackageInstallerActivity and deletes staged install file onResult.
  */
@@ -52,8 +52,13 @@
         super.onDestroy();
 
         if (isFinishing()) {
-            File sourceFile = new File(getIntent().getData().getPath());
-            new Thread(sourceFile::delete).start();
+            // While we expect PIA/InstallStaging to abandon/commit the session, still there
+            // might be cases when the session becomes orphan.
+            int sessionId = getIntent().getIntExtra(EXTRA_STAGED_SESSION_ID, 0);
+            try {
+                getPackageManager().getPackageInstaller().abandonSession(sessionId);
+            } catch (SecurityException ignored) {
+            }
         }
     }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index c6217ec..7bea339 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -16,17 +16,17 @@
 
 package com.android.packageinstaller;
 
+import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
+
 import android.app.PendingIntent;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.InstallInfo;
 import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
-import android.os.Process;
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
@@ -34,10 +34,7 @@
 import androidx.annotation.Nullable;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 
 /**
  * Send package to the package manager and handle results from package manager. Once the
@@ -77,7 +74,7 @@
                 .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
         mPackageURI = getIntent().getData();
 
-        if ("package".equals(mPackageURI.getScheme())) {
+        if (PackageInstallerActivity.SCHEME_PACKAGE.equals(mPackageURI.getScheme())) {
             try {
                 getPackageManager().installExistingPackage(appInfo.packageName);
                 launchSuccess();
@@ -86,6 +83,8 @@
                         PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
             }
         } else {
+            // ContentResolver.SCHEME_FILE
+            // STAGED_SESSION_ID extra contains an ID of a previously staged install session.
             final File sourceFile = new File(mPackageURI.getPath());
             PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
 
@@ -122,41 +121,6 @@
                     // Does not happen
                 }
             } else {
-                PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
-                        PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-                final Uri referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
-                params.setPackageSource(
-                        referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
-                                : PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE);
-                params.setInstallAsInstantApp(false);
-                params.setReferrerUri(referrerUri);
-                params.setOriginatingUri(getIntent()
-                        .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI));
-                params.setOriginatingUid(getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
-                        Process.INVALID_UID));
-                params.setInstallerPackageName(getIntent().getStringExtra(
-                        Intent.EXTRA_INSTALLER_PACKAGE_NAME));
-                params.setInstallReason(PackageManager.INSTALL_REASON_USER);
-
-                File file = new File(mPackageURI.getPath());
-                try {
-                    final InstallInfo result = getPackageManager().getPackageInstaller()
-                            .readInstallInfo(file, 0);
-                    params.setAppPackageName(result.getPackageName());
-                    params.setInstallLocation(result.getInstallLocation());
-                    try {
-                        params.setSize(result.calculateInstalledSize(params));
-                    } catch (IOException e) {
-                        e.printStackTrace();
-                        params.setSize(file.length());
-                    }
-                } catch (PackageInstaller.PackageParsingException e) {
-
-                    Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.", e);
-                    Log.e(LOG_TAG,
-                            "Cannot calculate installed size " + file + ". Try only apk size.");
-                    params.setSize(file.length());
-                }
                 try {
                     mInstallId = InstallEventReceiver
                             .addObserver(this, EventResultPersister.GENERATE_NEW_ID,
@@ -166,9 +130,14 @@
                             PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
                 }
 
-                try {
-                    mSessionId = getPackageManager().getPackageInstaller().createSession(params);
-                } catch (IOException e) {
+                mSessionId = getIntent().getIntExtra(EXTRA_STAGED_SESSION_ID, 0);
+                // Try to open session previously staged in InstallStaging.
+                try (PackageInstaller.Session ignored =
+                             getPackageManager().getPackageInstaller().openSession(
+                        mSessionId)) {
+                    Log.d(LOG_TAG, "Staged session is valid, proceeding with the install");
+                } catch (IOException | SecurityException e) {
+                    Log.e(LOG_TAG, "Invalid session id passed", e);
                     launchFailure(PackageInstaller.STATUS_FAILURE,
                             PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
                 }
@@ -293,57 +262,9 @@
 
         @Override
         protected PackageInstaller.Session doInBackground(Void... params) {
-            PackageInstaller.Session session;
             try {
-                session = getPackageManager().getPackageInstaller().openSession(mSessionId);
+                return getPackageManager().getPackageInstaller().openSession(mSessionId);
             } catch (IOException e) {
-                synchronized (this) {
-                    isDone = true;
-                    notifyAll();
-                }
-                return null;
-            }
-
-            session.setStagingProgress(0);
-
-            try {
-                File file = new File(mPackageURI.getPath());
-
-                try (InputStream in = new FileInputStream(file)) {
-                    long sizeBytes = file.length();
-                    long totalRead = 0;
-                    try (OutputStream out = session
-                            .openWrite("PackageInstaller", 0, sizeBytes)) {
-                        byte[] buffer = new byte[1024 * 1024];
-                        while (true) {
-                            int numRead = in.read(buffer);
-
-                            if (numRead == -1) {
-                                session.fsync(out);
-                                break;
-                            }
-
-                            if (isCancelled()) {
-                                session.close();
-                                break;
-                            }
-
-                            out.write(buffer, 0, numRead);
-                            if (sizeBytes > 0) {
-                                totalRead += numRead;
-                                float fraction = ((float) totalRead / (float) sizeBytes);
-                                session.setStagingProgress(fraction);
-                            }
-                        }
-                    }
-                }
-
-                return session;
-            } catch (IOException | SecurityException e) {
-                Log.e(LOG_TAG, "Could not write package", e);
-
-                session.close();
-
                 return null;
             } finally {
                 synchronized (this) {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index 68de2f6..097e47f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -16,6 +16,10 @@
 
 package com.android.packageinstaller;
 
+import static android.content.res.AssetFileDescriptor.UNKNOWN_LENGTH;
+
+import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
+
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -23,40 +27,49 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.util.Log;
 import android.view.View;
+import android.widget.ProgressBar;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 
 /**
- * If a package gets installed from an content URI this step loads the package and turns it into
- * and installation from a file. Then it re-starts the installation as usual.
+ * If a package gets installed from a content URI this step stages the installation session
+ * reading bytes from the URI.
  */
 public class InstallStaging extends AlertActivity {
     private static final String LOG_TAG = InstallStaging.class.getSimpleName();
 
-    private static final String STAGED_FILE = "STAGED_FILE";
+    private static final String STAGED_SESSION_ID = "STAGED_SESSION_ID";
+
+    private @Nullable PackageInstaller mInstaller;
 
     /** Currently running task that loads the file from the content URI into a file */
     private @Nullable StagingAsyncTask mStagingTask;
 
-    /** The file the package is in */
-    private @Nullable File mStagedFile;
+    /** The session the package is in */
+    private int mStagedSessionId;
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        mInstaller = getPackageManager().getPackageInstaller();
+
         setFinishOnTouchOutside(true);
         mAlert.setIcon(R.drawable.ic_file_download);
         mAlert.setTitle(getString(R.string.app_name_unknown));
@@ -66,6 +79,9 @@
                     if (mStagingTask != null) {
                         mStagingTask.cancel(true);
                     }
+
+                    cleanupStagingSession();
+
                     setResult(RESULT_CANCELED);
                     finish();
                 }, null);
@@ -73,11 +89,7 @@
         requireViewById(R.id.staging).setVisibility(View.VISIBLE);
 
         if (savedInstanceState != null) {
-            mStagedFile = new File(savedInstanceState.getString(STAGED_FILE));
-
-            if (!mStagedFile.exists()) {
-                mStagedFile = null;
-            }
+            mStagedSessionId = savedInstanceState.getInt(STAGED_SESSION_ID, 0);
         }
     }
 
@@ -85,21 +97,41 @@
     protected void onResume() {
         super.onResume();
 
-        // This is the first onResume in a single life of the activity
+        // This is the first onResume in a single life of the activity.
         if (mStagingTask == null) {
-            // File does not exist, or became invalid
-            if (mStagedFile == null) {
-                // Create file delayed to be able to show error
+            if (mStagedSessionId > 0) {
+                final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(
+                        mStagedSessionId);
+                if (info == null || !info.isActive() || info.getResolvedBaseApkPath() == null) {
+                    Log.w(LOG_TAG, "Session " + mStagedSessionId + " in funky state; ignoring");
+                    if (info != null) {
+                        cleanupStagingSession();
+                    }
+                    mStagedSessionId = 0;
+                }
+            }
+
+            // Session does not exist, or became invalid.
+            if (mStagedSessionId <= 0) {
+                // Create session here to be able to show error.
+                final Uri packageUri = getIntent().getData();
+                final AssetFileDescriptor afd = openAssetFileDescriptor(packageUri);
                 try {
-                    mStagedFile = TemporaryFileManager.getStagedFile(this);
+                    ParcelFileDescriptor pfd = afd != null ? afd.getParcelFileDescriptor() : null;
+                    PackageInstaller.SessionParams params = createSessionParams(
+                            mInstaller, getIntent(), pfd, packageUri.toString());
+                    mStagedSessionId = mInstaller.createSession(params);
                 } catch (IOException e) {
+                    Log.w(LOG_TAG, "Failed to create a staging session", e);
                     showError();
                     return;
+                } finally {
+                    PackageUtil.safeClose(afd);
                 }
             }
 
             mStagingTask = new StagingAsyncTask();
-            mStagingTask.execute(getIntent().getData());
+            mStagingTask.execute();
         }
     }
 
@@ -107,7 +139,7 @@
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
 
-        outState.putString(STAGED_FILE, mStagedFile.getPath());
+        outState.putInt(STAGED_SESSION_ID, mStagedSessionId);
     }
 
     @Override
@@ -119,6 +151,65 @@
         super.onDestroy();
     }
 
+    private AssetFileDescriptor openAssetFileDescriptor(Uri uri) {
+        try {
+            return getContentResolver().openAssetFileDescriptor(uri, "r");
+        } catch (Exception e) {
+            Log.w(LOG_TAG, "Failed to open asset file descriptor", e);
+            return null;
+        }
+    }
+
+    private static PackageInstaller.SessionParams createSessionParams(
+            @NonNull PackageInstaller installer, @NonNull Intent intent,
+            @Nullable ParcelFileDescriptor pfd, @NonNull String debugPathName) {
+        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+        final Uri referrerUri = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
+        params.setPackageSource(
+                referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
+                        : PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE);
+        params.setInstallAsInstantApp(false);
+        params.setReferrerUri(referrerUri);
+        params.setOriginatingUri(intent
+                .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI));
+        params.setOriginatingUid(intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
+                Process.INVALID_UID));
+        params.setInstallerPackageName(intent.getStringExtra(
+                Intent.EXTRA_INSTALLER_PACKAGE_NAME));
+        params.setInstallReason(PackageManager.INSTALL_REASON_USER);
+
+        if (pfd != null) {
+            try {
+                final PackageInstaller.InstallInfo result = installer.readInstallInfo(pfd,
+                        debugPathName, 0);
+                params.setAppPackageName(result.getPackageName());
+                params.setInstallLocation(result.getInstallLocation());
+                params.setSize(result.calculateInstalledSize(params, pfd));
+            } catch (PackageInstaller.PackageParsingException | IOException e) {
+                Log.e(LOG_TAG, "Cannot parse package " + debugPathName + ". Assuming defaults.", e);
+                Log.e(LOG_TAG,
+                        "Cannot calculate installed size " + debugPathName
+                                + ". Try only apk size.");
+                params.setSize(pfd.getStatSize());
+            }
+        } else {
+            Log.e(LOG_TAG, "Cannot parse package " + debugPathName + ". Assuming defaults.");
+        }
+        return params;
+    }
+
+    private void cleanupStagingSession() {
+        if (mStagedSessionId > 0) {
+            try {
+                mInstaller.abandonSession(mStagedSessionId);
+            } catch (SecurityException ignored) {
+
+            }
+            mStagedSessionId = 0;
+        }
+    }
+
     /**
      * Show an error message and set result as error.
      */
@@ -165,58 +256,109 @@
         }
     }
 
-    private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {
-        @Override
-        protected Boolean doInBackground(Uri... params) {
-            if (params == null || params.length <= 0) {
-                return false;
-            }
-            Uri packageUri = params[0];
-            try (InputStream in = getContentResolver().openInputStream(packageUri)) {
-                // Despite the comments in ContentResolver#openInputStream the returned stream can
-                // be null.
-                if (in == null) {
-                    return false;
-                }
+    private final class StagingAsyncTask extends
+            AsyncTask<Void, Integer, PackageInstaller.SessionInfo> {
+        private ProgressBar mProgressBar = null;
 
-                try (OutputStream out = new FileOutputStream(mStagedFile)) {
-                    byte[] buffer = new byte[1024 * 1024];
-                    int bytesRead;
-                    while ((bytesRead = in.read(buffer)) >= 0) {
-                        // Be nice and respond to a cancellation
-                        if (isCancelled()) {
-                            return false;
-                        }
-                        out.write(buffer, 0, bytesRead);
-                    }
-                }
-            } catch (IOException | SecurityException | IllegalStateException
-                    | IllegalArgumentException e) {
-                Log.w(LOG_TAG, "Error staging apk from content URI", e);
-                return false;
+        private long getContentSizeBytes() {
+            try (AssetFileDescriptor afd = openAssetFileDescriptor(getIntent().getData())) {
+                return afd != null ? afd.getLength() : UNKNOWN_LENGTH;
+            } catch (IOException ignored) {
+                return UNKNOWN_LENGTH;
             }
-            return true;
         }
 
         @Override
-        protected void onPostExecute(Boolean success) {
-            if (success) {
-                // Now start the installation again from a file
-                Intent installIntent = new Intent(getIntent());
-                installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
-                installIntent.setData(Uri.fromFile(mStagedFile));
+        protected void onPreExecute() {
+            final long sizeBytes = getContentSizeBytes();
 
-                if (installIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
-                    installIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+            mProgressBar = sizeBytes > 0 ? requireViewById(R.id.progress_indeterminate) : null;
+            if (mProgressBar != null) {
+                mProgressBar.setProgress(0);
+                mProgressBar.setMax(100);
+                mProgressBar.setIndeterminate(false);
+            }
+        }
+
+        @Override
+        protected PackageInstaller.SessionInfo doInBackground(Void... params) {
+            Uri packageUri = getIntent().getData();
+            try (PackageInstaller.Session session = mInstaller.openSession(mStagedSessionId);
+                 InputStream in = getContentResolver().openInputStream(packageUri)) {
+                session.setStagingProgress(0);
+
+                if (in == null) {
+                    return null;
                 }
 
-                installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-                startActivity(installIntent);
+                long sizeBytes = getContentSizeBytes();
 
-                InstallStaging.this.finish();
-            } else {
-                showError();
+                long totalRead = 0;
+                try (OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes)) {
+                    byte[] buffer = new byte[1024 * 1024];
+                    while (true) {
+                        int numRead = in.read(buffer);
+
+                        if (numRead == -1) {
+                            session.fsync(out);
+                            break;
+                        }
+
+                        if (isCancelled()) {
+                            break;
+                        }
+
+                        out.write(buffer, 0, numRead);
+                        if (sizeBytes > 0) {
+                            totalRead += numRead;
+                            float fraction = ((float) totalRead / (float) sizeBytes);
+                            session.setStagingProgress(fraction);
+                            publishProgress((int) (fraction * 100.0));
+                        }
+                    }
+                }
+
+                return mInstaller.getSessionInfo(mStagedSessionId);
+            } catch (IOException | SecurityException | IllegalStateException
+                     | IllegalArgumentException e) {
+                Log.w(LOG_TAG, "Error staging apk from content URI", e);
+                return null;
             }
         }
+
+        @Override
+        protected void onProgressUpdate(Integer... progress) {
+            if (mProgressBar != null && progress != null && progress.length > 0) {
+                mProgressBar.setProgress(progress[0], true);
+            }
+        }
+
+        @Override
+        protected void onPostExecute(PackageInstaller.SessionInfo sessionInfo) {
+            if (sessionInfo == null || !sessionInfo.isActive()
+                    || sessionInfo.getResolvedBaseApkPath() == null) {
+                Log.w(LOG_TAG, "Session info is invalid: " + sessionInfo);
+                cleanupStagingSession();
+                showError();
+                return;
+            }
+
+            // Pass the staged session to the installer.
+            Intent installIntent = new Intent(getIntent());
+            installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
+            installIntent.setData(Uri.fromFile(new File(sessionInfo.getResolvedBaseApkPath())));
+
+            installIntent.putExtra(EXTRA_STAGED_SESSION_ID, mStagedSessionId);
+
+            if (installIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
+                installIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+            }
+
+            installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+
+            startActivity(installIntent);
+
+            InstallStaging.this.finish();
+        }
     }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index ac32020..3f98867 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -77,7 +77,21 @@
         }
 
         final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
-        final int originatingUid = getOriginatingUid(sourceInfo);
+        // Uid of the source package, coming from ActivityManager
+        int callingUid = getLaunchedFromUid();
+        if (callingUid == Process.INVALID_UID) {
+            // Cannot reach ActivityManager. Aborting install.
+            Log.e(LOG_TAG, "Could not determine the launching uid.");
+        }
+        // Uid of the source package, with a preference to uid from ApplicationInfo
+        final int originatingUid = sourceInfo != null ? sourceInfo.uid : callingUid;
+
+        if (callingUid == Process.INVALID_UID && sourceInfo == null) {
+            mAbortInstall = true;
+        }
+
+        boolean isDocumentsManager = checkPermission(Manifest.permission.MANAGE_DOCUMENTS,
+                -1, callingUid) == PackageManager.PERMISSION_GRANTED;
         boolean isTrustedSource = false;
         if (sourceInfo != null && sourceInfo.isPrivilegedApp()) {
             isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false) || (
@@ -86,7 +100,8 @@
                             == PackageManager.PERMISSION_GRANTED);
         }
 
-        if (!isTrustedSource && originatingUid != Process.INVALID_UID) {
+        if (!isTrustedSource && !isSystemDownloadsProvider(callingUid) && !isDocumentsManager
+                && originatingUid != Process.INVALID_UID) {
             final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
             if (targetSdkVersion < 0) {
                 Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
@@ -144,14 +159,15 @@
 
             if (packageUri != null
                     && packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)
-                    && canPackageQuery(originatingUid, packageUri)) {
+                    && canPackageQuery(callingUid, packageUri)) {
                 // [IMPORTANT] This path is deprecated, but should still work. Only necessary
                 // features should be added.
 
-                // Copy file to prevent it from being changed underneath this process
+                // Stage a session with this file to prevent it from being changed underneath
+                // this process.
                 nextActivity.setClass(this, InstallStaging.class);
-            } else if (packageUri != null && packageUri.getScheme().equals(
-                    PackageInstallerActivity.SCHEME_PACKAGE)) {
+            } else if (packageUri != null && PackageInstallerActivity.SCHEME_PACKAGE.equals(
+                    packageUri.getScheme())) {
                 nextActivity.setClass(this, PackageInstallerActivity.class);
             } else {
                 Intent result = new Intent();
@@ -212,41 +228,6 @@
         return null;
     }
 
-    /**
-     * Get the originating uid if possible, or {@link Process#INVALID_UID} if not available
-     *
-     * @param sourceInfo The source of this installation
-     * @return The UID of the installation source or INVALID_UID
-     */
-    private int getOriginatingUid(@Nullable ApplicationInfo sourceInfo) {
-        // The originating uid from the intent. We only trust/use this if it comes from either
-        // the document manager app or the downloads provider
-        final int uidFromIntent = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
-                Process.INVALID_UID);
-
-        final int callingUid;
-        if (sourceInfo != null) {
-            callingUid = sourceInfo.uid;
-        } else {
-            callingUid = getLaunchedFromUid();
-            if (callingUid == Process.INVALID_UID) {
-                // Cannot reach ActivityManager. Aborting install.
-                Log.e(LOG_TAG, "Could not determine the launching uid.");
-                mAbortInstall = true;
-                return Process.INVALID_UID;
-            }
-        }
-        if (checkPermission(Manifest.permission.MANAGE_DOCUMENTS, -1, callingUid)
-                == PackageManager.PERMISSION_GRANTED) {
-            return uidFromIntent;
-        }
-        if (isSystemDownloadsProvider(callingUid)) {
-            return uidFromIntent;
-        }
-        // We don't trust uid from the intent. Use the calling uid instead.
-        return callingUid;
-    }
-
     private boolean isSystemDownloadsProvider(int uid) {
         final ProviderInfo downloadProviderPackage = getPackageManager().resolveContentProvider(
                 DOWNLOADS_AUTHORITY, 0);
@@ -260,8 +241,7 @@
     }
 
     @NonNull
-    private boolean canPackageQuery(int originatingUid, Uri packageUri) {
-        String callingPackage = mPackageManager.getPackagesForUid(originatingUid)[0];
+    private boolean canPackageQuery(int callingUid, Uri packageUri) {
         ProviderInfo info = mPackageManager.resolveContentProvider(packageUri.getAuthority(),
                 PackageManager.ComponentInfoFlags.of(0));
         if (info == null) {
@@ -269,11 +249,20 @@
         }
         String targetPackage = info.packageName;
 
-        try {
-            return mPackageManager.canPackageQuery(callingPackage, targetPackage);
-        } catch (PackageManager.NameNotFoundException e) {
+        String[] callingPackages = mPackageManager.getPackagesForUid(callingUid);
+        if (callingPackages == null) {
             return false;
         }
+        for (String callingPackage: callingPackages) {
+            try {
+                if (mPackageManager.canPackageQuery(callingPackage, targetPackage)) {
+                    return true;
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // no-op
+            }
+        }
+        return false;
     }
 
     private boolean isCallerSessionOwner(int originatingUid, int sessionId) {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 3ba2acb..7e294ee 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -82,6 +82,7 @@
     static final String EXTRA_CALLING_PACKAGE = "EXTRA_CALLING_PACKAGE";
     static final String EXTRA_CALLING_ATTRIBUTION_TAG = "EXTRA_CALLING_ATTRIBUTION_TAG";
     static final String EXTRA_ORIGINAL_SOURCE_INFO = "EXTRA_ORIGINAL_SOURCE_INFO";
+    static final String EXTRA_STAGED_SESSION_ID = "EXTRA_STAGED_SESSION_ID";
     private static final String ALLOW_UNKNOWN_SOURCES_KEY =
             PackageInstallerActivity.class.getName() + "ALLOW_UNKNOWN_SOURCES_KEY";
 
@@ -403,6 +404,10 @@
             mReferrerURI = null;
             mPendingUserActionReason = info.getPendingUserActionReason();
         } else {
+            // Two possible callers:
+            // 1. InstallStart with "SCHEME_PACKAGE".
+            // 2. InstallStaging with "SCHEME_FILE" and EXTRA_STAGED_SESSION_ID with staged
+            // session id.
             mSessionId = -1;
             packageSource = intent.getData();
             mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
@@ -721,14 +726,16 @@
     }
 
     private void startInstall() {
+        String installerPackageName = getIntent().getStringExtra(
+                Intent.EXTRA_INSTALLER_PACKAGE_NAME);
+        int stagedSessionId = getIntent().getIntExtra(EXTRA_STAGED_SESSION_ID, 0);
+
         // Start subactivity to actually install the application
         Intent newIntent = new Intent();
         newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
                 mPkgInfo.applicationInfo);
         newIntent.setData(mPackageURI);
         newIntent.setClass(this, InstallInstalling.class);
-        String installerPackageName = getIntent().getStringExtra(
-                Intent.EXTRA_INSTALLER_PACKAGE_NAME);
         if (mOriginatingURI != null) {
             newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
         }
@@ -745,6 +752,9 @@
         if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
             newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
         }
+        if (stagedSessionId > 0) {
+            newIntent.putExtra(EXTRA_STAGED_SESSION_ID, stagedSessionId);
+        }
         newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
         if (mLocalLOGV) Log.i(TAG, "downloaded app uri=" + mPackageURI);
         startActivity(newIntent);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
index 698159f..0270591 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
@@ -33,7 +33,9 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import java.io.Closeable;
 import java.io.File;
+import java.io.IOException;
 
 /**
  * This is a utility class for defining some utility methods and constants
@@ -190,4 +192,19 @@
         }
         return targetSdkVersion;
     }
+
+
+    /**
+     * Quietly close a closeable resource (e.g. a stream or file). The input may already
+     * be closed and it may even be null.
+     */
+    static void safeClose(Closeable resource) {
+        if (resource != null) {
+            try {
+                resource.close();
+            } catch (IOException ioe) {
+                // Catch and discard the error
+            }
+        }
+    }
 }
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 01f92c4..19b7e85 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -17,6 +17,7 @@
 package com.android.settingslib.collapsingtoolbar;
 
 import android.app.ActionBar;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -59,7 +60,8 @@
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        if (mCustomizeLayoutResId > 0 && !BuildCompatUtils.isAtLeastS()) {
+        // for backward compatibility on R devices or wearable devices due to small device size.
+        if (mCustomizeLayoutResId > 0 && (!BuildCompatUtils.isAtLeastS() || isWatch())) {
             super.setContentView(mCustomizeLayoutResId);
             return;
         }
@@ -157,6 +159,14 @@
         return getToolbarDelegate().getAppBarLayout();
     }
 
+    private boolean isWatch() {
+        PackageManager packageManager = getPackageManager();
+        if (packageManager == null) {
+            return false;
+        }
+        return packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH);
+    }
+
     private CollapsingToolbarDelegate getToolbarDelegate() {
         if (mToolbardelegate == null) {
             mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback());
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java b/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
index 3c45112..dae48db 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
@@ -17,6 +17,7 @@
 package com.android.settingslib.applications;
 
 import android.app.AppGlobals;
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -94,6 +95,10 @@
 
     }
 
+    public @Nullable String getSummary() {
+        return this.summary;
+    }
+
     @Override
     public Drawable loadIcon() {
         final IconDrawableFactory factory = IconDrawableFactory.newInstance(mContext);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index c9e8312..6cf6825 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -50,6 +50,7 @@
 import android.util.Log;
 
 import androidx.annotation.DoNotInline;
+import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -638,6 +639,11 @@
         }
 
         @Override
+        public void onSessionReleased(@NonNull RoutingSessionInfo session) {
+            refreshDevices();
+        }
+
+        @Override
         public void onRouteListingPreferenceUpdated(
                 String packageName,
                 RouteListingPreference routeListingPreference) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 270fda8..0969327 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -141,6 +141,56 @@
     }
 
     @Test
+    public void onSessionReleased_shouldUpdateConnectedDevice() {
+        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+        final RoutingSessionInfo sessionInfo1 = mock(RoutingSessionInfo.class);
+        routingSessionInfos.add(sessionInfo1);
+        final RoutingSessionInfo sessionInfo2 = mock(RoutingSessionInfo.class);
+        routingSessionInfos.add(sessionInfo2);
+
+        final List<String> selectedRoutesSession1 = new ArrayList<>();
+        selectedRoutesSession1.add(TEST_ID_1);
+        when(sessionInfo1.getSelectedRoutes()).thenReturn(selectedRoutesSession1);
+
+        final List<String> selectedRoutesSession2 = new ArrayList<>();
+        selectedRoutesSession2.add(TEST_ID_2);
+        when(sessionInfo2.getSelectedRoutes()).thenReturn(selectedRoutesSession2);
+
+        mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
+
+        final MediaRoute2Info info1 = mock(MediaRoute2Info.class);
+        when(info1.getId()).thenReturn(TEST_ID_1);
+        when(info1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        final MediaRoute2Info info2 = mock(MediaRoute2Info.class);
+        when(info2.getId()).thenReturn(TEST_ID_2);
+        when(info2.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        final List<MediaRoute2Info> routes = new ArrayList<>();
+        routes.add(info1);
+        routes.add(info2);
+        mShadowRouter2Manager.setAllRoutes(routes);
+        mShadowRouter2Manager.setTransferableRoutes(routes);
+
+        final MediaDevice mediaDevice1 = mInfoMediaManager.findMediaDevice(TEST_ID_1);
+        assertThat(mediaDevice1).isNull();
+        final MediaDevice mediaDevice2 = mInfoMediaManager.findMediaDevice(TEST_ID_2);
+        assertThat(mediaDevice2).isNull();
+
+        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
+        final MediaDevice infoDevice1 = mInfoMediaManager.mMediaDevices.get(0);
+        assertThat(infoDevice1.getId()).isEqualTo(TEST_ID_1);
+        final MediaDevice infoDevice2 = mInfoMediaManager.mMediaDevices.get(1);
+        assertThat(infoDevice2.getId()).isEqualTo(TEST_ID_2);
+        // The active routing session is the last one in the list, which maps to infoDevice2.
+        assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice2);
+
+        routingSessionInfos.remove(sessionInfo2);
+        mInfoMediaManager.mMediaRouterCallback.onSessionReleased(sessionInfo2);
+        assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice1);
+    }
+
+    @Test
     public void onRouteAdded_buildAllRoutes_shouldAddMediaDevice() {
         final MediaRoute2Info info = mock(MediaRoute2Info.class);
         when(info.getId()).thenReturn(TEST_ID);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index e4cc9f1..6a5535d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -100,6 +100,5 @@
         Settings.System.CAMERA_FLASH_NOTIFICATION,
         Settings.System.SCREEN_FLASH_NOTIFICATION,
         Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
-        Settings.System.SMOOTH_DISPLAY
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 4b72063..85623b2 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -226,6 +226,5 @@
         VALIDATORS.put(System.CAMERA_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION_COLOR, ANY_INTEGER_VALIDATOR);
-        VALIDATORS.put(System.SMOOTH_DISPLAY, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index d1bd5e6..46b45d1 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -34,7 +34,6 @@
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 import static com.android.internal.accessibility.util.AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM;
-import static com.android.internal.display.RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE;
 import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX;
 import static com.android.providers.settings.SettingsState.getTypeFromKey;
 import static com.android.providers.settings.SettingsState.getUserIdFromKey;
@@ -5674,7 +5673,7 @@
                             providers.addAll(Arrays.asList(resources.getStringArray(resourceId)));
                         } catch (Resources.NotFoundException e) {
                             Slog.w(LOG_TAG,
-                                    "Get default array Cred Provider not found: " + e.toString());
+                                "Get default array Cred Provider not found: " + e.toString());
                         }
                         try {
                             final String storedValue = resources.getString(resourceId);
@@ -5683,7 +5682,7 @@
                             }
                         } catch (Resources.NotFoundException e) {
                             Slog.w(LOG_TAG,
-                                    "Get default Cred Provider not found: " + e.toString());
+                                "Get default Cred Provider not found: " + e.toString());
                         }
 
                         if (!providers.isEmpty()) {
@@ -5732,8 +5731,8 @@
                     final Setting currentSetting = secureSettings
                             .getSettingLocked(Settings.Secure.CREDENTIAL_SERVICE);
                     if (currentSetting.isNull()) {
-                        final int resourceId = com.android.internal.R.array
-                                .config_defaultCredentialProviderService;
+                        final int resourceId =
+                            com.android.internal.R.array.config_defaultCredentialProviderService;
                         final Resources resources = getContext().getResources();
                         // If the config has not be defined we might get an exception.
                         final List<String> providers = new ArrayList<>();
@@ -5741,7 +5740,7 @@
                             providers.addAll(Arrays.asList(resources.getStringArray(resourceId)));
                         } catch (Resources.NotFoundException e) {
                             Slog.w(LOG_TAG,
-                                    "Get default array Cred Provider not found: " + e.toString());
+                                "Get default array Cred Provider not found: " + e.toString());
                         }
 
                         if (!providers.isEmpty()) {
@@ -5840,44 +5839,12 @@
                     currentVersion = 218;
                 }
 
-                // v218: Convert Smooth Display and Force Peak Refresh Rate to a boolean
                 if (currentVersion == 218) {
-                    final String peakRefreshRateSettingName = "peak_refresh_rate";
-                    final String minRefreshRateSettingName = "min_refresh_rate";
-
-                    final SettingsState systemSettings = getSystemSettingsLocked(userId);
-                    final Setting peakRefreshRateSetting =
-                            systemSettings.getSettingLocked(peakRefreshRateSettingName);
-                    final Setting minRefreshRateSetting =
-                            systemSettings.getSettingLocked(minRefreshRateSettingName);
-
-                    float peakRefreshRate = DEFAULT_REFRESH_RATE;
-                    float minRefreshRate = 0;
-                    try {
-                        if (!peakRefreshRateSetting.isNull()) {
-                            peakRefreshRate = Float.parseFloat(peakRefreshRateSetting.getValue());
-                        }
-                    } catch (NumberFormatException e) {
-                        // Do nothing. Overwrite with default value.
-                    }
-                    try {
-                        if (!minRefreshRateSetting.isNull()) {
-                            minRefreshRate = Float.parseFloat(minRefreshRateSetting.getValue());
-                        }
-                    } catch (NumberFormatException e) {
-                        // Do nothing. Overwrite with default value.
-                    }
-
-                    systemSettings.deleteSettingLocked(peakRefreshRateSettingName);
-                    systemSettings.deleteSettingLocked(minRefreshRateSettingName);
-
-                    systemSettings.insertSettingLocked(Settings.System.SMOOTH_DISPLAY,
-                            peakRefreshRate > DEFAULT_REFRESH_RATE ? "1" : "0", /* tag= */ null,
-                            /* makeDefault= */ false, SettingsState.SYSTEM_PACKAGE_NAME);
-                    systemSettings.insertSettingLocked(Settings.System.FORCE_PEAK_REFRESH_RATE,
-                            minRefreshRate > 0 ? "1" : "0", /* tag= */ null,
-                            /* makeDefault= */ false, SettingsState.SYSTEM_PACKAGE_NAME);
-
+                    // Version 219: Removed
+                    // TODO(b/211737588): Back up the Smooth Display setting
+                    // Future upgrades to the `peak_refresh_rate` and `min_refresh_rate` settings
+                    // should account for the database in a non-upgraded and upgraded (change id:
+                    // Ib2cb2dd100f06f5452083b7606109a486e795a0e) state.
                     currentVersion = 219;
                 }
 
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 36aa2ac..2e49dd5 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -97,7 +97,8 @@
                     Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
                     Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
                     Settings.System.DESKTOP_MODE, // developer setting for internal prototyping
-                    Settings.System.FORCE_PEAK_REFRESH_RATE, // depends on hardware capabilities
+                    Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities
+                    Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities
                     Settings.System.SCREEN_BRIGHTNESS_FLOAT,
                     Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
                     Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific
@@ -840,7 +841,8 @@
                  Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
                  Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
                  Settings.Secure.UI_TRANSLATION_ENABLED,
-                 Settings.Secure.CREDENTIAL_SERVICE);
+                 Settings.Secure.CREDENTIAL_SERVICE,
+                 Settings.Secure.CREDENTIAL_SERVICE_PRIMARY);
 
     @Test
     public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index a110f56..8b3fd41 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -17,12 +17,12 @@
  */
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.shell"
-        coreApp="true"
-        android:sharedUserId="android.uid.shell"
-        >
+   package="com.android.shell"
+   coreApp="true"
+   android:sharedUserId="android.uid.shell"
+   >
 
-        <!-- Standard permissions granted to the shell. -->
+    <!-- Standard permissions granted to the shell. -->
     <uses-permission android:name="android.permission.MANAGE_HEALTH_PERMISSIONS" />
     <uses-permission android:name="android.permission.MANAGE_HEALTH_DATA" />
     <uses-permission android:name="android.permission.health.READ_EXERCISE_ROUTE" />
@@ -125,7 +125,7 @@
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
-    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
     <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
     <!-- BLUETOOTH_PRIVILEGED is needed for testing purposes only. -->
     <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
@@ -136,7 +136,7 @@
     <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
     <uses-permission android:name="android.permission.MANAGE_USB" />
     <uses-permission android:name="android.permission.USE_RESERVED_DISK" />
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
     <!-- System tool permissions granted to the shell. -->
     <uses-permission android:name="android.permission.REAL_GET_TASKS" />
     <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
@@ -231,16 +231,16 @@
     <uses-permission android:name="android.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS" />
     <uses-permission android:name="android.permission.CLEAR_FREEZE_PERIOD" />
     <uses-permission android:name="android.permission.MODIFY_QUIET_MODE" />
-    <uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE"/>
-    <uses-permission android:name="android.permission.CHANGE_LOWPAN_STATE"/>
-    <uses-permission android:name="android.permission.READ_LOWPAN_CREDENTIAL"/>
+    <uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_LOWPAN_STATE" />
+    <uses-permission android:name="android.permission.READ_LOWPAN_CREDENTIAL" />
     <uses-permission android:name="android.permission.BLUETOOTH_STACK" />
     <uses-permission android:name="android.permission.GET_ACCOUNTS" />
     <uses-permission android:name="android.permission.RETRIEVE_WINDOW_TOKEN" />
     <uses-permission android:name="android.permission.FRAME_STATS" />
     <uses-permission android:name="android.permission.BIND_APPWIDGET" />
     <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
-    <uses-permission android:name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
+    <uses-permission android:name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS" />
     <uses-permission android:name="android.permission.CHANGE_APP_IDLE_STATE" />
     <uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
     <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
@@ -304,7 +304,7 @@
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.BACKGROUND_CAMERA" />
     <uses-permission android:name="android.permission.SYSTEM_CAMERA" />
-      <!-- Permissions needed to test onCameraOpened/Closed callbacks -->
+    <!-- Permissions needed to test onCameraOpened/Closed callbacks -->
     <uses-permission android:name="android.permission.CAMERA_OPEN_CLOSE_LISTENER" />
     <!-- Permissions needed for CTS camera test: RecordingTest.java when assuming shell id -->
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
@@ -344,7 +344,7 @@
     <uses-permission android:name="android.permission.LOADER_USAGE_STATS" />
 
     <!-- Permission required for storage tests - FuseDaemonHostTest -->
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
 
     <!-- Permission needed to run network tests in CTS -->
     <uses-permission android:name="android.permission.MANAGE_TEST_NETWORKS" />
@@ -386,54 +386,54 @@
     <uses-permission android:name="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE" />
 
     <!-- Permission required for CTS test - CrossProfileAppsHostSideTest -->
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES" />
 
     <!-- Permission required for CTS test - CrossProfileAppsHostSideTest -->
-    <uses-permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"/>
+    <uses-permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES" />
 
     <!-- permissions required for CTS test - PhoneStateListenerTest -->
     <uses-permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH" />
 
     <!-- Permissions required for granting and logging -->
-    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
-    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
-    <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/>
-    <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
+    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+    <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG" />
+    <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD" />
 
     <!-- Permission required for CTS test - BatterySaverTest -->
-    <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
+    <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" />
 
     <!-- Permission required for CTS test - UiModeManagerTest -->
-    <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/>
-    <uses-permission android:name="android.permission.READ_PROJECTION_STATE"/>
+    <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED" />
+    <uses-permission android:name="android.permission.READ_PROJECTION_STATE" />
 
     <!-- Permission required for CTS tests - UiModeManagerTest, CarModeInCallServiceTest -->
-    <uses-permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION"/>
+    <uses-permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION" />
 
     <!-- Permission required for CTS test - SystemConfigTest -->
-    <uses-permission android:name="android.permission.READ_CARRIER_APP_INFO"/>
+    <uses-permission android:name="android.permission.READ_CARRIER_APP_INFO" />
 
     <!-- Permission required for CTS test - CarModeInCallServiceTest -->
-    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
 
     <!-- Permission requried for CTS test - CellBroadcastIntentsTest -->
-    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS"/>
+    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS" />
 
     <!-- Permission required for CTS test - TetheringManagerTest -->
-    <uses-permission android:name="android.permission.TETHER_PRIVILEGED"/>
+    <uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
 
     <!-- Permission required for CTS test - CtsOsTestCases -->
-    <uses-permission android:name="android.permission.MANAGE_CRATES"/>
+    <uses-permission android:name="android.permission.MANAGE_CRATES" />
 
     <!-- Allows setting brightness from the shell -->
-    <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
+    <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
 
     <!-- Permission required for CTS test - ShortcutManagerUsageTest -->
-    <uses-permission android:name="android.permission.ACCESS_SHORTCUTS"/>
+    <uses-permission android:name="android.permission.ACCESS_SHORTCUTS" />
 
     <!-- Permissions required for CTS test - UsageStatsTest -->
-    <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS"/>
-    <uses-permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"/>
+    <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" />
+    <uses-permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS" />
 
     <!-- Permission needed for CTS test - MusicRecognitionManagerTest -->
     <uses-permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION" />
@@ -442,8 +442,8 @@
     <uses-permission android:name="android.permission.MANAGE_SPEECH_RECOGNITION" />
 
     <!-- Permissions required to test ambient display. -->
-    <uses-permission android:name="android.permission.READ_DREAM_STATE"/>
-    <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/>
+    <uses-permission android:name="android.permission.READ_DREAM_STATE" />
+    <uses-permission android:name="android.permission.WRITE_DREAM_STATE" />
 
     <!-- Permission required for CTS test - CtsLightsManagerTest -->
     <uses-permission android:name="android.permission.CONTROL_DEVICE_LIGHTS" />
@@ -470,7 +470,7 @@
     <uses-permission android:name="android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE" />
 
     <!-- Permission required for testing system audio effect APIs. -->
-    <uses-permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS"/>
+    <uses-permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" />
 
     <!-- Permission required for running networking unit tests -->
     <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
@@ -495,7 +495,7 @@
     <uses-permission android:name="android.permission.TV_INPUT_HARDWARE" />
     <uses-permission android:name="android.permission.TIS_EXTENSION_INTERFACE" />
     <uses-permission android:name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS" />
-    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA"/>
+    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
 
     <!-- Permission needed for CTS test - PrivilegedLocationPermissionTest -->
     <uses-permission android:name="android.permission.LOCATION_HARDWARE" />
@@ -560,14 +560,14 @@
     <uses-permission android:name="android.permission.BIND_CARRIER_SERVICES" />
 
     <!-- Allows overriding the system's device state from the shell -->
-    <uses-permission android:name="android.permission.CONTROL_DEVICE_STATE"/>
+    <uses-permission android:name="android.permission.CONTROL_DEVICE_STATE" />
 
     <!-- Permissions required for CTS tests to close system dialogs -->
     <uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
 
     <!-- Permissions required for CTS test - HideOverlayWindowsTest -->
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
-    <uses-permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY" />
 
     <!-- Permission required for CTS test - CtsHdmiCecHostTestCases -->
     <uses-permission android:name="android.permission.HDMI_CEC" />
@@ -630,21 +630,21 @@
     <uses-permission android:name="android.permission.UPDATE_FONTS" />
 
     <!-- Permission required for Launcher testing - DigitalWellbeingToastTest -->
-    <uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO"/>
+    <uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" />
 
     <!-- Permission required for hotword detection service CTS tests -->
     <uses-permission android:name="android.permission.MANAGE_HOTWORD_DETECTION" />
     <uses-permission android:name="android.permission.BIND_HOTWORD_DETECTION_SERVICE" />
 
     <!-- Permission required for CTS test - CtsVoiceInteractionTestCases -->
-    <uses-permission android:name="android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER"/>
+    <uses-permission android:name="android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER" />
 
     <uses-permission android:name="android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE" />
 
     <!-- Permission required for CTS test - KeyguardLockedStateApiTest -->
     <uses-permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
 
-    <uses-permission android:name="android.permission.MANAGE_APP_HIBERNATION"/>
+    <uses-permission android:name="android.permission.MANAGE_APP_HIBERNATION" />
 
     <!-- Permission required for CTS test - MediaCodecResourceTest -->
     <uses-permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" />
@@ -820,8 +820,8 @@
     <uses-permission android:name="android.permission.DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA" />
     <uses-permission android:name="android.permission.STAGE_HEALTH_CONNECT_REMOTE_DATA" />
 
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
 
     <!-- Permissions required for CTS test - CtsBroadcastRadioTestCases -->
     <uses-permission android:name="android.permission.ACCESS_BROADCAST_RADIO" />
@@ -832,12 +832,17 @@
     <!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
     <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
 
-    <uses-permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE"/>
+    <uses-permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE" />
+    <!-- Permission required for GTS test - GtsAttestationVerificationDeviceSideTestCases -->
+    <uses-permission android:name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE" />
+    <!-- Permission required for GTS test - GtsCredentialsTestCases -->
+    <uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR" />
 
-    <application android:label="@string/app_label"
-                android:theme="@android:style/Theme.DeviceDefault.DayNight"
-                android:defaultToDeviceProtectedStorage="true"
-                android:directBootAware="true">
+    <application
+        android:label="@string/app_label"
+        android:theme="@android:style/Theme.DeviceDefault.DayNight"
+        android:defaultToDeviceProtectedStorage="true"
+        android:directBootAware="true">
         <provider
             android:name="androidx.core.content.FileProvider"
             android:authorities="com.android.shell"
@@ -860,10 +865,11 @@
             </intent-filter>
         </provider>
 
-        <provider android:name=".HeapDumpProvider"
-                  android:authorities="com.android.shell.heapdump"
-                  android:grantUriPermissions="true"
-                  android:exported="false" />
+        <provider
+            android:name=".HeapDumpProvider"
+            android:authorities="com.android.shell.heapdump"
+            android:grantUriPermissions="true"
+            android:exported="false" />
 
         <activity
             android:name=".BugreportWarningActivity"
@@ -872,13 +878,14 @@
             android:excludeFromRecents="true"
             android:exported="false" />
 
-        <activity android:name=".HeapDumpActivity"
-                  android:theme="@*android:style/Theme.Translucent.NoTitleBar"
-                  android:label="@*android:string/dump_heap_title"
-                  android:finishOnCloseSystemDialogs="true"
-                  android:noHistory="true"
-                  android:excludeFromRecents="true"
-                  android:exported="false" />
+        <activity
+            android:name=".HeapDumpActivity"
+            android:theme="@*android:style/Theme.Translucent.NoTitleBar"
+            android:label="@*android:string/dump_heap_title"
+            android:finishOnCloseSystemDialogs="true"
+            android:noHistory="true"
+            android:excludeFromRecents="true"
+            android:exported="false" />
 
         <receiver
             android:name=".BugreportRequestedReceiver"
@@ -903,7 +910,7 @@
         <receiver
             android:name=".ProfcollectUploadReceiver"
             android:exported="true"
-            android:permission="android.permission.TRIGGER_SHELL_PROFCOLLECT_UPLOAD" >
+            android:permission="android.permission.TRIGGER_SHELL_PROFCOLLECT_UPLOAD">
             <intent-filter>
                 <action android:name="com.android.shell.action.PROFCOLLECT_UPLOAD" />
             </intent-filter>
@@ -912,6 +919,6 @@
         <service
             android:name=".BugreportProgressService"
             android:foregroundServiceType="systemExempted"
-            android:exported="false"/>
+            android:exported="false" />
     </application>
 </manifest>
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index e6bbf97..6a7b8cd 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -158,6 +158,7 @@
         "SystemUIAnimationLib",
         "SystemUICommon",
         "SystemUICustomizationLib",
+        "SystemUILogLib",
         "SystemUIPluginLib",
         "SystemUISharedLib",
         "SystemUI-statsd",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6e55000..a27f113 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -82,6 +82,7 @@
     <uses-permission android:name="android.permission.CONTROL_VPN" />
     <uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/>
     <uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL"/>
+    <uses-permission android:name="android.permission.LOCATION_HARDWARE" />
     <!-- Physical hardware -->
     <uses-permission android:name="android.permission.MANAGE_USB" />
     <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
@@ -992,6 +993,18 @@
             android:excludeFromRecents="true"
             android:resizeableActivity="false"
             android:theme="@android:style/Theme.NoDisplay" />
+
+        <activity
+            android:name=".notetask.LaunchNotesRoleSettingsTrampolineActivity"
+            android:exported="true"
+            android:excludeFromRecents="true"
+            android:resizeableActivity="false"
+            android:theme="@android:style/Theme.NoDisplay" >
+            <intent-filter>
+                <action android:name="com.android.systemui.action.MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
         <!-- endregion -->
 
         <!-- started from ControlsRequestReceiver -->
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 77ddc6e..1ce3472 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -55,6 +55,7 @@
 madym@google.com
 mankoff@google.com
 mateuszc@google.com
+mgalhardo@google.com
 michaelmikhil@google.com
 michschn@google.com
 mkephart@google.com
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 5b5871f..8eb012d 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -43,6 +43,7 @@
         "androidx.core_core-ktx",
         "androidx.annotation_annotation",
         "SystemUIShaderLib",
+        "animationlib",
     ],
 
     manifest: "AndroidManifest.xml",
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 94b3740..4037fd4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -39,9 +39,9 @@
 import android.view.animation.PathInterpolator
 import androidx.annotation.BinderThread
 import androidx.annotation.UiThread
+import com.android.app.animation.Interpolators
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.policy.ScreenDecorationsUtils
-import java.lang.IllegalArgumentException
 import kotlin.math.roundToInt
 
 private const val TAG = "ActivityLaunchAnimator"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 42a8636..48dd08f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -33,10 +33,10 @@
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
 import android.widget.FrameLayout
+import com.android.app.animation.Interpolators
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.jank.InteractionJankMonitor.CujType
 import com.android.systemui.util.registerAnimationOnBackInvoked
-import java.lang.IllegalArgumentException
 import kotlin.math.roundToInt
 
 private const val TAG = "DialogLaunchAnimator"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
deleted file mode 100644
index 9dbb920..0000000
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2016 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.animation;
-
-import android.graphics.Path;
-import android.util.MathUtils;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.BounceInterpolator;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.PathInterpolator;
-
-/**
- * Utility class to receive interpolators from.
- *
- * Make sure that changes made to this class are also reflected in {@link InterpolatorsAndroidX}.
- * Please consider using the androidx dependencies featuring better testability altogether.
- */
-public class Interpolators {
-
-    /*
-     * ============================================================================================
-     * Emphasized interpolators.
-     * ============================================================================================
-     */
-
-    /**
-     * The default emphasized interpolator. Used for hero / emphasized movement of content.
-     */
-    public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
-
-    /**
-     * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
-     * is disappearing e.g. when moving off screen.
-     */
-    public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
-            0.3f, 0f, 0.8f, 0.15f);
-
-    /**
-     * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
-     * is appearing e.g. when coming from off screen
-     */
-    public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
-            0.05f, 0.7f, 0.1f, 1f);
-
-
-    /*
-     * ============================================================================================
-     * Standard interpolators.
-     * ============================================================================================
-     */
-
-    /**
-     * The standard interpolator that should be used on every normal animation
-     */
-    public static final Interpolator STANDARD = new PathInterpolator(
-            0.2f, 0f, 0f, 1f);
-
-    /**
-     * The standard accelerating interpolator that should be used on every regular movement of
-     * content that is disappearing e.g. when moving off screen.
-     */
-    public static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(
-            0.3f, 0f, 1f, 1f);
-
-    /**
-     * The standard decelerating interpolator that should be used on every regular movement of
-     * content that is appearing e.g. when coming from off screen.
-     */
-    public static final Interpolator STANDARD_DECELERATE = new PathInterpolator(
-            0f, 0f, 0f, 1f);
-
-    /*
-     * ============================================================================================
-     * Legacy
-     * ============================================================================================
-     */
-
-    /**
-     * The default legacy interpolator as defined in Material 1. Also known as FAST_OUT_SLOW_IN.
-     */
-    public static final Interpolator LEGACY = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
-
-    /**
-     * The default legacy accelerating interpolator as defined in Material 1.
-     * Also known as FAST_OUT_LINEAR_IN.
-     */
-    public static final Interpolator LEGACY_ACCELERATE = new PathInterpolator(0.4f, 0f, 1f, 1f);
-
-    /**
-     * The default legacy decelerating interpolator as defined in Material 1.
-     * Also known as LINEAR_OUT_SLOW_IN.
-     */
-    public static final Interpolator LEGACY_DECELERATE = new PathInterpolator(0f, 0f, 0.2f, 1f);
-
-    /**
-     * Linear interpolator. Often used if the interpolator is for different properties who need
-     * different interpolations.
-     */
-    public static final Interpolator LINEAR = new LinearInterpolator();
-
-    /*
-    * ============================================================================================
-    * Custom interpolators
-    * ============================================================================================
-    */
-
-    public static final Interpolator FAST_OUT_SLOW_IN = LEGACY;
-    public static final Interpolator FAST_OUT_LINEAR_IN = LEGACY_ACCELERATE;
-    public static final Interpolator LINEAR_OUT_SLOW_IN = LEGACY_DECELERATE;
-
-    /**
-     * Like {@link #FAST_OUT_SLOW_IN}, but used in case the animation is played in reverse (i.e. t
-     * goes from 1 to 0 instead of 0 to 1).
-     */
-    public static final Interpolator FAST_OUT_SLOW_IN_REVERSE =
-            new PathInterpolator(0.8f, 0f, 0.6f, 1f);
-    public static final Interpolator SLOW_OUT_LINEAR_IN = new PathInterpolator(0.8f, 0f, 1f, 1f);
-    public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
-    public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
-    public static final Interpolator ACCELERATE = new AccelerateInterpolator();
-    public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();
-    public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f);
-    public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f);
-    public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
-    public static final Interpolator ICON_OVERSHOT_LESS = new PathInterpolator(0.4f, 0f, 0.2f,
-            1.1f);
-    public static final Interpolator PANEL_CLOSE_ACCELERATED = new PathInterpolator(0.3f, 0, 0.5f,
-            1);
-    public static final Interpolator BOUNCE = new BounceInterpolator();
-    /**
-     * For state transitions on the control panel that lives in GlobalActions.
-     */
-    public static final Interpolator CONTROL_STATE = new PathInterpolator(0.4f, 0f, 0.2f,
-            1.0f);
-
-    /**
-     * Interpolator to be used when animating a move based on a click. Pair with enough duration.
-     */
-    public static final Interpolator TOUCH_RESPONSE =
-            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
-
-    /**
-     * Like {@link #TOUCH_RESPONSE}, but used in case the animation is played in reverse (i.e. t
-     * goes from 1 to 0 instead of 0 to 1).
-     */
-    public static final Interpolator TOUCH_RESPONSE_REVERSE =
-            new PathInterpolator(0.9f, 0f, 0.7f, 1f);
-
-    /*
-     * ============================================================================================
-     * Functions / Utilities
-     * ============================================================================================
-     */
-
-    /**
-     * Calculate the amount of overshoot using an exponential falloff function with desired
-     * properties, where the overshoot smoothly transitions at the 1.0f boundary into the
-     * overshoot, retaining its acceleration.
-     *
-     * @param progress a progress value going from 0 to 1
-     * @param overshootAmount the amount > 0 of overshoot desired. A value of 0.1 means the max
-     *                        value of the overall progress will be at 1.1.
-     * @param overshootStart the point in (0,1] where the result should reach 1
-     * @return the interpolated overshoot
-     */
-    public static float getOvershootInterpolation(float progress, float overshootAmount,
-            float overshootStart) {
-        if (overshootAmount == 0.0f || overshootStart == 0.0f) {
-            throw new IllegalArgumentException("Invalid values for overshoot");
-        }
-        float b = MathUtils.log((overshootAmount + 1) / (overshootAmount)) / overshootStart;
-        return MathUtils.max(0.0f,
-                (float) (1.0f - Math.exp(-b * progress)) * (overshootAmount + 1.0f));
-    }
-
-    /**
-     * Similar to {@link #getOvershootInterpolation(float, float, float)} but the overshoot
-     * starts immediately here, instead of first having a section of non-overshooting
-     *
-     * @param progress a progress value going from 0 to 1
-     */
-    public static float getOvershootInterpolation(float progress) {
-        return MathUtils.max(0.0f, (float) (1.0f - Math.exp(-4 * progress)));
-    }
-
-    // Create the default emphasized interpolator
-    private static PathInterpolator createEmphasizedInterpolator() {
-        Path path = new Path();
-        // Doing the same as fast_out_extra_slow_in
-        path.moveTo(0f, 0f);
-        path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
-        path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
-        return new PathInterpolator(path);
-    }
-}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java b/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java
deleted file mode 100644
index 8da87feb..0000000
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.animation;
-
-import android.graphics.Path;
-import android.util.MathUtils;
-
-import androidx.core.animation.AccelerateDecelerateInterpolator;
-import androidx.core.animation.AccelerateInterpolator;
-import androidx.core.animation.BounceInterpolator;
-import androidx.core.animation.DecelerateInterpolator;
-import androidx.core.animation.Interpolator;
-import androidx.core.animation.LinearInterpolator;
-import androidx.core.animation.PathInterpolator;
-
-/**
- * Utility class to receive interpolators from. (androidx compatible version)
- *
- * This is the androidx compatible version of {@link Interpolators}. Make sure that changes made to
- * this class are also reflected in {@link Interpolators}.
- *
- * Using the androidx versions of {@link androidx.core.animation.ValueAnimator} or
- * {@link androidx.core.animation.ObjectAnimator} improves animation testability. This file provides
- * the androidx compatible versions of the interpolators defined in {@link Interpolators}.
- * AnimatorTestRule can be used in Tests to manipulate the animation under test (e.g. artificially
- * advancing the time).
- */
-public class InterpolatorsAndroidX {
-
-    /*
-     * ============================================================================================
-     * Emphasized interpolators.
-     * ============================================================================================
-     */
-
-    /**
-     * The default emphasized interpolator. Used for hero / emphasized movement of content.
-     */
-    public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
-
-    /**
-     * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
-     * is disappearing e.g. when moving off screen.
-     */
-    public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
-            0.3f, 0f, 0.8f, 0.15f);
-
-    /**
-     * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
-     * is appearing e.g. when coming from off screen
-     */
-    public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
-            0.05f, 0.7f, 0.1f, 1f);
-
-
-    /*
-     * ============================================================================================
-     * Standard interpolators.
-     * ============================================================================================
-     */
-
-    /**
-     * The standard interpolator that should be used on every normal animation
-     */
-    public static final Interpolator STANDARD = new PathInterpolator(
-            0.2f, 0f, 0f, 1f);
-
-    /**
-     * The standard accelerating interpolator that should be used on every regular movement of
-     * content that is disappearing e.g. when moving off screen.
-     */
-    public static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(
-            0.3f, 0f, 1f, 1f);
-
-    /**
-     * The standard decelerating interpolator that should be used on every regular movement of
-     * content that is appearing e.g. when coming from off screen.
-     */
-    public static final Interpolator STANDARD_DECELERATE = new PathInterpolator(
-            0f, 0f, 0f, 1f);
-
-    /*
-     * ============================================================================================
-     * Legacy
-     * ============================================================================================
-     */
-
-    /**
-     * The default legacy interpolator as defined in Material 1. Also known as FAST_OUT_SLOW_IN.
-     */
-    public static final Interpolator LEGACY = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
-
-    /**
-     * The default legacy accelerating interpolator as defined in Material 1.
-     * Also known as FAST_OUT_LINEAR_IN.
-     */
-    public static final Interpolator LEGACY_ACCELERATE = new PathInterpolator(0.4f, 0f, 1f, 1f);
-
-    /**
-     * The default legacy decelerating interpolator as defined in Material 1.
-     * Also known as LINEAR_OUT_SLOW_IN.
-     */
-    public static final Interpolator LEGACY_DECELERATE = new PathInterpolator(0f, 0f, 0.2f, 1f);
-
-    /**
-     * Linear interpolator. Often used if the interpolator is for different properties who need
-     * different interpolations.
-     */
-    public static final Interpolator LINEAR = new LinearInterpolator();
-
-    /*
-    * ============================================================================================
-    * Custom interpolators
-    * ============================================================================================
-    */
-
-    public static final Interpolator FAST_OUT_SLOW_IN = LEGACY;
-    public static final Interpolator FAST_OUT_LINEAR_IN = LEGACY_ACCELERATE;
-    public static final Interpolator LINEAR_OUT_SLOW_IN = LEGACY_DECELERATE;
-
-    /**
-     * Like {@link #FAST_OUT_SLOW_IN}, but used in case the animation is played in reverse (i.e. t
-     * goes from 1 to 0 instead of 0 to 1).
-     */
-    public static final Interpolator FAST_OUT_SLOW_IN_REVERSE =
-            new PathInterpolator(0.8f, 0f, 0.6f, 1f);
-    public static final Interpolator SLOW_OUT_LINEAR_IN = new PathInterpolator(0.8f, 0f, 1f, 1f);
-    public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
-    public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
-    public static final Interpolator ACCELERATE = new AccelerateInterpolator();
-    public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();
-    public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f);
-    public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f);
-    public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
-    public static final Interpolator ICON_OVERSHOT_LESS = new PathInterpolator(0.4f, 0f, 0.2f,
-            1.1f);
-    public static final Interpolator PANEL_CLOSE_ACCELERATED = new PathInterpolator(0.3f, 0, 0.5f,
-            1);
-    public static final Interpolator BOUNCE = new BounceInterpolator();
-    /**
-     * For state transitions on the control panel that lives in GlobalActions.
-     */
-    public static final Interpolator CONTROL_STATE = new PathInterpolator(0.4f, 0f, 0.2f,
-            1.0f);
-
-    /**
-     * Interpolator to be used when animating a move based on a click. Pair with enough duration.
-     */
-    public static final Interpolator TOUCH_RESPONSE =
-            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
-
-    /**
-     * Like {@link #TOUCH_RESPONSE}, but used in case the animation is played in reverse (i.e. t
-     * goes from 1 to 0 instead of 0 to 1).
-     */
-    public static final Interpolator TOUCH_RESPONSE_REVERSE =
-            new PathInterpolator(0.9f, 0f, 0.7f, 1f);
-
-    /*
-     * ============================================================================================
-     * Functions / Utilities
-     * ============================================================================================
-     */
-
-    /**
-     * Calculate the amount of overshoot using an exponential falloff function with desired
-     * properties, where the overshoot smoothly transitions at the 1.0f boundary into the
-     * overshoot, retaining its acceleration.
-     *
-     * @param progress a progress value going from 0 to 1
-     * @param overshootAmount the amount > 0 of overshoot desired. A value of 0.1 means the max
-     *                        value of the overall progress will be at 1.1.
-     * @param overshootStart the point in (0,1] where the result should reach 1
-     * @return the interpolated overshoot
-     */
-    public static float getOvershootInterpolation(float progress, float overshootAmount,
-            float overshootStart) {
-        if (overshootAmount == 0.0f || overshootStart == 0.0f) {
-            throw new IllegalArgumentException("Invalid values for overshoot");
-        }
-        float b = MathUtils.log((overshootAmount + 1) / (overshootAmount)) / overshootStart;
-        return MathUtils.max(0.0f,
-                (float) (1.0f - Math.exp(-b * progress)) * (overshootAmount + 1.0f));
-    }
-
-    /**
-     * Similar to {@link #getOvershootInterpolation(float, float, float)} but the overshoot
-     * starts immediately here, instead of first having a section of non-overshooting
-     *
-     * @param progress a progress value going from 0 to 1
-     */
-    public static float getOvershootInterpolation(float progress) {
-        return MathUtils.max(0.0f, (float) (1.0f - Math.exp(-4 * progress)));
-    }
-
-    // Create the default emphasized interpolator
-    private static PathInterpolator createEmphasizedInterpolator() {
-        Path path = new Path();
-        // Doing the same as fast_out_extra_slow_in
-        path.moveTo(0f, 0f);
-        path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
-        path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
-        return new PathInterpolator(path);
-    }
-}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
index 3417ffd..142fd21 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
@@ -28,7 +28,7 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.animation.Interpolator
-import com.android.systemui.animation.Interpolators.LINEAR
+import com.android.app.animation.Interpolators.LINEAR
 import kotlin.math.roundToInt
 
 private const val TAG = "LaunchAnimator"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 9346a2f..0010894 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -23,8 +23,8 @@
 import android.graphics.Canvas
 import android.graphics.Typeface
 import android.graphics.fonts.Font
+import android.graphics.fonts.FontVariationAxis
 import android.text.Layout
-import android.text.TextPaint
 import android.util.LruCache
 
 private const val DEFAULT_ANIMATION_DURATION: Long = 300
@@ -33,18 +33,39 @@
 typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
 
 interface TypefaceVariantCache {
-    fun getTypefaceForVariant(fvar: String, targetPaint: TextPaint): Typeface?
+    fun getTypefaceForVariant(fvar: String?): Typeface?
+
+    companion object {
+        fun createVariantTypeface(baseTypeface: Typeface, fVar: String?): Typeface {
+            if (fVar.isNullOrEmpty()) {
+                return baseTypeface
+            }
+
+            val axes = FontVariationAxis.fromFontVariationSettings(fVar).toMutableList()
+            axes.removeIf { !baseTypeface.isSupportedAxes(it.getOpenTypeTagValue()) }
+            if (axes.isEmpty()) {
+                return baseTypeface
+            }
+            return Typeface.createFromTypefaceWithVariation(baseTypeface, axes)
+        }
+    }
 }
 
-class TypefaceVariantCacheImpl() : TypefaceVariantCache {
+class TypefaceVariantCacheImpl(
+    var baseTypeface: Typeface,
+) : TypefaceVariantCache {
     private val cache = LruCache<String, Typeface>(TYPEFACE_CACHE_MAX_ENTRIES)
-    override fun getTypefaceForVariant(fvar: String, targetPaint: TextPaint): Typeface? {
+    override fun getTypefaceForVariant(fvar: String?): Typeface? {
+        if (fvar == null) {
+            return baseTypeface
+        }
         cache.get(fvar)?.let {
             return it
         }
 
-        targetPaint.fontVariationSettings = fvar
-        return targetPaint.typeface?.also { cache.put(fvar, it) }
+        return TypefaceVariantCache
+            .createVariantTypeface(baseTypeface, fvar)
+            .also { cache.put(fvar, it) }
     }
 }
 
@@ -78,7 +99,7 @@
     layout: Layout,
     private val invalidateCallback: () -> Unit,
 ) {
-    var typefaceCache: TypefaceVariantCache = TypefaceVariantCacheImpl()
+    var typefaceCache: TypefaceVariantCache = TypefaceVariantCacheImpl(layout.paint.typeface)
         get() = field
         set(value) {
             field = value
@@ -244,8 +265,7 @@
         }
 
         if (!fvar.isNullOrBlank()) {
-            textInterpolator.targetPaint.typeface =
-                typefaceCache.getTypefaceForVariant(fvar, textInterpolator.targetPaint)
+            textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
         }
 
         if (color != null) {
@@ -339,4 +359,3 @@
         )
     }
 }
-
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index 79189bc..8ed8d8f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -38,6 +38,8 @@
      * Once you modified the style parameters, you have to call reshapeText to recalculate base text
      * layout.
      *
+     * Do not bypass the cache and update the typeface or font variation directly.
+     *
      * @return a paint object
      */
     val basePaint = TextPaint(layout.paint)
@@ -48,6 +50,8 @@
      * Once you modified the style parameters, you have to call reshapeText to recalculate target
      * text layout.
      *
+     * Do not bypass the cache and update the typeface or font variation directly.
+     *
      * @return a paint object
      */
     val targetPaint = TextPaint(layout.paint)
@@ -217,14 +221,8 @@
                 run.fontRuns.forEach { fontRun ->
                     fontRun.baseFont =
                         fontInterpolator.lerp(fontRun.baseFont, fontRun.targetFont, progress)
-                    val fvar = run {
-                        val tmpFontVariationsArray = mutableListOf<FontVariationAxis>()
-                        fontRun.baseFont.axes.forEach {
-                            tmpFontVariationsArray.add(FontVariationAxis(it.tag, it.styleValue))
-                        }
-                        FontVariationAxis.toFontVariationSettings(tmpFontVariationsArray)
-                    }
-                    basePaint.typeface = typefaceCache.getTypefaceForVariant(fvar, basePaint)
+                    val fvar = FontVariationAxis.toFontVariationSettings(fontRun.baseFont.axes)
+                    basePaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
                 }
             }
         }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 58ffef2..8e79e3c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -25,6 +25,7 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.animation.Interpolator
+import com.android.app.animation.Interpolators
 import kotlin.math.max
 import kotlin.math.min
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
index f3d8b17..dd32851 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
@@ -19,7 +19,7 @@
 import android.util.DisplayMetrics
 import android.view.animation.Interpolator
 import android.window.BackEvent
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.util.dpToPx
 
 /** Used to convert [BackEvent] into a [BackTransformation]. */
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 3a19990..8dd2c39 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -28,13 +28,13 @@
 import android.util.AttributeSet
 import android.util.MathUtils.constrainedMap
 import android.widget.TextView
+import com.android.app.animation.Interpolators
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.animation.GlyphCallback
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.TextAnimator
 import com.android.systemui.customization.R
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
 import java.io.PrintWriter
 import java.util.Calendar
 import java.util.Locale
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index b0c0240..aa1bb3f 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -23,6 +23,12 @@
 import android.provider.Settings
 import android.util.Log
 import androidx.annotation.OpenForTesting
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogMessage
+import com.android.systemui.log.LogMessageImpl
+import com.android.systemui.log.MessageInitializer
+import com.android.systemui.log.MessagePrinter
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.ClockId
 import com.android.systemui.plugins.ClockMetadata
@@ -32,12 +38,6 @@
 import com.android.systemui.plugins.PluginLifecycleManager
 import com.android.systemui.plugins.PluginListener
 import com.android.systemui.plugins.PluginManager
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogMessage
-import com.android.systemui.plugins.log.LogMessageImpl
-import com.android.systemui.plugins.log.MessageInitializer
-import com.android.systemui.plugins.log.MessagePrinter
 import com.android.systemui.util.Assert
 import java.io.PrintWriter
 import java.util.concurrent.ConcurrentHashMap
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 2cc3600..6aa74fb 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -24,6 +24,7 @@
 import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.customization.R
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.plugins.ClockAnimations
 import com.android.systemui.plugins.ClockConfig
 import com.android.systemui.plugins.ClockController
@@ -32,7 +33,6 @@
 import com.android.systemui.plugins.ClockFaceController
 import com.android.systemui.plugins.ClockFaceEvents
 import com.android.systemui.plugins.ClockSettings
-import com.android.systemui.plugins.log.LogBuffer
 import java.io.PrintWriter
 import java.util.Locale
 import java.util.TimeZone
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
index 08ee602..6f363a4 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
@@ -20,6 +20,8 @@
 object KeyguardPreviewConstants {
     const val MESSAGE_ID_HIDE_SMART_SPACE = 1111
     const val KEY_HIDE_SMART_SPACE = "hide_smart_space"
+    const val MESSAGE_ID_COLOR_OVERRIDE = 1234
+    const val KEY_COLOR_OVERRIDE = "color_override" // ColorInt Encoded as string
     const val MESSAGE_ID_SLOT_SELECTED = 1337
     const val KEY_SLOT_ID = "slot_id"
     const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id"
diff --git a/packages/SystemUI/log/.gitignore b/packages/SystemUI/log/.gitignore
new file mode 100644
index 0000000..f9a33db
--- /dev/null
+++ b/packages/SystemUI/log/.gitignore
@@ -0,0 +1,9 @@
+.idea/
+.gradle/
+gradle/
+build/
+gradlew*
+local.properties
+*.iml
+android.properties
+buildSrc
\ No newline at end of file
diff --git a/packages/SystemUI/log/Android.bp b/packages/SystemUI/log/Android.bp
new file mode 100644
index 0000000..627ac4b
--- /dev/null
+++ b/packages/SystemUI/log/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+    name: "SystemUILogLib",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "androidx.core_core-ktx",
+        "androidx.annotation_annotation",
+        "error_prone_annotations",
+        "SystemUICommon",
+    ],
+    manifest: "AndroidManifest.xml",
+    kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SystemUI/log/AndroidManifest.xml b/packages/SystemUI/log/AndroidManifest.xml
new file mode 100644
index 0000000..4021e1a
--- /dev/null
+++ b/packages/SystemUI/log/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.systemui.log">
+
+
+</manifest>
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/ConstantStringsLogger.kt b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLogger.kt
similarity index 95%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/ConstantStringsLogger.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLogger.kt
index f95b187..bc35095 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/ConstantStringsLogger.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLogger.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.plugins.log
+package com.android.systemui.log
 
 import com.google.errorprone.annotations.CompileTimeConstant
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/ConstantStringsLoggerImpl.kt b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt
similarity index 96%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/ConstantStringsLoggerImpl.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt
index 91b39e6..6fc52536 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/ConstantStringsLoggerImpl.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.plugins.log
+package com.android.systemui.log
 
 import com.google.errorprone.annotations.CompileTimeConstant
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
similarity index 99%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
index 0a7ccc5..af1a11f 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.plugins.log
+package com.android.systemui.log
 
 import android.os.Trace
 import android.util.Log
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt
similarity index 95%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt
index b036cf0..7d9647a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.plugins.log
+package com.android.systemui.log
 
 import android.util.Log
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt
similarity index 98%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt
index 9468681..88c34f8 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.plugins.log
+package com.android.systemui.log
 
 import java.io.PrintWriter
 import java.text.SimpleDateFormat
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt
similarity index 97%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt
index f2a6a91..5e10f78 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.plugins.log
+package com.android.systemui.log
 
 /** Recyclable implementation of [LogMessage]. */
 data class LogMessageImpl(
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt
similarity index 96%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt
index cfe894f..55f3a73 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.plugins.log
+package com.android.systemui.log
 
 /** Keeps track of which [LogBuffer] messages should also appear in logcat. */
 interface LogcatEchoTracker {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
similarity index 98%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
index 7a125ac..d0ad28f 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.plugins.log
+package com.android.systemui.log
 
 import android.content.ContentResolver
 import android.database.ContentObserver
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
similarity index 95%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
index 3c8bda4..56966773 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.plugins.log
+package com.android.systemui.log
 
 /** Production version of [LogcatEchoTracker] that isn't configurable. */
 class LogcatEchoTrackerProd : LogcatEchoTracker {
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index e306d4a..fec093b 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -32,12 +32,14 @@
         "bcsmartspace/src/**/*.kt",
     ],
 
+    // If you add a static lib here, you may need to also add the package to the ClassLoaderFilter
+    // in PluginInstance. That will ensure that loaded plugins have access to the related classes.
     static_libs: [
         "androidx.annotation_annotation",
-        "error_prone_annotations",
         "PluginCoreLib",
         "SystemUIAnimationLib",
         "SystemUICommon",
+        "SystemUILogLib",
     ],
 
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index ca3e710..7bf139e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -18,8 +18,8 @@
 import android.graphics.drawable.Drawable
 import android.view.View
 import com.android.internal.annotations.Keep
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.plugins.annotations.ProvidesInterface
-import com.android.systemui.plugins.log.LogBuffer
 import java.io.PrintWriter
 import java.util.Locale
 import java.util.TimeZone
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index 436145e..3244eb4 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -131,7 +131,8 @@
     /**
      * A rounded corner clipping that makes QS feel as if it were behind everything.
      */
-    void setFancyClipping(int top, int bottom, int cornerRadius, boolean visible);
+    void setFancyClipping(int leftInset, int top, int rightInset, int bottom, int cornerRadius,
+            boolean visible, boolean fullWidth);
 
     /**
      * @return if quick settings is fully collapsed currently
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 4fc411e..4d289eb 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -22,6 +22,7 @@
     <!-- Keyguard PIN pad styles -->
     <style name="Keyguard.TextView" parent="@android:style/Widget.DeviceDefault.TextView">
         <item name="android:textSize">@dimen/kg_status_line_font_size</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
     </style>
     <style name="Keyguard.TextView.EmergencyButton" parent="Theme.SystemUI">
         <item name="android:textColor">?androidprv:attr/materialColorOnTertiaryFixed</item>
diff --git a/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml b/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml
index d123caf..cff2839 100644
--- a/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml
+++ b/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml
@@ -2,11 +2,11 @@
     android:viewportWidth="22" android:width="20.166666dp" xmlns:android="http://schemas.android.com/apk/res/android">
     <group>
         <clip-path android:pathData="M0,0.5h22v11h-22z"/>
-        <path android:fillColor="#231F20" android:pathData="M6.397,9.908H0V11.5H6.397V9.908Z"/>
-        <path android:fillColor="#231F20" android:pathData="M14.199,9.908H7.801V11.5H14.199V9.908Z"/>
-        <path android:fillColor="#231F20" android:pathData="M11.858,0.5H10.142V6.434H11.858V0.5Z"/>
-        <path android:fillColor="#231F20" android:pathData="M8.348,7.129L3.885,2.975L3.823,2.932L2.668,4.003L2.621,4.046L7.084,8.2L7.146,8.243L8.301,7.172L8.348,7.129Z"/>
-        <path android:fillColor="#231F20" android:pathData="M18.224,2.975L18.177,2.932L13.653,7.129L14.807,8.2L14.854,8.243L19.379,4.046L18.224,2.975Z"/>
-        <path android:fillColor="#231F20" android:pathData="M22,9.908H15.603V11.5H22V9.908Z"/>
+        <path android:fillColor="@android:color/white" android:pathData="M6.397,9.908H0V11.5H6.397V9.908Z"/>
+        <path android:fillColor="@android:color/white" android:pathData="M14.199,9.908H7.801V11.5H14.199V9.908Z"/>
+        <path android:fillColor="@android:color/white" android:pathData="M11.858,0.5H10.142V6.434H11.858V0.5Z"/>
+        <path android:fillColor="@android:color/white" android:pathData="M8.348,7.129L3.885,2.975L3.823,2.932L2.668,4.003L2.621,4.046L7.084,8.2L7.146,8.243L8.301,7.172L8.348,7.129Z"/>
+        <path android:fillColor="@android:color/white" android:pathData="M18.224,2.975L18.177,2.932L13.653,7.129L14.807,8.2L14.854,8.243L19.379,4.046L18.224,2.975Z"/>
+        <path android:fillColor="@android:color/white" android:pathData="M22,9.908H15.603V11.5H22V9.908Z"/>
     </group>
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml b/packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml
index ee8d4883..a35504f 100644
--- a/packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml
+++ b/packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml
@@ -16,16 +16,12 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"
-    android:viewportHeight="24"
-    android:viewportWidth="24">
+    android:viewportWidth="24"
+    android:viewportHeight="24">
     <path
-        android:fillAlpha="1"
-        android:fillColor="@android:color/white"
-        android:fillType="nonZero"
-        android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z" />
+        android:pathData="M23.41,6L22,4.59C21.63,4.21 21.12,4 20.59,4C20.06,4 19.55,4.21 19.18,4.59L11.39,12.38L9.09,14.68L8.04,18.9C8.01,18.96 8,19.04 8,19.11C8,19.6 8.4,20 8.89,20C8.96,20 9.04,19.99 9.11,19.97L13.33,18.92L15.63,16.62L23.42,8.83C23.79,8.45 24,7.94 24,7.41C24,6.88 23.79,6.37 23.41,6ZM14.21,15.21L13.21,16.21L11.8,14.8L12.8,13.8L20.59,6L22,7.41L14.21,15.21Z"
+        android:fillColor="@android:color/white"/>
     <path
-        android:fillAlpha="1"
-        android:fillColor="@android:color/white"
-        android:fillType="nonZero"
-        android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z" />
+        android:pathData="M6.688,20C2.047,20 0.333,18.65 0.333,16C0.333,13.61 2.439,12.474 5.713,12C6.792,11.844 7.344,11.397 7.344,10.927C7.344,9.625 4.679,9.705 3.833,9.667V7.667C3.833,7.667 6.792,7.667 8.208,8.729C8.932,9.272 9.333,9.979 9.333,11.05C9.333,12.52 8.281,13.677 5.713,13.885C4.017,14.023 2.333,14.52 2.333,16C2.333,17.33 4.013,18 7.333,18L6.688,20Z"
+        android:fillColor="@android:color/white"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml
index 7590182..860fc7d 100644
--- a/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml
+++ b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml
@@ -13,19 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportHeight="24"
-    android:viewportWidth="24">
-    <path
-        android:fillAlpha="1"
-        android:fillColor="#636C6F"
-        android:fillType="nonZero"
-        android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z" />
-    <path
-        android:fillAlpha="1"
-        android:fillColor="#636C6F"
-        android:fillType="nonZero"
-        android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z" />
-</vector>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_note_task_shortcut_widget_background" />
+    <foreground android:drawable="@drawable/ic_note_task_shortcut_widget_foreground" />
+</adaptive-icon>
diff --git a/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_background.xml b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_background.xml
new file mode 100644
index 0000000..9f98f07
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_background.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+  <path
+      android:pathData="M0,0h108v108h-108z"
+      android:fillColor="#0B57D0"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_foreground.xml b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_foreground.xml
new file mode 100644
index 0000000..fcb3ef4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_foreground.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+  <path
+      android:pathData="M74.92,43L72.33,40.42C71.65,39.72 70.72,39.33 69.75,39.33C68.78,39.33 67.84,39.72 67.16,40.42L52.88,54.7L48.67,58.91L46.74,66.65C46.69,66.76 46.67,66.91 46.67,67.04C46.67,67.93 47.4,68.67 48.3,68.67C48.43,68.67 48.57,68.65 48.7,68.61L56.44,66.69L60.65,62.47L74.94,48.19C75.61,47.49 76,46.56 76,45.58C76,44.61 75.61,43.68 74.92,43ZM58.05,59.88L56.22,61.72L53.63,59.13L55.47,57.3L69.75,43L72.33,45.58L58.05,59.88Z"
+      android:fillColor="#ffffff"/>
+  <path
+      android:pathData="M44.26,68.67C35.75,68.67 32.61,66.19 32.61,61.33C32.61,56.95 36.47,54.87 42.47,54C44.45,53.71 45.46,52.89 45.46,52.03C45.46,49.65 40.58,49.79 39.03,49.72V46.06C39.03,46.06 44.45,46.06 47.05,48C48.37,49 49.11,50.3 49.11,52.26C49.11,54.95 47.18,57.07 42.47,57.46C39.36,57.71 36.28,58.62 36.28,61.33C36.28,63.77 39.36,65 45.44,65L44.26,68.67Z"
+      android:fillColor="#ffffff"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index 441f963..386c9d6 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -14,12 +14,17 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
+
+<!--
+keep split_shade_status_bar height constant to avoid requestLayout calls on each
+frame when animating QS <-> QQS transition
+-->
 <com.android.systemui.util.NoRemeasureMotionLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/split_shade_status_bar"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
+    android:layout_height="@dimen/qs_header_height"
     android:minHeight="@dimen/large_screen_shade_header_min_height"
     android:clickable="false"
     android:focusable="true"
@@ -30,6 +35,14 @@
     app:layoutDescription="@xml/combined_qs_header_scene">
 
     <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/qqs_header_bottom_guideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_begin="@dimen/large_screen_shade_header_min_height"
+        />
+
+    <androidx.constraintlayout.widget.Guideline
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:id="@+id/begin_guide"
diff --git a/packages/SystemUI/res/layout/smart_action_button.xml b/packages/SystemUI/res/layout/smart_action_button.xml
index 488be3a..4e5785d 100644
--- a/packages/SystemUI/res/layout/smart_action_button.xml
+++ b/packages/SystemUI/res/layout/smart_action_button.xml
@@ -29,8 +29,8 @@
         android:textSize="@dimen/smart_reply_button_font_size"
         android:lineSpacingExtra="@dimen/smart_reply_button_line_spacing_extra"
         android:textColor="@color/smart_reply_button_text"
-        android:paddingLeft="@dimen/smart_reply_button_action_padding_left"
-        android:paddingRight="@dimen/smart_reply_button_padding_horizontal"
+        android:paddingStart="@dimen/smart_reply_button_action_padding_left"
+        android:paddingEnd="@dimen/smart_reply_button_padding_horizontal"
         android:drawablePadding="@dimen/smart_action_button_icon_padding"
         android:textStyle="normal"
         android:ellipsize="none"/>
diff --git a/packages/SystemUI/res/layout/smart_reply_button.xml b/packages/SystemUI/res/layout/smart_reply_button.xml
index ddf16e0..b24362f 100644
--- a/packages/SystemUI/res/layout/smart_reply_button.xml
+++ b/packages/SystemUI/res/layout/smart_reply_button.xml
@@ -31,7 +31,7 @@
         android:textSize="@dimen/smart_reply_button_font_size"
         android:lineSpacingExtra="@dimen/smart_reply_button_line_spacing_extra"
         android:textColor="@color/smart_reply_button_text"
-        android:paddingLeft="@dimen/smart_reply_button_padding_horizontal"
-        android:paddingRight="@dimen/smart_reply_button_padding_horizontal"
+        android:paddingStart="@dimen/smart_reply_button_padding_horizontal"
+        android:paddingEnd="@dimen/smart_reply_button_padding_horizontal"
         android:textStyle="normal"
         android:ellipsize="none"/>
diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml b/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml
index 1838663..191158e 100644
--- a/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml
+++ b/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml
@@ -20,7 +20,8 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/udfps_animation_view_internal"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="match_parent"
+    android:contentDescription="@string/accessibility_fingerprint_label">
 
     <!-- Background protection -->
     <ImageView
diff --git a/packages/SystemUI/res/raw/biometricprompt_rear_landscape_base.json b/packages/SystemUI/res/raw/biometricprompt_rear_landscape_base.json
new file mode 100644
index 0000000..49c1c40
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_rear_landscape_base.json
@@ -0,0 +1 @@
+{"v":"5.8.1","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Rear_Landscape_Base_Foldable","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 18","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[169.478,169.749,0],"ix":2,"l":2},"a":{"a":0,"k":[-48.123,-30.19,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".grey400","cl":"grey400","parent":13,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"black circle matte","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey904","cl":"grey904","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,35.536,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.552,0.087],[0,0]],"o":[[0,0],[0,-3.287],[0,0],[0,0]],"v":[[-2.301,8.869],[-2.301,-3.772],[2.301,-9.806],[2.301,9.806]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"black circle matte 2","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue401","cl":"blue401","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,-27.655,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.286],[0,0],[-2.552,0.086],[0,0]],"o":[[0,0],[0,-3.286],[0,0],[-2.552,-0.086]],"v":[[-2.301,16.282],[-2.301,-16.281],[2.301,-22.313],[2.301,22.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"black circle matte 3","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-129,"s":[-67]},{"t":-29,"s":[0]}],"ix":10},"p":{"a":0,"k":[-75.352,41.307,0],"ix":2,"l":2},"a":{"a":0,"k":[94.648,211.307,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[6.72,-5.642],[0,0],[-9.394,-0.562],[-0.298,-0.038]],"o":[[-5.153,4.329],[3.882,-16.05],[0.31,0.019],[-0.044,0.75]],"v":[[0.863,12.222],[-8.931,14.755],[8.005,-15.108],[8.931,-15.021]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[81.486,130.081],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.459,6.045],[-5.153,4.329],[-0.044,0.75],[3.116,-24.664],[5.23,-22.052],[8.666,11.92],[-2.9,9.135]],"o":[[0,0],[6.72,-5.642],[12.723,1.335],[-2.369,18.762],[-13.993,-5.333],[2.255,-5.502],[1.843,-5.815]],"v":[[-9.99,-18.348],[-0.196,-20.881],[7.872,-48.124],[21.578,-9.331],[12.104,48.124],[-22.574,21.555],[-14.791,-0.206]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82.545,163.184],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 8","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"black circle matte 4","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey903","cl":"grey903","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-18.345,-92.442,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[24.07,0],[0,0],[-8.27,0],[0,0]],"o":[[0,0],[0,8.269],[0,0],[-14.024,-17.379]],"v":[[-29.778,-14.252],[-29.778,-0.721],[-14.805,14.252],[29.778,14.252]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"black circle matte 5","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey902","cl":"grey902","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.947,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[154.053,139.81,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.3,0.367],[0,0],[-2.364,0.157],[0,0]],"o":[[0,0],[2.3,-0.367],[0,0],[-2.364,-0.157]],"v":[[-3.5,75.533],[-3.5,-75.533],[3.5,-76.312],[3.5,76.312]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[113.225,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,8.269],[0,0],[2.181,-0.187],[0,0],[-2.23,0],[0,42.252],[10.593,13.127],[0,0]],"o":[[0,0],[-2.23,0],[0,0],[2.181,0.187],[42.252,0],[0,-18.182],[0,0],[-8.27,0]],"v":[[-34.946,-62.973],[-34.946,-76.504],[-41.558,-76.201],[-41.558,76.201],[-34.946,76.504],[41.558,0],[24.61,-48],[-19.973,-48]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156.824,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".black 2","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-48.123,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-129,"s":[0,0,100]},{"t":-79,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey700","cl":"grey700","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-56.481,-59.936,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.767,0],[0,0],[0,-3.767],[0,0],[-3.767,0],[0,0],[0,3.767],[0,0]],"o":[[0,0],[-3.767,0],[0,0],[0,3.767],[0,0],[3.767,0],[0,0],[0,-3.767]],"v":[[46.055,-14.479],[-46.056,-14.479],[-52.876,-7.659],[-52.876,7.658],[-46.056,14.479],[46.055,14.479],[52.876,7.658],[52.876,-7.659]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".grey901","cl":"grey901","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.485,2.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.184,0],[0,0],[0,0],[0,0],[0,-4.375]],"o":[[0,4.184],[0,0],[0,0],[0,0],[4.375,0],[0,0]],"v":[[114.116,92.129],[106.54,99.705],[7.788,99.705],[7.788,-99.704],[106.161,-99.704],[114.116,-91.749]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[5.707,0],[0,0],[1.894,-1.05],[0.886,0.346],[0,0],[2.166,0],[0,0],[0,-5.707],[0,0],[0,-1.46],[0,0],[-1.133,-0.038],[0,0],[0,-1.459],[0,0],[-1.133,-0.038],[0,0],[-5.708,0],[0,0],[-1.894,1.05],[-0.846,-0.289],[0,0],[-2.166,0],[0,0],[0,5.706],[0,0]],"o":[[0,0],[-2.166,0],[-0.883,0.354],[0,0],[-1.895,-1.05],[0,0],[-5.708,0],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[0,5.707],[0,0],[2.165,0],[0.833,-0.334],[0,0],[1.894,1.05],[0,0],[5.707,0],[0,0],[0,-5.707]],"v":[[106.16,-102.082],[8.455,-102.082],[2.265,-100.48],[-0.488,-100.468],[-0.519,-100.48],[-6.71,-102.082],[-104.116,-102.082],[-114.45,-91.748],[-114.45,-36.119],[-116.494,-33.44],[-116.494,-18.979],[-114.45,-16.3],[-114.45,-0.877],[-116.494,1.802],[-116.494,28.704],[-114.45,31.383],[-114.45,91.749],[-104.116,102.083],[-6.495,102.083],[-0.305,100.481],[2.294,100.425],[2.395,100.481],[9.872,102.083],[106.161,102.083],[116.494,91.75],[116.494,-91.748]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.529411792755,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_rear_portrait_base.json b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_base.json
new file mode 100644
index 0000000..9ea0d35
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_base.json
@@ -0,0 +1 @@
+{"v":"5.8.1","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Rear_Portrait_Base_Foldable","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 18","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[169.478,169.749,0],"ix":2,"l":2},"a":{"a":0,"k":[-48.123,-30.19,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".grey400","cl":"grey400","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"black circle matte","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey904","cl":"grey904","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,35.536,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.552,0.087],[0,0]],"o":[[0,0],[0,-3.287],[0,0],[0,0]],"v":[[-2.301,8.869],[-2.301,-3.772],[2.301,-9.806],[2.301,9.806]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"black circle matte 2","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue401","cl":"blue401","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,-27.655,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.286],[0,0],[-2.552,0.086],[0,0]],"o":[[0,0],[0,-3.286],[0,0],[-2.552,-0.086]],"v":[[-2.301,16.282],[-2.301,-16.281],[2.301,-22.313],[2.301,22.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"black circle matte 3","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Finger 3","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-2,"ix":10},"p":{"a":0,"k":[260.134,83.782,0],"ix":2,"l":2},"a":{"a":0,"k":[302.634,38.782,0],"ix":1,"l":2},"s":{"a":0,"k":[178,178,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.262,5.076],[0,0],[-0.424,-7.095],[-0.028,-0.225]],"o":[[3.269,-3.892],[-12.123,2.932],[0.015,0.234],[0.567,-0.034]],"v":[[9.232,0.652],[11.145,-6.746],[-11.412,6.046],[-11.346,6.746]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[241.281,55.033],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[4.565,-1.102],[3.269,-3.892],[0.566,-0.033],[-18.63,2.353],[-16.656,3.951],[9.004,6.546],[6.9,-2.19]],"o":[[0,0],[-4.262,5.076],[1.008,9.61],[14.171,-1.79],[-4.028,-10.569],[-4.156,1.703],[-4.392,1.392]],"v":[[-13.858,-7.546],[-15.771,-0.148],[-36.349,5.946],[-7.047,16.299],[36.349,9.142],[16.281,-17.051],[-0.156,-11.172]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[266.285,55.833],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"black circle matte 4","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".grey903","cl":"grey903","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-18.345,-92.442,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[24.07,0],[0,0],[-8.27,0],[0,0]],"o":[[0,0],[0,8.269],[0,0],[-14.024,-17.379]],"v":[[-29.778,-14.252],[-29.778,-0.721],[-14.805,14.252],[29.778,14.252]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"black circle matte 5","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".grey902","cl":"grey902","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.947,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[154.053,139.81,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.3,0.367],[0,0],[-2.364,0.157],[0,0]],"o":[[0,0],[2.3,-0.367],[0,0],[-2.364,-0.157]],"v":[[-3.5,75.533],[-3.5,-75.533],[3.5,-76.312],[3.5,76.312]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[113.225,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,8.269],[0,0],[2.181,-0.187],[0,0],[-2.23,0],[0,42.252],[10.593,13.127],[0,0]],"o":[[0,0],[-2.23,0],[0,0],[2.181,0.187],[42.252,0],[0,-18.182],[0,0],[-8.27,0]],"v":[[-34.946,-62.973],[-34.946,-76.504],[-41.558,-76.201],[-41.558,76.201],[-34.946,76.504],[41.558,0],[24.61,-48],[-19.973,-48]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156.824,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".black 2","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-48.123,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-129,"s":[0,0,100]},{"t":-79,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".grey700","cl":"grey700","parent":16,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-56.481,-59.936,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.767,0],[0,0],[0,-3.767],[0,0],[-3.767,0],[0,0],[0,3.767],[0,0]],"o":[[0,0],[-3.767,0],[0,0],[0,3.767],[0,0],[3.767,0],[0,0],[0,-3.767]],"v":[[46.055,-14.479],[-46.056,-14.479],[-52.876,-7.659],[-52.876,7.658],[-46.056,14.479],[46.055,14.479],[52.876,7.658],[52.876,-7.659]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".grey901","cl":"grey901","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.485,2.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.184,0],[0,0],[0,0],[0,0],[0,-4.375]],"o":[[0,4.184],[0,0],[0,0],[0,0],[4.375,0],[0,0]],"v":[[114.116,92.129],[106.54,99.705],[7.788,99.705],[7.788,-99.704],[106.161,-99.704],[114.116,-91.749]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[5.707,0],[0,0],[1.894,-1.05],[0.886,0.346],[0,0],[2.166,0],[0,0],[0,-5.707],[0,0],[0,-1.46],[0,0],[-1.133,-0.038],[0,0],[0,-1.459],[0,0],[-1.133,-0.038],[0,0],[-5.708,0],[0,0],[-1.894,1.05],[-0.846,-0.289],[0,0],[-2.166,0],[0,0],[0,5.706],[0,0]],"o":[[0,0],[-2.166,0],[-0.883,0.354],[0,0],[-1.895,-1.05],[0,0],[-5.708,0],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[0,5.707],[0,0],[2.165,0],[0.833,-0.334],[0,0],[1.894,1.05],[0,0],[5.707,0],[0,0],[0,-5.707]],"v":[[106.16,-102.082],[8.455,-102.082],[2.265,-100.48],[-0.488,-100.468],[-0.519,-100.48],[-6.71,-102.082],[-104.116,-102.082],[-114.45,-91.748],[-114.45,-36.119],[-116.494,-33.44],[-116.494,-18.979],[-114.45,-16.3],[-114.45,-0.877],[-116.494,1.802],[-116.494,28.704],[-114.45,31.383],[-114.45,91.749],[-104.116,102.083],[-6.495,102.083],[-0.305,100.481],[2.294,100.425],[2.395,100.481],[9.872,102.083],[106.161,102.083],[116.494,91.75],[116.494,-91.748]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.529411792755,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_rear_portrait_reverse_base.json b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_reverse_base.json
new file mode 100644
index 0000000..f2b2593
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_reverse_base.json
@@ -0,0 +1 @@
+{"v":"5.8.1","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Rear_Portrait_Reverse_Base_Foldable","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 18","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":270,"ix":10},"p":{"a":0,"k":[169.478,169.749,0],"ix":2,"l":2},"a":{"a":0,"k":[-48.123,-30.19,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".grey400","cl":"grey400","parent":13,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"black circle matte","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey904","cl":"grey904","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,35.536,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.552,0.087],[0,0]],"o":[[0,0],[0,-3.287],[0,0],[0,0]],"v":[[-2.301,8.869],[-2.301,-3.772],[2.301,-9.806],[2.301,9.806]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"black circle matte 2","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue401","cl":"blue401","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,-27.655,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.286],[0,0],[-2.552,0.086],[0,0]],"o":[[0,0],[0,-3.286],[0,0],[-2.552,-0.086]],"v":[[-2.301,16.282],[-2.301,-16.281],[2.301,-22.313],[2.301,22.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"black circle matte 3","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-75.352,41.307,0],"ix":2,"l":2},"a":{"a":0,"k":[94.648,211.307,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[6.72,-5.642],[0,0],[-9.394,-0.562],[-0.298,-0.038]],"o":[[-5.153,4.329],[3.882,-16.05],[0.31,0.019],[-0.044,0.75]],"v":[[0.863,12.222],[-8.931,14.755],[8.005,-15.108],[8.931,-15.021]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[81.486,130.081],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.459,6.045],[-5.153,4.329],[-0.044,0.75],[3.116,-24.664],[5.23,-22.052],[8.666,11.92],[-2.9,9.135]],"o":[[0,0],[6.72,-5.642],[12.723,1.335],[-2.369,18.762],[-13.993,-5.333],[2.255,-5.502],[1.843,-5.815]],"v":[[-9.99,-18.348],[-0.196,-20.881],[7.872,-48.124],[21.578,-9.331],[12.104,48.124],[-22.574,21.555],[-14.791,-0.206]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82.545,163.184],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 8","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"black circle matte 4","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey903","cl":"grey903","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-18.345,-92.442,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[24.07,0],[0,0],[-8.27,0],[0,0]],"o":[[0,0],[0,8.269],[0,0],[-14.024,-17.379]],"v":[[-29.778,-14.252],[-29.778,-0.721],[-14.805,14.252],[29.778,14.252]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"black circle matte 5","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey902","cl":"grey902","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.947,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[154.053,139.81,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.3,0.367],[0,0],[-2.364,0.157],[0,0]],"o":[[0,0],[2.3,-0.367],[0,0],[-2.364,-0.157]],"v":[[-3.5,75.533],[-3.5,-75.533],[3.5,-76.312],[3.5,76.312]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[113.225,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,8.269],[0,0],[2.181,-0.187],[0,0],[-2.23,0],[0,42.252],[10.593,13.127],[0,0]],"o":[[0,0],[-2.23,0],[0,0],[2.181,0.187],[42.252,0],[0,-18.182],[0,0],[-8.27,0]],"v":[[-34.946,-62.973],[-34.946,-76.504],[-41.558,-76.201],[-41.558,76.201],[-34.946,76.504],[41.558,0],[24.61,-48],[-19.973,-48]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156.824,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".black 2","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-48.123,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-129,"s":[0,0,100]},{"t":-79,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey700","cl":"grey700","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-56.481,-59.936,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.767,0],[0,0],[0,-3.767],[0,0],[-3.767,0],[0,0],[0,3.767],[0,0]],"o":[[0,0],[-3.767,0],[0,0],[0,3.767],[0,0],[3.767,0],[0,0],[0,-3.767]],"v":[[46.055,-14.479],[-46.056,-14.479],[-52.876,-7.659],[-52.876,7.658],[-46.056,14.479],[46.055,14.479],[52.876,7.658],[52.876,-7.659]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".grey901","cl":"grey901","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.485,2.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.184,0],[0,0],[0,0],[0,0],[0,-4.375]],"o":[[0,4.184],[0,0],[0,0],[0,0],[4.375,0],[0,0]],"v":[[114.116,92.129],[106.54,99.705],[7.788,99.705],[7.788,-99.704],[106.161,-99.704],[114.116,-91.749]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[5.707,0],[0,0],[1.894,-1.05],[0.886,0.346],[0,0],[2.166,0],[0,0],[0,-5.707],[0,0],[0,-1.46],[0,0],[-1.133,-0.038],[0,0],[0,-1.459],[0,0],[-1.133,-0.038],[0,0],[-5.708,0],[0,0],[-1.894,1.05],[-0.846,-0.289],[0,0],[-2.166,0],[0,0],[0,5.706],[0,0]],"o":[[0,0],[-2.166,0],[-0.883,0.354],[0,0],[-1.895,-1.05],[0,0],[-5.708,0],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[0,5.707],[0,0],[2.165,0],[0.833,-0.334],[0,0],[1.894,1.05],[0,0],[5.707,0],[0,0],[0,-5.707]],"v":[[106.16,-102.082],[8.455,-102.082],[2.265,-100.48],[-0.488,-100.468],[-0.519,-100.48],[-6.71,-102.082],[-104.116,-102.082],[-114.45,-91.748],[-114.45,-36.119],[-116.494,-33.44],[-116.494,-18.979],[-114.45,-16.3],[-114.45,-0.877],[-116.494,1.802],[-116.494,28.704],[-114.45,31.383],[-114.45,91.749],[-104.116,102.083],[-6.495,102.083],[-0.305,100.481],[2.294,100.425],[2.395,100.481],[9.872,102.083],[106.161,102.083],[116.494,91.75],[116.494,-91.748]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.529411792755,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index cb8c2a7..db7eb7a 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -207,11 +207,6 @@
     <color name="controls_task_view_bg">#CC191C1D</color>
     <color name="control_popup_dim">#8A000000</color>
 
-    <!-- Keyboard backlight indicator-->
-    <color name="backlight_indicator_step_filled">#F6E388</color>
-    <color name="backlight_indicator_step_empty">#494740</color>
-    <color name="backlight_indicator_background">#32302A</color>
-
     <!-- Docked misalignment message -->
     <color name="misalignment_text_color">#F28B82</color>
 
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1602189..1252695 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -889,4 +889,8 @@
     -->
     <dimen name="shade_swipe_collapse_threshold">0.5</dimen>
     <!-- [END] MULTI SHADE -->
+
+    <!-- Time (in ms) to delay the bouncer views from showing when passive auth may be used for
+    device entry. -->
+    <integer name="primary_bouncer_passive_auth_delay">250</integer>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0aa880f..aff0e80 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -615,6 +615,7 @@
     <dimen name="qs_header_carrier_separator_width">6dp</dimen>
     <dimen name="qs_carrier_margin_width">4dp</dimen>
     <dimen name="qs_footer_icon_size">20dp</dimen>
+    <dimen name="qs_header_height">120dp</dimen>
     <dimen name="qs_header_row_min_height">48dp</dimen>
 
     <dimen name="qs_header_non_clickable_element_height">24dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 499dfa4..eaeaabe 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -198,6 +198,9 @@
     <item type="id" name="pm_lite"/>
     <item type="id" name="settings_button_container"/>
 
+    <!--Keyboard Backlight Dialog -->
+    <item type="id" name="keyboard_backlight_dialog_container"/>
+
     <item type="id" name="log_access_dialog_allow_button" />
     <item type="id" name="log_access_dialog_deny_button" />
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 853930e..c57fef1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1257,7 +1257,10 @@
     <string name="monitoring_description_managed_profile_network_logging">Your admin has turned on network logging, which monitors traffic in your work profile but not in your personal profile.</string>
 
     <!-- Monitoring dialog: Description of an active VPN. [CHAR LIMIT=NONE]-->
-    <string name="monitoring_description_named_vpn">This device is connected to the internet through <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your IT admin.</string>
+    <string name="monitoring_description_named_vpn">This device is connected to the internet through <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g>. Your network activity, including emails and browsing data, is visible to the VPN provider.</string>
+
+    <!-- Monitoring dialog: Description of an active VPN on a managed device. [CHAR LIMIT=NONE]-->
+    <string name="monitoring_description_managed_device_named_vpn">This device is connected to the internet through <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your IT admin.</string>
 
     <!-- Monitoring dialog: Description of two active VPNs. [CHAR LIMIT=NONE]-->
     <string name="monitoring_description_two_named_vpns">This device is connected to the internet through <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g> and <xliff:g id="vpn_app" example="Bar VPN App">%2$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your IT admin.</string>
@@ -3035,6 +3038,19 @@
     -->
     <string name="keyguard_affordance_enablement_dialog_home_instruction_2">&#8226; At least one device is available</string>
 
+    <!---
+    Requirement for the notes app to be available for the user to use. This is shown as part of a
+    bulleted list of requirements. When all requirements are met, the app can be accessed through a
+    shortcut button on the lock screen. [CHAR LIMIT=NONE] -->
+    <string name="keyguard_affordance_enablement_dialog_notes_app_instruction">Select a default notes app to use the notetaking shortcut</string>
+
+    <!---
+    The action to make the lock screen shortcut for the notes app to be available for the user to
+    use. This is shown as the action button in the dialog listing the requirements. When all
+    requirements are met, the app can be accessed through a shortcut button on the lock screen.
+    [CHAR LIMIT=NONE] -->
+    <string name="keyguard_affordance_enablement_dialog_notes_app_action">Select app</string>
+
     <!--
     Error message shown when a shortcut must be pressed and held to activate it, usually shown when
     the user tried to tap the shortcut or held it for too short a time. [CHAR LIMIT=32].
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index 00a0444..1950965 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -28,7 +28,7 @@
             android:layout_height="@dimen/large_screen_shade_header_min_height"
             app:layout_constraintStart_toStartOf="@id/begin_guide"
             app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline"
             app:layout_constraintEnd_toStartOf="@id/date"
             app:layout_constraintHorizontal_bias="0"
             app:layout_constraintHorizontal_chainStyle="packed"
@@ -62,7 +62,7 @@
             app:layout_constraintStart_toEndOf="@id/date"
             app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
             app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline"
             app:layout_constraintHorizontal_bias="1"
             app:layout_constraintHorizontal_chainStyle="packed"
             />
@@ -77,7 +77,7 @@
             app:layout_constraintStart_toEndOf="@id/statusIcons"
             app:layout_constraintEnd_toEndOf="@id/end_guide"
             app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline"
             app:layout_constraintHorizontal_bias="1"
             app:layout_constraintHorizontal_chainStyle="packed"
             />
@@ -105,7 +105,7 @@
             app:layout_constraintStart_toEndOf="@id/date"
             app:layout_constraintEnd_toEndOf="@id/end_guide"
             app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline"
             app:layout_constraintHorizontal_bias="1"
         />
     </Constraint>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt
index 2d83458..a2b6e2c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt
@@ -16,27 +16,179 @@
 
 package com.android.systemui.shared.condition
 
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
+
 /**
  * A higher order [Condition] which combines multiple conditions with a specified
- * [Evaluator.ConditionOperand].
+ * [Evaluator.ConditionOperand]. Conditions are executed lazily as-needed.
+ *
+ * @param scope The [CoroutineScope] to execute in.
+ * @param conditions The list of conditions to evaluate. Since conditions are executed lazily, the
+ *   ordering is important here.
+ * @param operand The [Evaluator.ConditionOperand] to apply to the conditions.
  */
-internal class CombinedCondition
+@OptIn(ExperimentalCoroutinesApi::class)
+class CombinedCondition
 constructor(
+    private val scope: CoroutineScope,
     private val conditions: Collection<Condition>,
     @Evaluator.ConditionOperand private val operand: Int
-) : Condition(null, false), Condition.Callback {
+) : Condition(scope, null, false) {
+
+    private var job: Job? = null
+    private val _startStrategy by lazy { calculateStartStrategy() }
 
     override fun start() {
-        onConditionChanged(this)
-        conditions.forEach { it.addCallback(this) }
-    }
+        job =
+            scope.launch {
+                val groupedConditions = conditions.groupBy { it.isOverridingCondition }
 
-    override fun onConditionChanged(condition: Condition) {
-        Evaluator.evaluate(conditions, operand)?.also { value -> updateCondition(value) }
-            ?: clearCondition()
+                lazilyEvaluate(
+                        conditions = groupedConditions.getOrDefault(true, emptyList()),
+                        filterUnknown = true
+                    )
+                    .distinctUntilChanged()
+                    .flatMapLatest { overriddenValue ->
+                        // If there are overriding conditions with values set, they take precedence.
+                        if (overriddenValue == null) {
+                            lazilyEvaluate(
+                                conditions = groupedConditions.getOrDefault(false, emptyList()),
+                                filterUnknown = false
+                            )
+                        } else {
+                            flowOf(overriddenValue)
+                        }
+                    }
+                    .collect { conditionMet ->
+                        if (conditionMet == null) {
+                            clearCondition()
+                        } else {
+                            updateCondition(conditionMet)
+                        }
+                    }
+            }
     }
 
     override fun stop() {
-        conditions.forEach { it.removeCallback(this) }
+        job?.cancel()
+        job = null
+    }
+
+    /**
+     * Evaluates a list of conditions lazily with support for short-circuiting. Conditions are
+     * executed serially in the order provided. At any point if the result can be determined, we
+     * short-circuit and return the result without executing all conditions.
+     */
+    private fun lazilyEvaluate(
+        conditions: Collection<Condition>,
+        filterUnknown: Boolean,
+    ): Flow<Boolean?> = callbackFlow {
+        val jobs = MutableList<Job?>(conditions.size) { null }
+        val values = MutableList<Boolean?>(conditions.size) { null }
+        val flows = conditions.map { it.toFlow() }
+
+        fun cancelAllExcept(indexToSkip: Int) {
+            for (index in 0 until jobs.size) {
+                if (index == indexToSkip) {
+                    continue
+                }
+                if (
+                    indexToSkip == -1 ||
+                        conditions.elementAt(index).startStrategy == START_WHEN_NEEDED
+                ) {
+                    jobs[index]?.cancel()
+                    jobs[index] = null
+                    values[index] = null
+                }
+            }
+        }
+
+        fun collectFlow(index: Int) {
+            // Base case which is triggered once we have collected all the flows. In this case,
+            // we never short-circuited and therefore should return the fully evaluated
+            // conditions.
+            if (flows.isEmpty() || index == -1) {
+                val filteredValues =
+                    if (filterUnknown) {
+                        values.filterNotNull()
+                    } else {
+                        values
+                    }
+                trySend(Evaluator.evaluate(filteredValues, operand))
+                return
+            }
+            jobs[index] =
+                scope.launch {
+                    flows.elementAt(index).collect { value ->
+                        values[index] = value
+                        if (shouldEarlyReturn(value)) {
+                            trySend(value)
+                            // The overall result is contingent on this condition, so we don't need
+                            // to monitor any other conditions.
+                            cancelAllExcept(index)
+                        } else {
+                            collectFlow(jobs.indexOfFirst { it == null })
+                        }
+                    }
+                }
+        }
+
+        // Collect any eager conditions immediately.
+        var started = false
+        for ((index, condition) in conditions.withIndex()) {
+            if (condition.startStrategy == START_EAGERLY) {
+                collectFlow(index)
+                started = true
+            }
+        }
+
+        // If no eager conditions started, start the first condition to kick off evaluation.
+        if (!started) {
+            collectFlow(0)
+        }
+        awaitClose { cancelAllExcept(-1) }
+    }
+
+    private fun shouldEarlyReturn(conditionMet: Boolean?): Boolean {
+        return when (operand) {
+            Evaluator.OP_AND -> conditionMet == false
+            Evaluator.OP_OR -> conditionMet == true
+            else -> false
+        }
+    }
+
+    /**
+     * Calculate the start strategy for this condition. This depends on the strategies of the child
+     * conditions. If there are any eager conditions, we must also start this condition eagerly. In
+     * the absence of eager conditions, we check for lazy conditions. In the absence of either, we
+     * make the condition only start when needed.
+     */
+    private fun calculateStartStrategy(): Int {
+        var startStrategy = START_WHEN_NEEDED
+        for (condition in conditions) {
+            when (condition.startStrategy) {
+                START_EAGERLY -> return START_EAGERLY
+                START_LAZILY -> {
+                    startStrategy = START_LAZILY
+                }
+                START_WHEN_NEEDED -> {
+                    // this is the default, so do nothing
+                }
+            }
+        }
+        return startStrategy
+    }
+
+    override fun getStartStrategy(): Int {
+        return _startStrategy
     }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java
index cc48090e..6bf1ce5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java
@@ -18,11 +18,14 @@
 
 import android.util.Log;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LifecycleEventObserver;
 import androidx.lifecycle.LifecycleOwner;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -30,6 +33,8 @@
 import java.util.Iterator;
 import java.util.List;
 
+import kotlinx.coroutines.CoroutineScope;
+
 /**
  * Base class for a condition that needs to be fulfilled in order for {@link Monitor} to inform
  * its callbacks.
@@ -39,24 +44,27 @@
 
     private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
     private final boolean mOverriding;
+    private final CoroutineScope mScope;
     private Boolean mIsConditionMet;
     private boolean mStarted = false;
 
     /**
      * By default, conditions have an initial value of false and are not overriding.
      */
-    public Condition() {
-        this(false, false);
+    public Condition(CoroutineScope scope) {
+        this(scope, false, false);
     }
 
     /**
      * Constructor for specifying initial state and overriding condition attribute.
+     *
      * @param initialConditionMet Initial state of the condition.
-     * @param overriding Whether this condition overrides others.
+     * @param overriding          Whether this condition overrides others.
      */
-    protected Condition(Boolean initialConditionMet, boolean overriding) {
+    protected Condition(CoroutineScope scope, Boolean initialConditionMet, boolean overriding) {
         mIsConditionMet = initialConditionMet;
         mOverriding = overriding;
+        mScope = scope;
     }
 
     /**
@@ -70,6 +78,29 @@
     protected abstract void stop();
 
     /**
+     * Condition should be started as soon as there is an active subscription.
+     */
+    public static final int START_EAGERLY = 0;
+    /**
+     * Condition should be started lazily only if needed. But once started, it will not be cancelled
+     * unless there are no more active subscriptions.
+     */
+    public static final int START_LAZILY = 1;
+    /**
+     * Condition should be started lazily only if needed, and can be stopped when not needed. This
+     * should be used for conditions which are expensive to keep running.
+     */
+    public static final int START_WHEN_NEEDED = 2;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({START_EAGERLY, START_LAZILY, START_WHEN_NEEDED})
+    @interface StartStrategy {
+    }
+
+    @StartStrategy
+    protected abstract int getStartStrategy();
+
+    /**
      * Returns whether the current condition overrides
      */
     public boolean isOverridingCondition() {
@@ -183,6 +214,7 @@
     /**
      * Returns whether the condition is set. This method should be consulted to understand the
      * value of {@link #isConditionMet()}.
+     *
      * @return {@code true} if value is present, {@code false} otherwise.
      */
     public boolean isConditionSet() {
@@ -210,17 +242,18 @@
      * conditions are true.
      */
     public Condition and(@NonNull Collection<Condition> others) {
-        final List<Condition> conditions = new ArrayList<>(others);
+        final List<Condition> conditions = new ArrayList<>();
         conditions.add(this);
-        return new CombinedCondition(conditions, Evaluator.OP_AND);
+        conditions.addAll(others);
+        return new CombinedCondition(mScope, conditions, Evaluator.OP_AND);
     }
 
     /**
      * Creates a new condition which will only be true when both this condition and the provided
      * condition is true.
      */
-    public Condition and(@NonNull Condition other) {
-        return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_AND);
+    public Condition and(@NonNull Condition... others) {
+        return and(Arrays.asList(others));
     }
 
     /**
@@ -228,17 +261,18 @@
      * provided conditions are true.
      */
     public Condition or(@NonNull Collection<Condition> others) {
-        final List<Condition> conditions = new ArrayList<>(others);
+        final List<Condition> conditions = new ArrayList<>();
         conditions.add(this);
-        return new CombinedCondition(conditions, Evaluator.OP_OR);
+        conditions.addAll(others);
+        return new CombinedCondition(mScope, conditions, Evaluator.OP_OR);
     }
 
     /**
      * Creates a new condition which will only be true when either this condition or the provided
      * condition is true.
      */
-    public Condition or(@NonNull Condition other) {
-        return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_OR);
+    public Condition or(@NonNull Condition... others) {
+        return or(Arrays.asList(others));
     }
 
     /**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt
index 8f8bff8..84edc35 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt
@@ -1,14 +1,22 @@
 package com.android.systemui.shared.condition
 
+import com.android.systemui.shared.condition.Condition.StartStrategy
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.launch
 
 /** Converts a boolean flow to a [Condition] object which can be used with a [Monitor] */
 @JvmOverloads
-fun Flow<Boolean>.toCondition(scope: CoroutineScope, initialValue: Boolean? = null): Condition {
-    return object : Condition(initialValue, false) {
+fun Flow<Boolean>.toCondition(
+    scope: CoroutineScope,
+    @StartStrategy strategy: Int,
+    initialValue: Boolean? = null
+): Condition {
+    return object : Condition(scope, initialValue, false) {
         var job: Job? = null
 
         override fun start() {
@@ -19,5 +27,25 @@
             job?.cancel()
             job = null
         }
+
+        override fun getStartStrategy() = strategy
     }
 }
+
+/** Converts a [Condition] to a boolean flow */
+fun Condition.toFlow(): Flow<Boolean?> {
+    return callbackFlow {
+            val callback =
+                Condition.Callback { condition ->
+                    if (condition.isConditionSet) {
+                        trySend(condition.isConditionMet)
+                    } else {
+                        trySend(null)
+                    }
+                }
+            addCallback(callback)
+            callback.onConditionChanged(this@toFlow)
+            awaitClose { removeCallback(callback) }
+        }
+        .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt
index 454294f..584d978 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt
@@ -22,7 +22,7 @@
  * Helper for evaluating a collection of [Condition] objects with a given
  * [Evaluator.ConditionOperand]
  */
-internal object Evaluator {
+object Evaluator {
     /** Operands for combining multiple conditions together */
     @Retention(AnnotationRetention.SOURCE)
     @IntDef(value = [OP_AND, OP_OR])
@@ -70,15 +70,31 @@
     fun evaluate(conditions: Collection<Condition>, @ConditionOperand operand: Int): Boolean? {
         if (conditions.isEmpty()) return null
         // If there are overriding conditions with values set, they take precedence.
-        val targetConditions =
+        val values: Collection<Boolean?> =
             conditions
                 .filter { it.isConditionSet && it.isOverridingCondition }
                 .ifEmpty { conditions }
+                .map { condition ->
+                    if (condition.isConditionSet) {
+                        condition.isConditionMet
+                    } else {
+                        null
+                    }
+                }
+        return evaluate(values = values, operand = operand)
+    }
+
+    /**
+     * Evaluates a set of booleans with a given operand
+     *
+     * @param operand The operand to use when evaluating.
+     * @return Either true or false if the value is known, or null if value is unknown
+     */
+    internal fun evaluate(values: Collection<Boolean?>, @ConditionOperand operand: Int): Boolean? {
+        if (values.isEmpty()) return null
         return when (operand) {
-            OP_AND ->
-                threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = false)
-            OP_OR ->
-                threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = true)
+            OP_AND -> threeValuedAndOrOr(values = values, returnValueIfAnyMatches = false)
+            OP_OR -> threeValuedAndOrOr(values = values, returnValueIfAnyMatches = true)
             else -> null
         }
     }
@@ -90,16 +106,16 @@
      *   any value is true.
      */
     private fun threeValuedAndOrOr(
-        conditions: Collection<Condition>,
+        values: Collection<Boolean?>,
         returnValueIfAnyMatches: Boolean
     ): Boolean? {
         var hasUnknown = false
-        for (condition in conditions) {
-            if (!condition.isConditionSet) {
+        for (value in values) {
+            if (value == null) {
                 hasUnknown = true
                 continue
             }
-            if (condition.isConditionMet == returnValueIfAnyMatches) {
+            if (value == returnValueIfAnyMatches) {
                 return returnValueIfAnyMatches
             }
         }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index 016d573..4a66562 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -230,7 +230,7 @@
 
         private ClassLoader getParentClassLoader(ClassLoader baseClassLoader) {
             return new PluginManagerImpl.ClassLoaderFilter(
-                    baseClassLoader, "com.android.systemui.plugin");
+                    baseClassLoader, "com.android.systemui.log", "com.android.systemui.plugin");
         }
 
         /** Returns class loader specific for the given plugin. */
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
index 2f9f5b2..1e668b8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
@@ -248,19 +248,23 @@
     // This allows plugins to include any libraries or copied code they want by only including
     // classes from the plugin library.
     static class ClassLoaderFilter extends ClassLoader {
-        private final String mPackage;
+        private final String[] mPackages;
         private final ClassLoader mBase;
 
-        public ClassLoaderFilter(ClassLoader base, String pkg) {
+        ClassLoaderFilter(ClassLoader base, String... pkgs) {
             super(ClassLoader.getSystemClassLoader());
             mBase = base;
-            mPackage = pkg;
+            mPackages = pkgs;
         }
 
         @Override
         protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
-            if (!name.startsWith(mPackage)) super.loadClass(name, resolve);
-            return mBase.loadClass(name);
+            for (String pkg : mPackages) {
+                if (name.startsWith(pkg)) {
+                    return mBase.loadClass(name);
+                }
+            }
+            return super.loadClass(name, resolve);
         }
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
index 362d7a9..7cf3121 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -68,7 +68,7 @@
         onActivityLaunchOnSecondaryDisplayRerouted();
     }
 
-    default void onTaskProfileLocked(RunningTaskInfo taskInfo) { }
+    default void onTaskProfileLocked(RunningTaskInfo taskInfo, int userId) { }
     default void onTaskCreated(int taskId, ComponentName componentName) { }
     default void onTaskRemoved(int taskId) { }
     default void onTaskMovedToFront(int taskId) { }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index dd52cfb..c613afb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -262,8 +262,8 @@
         }
 
         @Override
-        public void onTaskProfileLocked(RunningTaskInfo taskInfo) {
-            mHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskInfo).sendToTarget();
+        public void onTaskProfileLocked(RunningTaskInfo taskInfo, int userId) {
+            mHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, userId, 0, taskInfo).sendToTarget();
         }
 
         @Override
@@ -418,8 +418,9 @@
                     }
                     case ON_TASK_PROFILE_LOCKED: {
                         final RunningTaskInfo info = (RunningTaskInfo) msg.obj;
+                        final int userId = msg.arg1;
                         for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onTaskProfileLocked(info);
+                            mTaskStackListeners.get(i).onTaskProfileLocked(info, userId);
                         }
                         break;
                     }
diff --git a/packages/SystemUI/src-debug/com/android/systemui/log/DebugLogger.kt b/packages/SystemUI/src-debug/com/android/systemui/log/DebugLogger.kt
new file mode 100644
index 0000000..af29b05
--- /dev/null
+++ b/packages/SystemUI/src-debug/com/android/systemui/log/DebugLogger.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log
+
+import android.os.Build
+import android.util.Log
+import android.util.Log.LOG_ID_MAIN
+
+/**
+ * A simplified debug logger built as a wrapper around Android's [Log]. Internal for development.
+ *
+ * The main advantages are:
+ * - Sensible defaults, automatically retrieving the class name from the call-site (i.e., tag);
+ * - The messages are purged from source on release builds (keep in mind they are visible on AOSP);
+ * - Lazily evaluate Strings for zero impact in production builds or when disabled;
+ *
+ * Usage example:
+ * ```kotlin
+ * // Logging a message:
+ * debugLog { "message" }
+ *
+ * // Logging an error:
+ * debugLog(error = exception) { "message" }
+ *
+ * // Logging the current stack trace, for debugging:
+ * debugLog(error = Throwable()) { "message" }
+ * ```
+ */
+object DebugLogger {
+
+    /**
+     * Log a debug message, with sensible defaults.
+     *
+     * For example:
+     * ```kotlin
+     * val one = 1
+     * debugLog { "message#$one" }
+     * ```
+     *
+     * The output will be: `D/NoteTaskController: message#1`
+     *
+     * Beware, the [debugLog] content is **REMOVED FROM SOURCE AND BINARY** in Release builds.
+     *
+     * @param enabled: whether or not the message should be logged. By default, it is
+     *   [Build.IS_DEBUGGABLE].
+     * @param priority: type of this log. By default, it is [Log.DEBUG].
+     * @param tag: identifies the source of a log. By default, it is the receiver's simple name.
+     * @param error: a [Throwable] to log.
+     * @param message: a lazily evaluated message you wish to log.
+     */
+    inline fun Any.debugLog(
+        enabled: Boolean = Build.IS_DEBUGGABLE,
+        priority: Int = Log.DEBUG,
+        tag: String = this::class.simpleName.orEmpty(),
+        error: Throwable? = null,
+        message: () -> String,
+    ) {
+        if (enabled) {
+            if (error == null) {
+                Log.println(priority, tag, message())
+            } else {
+                Log.printlns(LOG_ID_MAIN, priority, tag, message(), error)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src-release/com/android/systemui/log/DebugLogger.kt b/packages/SystemUI/src-release/com/android/systemui/log/DebugLogger.kt
new file mode 100644
index 0000000..2764a1f
--- /dev/null
+++ b/packages/SystemUI/src-release/com/android/systemui/log/DebugLogger.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log
+
+import android.os.Build
+import android.util.Log
+
+/** An empty logger for release builds. */
+object DebugLogger {
+
+    @JvmName("logcatMessage")
+    inline fun Any.debugLog(
+        enabled: Boolean = Build.IS_DEBUGGABLE,
+        priority: Int = Log.DEBUG,
+        tag: String = this::class.simpleName.orEmpty(),
+        error: Throwable? = null,
+        message: () -> String,
+    ) {
+        // no-op.
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index 62f4f22..a82f0e3 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -26,8 +26,8 @@
 import android.graphics.Color
 import android.util.AttributeSet
 import android.view.View
+import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.TITLE
 
 /** Displays security messages for the keyguard bouncer. */
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 8b87e2a..4bf7be6 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -42,14 +42,14 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
 import com.android.systemui.log.dagger.KeyguardLargeClockLog
 import com.android.systemui.log.dagger.KeyguardSmallClockLog
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.ClockFaceController
 import com.android.systemui.plugins.ClockTickRate
 import com.android.systemui.plugins.WeatherData
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
 import com.android.systemui.shared.regionsampling.RegionSampler
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardBouncerMessages.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBouncerMessages.kt
new file mode 100644
index 0000000..f4145db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardBouncerMessages.kt
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.annotation.IntDef
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FACE_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FINGERPRINT_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FACE_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
+import com.android.systemui.R.string.bouncer_face_not_recognized
+import com.android.systemui.R.string.keyguard_enter_password
+import com.android.systemui.R.string.keyguard_enter_pattern
+import com.android.systemui.R.string.keyguard_enter_pin
+import com.android.systemui.R.string.kg_bio_too_many_attempts_password
+import com.android.systemui.R.string.kg_bio_too_many_attempts_pattern
+import com.android.systemui.R.string.kg_bio_too_many_attempts_pin
+import com.android.systemui.R.string.kg_bio_try_again_or_password
+import com.android.systemui.R.string.kg_bio_try_again_or_pattern
+import com.android.systemui.R.string.kg_bio_try_again_or_pin
+import com.android.systemui.R.string.kg_face_locked_out
+import com.android.systemui.R.string.kg_fp_locked_out
+import com.android.systemui.R.string.kg_fp_not_recognized
+import com.android.systemui.R.string.kg_primary_auth_locked_out_password
+import com.android.systemui.R.string.kg_primary_auth_locked_out_pattern
+import com.android.systemui.R.string.kg_primary_auth_locked_out_pin
+import com.android.systemui.R.string.kg_prompt_after_dpm_lock
+import com.android.systemui.R.string.kg_prompt_after_user_lockdown_password
+import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pattern
+import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pin
+import com.android.systemui.R.string.kg_prompt_auth_timeout
+import com.android.systemui.R.string.kg_prompt_password_auth_timeout
+import com.android.systemui.R.string.kg_prompt_pattern_auth_timeout
+import com.android.systemui.R.string.kg_prompt_pin_auth_timeout
+import com.android.systemui.R.string.kg_prompt_reason_restart_password
+import com.android.systemui.R.string.kg_prompt_reason_restart_pattern
+import com.android.systemui.R.string.kg_prompt_reason_restart_pin
+import com.android.systemui.R.string.kg_prompt_unattended_update
+import com.android.systemui.R.string.kg_too_many_failed_attempts_countdown
+import com.android.systemui.R.string.kg_trust_agent_disabled
+import com.android.systemui.R.string.kg_unlock_with_password_or_fp
+import com.android.systemui.R.string.kg_unlock_with_pattern_or_fp
+import com.android.systemui.R.string.kg_unlock_with_pin_or_fp
+import com.android.systemui.R.string.kg_wrong_input_try_fp_suggestion
+import com.android.systemui.R.string.kg_wrong_password_try_again
+import com.android.systemui.R.string.kg_wrong_pattern_try_again
+import com.android.systemui.R.string.kg_wrong_pin_try_again
+
+typealias BouncerMessage = Pair<Int, Int>
+
+fun emptyBouncerMessage(): BouncerMessage = Pair(0, 0)
+
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(
+    PROMPT_REASON_TIMEOUT,
+    PROMPT_REASON_DEVICE_ADMIN,
+    PROMPT_REASON_USER_REQUEST,
+    PROMPT_REASON_AFTER_LOCKOUT,
+    PROMPT_REASON_PREPARE_FOR_UPDATE,
+    PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT,
+    PROMPT_REASON_TRUSTAGENT_EXPIRED,
+    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+    PROMPT_REASON_INCORRECT_FACE_INPUT,
+    PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT,
+    PROMPT_REASON_FACE_LOCKED_OUT,
+    PROMPT_REASON_FINGERPRINT_LOCKED_OUT,
+    PROMPT_REASON_DEFAULT,
+    PROMPT_REASON_NONE,
+    PROMPT_REASON_RESTART,
+    PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT,
+)
+annotation class BouncerPromptReason
+
+/**
+ * Helper method that provides the relevant bouncer message that should be shown for different
+ * scenarios indicated by [reason]. [securityMode] & [fpAllowedInBouncer] parameters are used to
+ * provide a more specific message.
+ */
+@JvmOverloads
+fun getBouncerMessage(
+    @BouncerPromptReason reason: Int,
+    securityMode: SecurityMode,
+    fpAllowedInBouncer: Boolean = false
+): BouncerMessage {
+    return when (reason) {
+        PROMPT_REASON_RESTART -> authRequiredAfterReboot(securityMode)
+        PROMPT_REASON_TIMEOUT -> authRequiredAfterPrimaryAuthTimeout(securityMode)
+        PROMPT_REASON_DEVICE_ADMIN -> authRequiredAfterAdminLockdown(securityMode)
+        PROMPT_REASON_USER_REQUEST -> authRequiredAfterUserLockdown(securityMode)
+        PROMPT_REASON_AFTER_LOCKOUT -> biometricLockout(securityMode)
+        PROMPT_REASON_PREPARE_FOR_UPDATE -> authRequiredForUnattendedUpdate(securityMode)
+        PROMPT_REASON_FINGERPRINT_LOCKED_OUT -> fingerprintUnlockUnavailable(securityMode)
+        PROMPT_REASON_FACE_LOCKED_OUT -> faceUnlockUnavailable(securityMode)
+        PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT ->
+            if (fpAllowedInBouncer) incorrectSecurityInputWithFingerprint(securityMode)
+            else incorrectSecurityInput(securityMode)
+        PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT ->
+            if (fpAllowedInBouncer) nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
+            else nonStrongAuthTimeout(securityMode)
+        PROMPT_REASON_TRUSTAGENT_EXPIRED ->
+            if (fpAllowedInBouncer) trustAgentDisabledWithFingerprintAllowed(securityMode)
+            else trustAgentDisabled(securityMode)
+        PROMPT_REASON_INCORRECT_FACE_INPUT ->
+            if (fpAllowedInBouncer) incorrectFaceInputWithFingerprintAllowed(securityMode)
+            else incorrectFaceInput(securityMode)
+        PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT -> incorrectFingerprintInput(securityMode)
+        PROMPT_REASON_DEFAULT ->
+            if (fpAllowedInBouncer) defaultMessageWithFingerprint(securityMode)
+            else defaultMessage(securityMode)
+        PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT -> primaryAuthLockedOut(securityMode)
+        else -> emptyBouncerMessage()
+    }
+}
+
+fun defaultMessage(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
+        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
+        else -> Pair(0, 0)
+    }
+}
+
+fun defaultMessageWithFingerprint(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, 0)
+        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, 0)
+        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, 0)
+        else -> Pair(0, 0)
+    }
+}
+
+fun incorrectSecurityInput(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, 0)
+        SecurityMode.Password -> Pair(kg_wrong_password_try_again, 0)
+        SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, 0)
+        else -> Pair(0, 0)
+    }
+}
+
+fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, kg_wrong_input_try_fp_suggestion)
+        SecurityMode.Password -> Pair(kg_wrong_password_try_again, kg_wrong_input_try_fp_suggestion)
+        SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, kg_wrong_input_try_fp_suggestion)
+        else -> Pair(0, 0)
+    }
+}
+
+fun incorrectFingerprintInput(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pattern)
+        SecurityMode.Password -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_password)
+        SecurityMode.PIN -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pin)
+        else -> Pair(0, 0)
+    }
+}
+
+fun incorrectFaceInput(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pattern)
+        SecurityMode.Password -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_password)
+        SecurityMode.PIN -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pin)
+        else -> Pair(0, 0)
+    }
+}
+
+fun incorrectFaceInputWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, bouncer_face_not_recognized)
+        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, bouncer_face_not_recognized)
+        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, bouncer_face_not_recognized)
+        else -> Pair(0, 0)
+    }
+}
+
+fun biometricLockout(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin)
+        else -> Pair(0, 0)
+    }
+}
+
+fun authRequiredAfterReboot(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_reason_restart_pattern)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_reason_restart_password)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin)
+        else -> Pair(0, 0)
+    }
+}
+
+fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_dpm_lock)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_dpm_lock)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock)
+        else -> Pair(0, 0)
+    }
+}
+
+fun authRequiredAfterUserLockdown(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern)
+        SecurityMode.Password ->
+            Pair(keyguard_enter_password, kg_prompt_after_user_lockdown_password)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin)
+        else -> Pair(0, 0)
+    }
+}
+
+fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_unattended_update)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_unattended_update)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_unattended_update)
+        else -> Pair(0, 0)
+    }
+}
+
+fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_password_auth_timeout)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout)
+        else -> Pair(0, 0)
+    }
+}
+
+fun nonStrongAuthTimeout(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_auth_timeout)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_auth_timeout)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_auth_timeout)
+        else -> Pair(0, 0)
+    }
+}
+
+fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_prompt_auth_timeout)
+        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_prompt_auth_timeout)
+        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_auth_timeout)
+        else -> Pair(0, 0)
+    }
+}
+
+fun faceUnlockUnavailable(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_face_locked_out)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_face_locked_out)
+        else -> Pair(0, 0)
+    }
+}
+
+fun fingerprintUnlockUnavailable(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_fp_locked_out)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_fp_locked_out)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_fp_locked_out)
+        else -> Pair(0, 0)
+    }
+}
+
+fun trustAgentDisabled(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_trust_agent_disabled)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_trust_agent_disabled)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_trust_agent_disabled)
+        else -> Pair(0, 0)
+    }
+}
+
+fun trustAgentDisabledWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_trust_agent_disabled)
+        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_trust_agent_disabled)
+        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_trust_agent_disabled)
+        else -> Pair(0, 0)
+    }
+}
+
+fun primaryAuthLockedOut(securityMode: SecurityMode): BouncerMessage {
+    return when (securityMode) {
+        SecurityMode.Pattern ->
+            Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pattern)
+        SecurityMode.Password ->
+            Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_password)
+        SecurityMode.PIN ->
+            Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pin)
+        else -> Pair(0, 0)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index a6c782d..644a9bc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -15,12 +15,13 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.app.animation.Interpolators;
 import com.android.keyguard.dagger.KeyguardStatusViewScope;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogLevel;
 import com.android.systemui.plugins.ClockController;
-import com.android.systemui.plugins.log.LogBuffer;
-import com.android.systemui.plugins.log.LogLevel;
+import com.android.systemui.shared.clocks.DefaultClockController;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -46,6 +47,9 @@
 
     public static final int LARGE = 0;
     public static final int SMALL = 1;
+    // compensate for translation of parents subject to device screen
+    // In this case, the translation comes from KeyguardStatusView
+    public int screenOffsetYPadding = 0;
 
     /** Returns a region for the large clock to position itself, based on the given parent. */
     public static Rect getLargeClockRegion(ViewGroup parent) {
@@ -161,8 +165,18 @@
             }
 
             if (mLargeClockFrame.isLaidOut()) {
-                mClock.getLargeClock().getEvents().onTargetRegionChanged(
-                        getLargeClockRegion(mLargeClockFrame));
+                Rect targetRegion = getLargeClockRegion(mLargeClockFrame);
+                if (mClock instanceof DefaultClockController) {
+                    mClock.getLargeClock().getEvents().onTargetRegionChanged(
+                            targetRegion);
+                } else {
+                    mClock.getLargeClock().getEvents().onTargetRegionChanged(
+                            new Rect(
+                                    targetRegion.left,
+                                    targetRegion.top - screenOffsetYPadding,
+                                    targetRegion.right,
+                                    targetRegion.bottom - screenOffsetYPadding));
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index a34c9fa..d8bf570 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -39,10 +39,10 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogLevel;
 import com.android.systemui.log.dagger.KeyguardClockLog;
 import com.android.systemui.plugins.ClockController;
-import com.android.systemui.plugins.log.LogBuffer;
-import com.android.systemui.plugins.log.LogLevel;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.clocks.ClockRegistry;
 import com.android.systemui.shared.regionsampling.RegionSampler;
@@ -169,6 +169,16 @@
     }
 
     /**
+     * Used for status view to pass the screen offset from parent view
+     */
+    public void setLockscreenClockY(int clockY) {
+        if (mView.screenOffsetYPadding != clockY) {
+            mView.screenOffsetYPadding = clockY;
+            mView.updateClockTargetRegions();
+        }
+    }
+
+    /**
      * Attach the controller to the view it relates to.
      */
     @Override
@@ -394,13 +404,6 @@
             PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X,
                     x, props, animate);
         }
-
-    }
-
-    void updateKeyguardStatusViewOffset() {
-        // updateClockTargetRegions will call onTargetRegionChanged
-        // which will require the correct translationY property of keyguardStatusView after updating
-        mView.updateClockTargetRegions();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 0394754..58807e4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_APPEAR;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_DISAPPEAR;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
@@ -32,9 +33,9 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.ConstraintSet;
 
+import com.android.app.animation.Interpolators;
 import com.android.settingslib.animation.DisappearAnimationUtils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;
 
 /**
@@ -184,6 +185,7 @@
         }
         mAppearAnimator.setDuration(ANIMATION_DURATION);
         mAppearAnimator.addUpdateListener(animation -> animate(animation.getAnimatedFraction()));
+        mAppearAnimator.addListener(getAnimationListener(CUJ_LOCKSCREEN_PIN_APPEAR));
         mAppearAnimator.start();
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 33bea02..1d7c35d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -45,11 +45,11 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.TextViewInputDisabler;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 /**
  * Displays an alphanumeric (latin-1) key entry for the user to enter
  * an unlock password
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 0a91150..b4ddc9a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -34,9 +34,9 @@
 import android.view.KeyEvent;
 import android.view.View;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index b88d85c..5cc0547 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -32,7 +32,7 @@
 import static androidx.constraintlayout.widget.ConstraintSet.TOP;
 import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
 
-import static com.android.systemui.animation.InterpolatorsAndroidX.DECELERATE_QUINT;
+import static com.android.app.animation.InterpolatorsAndroidX.DECELERATE_QUINT;
 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
 
 import static java.lang.Integer.max;
@@ -87,6 +87,7 @@
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringAnimation;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
@@ -97,7 +98,6 @@
 import com.android.settingslib.drawable.CircleFramedDrawable;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.shared.system.SysUiStatsLog;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 22ad725..419303d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -67,6 +67,42 @@
     int PROMPT_REASON_TRUSTAGENT_EXPIRED = 8;
 
     /**
+     * Prompt that is shown when there is an incorrect primary authentication input.
+     */
+    int PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT = 9;
+
+    /**
+     * Prompt that is shown when there is an incorrect face biometric input.
+     */
+    int PROMPT_REASON_INCORRECT_FACE_INPUT = 10;
+
+    /**
+     * Prompt that is shown when there is an incorrect fingerprint biometric input.
+     */
+    int PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT = 11;
+
+    /**
+     * Prompt that is shown when face authentication is in locked out state.
+     */
+    int PROMPT_REASON_FACE_LOCKED_OUT = 12;
+
+    /**
+     * Prompt that is shown when fingerprint authentication is in locked out state.
+     */
+    int PROMPT_REASON_FINGERPRINT_LOCKED_OUT = 13;
+
+    /**
+     * Default prompt that is shown on the bouncer.
+     */
+    int PROMPT_REASON_DEFAULT = 14;
+
+    /**
+     * Prompt that is shown when primary authentication is in locked out state after too many
+     * attempts
+     */
+    int PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT = 15;
+
+    /**
      * Reset the view and prepare to take input. This should do things like clearing the
      * password or pattern and clear error messages.
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
index c9128e5..96ac8ad 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
@@ -26,9 +26,9 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.animation.AnimationUtils
+import com.android.app.animation.Interpolators
 import com.android.internal.R.interpolator.fast_out_extra_slow_in
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 
 /** Animates constraint layout changes for the security view. */
 class KeyguardSecurityViewTransition : Transition() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 65a7166..b4f124a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -44,11 +44,11 @@
 import androidx.slice.widget.RowContent;
 import androidx.slice.widget.SliceContent;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
 
 import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 0826f8a..794eeda 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -40,11 +40,11 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.ConstraintSet;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.keyguard.KeyguardClockSwitch.ClockSize;
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ClockController;
@@ -215,6 +215,15 @@
     }
 
     /**
+     * Pass top margin from ClockPositionAlgorithm in NotificationPanelViewController
+     * Use for clock view in LS to compensate for top margin to align to the screen
+     * Regardless of translation from AOD and unlock gestures
+     */
+    public void setLockscreenClockY(int clockY) {
+        mKeyguardClockSwitchController.setLockscreenClockY(clockY);
+    }
+
+    /**
      * Set whether the view accessibility importance mode.
      */
     public void setStatusAccessibilityImportance(int mode) {
@@ -230,7 +239,6 @@
      * Update position of the view with an optional animation
      */
     public void updatePosition(int x, int y, float scale, boolean animate) {
-        float oldY = mView.getY();
         setProperty(AnimatableProperty.Y, y, animate);
 
         ClockController clock = mKeyguardClockSwitchController.getClock();
@@ -246,10 +254,6 @@
             setProperty(AnimatableProperty.SCALE_X, 1f, animate);
             setProperty(AnimatableProperty.SCALE_Y, 1f, animate);
         }
-
-        if (oldY != y) {
-            mKeyguardClockSwitchController.updateKeyguardStatusViewOffset();
-        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9573913..c1f70fb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -522,6 +522,14 @@
                     FACE_AUTH_TRIGGERED_TRUST_DISABLED);
         }
 
+        mLogger.logTrustChanged(wasTrusted, enabled, userId);
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onTrustChanged(userId);
+            }
+        }
+
         if (enabled) {
             String message = null;
             if (KeyguardUpdateMonitor.getCurrentUser() == userId
@@ -560,14 +568,6 @@
                 }
             }
         }
-
-        mLogger.logTrustChanged(wasTrusted, enabled, userId);
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
-            if (cb != null) {
-                cb.onTrustChanged(userId);
-            }
-        }
     }
 
     /**
@@ -2884,7 +2884,19 @@
         updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
     }
 
+    /**
+     * If the current state of the device allows for triggering active unlock. This does not
+     * include active unlock availability.
+     */
+    public boolean canTriggerActiveUnlockBasedOnDeviceState() {
+        return shouldTriggerActiveUnlock(/* shouldLog */ false);
+    }
+
     private boolean shouldTriggerActiveUnlock() {
+        return shouldTriggerActiveUnlock(/* shouldLog */ true);
+    }
+
+    private boolean shouldTriggerActiveUnlock(boolean shouldLog) {
         // Triggers:
         final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
         final boolean awakeKeyguard = mPrimaryBouncerFullyShown || mAlternateBouncerShowing
@@ -2914,19 +2926,21 @@
                         && !mKeyguardGoingAway
                         && !mSecureCameraLaunched;
 
-        // Aggregate relevant fields for debug logging.
-        logListenerModelData(
-                new KeyguardActiveUnlockModel(
-                        System.currentTimeMillis(),
-                        user,
-                        shouldTriggerActiveUnlock,
-                        awakeKeyguard,
-                        mAuthInterruptActive,
-                        fpLockedOut,
-                        primaryAuthRequired,
-                        mSwitchingUser,
-                        triggerActiveUnlockForAssistant,
-                        userCanDismissLockScreen));
+        if (shouldLog) {
+            // Aggregate relevant fields for debug logging.
+            logListenerModelData(
+                    new KeyguardActiveUnlockModel(
+                            System.currentTimeMillis(),
+                            user,
+                            shouldTriggerActiveUnlock,
+                            awakeKeyguard,
+                            mAuthInterruptActive,
+                            fpLockedOut,
+                            primaryAuthRequired,
+                            mSwitchingUser,
+                            triggerActiveUnlockForAssistant,
+                            userCanDismissLockScreen));
+        }
 
         return shouldTriggerActiveUnlock;
     }
@@ -4379,9 +4393,11 @@
      */
     public void startBiometricWatchdog() {
         if (mFaceManager != null && !isFaceAuthInteractorEnabled()) {
+            mLogger.scheduleWatchdog("face");
             mFaceManager.scheduleWatchdog();
         }
         if (mFpm != null) {
+            mLogger.scheduleWatchdog("fingerprint");
             mFpm.scheduleWatchdog();
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 651c979..61af722 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -21,9 +21,9 @@
 import android.util.Property;
 import android.view.View;
 
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.plugins.log.LogBuffer;
-import com.android.systemui.plugins.log.LogLevel;
+import com.android.app.animation.Interpolators;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogLevel;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index c6c7113..7d76f12 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -37,7 +37,7 @@
 
 import androidx.annotation.StyleRes;
 
-import com.android.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
 
 /**
  * Provides background color and radius animations for key pad buttons.
diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
index 4aeab97..4557b34 100644
--- a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
@@ -37,9 +37,9 @@
 
 import androidx.core.graphics.drawable.DrawableCompat;
 
+import com.android.app.animation.Interpolators;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 
 /**
  * This class contains implementation for methods that will be used when user has set a
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 6740375..2d0bf9c 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -27,9 +27,9 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.dagger.KeyguardClockLog;
 import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.shared.clocks.ClockRegistry;
 import com.android.systemui.shared.clocks.DefaultClockProvider;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
index 2bb75aa..e7295ef 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
@@ -17,9 +17,9 @@
 package com.android.keyguard.logging
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
 import com.android.systemui.log.dagger.BiometricLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
 import javax.inject.Inject
 
 /** Helper class for logging for [com.android.systemui.biometrics.FaceHelpMessageDeferral] */
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
index 20f9007..c00b2c6 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
@@ -18,11 +18,11 @@
 
 import android.hardware.biometrics.BiometricSourceType
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.INFO
 import com.android.systemui.log.dagger.BiometricLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.INFO
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_ONLY_WAKE
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 4d71a89..8b925b1 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -18,9 +18,9 @@
 
 import com.android.systemui.biometrics.AuthRippleController
 import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.KeyguardLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.statusbar.KeyguardIndicationController
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 1661806..c2d22c3 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -27,13 +27,13 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.keyguard.TrustGrantFlags
 import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.ERROR
-import com.android.systemui.plugins.log.LogLevel.INFO
-import com.android.systemui.plugins.log.LogLevel.VERBOSE
-import com.android.systemui.plugins.log.LogLevel.WARNING
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.LogLevel.WARNING
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
 
@@ -67,8 +67,10 @@
             "ActiveUnlock",
             DEBUG,
             { int1 = wakeReason },
-            { "Skip requesting active unlock from wake reason that doesn't trigger face auth" +
-                    " reason=${PowerManager.wakeReasonToString(int1)}" }
+            {
+                "Skip requesting active unlock from wake reason that doesn't trigger face auth" +
+                    " reason=${PowerManager.wakeReasonToString(int1)}"
+            }
         )
     }
 
@@ -207,17 +209,27 @@
     }
 
     fun logFaceDetected(userId: Int, isStrongBiometric: Boolean) {
-        logBuffer.log(TAG, DEBUG, {
-            int1 = userId
-            bool1 = isStrongBiometric
-        }, {"Face detected: userId: $int1, isStrongBiometric: $bool1"})
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                int1 = userId
+                bool1 = isStrongBiometric
+            },
+            { "Face detected: userId: $int1, isStrongBiometric: $bool1" }
+        )
     }
 
     fun logFingerprintDetected(userId: Int, isStrongBiometric: Boolean) {
-        logBuffer.log(TAG, DEBUG, {
-            int1 = userId
-            bool1 = isStrongBiometric
-        }, {"Fingerprint detected: userId: $int1, isStrongBiometric: $bool1"})
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                int1 = userId
+                bool1 = isStrongBiometric
+            },
+            { "Fingerprint detected: userId: $int1, isStrongBiometric: $bool1" }
+        )
     }
 
     fun logFingerprintError(msgId: Int, originalErrMsg: String) {
@@ -669,13 +681,10 @@
     }
 
     fun logHandleBatteryUpdate(isInteresting: Boolean) {
-        logBuffer.log(
-            TAG,
-            DEBUG,
-            {
-                bool1 = isInteresting
-            },
-            { "handleBatteryUpdate: $bool1" }
-        )
+        logBuffer.log(TAG, DEBUG, { bool1 = isInteresting }, { "handleBatteryUpdate: $bool1" })
+    }
+
+    fun scheduleWatchdog(@CompileTimeConstant watchdogType: String) {
+        logBuffer.log(TAG, DEBUG, "Scheduling biometric watchdog for $watchdogType")
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
index 249b3fe..daafea8 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
@@ -18,9 +18,9 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.TrustModel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
 /** Logging helper for trust repository. */
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index de82ca0..c1871e0 100644
--- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -36,7 +36,7 @@
 import android.view.View
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.util.asIndenting
 import java.io.PrintWriter
 
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index a3e7d71..76086df 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -34,7 +34,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.settingslib.Utils
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.log.ScreenDecorationsLogger
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -110,6 +110,10 @@
         if (showScanningAnimNow == showScanningAnim) {
             return
         }
+        logger.cameraProtectionShownOrHidden(keyguardUpdateMonitor.isFaceDetectionRunning,
+                authController.isShowing,
+                show,
+                showScanningAnim)
         showScanningAnim = showScanningAnimNow
         updateProtectionBoundingPath()
         // Delay the relayout until the end of the animation when hiding,
@@ -352,6 +356,7 @@
             if (biometricSourceType == BiometricSourceType.FACE) {
                 post {
                     faceAuthSucceeded = true
+                    logger.biometricEvent("biometricAuthenticated")
                     enableShowProtection(true)
                 }
             }
@@ -372,6 +377,7 @@
             if (biometricSourceType == BiometricSourceType.FACE) {
                 post {
                     faceAuthSucceeded = false
+                    logger.biometricEvent("biometricFailed")
                     enableShowProtection(false)
                 }
             }
@@ -385,6 +391,7 @@
             if (biometricSourceType == BiometricSourceType.FACE) {
                 post {
                     faceAuthSucceeded = false
+                    logger.biometricEvent("biometricError")
                     enableShowProtection(false)
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index adc0412..ea0f343 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -90,6 +90,8 @@
 import com.android.systemui.util.concurrency.ThreadFactory;
 import com.android.systemui.util.settings.SecureSettings;
 
+import kotlin.Pair;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -98,8 +100,6 @@
 
 import javax.inject.Inject;
 
-import kotlin.Pair;
-
 /**
  * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
  * for antialiasing and emulation purposes.
@@ -254,11 +254,13 @@
             new CameraAvailabilityListener.CameraTransitionCallback() {
         @Override
         public void onApplyCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
+            mLogger.cameraProtectionEvent("onApplyCameraProtection");
             showCameraProtection(protectionPath, bounds);
         }
 
         @Override
         public void onHideCameraProtection() {
+            mLogger.cameraProtectionEvent("onHideCameraProtection");
             hideCameraProtection();
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index aa94ad9..99d4662 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -43,8 +43,8 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.dynamicanimation.animation.SpringForce;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
index d6f0b59..d491975 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
@@ -32,8 +32,8 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 
 /**
  * Visually discloses that contextual data was provided to an assistant.
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 0002ae9..2aac056 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -45,9 +45,9 @@
 import androidx.annotation.StyleRes;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.DualToneHandler;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.policy.BatteryController;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 4b5c50f..5499d2c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -19,7 +19,6 @@
 import android.annotation.RawRes
 import android.content.Context
 import android.content.Context.FINGERPRINT_SERVICE
-import android.content.res.Configuration
 import android.hardware.fingerprint.FingerprintManager
 import android.view.DisplayInfo
 import android.view.Surface
@@ -35,21 +34,18 @@
 import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
 import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE
 import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
-import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
-import com.android.systemui.unfold.updates.FoldProvider
 
 /** Fingerprint only icon animator for BiometricPrompt.  */
 open class AuthBiometricFingerprintIconController(
         context: Context,
         iconView: LottieAnimationView,
         protected val iconViewOverlay: LottieAnimationView
-) : AuthIconController(context, iconView), FoldProvider.FoldCallback {
+) : AuthIconController(context, iconView) {
 
-    private var isDeviceFolded: Boolean = false
     private val isSideFps: Boolean
     private val isReverseDefaultRotation =
             context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
-    private val screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context)
+
     var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
         set(value) {
             if (field == value) {
@@ -77,20 +73,16 @@
         if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) {
             iconView.rotation = 180f
         }
-        screenSizeFoldProvider.registerCallback(this, context.mainExecutor)
-        screenSizeFoldProvider.onConfigurationChange(context.resources.configuration)
     }
 
     private fun updateIconSideFps(@BiometricState lastState: Int, @BiometricState newState: Int) {
         val displayInfo = DisplayInfo()
         context.display?.getDisplayInfo(displayInfo)
         val rotation = getRotationFromDefault(displayInfo.rotation)
-        val iconAnimation = getSideFpsAnimationForTransition(rotation)
         val iconViewOverlayAnimation =
                 getSideFpsOverlayAnimationForTransition(lastState, newState, rotation) ?: return
 
         if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
-            iconView.setAnimation(iconAnimation)
             iconViewOverlay.setAnimation(iconViewOverlayAnimation)
         }
 
@@ -132,10 +124,6 @@
         LottieColorUtils.applyDynamicColors(context, iconView)
     }
 
-    override fun onConfigurationChanged(newConfig: Configuration) {
-        screenSizeFoldProvider.onConfigurationChange(newConfig)
-    }
-
     override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
         if (isSideFps) {
             updateIconSideFps(lastState, newState)
@@ -230,25 +218,6 @@
             if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
 
     @RawRes
-    private fun getSideFpsAnimationForTransition(rotation: Int): Int = when (rotation) {
-        Surface.ROTATION_90 -> if (isDeviceFolded) {
-            R.raw.biometricprompt_folded_base_topleft
-        } else {
-            R.raw.biometricprompt_portrait_base_topleft
-        }
-        Surface.ROTATION_270 -> if (isDeviceFolded) {
-            R.raw.biometricprompt_folded_base_bottomright
-        } else {
-            R.raw.biometricprompt_portrait_base_bottomright
-        }
-        else -> if (isDeviceFolded) {
-            R.raw.biometricprompt_folded_base_default
-        } else {
-            R.raw.biometricprompt_landscape_base
-        }
-    }
-
-    @RawRes
     private fun getSideFpsOverlayAnimationForTransition(
             @BiometricState oldState: Int,
             @BiometricState newState: Int,
@@ -357,8 +326,4 @@
             )
         }
     }
-
-    override fun onFoldUpdated(isFolded: Boolean) {
-        isDeviceFolded = isFolded
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index e04dd06..f330c34 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -124,6 +124,7 @@
     protected final int mTextColorHint;
 
     private AuthPanelController mPanelController;
+
     private PromptInfo mPromptInfo;
     private boolean mRequireConfirmation;
     private int mUserId;
@@ -266,11 +267,9 @@
     /** Create the controller for managing the icons transitions during the prompt.*/
     @NonNull
     protected abstract AuthIconController createIconController();
-
     void setPanelController(AuthPanelController panelController) {
         mPanelController = panelController;
     }
-
     void setPromptInfo(PromptInfo promptInfo) {
         mPromptInfo = promptInfo;
     }
@@ -463,9 +462,16 @@
         return false;
     }
 
+    /**
+     * Updates mIconView animation on updates to fold state, device rotation, or rear display mode
+     * @param animation new asset to use for iconw
+     */
+    public void updateIconViewAnimation(int animation) {
+        mIconView.setAnimation(animation);
+    }
+
     public void updateState(@BiometricState int newState) {
         Log.v(TAG, "newState: " + newState);
-
         mIconController.updateState(mState, newState);
 
         switch (newState) {
@@ -625,7 +631,6 @@
     public void restoreState(@Nullable Bundle savedState) {
         mSavedState = savedState;
     }
-
     private void setTextOrHide(TextView view, CharSequence charSequence) {
         if (TextUtils.isEmpty(charSequence)) {
             view.setVisibility(View.GONE);
@@ -658,7 +663,6 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        mIconController.onConfigurationChanged(newConfig);
         if (mSavedState != null) {
             updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index aeebb01..ce85124 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -59,14 +59,16 @@
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.biometrics.AuthController.ScaleFactorProvider;
 import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
 import com.android.systemui.biometrics.ui.CredentialView;
+import com.android.systemui.biometrics.ui.binder.AuthBiometricFingerprintViewBinder;
+import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel;
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -126,6 +128,8 @@
 
     // TODO: these should be migrated out once ready
     private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor;
+    private final Provider<AuthBiometricFingerprintViewModel>
+            mAuthBiometricFingerprintViewModelProvider;
     private final Provider<CredentialViewModel> mCredentialViewModelProvider;
 
     @VisibleForTesting final BiometricCallback mBiometricCallback;
@@ -241,12 +245,14 @@
                 @NonNull LockPatternUtils lockPatternUtils,
                 @NonNull InteractionJankMonitor jankMonitor,
                 @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
+                @NonNull Provider<AuthBiometricFingerprintViewModel>
+                        authBiometricFingerprintViewModelProvider,
                 @NonNull Provider<CredentialViewModel> credentialViewModelProvider) {
             mConfig.mSensorIds = sensorIds;
             return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle,
                     panelInteractionDetector, userManager, lockPatternUtils, jankMonitor,
-                    biometricPromptInteractor, credentialViewModelProvider,
-                    new Handler(Looper.getMainLooper()), bgExecutor);
+                    biometricPromptInteractor, authBiometricFingerprintViewModelProvider,
+                    credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor);
         }
     }
 
@@ -339,6 +345,8 @@
             @NonNull LockPatternUtils lockPatternUtils,
             @NonNull InteractionJankMonitor jankMonitor,
             @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
+            @NonNull Provider<AuthBiometricFingerprintViewModel>
+                    authBiometricFingerprintViewModelProvider,
             @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
             @NonNull Handler mainHandler,
             @NonNull @Background DelayableExecutor bgExecutor) {
@@ -368,6 +376,7 @@
         mBackgroundExecutor = bgExecutor;
         mInteractionJankMonitor = jankMonitor;
         mBiometricPromptInteractor = biometricPromptInteractor;
+        mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider;
         mCredentialViewModelProvider = credentialViewModelProvider;
 
         // Inflate biometric view only if necessary.
@@ -386,6 +395,9 @@
                 fingerprintAndFaceView.updateOverrideIconLayoutParamsSize();
                 fingerprintAndFaceView.setFaceClass3(
                         faceProperties.sensorStrength == STRENGTH_STRONG);
+                final AuthBiometricFingerprintViewModel fpAndFaceViewModel =
+                        mAuthBiometricFingerprintViewModelProvider.get();
+                AuthBiometricFingerprintViewBinder.bind(fingerprintAndFaceView, fpAndFaceViewModel);
                 mBiometricView = fingerprintAndFaceView;
             } else if (fpProperties != null) {
                 final AuthBiometricFingerprintView fpView =
@@ -394,6 +406,9 @@
                 fpView.setSensorProperties(fpProperties);
                 fpView.setScaleFactorProvider(config.mScaleProvider);
                 fpView.updateOverrideIconLayoutParamsSize();
+                final AuthBiometricFingerprintViewModel fpViewModel =
+                        mAuthBiometricFingerprintViewModelProvider.get();
+                AuthBiometricFingerprintViewBinder.bind(fpView, fpViewModel);
                 mBiometricView = fpView;
             } else if (faceProperties != null) {
                 mBiometricView = (AuthBiometricFaceView) layoutInflater.inflate(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 6eb3c70..fd9cee0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -73,6 +73,7 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
+import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel;
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -127,6 +128,9 @@
 
     // TODO: these should be migrated out once ready
     @NonNull private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor;
+
+    @NonNull private final Provider<AuthBiometricFingerprintViewModel>
+            mAuthBiometricFingerprintViewModelProvider;
     @NonNull private final Provider<CredentialViewModel> mCredentialViewModelProvider;
     @NonNull private final LogContextInteractor mLogContextInteractor;
 
@@ -722,7 +726,6 @@
         }
         onDialogDismissed(reason);
     }
-
     @Inject
     public AuthController(Context context,
             Execution execution,
@@ -741,6 +744,8 @@
             @NonNull UdfpsLogger udfpsLogger,
             @NonNull LogContextInteractor logContextInteractor,
             @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
+            @NonNull Provider<AuthBiometricFingerprintViewModel>
+                    authBiometricFingerprintViewModelProvider,
             @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
             @NonNull InteractionJankMonitor jankMonitor,
             @Main Handler handler,
@@ -771,6 +776,7 @@
 
         mLogContextInteractor = logContextInteractor;
         mBiometricPromptInteractor = biometricPromptInteractor;
+        mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider;
         mCredentialViewModelProvider = credentialViewModelProvider;
 
         mOrientationListener = new BiometricDisplayListener(
@@ -823,9 +829,9 @@
 
             final Rect overlayBounds = new Rect(
                     0, /* left */
-                    0, /* top */
+                    mCachedDisplayInfo.getNaturalHeight() / 2, /* top */
                     mCachedDisplayInfo.getNaturalWidth(), /* right */
-                    mCachedDisplayInfo.getNaturalHeight() /* botom */);
+                    mCachedDisplayInfo.getNaturalHeight() /* bottom */);
 
             mUdfpsOverlayParams = new UdfpsOverlayParams(
                     mUdfpsBounds,
@@ -1299,7 +1305,7 @@
                 .build(bgExecutor, sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle,
                         panelInteractionDetector, userManager, lockPatternUtils,
                         mInteractionJankMonitor, mBiometricPromptInteractor,
-                        mCredentialViewModelProvider);
+                        mAuthBiometricFingerprintViewModelProvider, mCredentialViewModelProvider);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
index d6ad4da..f5f4640 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
@@ -18,7 +18,6 @@
 
 import android.annotation.DrawableRes
 import android.content.Context
-import android.content.res.Configuration
 import android.graphics.drawable.Animatable2
 import android.graphics.drawable.AnimatedVectorDrawable
 import android.graphics.drawable.Drawable
@@ -94,8 +93,6 @@
     /** Called during [onAnimationEnd] if the controller is not [deactivated]. */
     open fun handleAnimationEnd(drawable: Drawable) {}
 
-    open fun onConfigurationChanged(newConfig: Configuration) {}
-
     // TODO(b/251476085): Migrate this to an extension at the appropriate level?
     /** Load the given [rawResources] immediately so they are cached for use in the [context]. */
     protected fun cacheLottieAssetsInContext(context: Context, vararg rawResources: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index d0ac296..6f0f633 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -32,12 +32,12 @@
 import com.android.settingslib.udfps.UdfpsOverlayParams
 import com.android.systemui.CoreStartable
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogLevel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LiftReveal
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index b007134..5ede16d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -28,7 +28,7 @@
 import android.view.View
 import android.view.animation.PathInterpolator
 import com.android.internal.graphics.ColorUtils
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.surfaceeffects.ripple.RippleShader
 
 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 4319f01..962140f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -55,6 +55,7 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.Dumpable
 import com.android.systemui.R
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
@@ -84,6 +85,7 @@
     private val activityTaskManager: ActivityTaskManager,
     overviewProxyService: OverviewProxyService,
     displayManager: DisplayManager,
+    private val displayStateInteractor: DisplayStateInteractor,
     @Main private val mainExecutor: DelayableExecutor,
     @Main private val handler: Handler,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
@@ -203,14 +205,16 @@
         request: SideFpsUiRequestSource,
         @BiometricOverlayConstants.ShowReason reason: Int = BiometricOverlayConstants.REASON_UNKNOWN
     ) {
-        requests.add(request)
-        mainExecutor.execute {
-            if (overlayView == null) {
-                traceSection("SideFpsController#show(request=${request.name}, reason=$reason") {
-                    createOverlayForDisplay(reason)
+        if (!displayStateInteractor.isInRearDisplayMode.value) {
+            requests.add(request)
+            mainExecutor.execute {
+                if (overlayView == null) {
+                    traceSection("SideFpsController#show(request=${request.name}, reason=$reason") {
+                        createOverlayForDisplay(reason)
+                    }
+                } else {
+                    Log.v(TAG, "overlay already shown")
                 }
-            } else {
-                Log.v(TAG, "overlay already shown")
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index ef7dcb7..1dbafc6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -19,7 +19,7 @@
 import android.graphics.PointF
 import android.graphics.RectF
 import com.android.systemui.Dumpable
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionListener
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 7a23759..5ee38c3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -553,6 +553,10 @@
                     + mOverlay.getRequestId());
             return false;
         }
+        if (mLockscreenShadeTransitionController.getQSDragProgress() != 0f
+                || mPrimaryBouncerInteractor.isInTransit()) {
+            return false;
+        }
 
         final TouchProcessorResult result = mTouchProcessor.processTouch(event, mActivePointerId,
                 mOverlayParams);
@@ -626,9 +630,8 @@
             shouldPilfer = true;
         }
 
-        // Execute the pilfer, never pilfer if a vertical swipe is in progress
-        if (shouldPilfer && mLockscreenShadeTransitionController.getQSDragProgress() == 0f
-                && !mPrimaryBouncerInteractor.isInTransit()) {
+        // Execute the pilfer
+        if (shouldPilfer) {
             mInputManager.pilferPointers(
                     mOverlay.getOverlayView().getViewRootImpl().getInputToken());
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index f876aff..d953a88 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -364,7 +364,12 @@
                 if (accessibilityManager.isTouchExplorationEnabled && isEnrollment) {
                     Rect(overlayParams.sensorBounds)
                 } else {
-                    Rect(overlayParams.overlayBounds)
+                    Rect(
+                        0,
+                        0,
+                        overlayParams.naturalDisplayWidth,
+                        overlayParams.naturalDisplayHeight
+                    )
                 }
             } else {
                 Rect(overlayParams.sensorBounds)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index ba8e60a..52db4ab 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -40,9 +40,9 @@
 import androidx.annotation.Nullable;
 import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
 
+import com.android.app.animation.Interpolators;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 
 import com.airbnb.lottie.LottieAnimationView;
 import com.airbnb.lottie.LottieProperty;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index 935de02..9f5669f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -23,11 +23,11 @@
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
index 0d08b43..39199d1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
@@ -16,12 +16,12 @@
 
 package com.android.systemui.biometrics
 
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.LogLevel.WARNING
 import com.android.systemui.log.dagger.UdfpsLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogLevel.ERROR
-import com.android.systemui.plugins.log.LogLevel.VERBOSE
-import com.android.systemui.plugins.log.LogLevel.WARNING
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index f0b9f67..c831663 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -21,8 +21,12 @@
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl
 import com.android.systemui.biometrics.data.repository.PromptRepository
 import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl
+import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository
+import com.android.systemui.biometrics.data.repository.RearDisplayStateRepositoryImpl
 import com.android.systemui.biometrics.domain.interactor.CredentialInteractor
 import com.android.systemui.biometrics.domain.interactor.CredentialInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractor
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl
 import com.android.systemui.dagger.SysUISingleton
@@ -45,6 +49,9 @@
     @SysUISingleton
     fun fingerprintRepository(impl: FingerprintPropertyRepositoryImpl):
             FingerprintPropertyRepository
+    @Binds
+    @SysUISingleton
+    fun rearDisplayStateRepository(impl: RearDisplayStateRepositoryImpl): RearDisplayStateRepository
 
     @Binds
     @SysUISingleton
@@ -52,6 +59,10 @@
 
     @Binds
     @SysUISingleton
+    fun providesDisplayStateInteractor(impl: DisplayStateInteractorImpl): DisplayStateInteractor
+
+    @Binds
+    @SysUISingleton
     fun bindsLogContextInteractor(impl: LogContextInteractorImpl): LogContextInteractor
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepository.kt
new file mode 100644
index 0000000..d17d961
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepository.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import android.content.Context
+import android.hardware.devicestate.DeviceStateManager
+import com.android.internal.util.ArrayUtils
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+/** Provide current rear display state. */
+interface RearDisplayStateRepository {
+    /** Provides the current rear display state. */
+    val isInRearDisplayMode: StateFlow<Boolean>
+}
+
+@SysUISingleton
+class RearDisplayStateRepositoryImpl
+@Inject
+constructor(
+    @Application applicationScope: CoroutineScope,
+    @Application context: Context,
+    deviceStateManager: DeviceStateManager,
+    @Main mainExecutor: Executor
+) : RearDisplayStateRepository {
+    override val isInRearDisplayMode: StateFlow<Boolean> =
+        conflatedCallbackFlow {
+                val sendRearDisplayStateUpdate = { state: Boolean ->
+                    trySendWithFailureLogging(
+                        state,
+                        TAG,
+                        "Error sending rear display state update to $state"
+                    )
+                }
+
+                val callback =
+                    DeviceStateManager.DeviceStateCallback { state ->
+                        val isInRearDisplayMode =
+                            ArrayUtils.contains(
+                                context.resources.getIntArray(
+                                    com.android.internal.R.array.config_rearDisplayDeviceStates
+                                ),
+                                state
+                            )
+                        sendRearDisplayStateUpdate(isInRearDisplayMode)
+                    }
+
+                sendRearDisplayStateUpdate(false)
+                deviceStateManager.registerCallback(mainExecutor, callback)
+                awaitClose { deviceStateManager.unregisterCallback(callback) }
+            }
+            .stateIn(
+                applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
+            )
+
+    companion object {
+        const val TAG = "RearDisplayStateRepositoryImpl"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
new file mode 100644
index 0000000..c935aa2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.content.Context
+import android.content.res.Configuration
+import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
+import com.android.systemui.unfold.updates.FoldProvider
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+/** Aggregates display state information. */
+interface DisplayStateInteractor {
+
+    /** Whether the device is currently in rear display mode. */
+    val isInRearDisplayMode: StateFlow<Boolean>
+
+    /** Whether the device is currently folded. */
+    val isFolded: Flow<Boolean>
+
+    /** Called on configuration changes, used to keep the display state in sync */
+    fun onConfigurationChanged(newConfig: Configuration)
+}
+
+/** Encapsulates logic for interacting with the display state. */
+class DisplayStateInteractorImpl
+@Inject
+constructor(
+    @Application applicationScope: CoroutineScope,
+    @Application context: Context,
+    @Main mainExecutor: Executor,
+    rearDisplayStateRepository: RearDisplayStateRepository,
+) : DisplayStateInteractor {
+    private var screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context)
+
+    fun setScreenSizeFoldProvider(foldProvider: ScreenSizeFoldProvider) {
+        screenSizeFoldProvider = foldProvider
+    }
+
+    override val isFolded: Flow<Boolean> =
+        conflatedCallbackFlow {
+                val sendFoldStateUpdate = { state: Boolean ->
+                    trySendWithFailureLogging(
+                        state,
+                        TAG,
+                        "Error sending fold state update to $state"
+                    )
+                }
+
+                val callback =
+                    object : FoldProvider.FoldCallback {
+                        override fun onFoldUpdated(isFolded: Boolean) {
+                            sendFoldStateUpdate(isFolded)
+                        }
+                    }
+                sendFoldStateUpdate(false)
+                screenSizeFoldProvider.registerCallback(callback, mainExecutor)
+                awaitClose { screenSizeFoldProvider.unregisterCallback(callback) }
+            }
+            .stateIn(
+                applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
+            )
+
+    override val isInRearDisplayMode: StateFlow<Boolean> =
+        rearDisplayStateRepository.isInRearDisplayMode
+
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        screenSizeFoldProvider.onConfigurationChange(newConfig)
+    }
+
+    companion object {
+        private const val TAG = "DisplayStateInteractor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt
new file mode 100644
index 0000000..e776ab4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.ui.binder
+
+import android.view.Surface
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.biometrics.AuthBiometricFingerprintView
+import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.launch
+
+object AuthBiometricFingerprintViewBinder {
+
+    /** Binds a [AuthBiometricFingerprintView] to a [AuthBiometricFingerprintViewModel]. */
+    @JvmStatic
+    fun bind(view: AuthBiometricFingerprintView, viewModel: AuthBiometricFingerprintViewModel) {
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.onConfigurationChanged(view.context.resources.configuration)
+                viewModel.setRotation(view.context.display?.orientation ?: Surface.ROTATION_0)
+                launch {
+                    viewModel.iconAsset.collect { iconAsset ->
+                        view.updateIconViewAnimation(iconAsset)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
index e2d36dc..9292bd7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -6,8 +6,8 @@
 import android.widget.TextView
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.biometrics.AuthDialog
 import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.ui.CredentialPasswordView
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModel.kt
new file mode 100644
index 0000000..617d80c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModel.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.ui.viewmodel
+
+import android.annotation.RawRes
+import android.content.res.Configuration
+import android.view.Surface
+import com.android.systemui.R
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Models UI of AuthBiometricFingerprintView to support rear display state changes. */
+class AuthBiometricFingerprintViewModel
+@Inject
+constructor(private val interactor: DisplayStateInteractor) {
+    /** Current device rotation. */
+    private var rotation: Int = Surface.ROTATION_0
+
+    /** Current AuthBiometricFingerprintView asset. */
+    val iconAsset: Flow<Int> =
+        combine(interactor.isFolded, interactor.isInRearDisplayMode) {
+            isFolded: Boolean,
+            isInRearDisplayMode: Boolean ->
+            getSideFpsAnimationAsset(isFolded, isInRearDisplayMode)
+        }
+
+    @RawRes
+    private fun getSideFpsAnimationAsset(
+        isDeviceFolded: Boolean,
+        isInRearDisplayMode: Boolean,
+    ): Int =
+        when (rotation) {
+            Surface.ROTATION_90 ->
+                if (isInRearDisplayMode) {
+                    R.raw.biometricprompt_rear_portrait_reverse_base
+                } else if (isDeviceFolded) {
+                    R.raw.biometricprompt_folded_base_topleft
+                } else {
+                    R.raw.biometricprompt_portrait_base_topleft
+                }
+            Surface.ROTATION_270 ->
+                if (isInRearDisplayMode) {
+                    R.raw.biometricprompt_rear_portrait_base
+                } else if (isDeviceFolded) {
+                    R.raw.biometricprompt_folded_base_bottomright
+                } else {
+                    R.raw.biometricprompt_portrait_base_bottomright
+                }
+            else ->
+                if (isInRearDisplayMode) {
+                    R.raw.biometricprompt_rear_landscape_base
+                } else if (isDeviceFolded) {
+                    R.raw.biometricprompt_folded_base_default
+                } else {
+                    R.raw.biometricprompt_landscape_base
+                }
+        }
+
+    /** Called on configuration changes */
+    fun onConfigurationChanged(newConfig: Configuration) {
+        interactor.onConfigurationChanged(newConfig)
+    }
+
+    fun setRotation(newRotation: Int) {
+        rotation = newRotation
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
index d99625a..96af42b 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.bluetooth
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.BluetoothLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
 /** Helper class for logging bluetooth events. */
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
index d27708f..5b3a982 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
@@ -20,11 +20,11 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.INFO
-import com.android.systemui.plugins.log.LogMessage
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogMessage
 import com.android.systemui.log.dagger.BroadcastDispatcherLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 11ef749..7bf8f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -30,9 +30,9 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.app.animation.Interpolators;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.surfaceeffects.ripple.RippleShader;
 import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt
new file mode 100644
index 0000000..0542e13
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.clipboardoverlay
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.net.Uri
+import android.util.Log
+import android.util.Size
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import java.io.IOException
+import java.util.function.Consumer
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeoutOrNull
+
+class ClipboardImageLoader
+@Inject
+constructor(
+    private val context: Context,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Application private val mainScope: CoroutineScope
+) {
+    private val TAG: String = "ClipboardImageLoader"
+
+    suspend fun load(uri: Uri, timeoutMs: Long = 300) =
+        withTimeoutOrNull(timeoutMs) {
+            withContext(bgDispatcher) {
+                try {
+                    val size = context.resources.getDimensionPixelSize(R.dimen.overlay_x_scale)
+                    context.contentResolver.loadThumbnail(uri, Size(size, size * 4), null)
+                } catch (e: IOException) {
+                    Log.e(TAG, "Thumbnail loading failed!", e)
+                    null
+                }
+            }
+        }
+
+    fun loadAsync(uri: Uri, callback: Consumer<Bitmap?>) {
+        mainScope.launch { callback.accept(load(uri)) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 0aeab10..757ebf4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -32,6 +32,7 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
+import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -90,6 +91,7 @@
     private final ClipboardOverlayUtils mClipboardUtils;
     private final FeatureFlags mFeatureFlags;
     private final Executor mBgExecutor;
+    private final ClipboardImageLoader mClipboardImageLoader;
 
     private final ClipboardOverlayView mView;
 
@@ -109,6 +111,7 @@
 
     private Runnable mOnUiUpdate;
 
+    private boolean mShowingUi;
     private boolean mIsMinimized;
     private ClipboardModel mClipboardModel;
 
@@ -175,9 +178,11 @@
             FeatureFlags featureFlags,
             ClipboardOverlayUtils clipboardUtils,
             @Background Executor bgExecutor,
+            ClipboardImageLoader clipboardImageLoader,
             UiEventLogger uiEventLogger) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
+        mClipboardImageLoader = clipboardImageLoader;
 
         mClipboardLogger = new ClipboardLogger(uiEventLogger);
 
@@ -260,21 +265,42 @@
         boolean shouldAnimate = !model.dataMatches(mClipboardModel) || wasExiting;
         mClipboardModel = model;
         mClipboardLogger.setClipSource(mClipboardModel.getSource());
-        if (shouldAnimate) {
-            reset();
-            mClipboardLogger.setClipSource(mClipboardModel.getSource());
-            if (shouldShowMinimized(mWindow.getWindowInsets())) {
-                mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_MINIMIZED);
-                mIsMinimized = true;
-                mView.setMinimized(true);
-            } else {
-                mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_EXPANDED);
+        if (mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT)) {
+            if (shouldAnimate) {
+                reset();
+                mClipboardLogger.setClipSource(mClipboardModel.getSource());
+                if (shouldShowMinimized(mWindow.getWindowInsets())) {
+                    mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_MINIMIZED);
+                    mIsMinimized = true;
+                    mView.setMinimized(true);
+                } else {
+                    mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_EXPANDED);
+                    setExpandedView(this::animateIn);
+                }
+                mView.announceForAccessibility(
+                        getAccessibilityAnnouncement(mClipboardModel.getType()));
+            } else if (!mIsMinimized) {
+                setExpandedView(() -> {
+                });
+            }
+        } else {
+            if (shouldAnimate) {
+                reset();
+                mClipboardLogger.setClipSource(mClipboardModel.getSource());
+                if (shouldShowMinimized(mWindow.getWindowInsets())) {
+                    mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_MINIMIZED);
+                    mIsMinimized = true;
+                    mView.setMinimized(true);
+                } else {
+                    mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_EXPANDED);
+                    setExpandedView();
+                    animateIn();
+                }
+                mView.announceForAccessibility(
+                        getAccessibilityAnnouncement(mClipboardModel.getType()));
+            } else if (!mIsMinimized) {
                 setExpandedView();
             }
-            animateIn();
-            mView.announceForAccessibility(getAccessibilityAnnouncement(mClipboardModel.getType()));
-        } else if (!mIsMinimized) {
-            setExpandedView();
         }
         if (mClipboardModel.isRemote()) {
             mTimeoutHandler.cancelTimeout();
@@ -285,6 +311,58 @@
         }
     }
 
+    private void setExpandedView(Runnable onViewReady) {
+        final ClipboardModel model = mClipboardModel;
+        mView.setMinimized(false);
+        switch (model.getType()) {
+            case TEXT:
+                if (model.isRemote() || DeviceConfig.getBoolean(
+                        DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
+                    if (model.getTextLinks() != null) {
+                        classifyText(model);
+                    }
+                }
+                if (model.isSensitive()) {
+                    mView.showTextPreview(mContext.getString(R.string.clipboard_asterisks), true);
+                } else {
+                    mView.showTextPreview(model.getText().toString(), false);
+                }
+                mView.setEditAccessibilityAction(true);
+                mOnPreviewTapped = this::editText;
+                onViewReady.run();
+                break;
+            case IMAGE:
+                mView.setEditAccessibilityAction(true);
+                mOnPreviewTapped = () -> editImage(model.getUri());
+                if (model.isSensitive()) {
+                    mView.showImagePreview(null);
+                    onViewReady.run();
+                } else {
+                    mClipboardImageLoader.loadAsync(model.getUri(), (bitmap) -> mView.post(() -> {
+                        if (bitmap == null) {
+                            mView.showDefaultTextPreview();
+                        } else {
+                            mView.showImagePreview(bitmap);
+                        }
+                        onViewReady.run();
+                    }));
+                }
+                break;
+            case URI:
+            case OTHER:
+                mView.showDefaultTextPreview();
+                onViewReady.run();
+                break;
+        }
+        if (!model.isRemote()) {
+            maybeShowRemoteCopy(model.getClipData());
+        }
+        if (model.getType() != ClipboardModel.Type.OTHER) {
+            mOnShareTapped = () -> shareContent(model.getClipData());
+            mView.showShareChip();
+        }
+    }
+
     private void setExpandedView() {
         final ClipboardModel model = mClipboardModel;
         mView.setMinimized(false);
@@ -350,8 +428,12 @@
                     mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED);
                     mIsMinimized = false;
                 }
-                setExpandedView();
-                animateIn();
+                if (mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT)) {
+                    setExpandedView(() -> animateIn());
+                } else {
+                    setExpandedView();
+                    animateIn();
+                }
             }
         });
         mEnterAnimator.start();
@@ -412,7 +494,8 @@
                 mInputMonitor.getInputChannel(), Looper.getMainLooper()) {
             @Override
             public void onInputEvent(InputEvent event) {
-                if (event instanceof MotionEvent) {
+                if ((!mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT) || mShowingUi)
+                        && event instanceof MotionEvent) {
                     MotionEvent motionEvent = (MotionEvent) event;
                     if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
                         if (!mView.isInTouchRegion(
@@ -452,6 +535,12 @@
         mEnterAnimator = mView.getEnterAnimation();
         mEnterAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
+            public void onAnimationStart(Animator animation) {
+                super.onAnimationStart(animation);
+                mShowingUi = true;
+            }
+
+            @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
                 if (mOnUiUpdate != null) {
@@ -518,6 +607,7 @@
         mOnRemoteCopyTapped = null;
         mOnShareTapped = null;
         mOnPreviewTapped = null;
+        mShowingUi = false;
         mView.reset();
         mTimeoutHandler.cancelTimeout();
         mClipboardLogger.reset();
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
similarity index 61%
copy from services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java
copy to packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
index 7c339d2..9b0c3fa 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
@@ -14,14 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.server.companion.datatransfer.contextsync;
+package com.android.systemui.common.ui.data.repository
 
-/** Callback for call metadata syncing. */
-public abstract class CallMetadataSyncCallback {
+import dagger.Binds
+import dagger.Module
 
-    abstract void processCallControlAction(int crossDeviceCallId, int callControlAction);
-
-    abstract void requestCrossDeviceSync(int userId);
-
-    abstract void updateStatus(int userId, boolean shouldSyncCallMetadata);
+@Module
+interface CommonRepositoryModule {
+    @Binds fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
new file mode 100644
index 0000000..3e6ac86
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.common.ui.data.repository
+
+import android.content.Context
+import android.content.res.Configuration
+import android.view.DisplayInfo
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.wrapper.DisplayUtilsWrapper
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+interface ConfigurationRepository {
+    /** Called whenever ui mode, theme or configuration has changed. */
+    val onAnyConfigurationChange: Flow<Unit>
+    val scaleForResolution: Flow<Float>
+
+    fun getResolutionScale(): Float
+}
+
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class ConfigurationRepositoryImpl
+@Inject
+constructor(
+    private val configurationController: ConfigurationController,
+    private val context: Context,
+    @Application private val scope: CoroutineScope,
+    private val displayUtils: DisplayUtilsWrapper,
+) : ConfigurationRepository {
+    private val displayInfo = MutableStateFlow(DisplayInfo())
+
+    override val onAnyConfigurationChange: Flow<Unit> =
+        ConflatedCallbackFlow.conflatedCallbackFlow {
+            val callback =
+                object : ConfigurationController.ConfigurationListener {
+                    override fun onUiModeChanged() {
+                        sendUpdate("ConfigurationRepository#onUiModeChanged")
+                    }
+
+                    override fun onThemeChanged() {
+                        sendUpdate("ConfigurationRepository#onThemeChanged")
+                    }
+
+                    override fun onConfigChanged(newConfig: Configuration) {
+                        sendUpdate("ConfigurationRepository#onConfigChanged")
+                    }
+
+                    fun sendUpdate(reason: String) {
+                        trySendWithFailureLogging(Unit, reason)
+                    }
+                }
+            configurationController.addCallback(callback)
+            awaitClose { configurationController.removeCallback(callback) }
+        }
+
+    private val configurationChange: Flow<Unit> =
+        ConflatedCallbackFlow.conflatedCallbackFlow {
+            val callback =
+                object : ConfigurationController.ConfigurationListener {
+                    override fun onConfigChanged(newConfig: Configuration) {
+                        trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged")
+                    }
+                }
+            configurationController.addCallback(callback)
+            awaitClose { configurationController.removeCallback(callback) }
+        }
+
+    override val scaleForResolution: StateFlow<Float> =
+        configurationChange
+            .mapLatest { getResolutionScale() }
+            .distinctUntilChanged()
+            .stateIn(scope, SharingStarted.WhileSubscribed(), getResolutionScale())
+
+    override fun getResolutionScale(): Float {
+        context.display.getDisplayInfo(displayInfo.value)
+        val maxDisplayMode =
+            displayUtils.getMaximumResolutionDisplayMode(displayInfo.value.supportedModes)
+        maxDisplayMode?.let {
+            val scaleFactor =
+                displayUtils.getPhysicalPixelDisplaySizeRatio(
+                    maxDisplayMode.physicalWidth,
+                    maxDisplayMode.physicalHeight,
+                    displayInfo.value.naturalWidth,
+                    displayInfo.value.naturalHeight
+                )
+            return if (scaleFactor == Float.POSITIVE_INFINITY) 1f else scaleFactor
+        }
+        return 1f
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
index 8d0edf8..b447d66 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
@@ -32,7 +32,7 @@
 import androidx.lifecycle.LifecycleObserver
 import androidx.lifecycle.OnLifecycleEvent
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.controls.ui.ControlsUiController
 
 object ControlsAnimations {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 6a9aaf8..e6361f4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -50,7 +50,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.internal.graphics.ColorUtils
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.controls.ControlsMetricsLogger
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.util.concurrency.DelayableExecutor
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index fa36eee..1461135 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -38,7 +38,7 @@
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityNodeInfo
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.controls.ui.ControlViewHolder.Companion.MAX_LEVEL
 import com.android.systemui.controls.ui.ControlViewHolder.Companion.MIN_LEVEL
 import java.util.IllegalFormatException
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 8e6e0dd..17cf808 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -39,6 +39,7 @@
 import com.android.systemui.biometrics.dagger.UdfpsModule;
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
+import com.android.systemui.common.ui.data.repository.CommonRepositoryModule;
 import com.android.systemui.complication.dagger.ComplicationComponent;
 import com.android.systemui.controls.dagger.ControlsModule;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -68,6 +69,7 @@
 import com.android.systemui.qs.QSFragmentStartableModule;
 import com.android.systemui.qs.footer.dagger.FooterActionsModule;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.retail.dagger.RetailModeModule;
 import com.android.systemui.screenrecord.ScreenRecordModule;
 import com.android.systemui.screenshot.dagger.ScreenshotModule;
 import com.android.systemui.security.data.repository.SecurityRepositoryModule;
@@ -155,6 +157,7 @@
             ClipboardOverlayModule.class,
             ClockInfoModule.class,
             ClockRegistryModule.class,
+            CommonRepositoryModule.class,
             ConnectivityModule.class,
             CoroutinesModule.class,
             DreamModule.class,
@@ -177,6 +180,7 @@
             PrivacyModule.class,
             QRCodeScannerModule.class,
             QSFragmentStartableModule.class,
+            RetailModeModule.class,
             ScreenshotModule.class,
             SensorModule.class,
             SecurityRepositoryModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index d19c6ec..5369780 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -19,10 +19,10 @@
 import android.view.Display
 import com.android.systemui.doze.DozeLog.Reason
 import com.android.systemui.doze.DozeLog.reasonToString
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.ERROR
-import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.INFO
 import com.android.systemui.log.dagger.DozeLog
 import com.android.systemui.statusbar.policy.DevicePostureController
 import com.google.errorprone.annotations.CompileTimeConstant
diff --git a/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt
index d853e04..1057852 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt
@@ -24,4 +24,8 @@
     fun burnInOffset(amplitude: Int, xAxis: Boolean): Int {
         return getBurnInOffset(amplitude, xAxis)
     }
+
+    fun burnInProgressOffset(): Float {
+        return getBurnInProgressOffset()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
index eb79290..fdb7651 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.dreams
 
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.DreamLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
 /** Logs dream-related stuff to a {@link LogBuffer}. */
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index df46e07..c5e7e0d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -25,8 +25,8 @@
 import androidx.core.animation.doOnEnd
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.complication.ComplicationHostViewController
 import com.android.systemui.complication.ComplicationLayoutParams
 import com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTOM
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 15a32d2..c22019e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -33,9 +33,9 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.app.animation.Interpolators;
 import com.android.dream.lowlight.LowLightTransitionCoordinator;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.complication.ComplicationHostViewController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
index 250cfec..c889ac2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
@@ -18,11 +18,14 @@
 
 import com.android.internal.app.AssistUtils;
 import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.shared.condition.Condition;
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.CoroutineScope;
+
 /**
  * {@link AssistantAttentionCondition} provides a signal when assistant has the user's attention.
  */
@@ -58,8 +61,10 @@
 
     @Inject
     public AssistantAttentionCondition(
+            @Application CoroutineScope scope,
             DreamOverlayStateController dreamOverlayStateController,
             AssistUtils assistUtils) {
+        super(scope);
         mDreamOverlayStateController = dreamOverlayStateController;
         mAssistUtils = assistUtils;
     }
@@ -75,6 +80,11 @@
         mDreamOverlayStateController.removeCallback(mCallback);
     }
 
+    @Override
+    protected int getStartStrategy() {
+        return START_EAGERLY;
+    }
+
     private void enableVisualQueryDetection() {
         if (mEnabled) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
index 3ef19b7..99688be 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
@@ -19,19 +19,20 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.shared.condition.Condition;
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.CoroutineScope;
+
 /**
  * {@link DreamCondition} provides a signal when a dream begins and ends.
  */
 public class DreamCondition extends Condition {
     private final DreamManager mDreamManager;
-
     private final KeyguardUpdateMonitor mUpdateMonitor;
 
-
     private final KeyguardUpdateMonitorCallback mUpdateCallback =
             new KeyguardUpdateMonitorCallback() {
                 @Override
@@ -41,7 +42,11 @@
             };
 
     @Inject
-    public DreamCondition(DreamManager dreamManager, KeyguardUpdateMonitor monitor) {
+    public DreamCondition(
+            @Application CoroutineScope scope,
+            DreamManager dreamManager,
+            KeyguardUpdateMonitor monitor) {
+        super(scope);
         mDreamManager = dreamManager;
         mUpdateMonitor = monitor;
     }
@@ -56,4 +61,9 @@
     protected void stop() {
         mUpdateMonitor.removeCallback(mUpdateCallback);
     }
+
+    @Override
+    protected int getStartStrategy() {
+        return START_EAGERLY;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index a2f65ba..4b03fd3 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL
 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL
 import com.android.systemui.dump.nano.SystemUIProtoDump
-import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
 import com.google.protobuf.nano.MessageNano
 import java.io.BufferedOutputStream
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index 7d1ffca..2d57633 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -16,12 +16,12 @@
 
 package com.android.systemui.dump
 
-import android.util.ArrayMap
 import com.android.systemui.Dumpable
 import com.android.systemui.ProtoDumpable
 import com.android.systemui.dump.nano.SystemUIProtoDump
-import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.log.LogBuffer
 import java.io.PrintWriter
+import java.util.TreeMap
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -36,8 +36,9 @@
  */
 @Singleton
 open class DumpManager @Inject constructor() {
-    private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap()
-    private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
+    // NOTE: Using TreeMap ensures that iteration is in a predictable & alphabetical order.
+    private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = TreeMap()
+    private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = TreeMap()
 
     /** See [registerCriticalDumpable]. */
     fun registerCriticalDumpable(module: Dumpable) {
@@ -132,7 +133,8 @@
     }
 
     /**
-     * Dumps the first dumpable or buffer whose registered name ends with [target]
+     * Dumps the alphabetically first, shortest-named dumpable or buffer whose registered name ends
+     * with [target].
      */
     @Synchronized
     fun dumpTarget(
@@ -141,19 +143,14 @@
         args: Array<String>,
         tailLength: Int,
     ) {
-        for (dumpable in dumpables.values) {
-            if (dumpable.name.endsWith(target)) {
-                dumpDumpable(dumpable, pw, args)
-                return
+        sequence {
+            findBestTargetMatch(dumpables, target)?.let {
+                yield(it.name to { dumpDumpable(it, pw, args) })
             }
-        }
-
-        for (buffer in buffers.values) {
-            if (buffer.name.endsWith(target)) {
-                dumpBuffer(buffer, pw, tailLength)
-                return
+            findBestTargetMatch(buffers, target)?.let {
+                yield(it.name to { dumpBuffer(it, pw, tailLength) })
             }
-        }
+        }.sortedBy { it.first }.minByOrNull { it.first.length }?.second?.invoke()
     }
 
     @Synchronized
@@ -162,11 +159,8 @@
         protoDump: SystemUIProtoDump,
         args: Array<String>
     ) {
-        for (dumpable in dumpables.values) {
-            if (dumpable.dumpable is ProtoDumpable && dumpable.name.endsWith(target)) {
-                dumpProtoDumpable(dumpable.dumpable, protoDump, args)
-                return
-            }
+        findBestProtoTargetMatch(dumpables, target)?.let {
+            dumpProtoDumpable(it, protoDump, args)
         }
     }
 
@@ -303,6 +297,22 @@
         val existingDumpable = dumpables[name]?.dumpable ?: buffers[name]?.dumpable
         return existingDumpable == null || newDumpable == existingDumpable
     }
+
+    private fun <V : Any> findBestTargetMatch(map: Map<String, V>, target: String): V? = map
+        .asSequence()
+        .filter { it.key.endsWith(target) }
+        .minByOrNull { it.key.length }
+        ?.value
+
+    private fun findBestProtoTargetMatch(
+        map: Map<String, RegisteredDumpable<Dumpable>>,
+        target: String
+    ): ProtoDumpable? = map
+        .asSequence()
+        .filter { it.key.endsWith(target) }
+        .filter { it.value.dumpable is ProtoDumpable }
+        .minByOrNull { it.key.length }
+        ?.value?.dumpable as? ProtoDumpable
 }
 
 private data class RegisteredDumpable<T>(
diff --git a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
index 8299b13..0eab1af 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
@@ -19,7 +19,7 @@
 import android.content.Context
 import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.util.io.Files
 import com.android.systemui.util.time.SystemClock
 import java.io.IOException
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 75eab82..873755f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -61,6 +61,10 @@
     // TODO(b/254512538): Tracking Bug
     val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply")
 
+    // TODO(b/279735475): Tracking Bug
+    @JvmField
+    val NEW_LIGHT_BAR_LOGIC = unreleasedFlag(279735475, "new_light_bar_logic", teamfood = true)
+
     /**
      * This flag is server-controlled and should stay as [unreleasedFlag] since we never want to
      * enable it on release builds.
@@ -85,7 +89,7 @@
     // TODO(b/277338665): Tracking Bug
     @JvmField
     val NOTIFICATION_SHELF_REFACTOR =
-        unreleasedFlag(271161129, "notification_shelf_refactor")
+        unreleasedFlag(271161129, "notification_shelf_refactor", teamfood = true)
 
     @JvmField
     val ANIMATED_NOTIFICATION_SHADE_INSETS =
@@ -203,11 +207,6 @@
             )
 
     /** Whether to inflate the bouncer view on a background thread. */
-    // TODO(b/272091103): Tracking Bug
-    @JvmField
-    val ASYNC_INFLATE_BOUNCER = releasedFlag(229, "async_inflate_bouncer")
-
-    /** Whether to inflate the bouncer view on a background thread. */
     // TODO(b/273341787): Tracking Bug
     @JvmField
     val PREVENT_BYPASS_KEYGUARD = releasedFlag(230, "prevent_bypass_keyguard")
@@ -233,6 +232,11 @@
     val REVAMPED_BOUNCER_MESSAGES =
         unreleasedFlag(234, "revamped_bouncer_messages")
 
+    /** Whether to delay showing bouncer UI when face auth or active unlock are enrolled. */
+    // TODO(b/279794160): Tracking bug.
+    @JvmField
+    val DELAY_BOUNCER = unreleasedFlag(235, "delay_bouncer")
+
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
     @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -271,7 +275,7 @@
         )
 
     @JvmField
-    val QS_PIPELINE_NEW_HOST = unreleasedFlag(504, "qs_pipeline_new_host", teamfood = false)
+    val QS_PIPELINE_NEW_HOST = unreleasedFlag(504, "qs_pipeline_new_host", teamfood = true)
 
     // TODO(b/278068252): Tracking Bug
     @JvmField
@@ -388,7 +392,7 @@
     val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE = releasedFlag(912, "media_ttt_dismiss_gesture")
 
     // TODO(b/266157412): Tracking Bug
-    val MEDIA_RETAIN_SESSIONS = releasedFlag(913, "media_retain_sessions")
+    val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
 
     // TODO(b/266739309): Tracking Bug
     @JvmField
@@ -398,10 +402,10 @@
     val MEDIA_RESUME_PROGRESS = releasedFlag(915, "media_resume_progress")
 
     // TODO(b/267166152) : Tracking Bug
-    val MEDIA_RETAIN_RECOMMENDATIONS = releasedFlag(916, "media_retain_recommendations")
+    val MEDIA_RETAIN_RECOMMENDATIONS = unreleasedFlag(916, "media_retain_recommendations")
 
     // TODO(b/270437894): Tracking Bug
-    val MEDIA_REMOTE_RESUME = releasedFlag(917, "media_remote_resume")
+    val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume")
 
     // 1000 - dock
     val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
@@ -597,6 +601,8 @@
 
     // 1700 - clipboard
     @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
+    // TODO(b/278714186) Tracking Bug
+    @JvmField val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag(1702, "clipboard_image_timeout")
 
     // 1800 - shade container
     // TODO(b/265944639): Tracking Bug
@@ -620,7 +626,7 @@
     // TODO(b/269132640): Tracking Bug
     @JvmField
     val APP_PANELS_REMOVE_APPS_ALLOWED =
-        unreleasedFlag(2003, "app_panels_remove_apps_allowed", teamfood = true)
+        releasedFlag(2003, "app_panels_remove_apps_allowed")
 
     // 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.)
     // TODO(b/259264861): Tracking Bug
@@ -696,6 +702,5 @@
 
     // TODO(b/278761837): Tracking Bug
     @JvmField
-    val USE_NEW_ACTIVITY_STARTER = unreleasedFlag(2801, name = "use_new_activity_starter",
-            teamfood = true)
+    val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index d3b6fc2..2807107 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -97,6 +97,7 @@
 import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.LifecycleRegistry;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
@@ -116,7 +117,6 @@
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.animation.Expandable;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -128,6 +128,7 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -240,6 +241,7 @@
     private final ScreenshotHelper mScreenshotHelper;
     private final SysuiColorExtractor mSysuiColorExtractor;
     private final IStatusBarService mStatusBarService;
+    protected final LightBarController mLightBarController;
     protected final NotificationShadeWindowController mNotificationShadeWindowController;
     private final IWindowManager mIWindowManager;
     private final Executor mBackgroundExecutor;
@@ -349,6 +351,7 @@
             MetricsLogger metricsLogger,
             SysuiColorExtractor colorExtractor,
             IStatusBarService statusBarService,
+            LightBarController lightBarController,
             NotificationShadeWindowController notificationShadeWindowController,
             IWindowManager iWindowManager,
             @Background Executor backgroundExecutor,
@@ -381,6 +384,7 @@
         mUiEventLogger = uiEventLogger;
         mSysuiColorExtractor = colorExtractor;
         mStatusBarService = statusBarService;
+        mLightBarController = lightBarController;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mIWindowManager = iWindowManager;
         mBackgroundExecutor = backgroundExecutor;
@@ -694,6 +698,7 @@
         ActionsDialogLite dialog = new ActionsDialogLite(mContext,
                 com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActionsLite,
                 mAdapter, mOverflowAdapter, mSysuiColorExtractor, mStatusBarService,
+                mLightBarController,
                 mNotificationShadeWindowController, this::onRefresh, mKeyguardShowing,
                 mPowerAdapter, mUiEventLogger, mCentralSurfacesOptional, mKeyguardUpdateMonitor,
                 mLockPatternUtils);
@@ -2192,6 +2197,7 @@
         protected final SysuiColorExtractor mColorExtractor;
         private boolean mKeyguardShowing;
         protected float mScrimAlpha;
+        protected final LightBarController mLightBarController;
         protected final NotificationShadeWindowController mNotificationShadeWindowController;
         private ListPopupWindow mOverflowPopup;
         private Dialog mPowerOptionsDialog;
@@ -2267,6 +2273,7 @@
         ActionsDialogLite(Context context, int themeRes, MyAdapter adapter,
                 MyOverflowAdapter overflowAdapter,
                 SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
+                LightBarController lightBarController,
                 NotificationShadeWindowController notificationShadeWindowController,
                 Runnable onRefreshCallback, boolean keyguardShowing,
                 MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
@@ -2282,6 +2289,7 @@
             mPowerOptionsAdapter = powerAdapter;
             mColorExtractor = sysuiColorExtractor;
             mStatusBarService = statusBarService;
+            mLightBarController = lightBarController;
             mNotificationShadeWindowController = notificationShadeWindowController;
             mOnRefreshCallback = onRefreshCallback;
             mKeyguardShowing = keyguardShowing;
@@ -2474,6 +2482,7 @@
         @Override
         protected void start() {
             mGlobalActionsLayout.updateList();
+            mLightBarController.setGlobalActionsVisible(true);
 
             if (mBackgroundDrawable instanceof ScrimDrawable) {
                 mColorExtractor.addOnColorsChangedListener(this);
@@ -2504,6 +2513,7 @@
 
         @Override
         protected void stop() {
+            mLightBarController.setGlobalActionsVisible(false);
             mColorExtractor.removeOnColorsChangedListener(this);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index 2ef5e19..6bc763c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyboard.backlight.ui.view
 
+import android.annotation.AttrRes
 import android.annotation.ColorInt
 import android.app.Dialog
 import android.content.Context
@@ -31,6 +32,7 @@
 import android.widget.LinearLayout
 import android.widget.LinearLayout.LayoutParams
 import android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
+import com.android.settingslib.Utils
 import com.android.systemui.R
 import com.android.systemui.util.children
 
@@ -38,7 +40,7 @@
     context: Context,
     initialCurrentLevel: Int,
     initialMaxLevel: Int,
-) : Dialog(context) {
+) : Dialog(context, R.style.Theme_SystemUI_Dialog) {
 
     private data class RootProperties(
         val cornerRadius: Float,
@@ -69,9 +71,14 @@
     private lateinit var rootProperties: RootProperties
     private lateinit var iconProperties: BacklightIconProperties
     private lateinit var stepProperties: StepViewProperties
-    @ColorInt var filledRectangleColor: Int = 0
-    @ColorInt var emptyRectangleColor: Int = 0
-    @ColorInt var backgroundColor: Int = 0
+    @ColorInt
+    var filledRectangleColor = getColorFromStyle(com.android.internal.R.attr.materialColorPrimary)
+    @ColorInt
+    var emptyRectangleColor =
+        getColorFromStyle(com.android.internal.R.attr.materialColorOutlineVariant)
+    @ColorInt
+    var backgroundColor = getColorFromStyle(com.android.internal.R.attr.materialColorSurfaceBright)
+    @ColorInt var iconColor = getColorFromStyle(com.android.internal.R.attr.materialColorOnPrimary)
 
     init {
         currentLevel = initialCurrentLevel
@@ -90,9 +97,6 @@
 
     private fun updateResources() {
         context.resources.apply {
-            filledRectangleColor = getColor(R.color.backlight_indicator_step_filled, context.theme)
-            emptyRectangleColor = getColor(R.color.backlight_indicator_step_empty, context.theme)
-            backgroundColor = getColor(R.color.backlight_indicator_background, context.theme)
             rootProperties =
                 RootProperties(
                     cornerRadius =
@@ -126,6 +130,11 @@
         }
     }
 
+    @ColorInt
+    fun getColorFromStyle(@AttrRes colorId: Int): Int {
+        return Utils.getColorAttrDefaultColor(context, colorId)
+    }
+
     fun updateState(current: Int, max: Int, forceRefresh: Boolean = false) {
         if (maxLevel != max || forceRefresh) {
             maxLevel = max
@@ -159,6 +168,7 @@
     private fun buildRootView(): LinearLayout {
         val linearLayout =
             LinearLayout(context).apply {
+                id = R.id.keyboard_backlight_dialog_container
                 orientation = LinearLayout.HORIZONTAL
                 layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
                 setPadding(
@@ -214,6 +224,7 @@
     private fun createBacklightIconView(): ImageView {
         return ImageView(context).apply {
             setImageResource(R.drawable.ic_keyboard_backlight)
+            setColorFilter(iconColor)
             layoutParams =
                 FrameLayout.LayoutParams(iconProperties.width, iconProperties.height).apply {
                     gravity = Gravity.CENTER
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 2925d8d..9844ca0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -36,7 +36,7 @@
 import com.android.internal.R
 import com.android.keyguard.KeyguardClockSwitchController
 import com.android.keyguard.KeyguardViewController
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 1a126d7..2854de0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -98,6 +98,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
 import com.android.internal.policy.IKeyguardDismissCallback;
@@ -121,7 +122,6 @@
 import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.LaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollector;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index b92499e..b7ba201 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -56,11 +56,11 @@
         tscl.registerTaskStackListener(mLockListener);
     }
 
-    private void startWorkChallengeInTask(ActivityManager.RunningTaskInfo info) {
+    private void startWorkChallengeInTask(ActivityManager.RunningTaskInfo info, int userId) {
         String packageName = info.baseActivity != null ? info.baseActivity.getPackageName() : "";
         Intent intent = new Intent(KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER)
                 .setComponent(new ComponentName(mContext, WorkLockActivity.class))
-                .putExtra(Intent.EXTRA_USER_ID, info.userId)
+                .putExtra(Intent.EXTRA_USER_ID, userId)
                 .putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
                 .addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
                         | Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -76,10 +76,11 @@
         } else {
             // Starting the activity inside the task failed. We can't be sure why, so to be
             // safe just remove the whole task if it still exists.
+            Log.w(TAG, "Failed to start work lock activity, will remove task=" + info.taskId);
             try {
                 mIatm.removeTask(info.taskId);
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed to get description for task=" + info.taskId);
+                Log.e(TAG, "Failed to remove task=" + info.taskId);
             }
         }
     }
@@ -112,8 +113,8 @@
 
     private final TaskStackChangeListener mLockListener = new TaskStackChangeListener() {
         @Override
-        public void onTaskProfileLocked(ActivityManager.RunningTaskInfo info) {
-            startWorkChallengeInTask(info);
+        public void onTaskProfileLocked(ActivityManager.RunningTaskInfo info, int userId) {
+            startWorkChallengeInTask(info, userId);
         }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 5f2178df..5b71a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -32,6 +32,8 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -42,6 +44,7 @@
 import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.log.SessionTracker
@@ -63,6 +66,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.launchIn
@@ -135,6 +139,7 @@
     @FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
     @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val featureFlags: FeatureFlags,
     dumpManager: DumpManager,
 ) : DeviceEntryFaceAuthRepository, Dumpable {
     private var authCancellationSignal: CancellationSignal? = null
@@ -212,15 +217,21 @@
                 .collect(Collectors.toSet())
         dumpManager.registerCriticalDumpable("DeviceEntryFaceAuthRepositoryImpl", this)
 
-        observeFaceAuthGatingChecks()
-        observeFaceDetectGatingChecks()
-        observeFaceAuthResettingConditions()
-        listenForSchedulingWatchdog()
+        if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
+            observeFaceAuthGatingChecks()
+            observeFaceDetectGatingChecks()
+            observeFaceAuthResettingConditions()
+            listenForSchedulingWatchdog()
+        }
     }
 
     private fun listenForSchedulingWatchdog() {
         keyguardTransitionInteractor.anyStateToGoneTransition
-            .onEach { faceManager?.scheduleWatchdog() }
+            .filter { it.transitionState == TransitionState.FINISHED }
+            .onEach {
+                faceAuthLogger.watchdogScheduled()
+                faceManager?.scheduleWatchdog()
+            }
             .launchIn(applicationScope)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
index ef8b401..ee0eb2d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
@@ -46,8 +46,6 @@
         impl: SystemUIKeyguardFaceAuthInteractor
     ): KeyguardFaceAuthInteractor
 
-    @Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
-
     companion object {
         @Provides
         @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index f27f899..e7b9af6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -45,4 +45,6 @@
 
     @Binds
     fun keyguardBouncerRepository(impl: KeyguardBouncerRepositoryImpl): KeyguardBouncerRepository
+
+    @Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
index d90f328..74c5520 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
@@ -28,7 +28,9 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
@@ -40,6 +42,9 @@
 interface TrustRepository {
     /** Flow representing whether the current user is trusted. */
     val isCurrentUserTrusted: Flow<Boolean>
+
+    /** Flow representing whether active unlock is available for the current user. */
+    val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean>
 }
 
 @SysUISingleton
@@ -89,11 +94,13 @@
             }
             .shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
 
-    override val isCurrentUserTrusted: Flow<Boolean>
-        get() =
-            combine(trust, userRepository.selectedUserInfo, ::Pair)
-                .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false }
-                .distinctUntilChanged()
-                .onEach { logger.isCurrentUserTrusted(it) }
-                .onStart { emit(false) }
+    override val isCurrentUserTrusted: Flow<Boolean> =
+        combine(trust, userRepository.selectedUserInfo, ::Pair)
+            .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false }
+            .distinctUntilChanged()
+            .onEach { logger.isCurrentUserTrusted(it) }
+            .onStart { emit(false) }
+
+    // TODO: Implement based on TrustManager callback b/267322286
+    override val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean> = MutableStateFlow(true)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
new file mode 100644
index 0000000..252982f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
@@ -0,0 +1,125 @@
+/*
+ *  Copyright (C) 2023 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.content.Context
+import androidx.annotation.DimenRes
+import com.android.systemui.R
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+/** Encapsulates business-logic related to Ambient Display burn-in offsets. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class BurnInInteractor
+@Inject
+constructor(
+    private val context: Context,
+    private val burnInHelperWrapper: BurnInHelperWrapper,
+    @Application private val scope: CoroutineScope,
+    private val configurationRepository: ConfigurationRepository,
+    private val systemClock: SystemClock,
+) {
+    private val _dozeTimeTick = MutableStateFlow<Long>(0)
+    val dozeTimeTick: StateFlow<Long> = _dozeTimeTick.asStateFlow()
+
+    val udfpsBurnInXOffset: StateFlow<Int> =
+        burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_x, isXAxis = true)
+    val udfpsBurnInYOffset: StateFlow<Int> =
+        burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_y, isXAxis = false)
+    val udfpsBurnInProgress: StateFlow<Float> =
+        dozeTimeTick
+            .mapLatest { burnInHelperWrapper.burnInProgressOffset() }
+            .stateIn(scope, SharingStarted.Lazily, burnInHelperWrapper.burnInProgressOffset())
+
+    fun dozeTimeTick() {
+        _dozeTimeTick.value = systemClock.uptimeMillis()
+    }
+
+    /**
+     * Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the
+     * max burn-in offset on any configuration changes. If the max burn-in offset is specified in
+     * pixels, use [burnInOffsetDefinedInPixels].
+     */
+    private fun burnInOffset(
+        @DimenRes maxBurnInOffsetResourceId: Int,
+        isXAxis: Boolean,
+    ): StateFlow<Int> {
+        return configurationRepository.onAnyConfigurationChange
+            .flatMapLatest {
+                val maxBurnInOffsetPixels =
+                    context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
+                dozeTimeTick.mapLatest { calculateOffset(maxBurnInOffsetPixels, isXAxis) }
+            }
+            .stateIn(
+                scope,
+                SharingStarted.Lazily,
+                calculateOffset(
+                    context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId),
+                    isXAxis,
+                )
+            )
+    }
+
+    /**
+     * Use for max burn-in offBurn-in offsets that ARE specified in pixels. This flow will apply the
+     * a scale for any resolution changes. If the max burn-in offset is specified in dp, use
+     * [burnInOffset].
+     */
+    private fun burnInOffsetDefinedInPixels(
+        @DimenRes maxBurnInOffsetResourceId: Int,
+        isXAxis: Boolean,
+    ): StateFlow<Int> {
+        return configurationRepository.scaleForResolution
+            .flatMapLatest { scale ->
+                val maxBurnInOffsetPixels =
+                    context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
+                dozeTimeTick.mapLatest { calculateOffset(maxBurnInOffsetPixels, isXAxis, scale) }
+            }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                calculateOffset(
+                    context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId),
+                    isXAxis,
+                    configurationRepository.getResolutionScale(),
+                )
+            )
+    }
+
+    private fun calculateOffset(
+        maxBurnInOffsetPixels: Int,
+        isXAxis: Boolean,
+        scale: Float = 1f
+    ): Int {
+        return (burnInHelperWrapper.burnInOffset(maxBurnInOffsetPixels, isXAxis) * scale).toInt()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index e6568f2..cde67f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index c2d139c..7e9cbc1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 86f65dde..aca4019 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 3beac0b..fc7bfb4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index b5bcd45..39c630b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 87f3164..0505d37 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index c0d5abc..47846d1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index a681c43..bc55bd4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
+import com.android.app.animation.Interpolators
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index e650b9f..7c5641f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -19,7 +19,7 @@
 import com.android.keyguard.logging.KeyguardLogger
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import com.android.systemui.log.LogLevel.VERBOSE
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 970d004..110bcd7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -32,14 +32,16 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.shared.system.SysUiStatsLog
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -56,28 +58,21 @@
 class PrimaryBouncerInteractor
 @Inject
 constructor(
-    private val repository: KeyguardBouncerRepository,
-    private val primaryBouncerView: BouncerView,
-    @Main private val mainHandler: Handler,
-    private val keyguardStateController: KeyguardStateController,
-    private val keyguardSecurityModel: KeyguardSecurityModel,
-    private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
-    private val falsingCollector: FalsingCollector,
-    private val dismissCallbackRegistry: DismissCallbackRegistry,
-    private val context: Context,
-    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    keyguardBypassController: KeyguardBypassController,
+        private val repository: KeyguardBouncerRepository,
+        private val primaryBouncerView: BouncerView,
+        @Main private val mainHandler: Handler,
+        private val keyguardStateController: KeyguardStateController,
+        private val keyguardSecurityModel: KeyguardSecurityModel,
+        private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
+        private val falsingCollector: FalsingCollector,
+        private val dismissCallbackRegistry: DismissCallbackRegistry,
+        private val context: Context,
+        private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+        private val trustRepository: TrustRepository,
+        private val featureFlags: FeatureFlags,
 ) {
-    /** Whether we want to wait for face auth. */
-    private val primaryBouncerFaceDelay =
-        keyguardStateController.isFaceAuthEnabled &&
-            !keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                KeyguardUpdateMonitor.getCurrentUser()
-            ) &&
-            !needsFullscreenBouncer() &&
-            keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) &&
-            !keyguardBypassController.bypassEnabled
-
+    private val passiveAuthBouncerDelay = context.resources.getInteger(
+            R.integer.primary_bouncer_passive_auth_delay).toLong()
     /** Runnable to show the primary bouncer. */
     val showRunnable = Runnable {
         repository.setPrimaryShow(true)
@@ -160,8 +155,9 @@
         }
 
         repository.setPrimaryShowingSoon(true)
-        if (primaryBouncerFaceDelay) {
-            mainHandler.postDelayed(showRunnable, 1200L)
+        if (usePrimaryBouncerPassiveAuthDelay()) {
+            Log.d(TAG, "delay bouncer, passive auth may succeed")
+            mainHandler.postDelayed(showRunnable, passiveAuthBouncerDelay)
         } else {
             DejankUtils.postAfterTraversal(showRunnable)
         }
@@ -377,6 +373,17 @@
         return repository.primaryBouncerShow.value
     }
 
+    /** Whether we want to wait to show the bouncer in case passive auth succeeds. */
+    private fun usePrimaryBouncerPassiveAuthDelay(): Boolean {
+        val canRunFaceAuth = keyguardStateController.isFaceAuthEnabled &&
+                keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)
+        val canRunActiveUnlock = trustRepository.isCurrentUserActiveUnlockAvailable.value &&
+                keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()
+        return featureFlags.isEnabled(Flags.DELAY_BOUNCER) &&
+                !needsFullscreenBouncer() &&
+                (canRunFaceAuth || canRunActiveUnlock)
+    }
+
     companion object {
         private const val TAG = "PrimaryBouncerInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 38b9d50..9d7477c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -16,7 +16,7 @@
 package com.android.systemui.keyguard.ui
 
 import android.view.animation.Interpolator
-import com.android.systemui.animation.Interpolators.LINEAR
+import com.android.app.animation.Interpolators.LINEAR
 import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
 import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index d96609c..c8d37a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -32,11 +32,11 @@
 import androidx.core.view.updateLayoutParams
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.animation.Expandable
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.view.LaunchableLinearLayout
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.binder.IconViewBinder
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 6bbc6f6..c1aefc7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -112,8 +112,6 @@
                         viewModel.isShowing.collect { isShowing ->
                             view.visibility = if (isShowing) View.VISIBLE else View.INVISIBLE
                             if (isShowing) {
-                                // Reset Security Container entirely.
-                                view.visibility = View.VISIBLE
                                 securityContainerController.reinflateViewFlipper {
                                     // Reset Security Container entirely.
                                     securityContainerController.onBouncerVisibilityChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index a98a7d8..ad11360 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -17,10 +17,12 @@
 
 package com.android.systemui.keyguard.ui.preview
 
+import android.annotation.ColorInt
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.graphics.Rect
 import android.hardware.display.DisplayManager
 import android.os.Bundle
 import android.os.IBinder
@@ -33,6 +35,7 @@
 import com.android.keyguard.ClockEventController
 import com.android.keyguard.KeyguardClockSwitch
 import com.android.systemui.R
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
@@ -61,6 +64,7 @@
     private val clockRegistry: ClockRegistry,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val lockscreenSmartspaceController: LockscreenSmartspaceController,
+    private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
     @Assisted bundle: Bundle,
 ) {
 
@@ -82,6 +86,7 @@
 
     private var clockView: View? = null
     private var smartSpaceView: View? = null
+    private var colorOverride: Int? = null
 
     private val disposables = mutableSetOf<DisposableHandle>()
     private var isDestroyed = false
@@ -112,7 +117,9 @@
 
             setUpBottomArea(rootView)
 
-            setupSmartspace(rootView)
+            setUpSmartspace(rootView)
+
+            setUpUdfps(rootView)
 
             setUpClock(rootView)
 
@@ -166,52 +173,64 @@
         }
     }
 
+    /** Sets the clock's color to the overridden seed color. */
+    fun onColorOverridden(@ColorInt color: Int?) {
+        runBlocking(mainDispatcher) {
+            colorOverride = color
+            clockController.clock?.run { events.onSeedColorChanged(color) }
+        }
+    }
+
     /**
      * This sets up and shows a non-interactive smart space
      *
-     * The top padding is as follows:
-     *    Status bar height + clock top margin + keyguard smart space top offset
+     * The top padding is as follows: Status bar height + clock top margin + keyguard smart space
+     * top offset
      *
-     * The start padding is as follows:
-     *    Clock padding start + Below clock padding start
+     * The start padding is as follows: Clock padding start + Below clock padding start
      *
-     * The end padding is as follows:
-     *    Below clock padding end
+     * The end padding is as follows: Below clock padding end
      */
-    private fun setupSmartspace(parentView: ViewGroup) {
-        if (!lockscreenSmartspaceController.isEnabled() ||
-                !lockscreenSmartspaceController.isDateWeatherDecoupled()) {
+    private fun setUpSmartspace(parentView: ViewGroup) {
+        if (
+            !lockscreenSmartspaceController.isEnabled() ||
+                !lockscreenSmartspaceController.isDateWeatherDecoupled()
+        ) {
             return
         }
 
         smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView)
 
-        val topPadding: Int = with(context.resources) {
-            getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) +
+        val topPadding: Int =
+            with(context.resources) {
+                getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) +
                     getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
                     getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
-        }
+            }
 
-        val startPadding: Int = with(context.resources) {
-            getDimensionPixelSize(R.dimen.clock_padding_start) +
+        val startPadding: Int =
+            with(context.resources) {
+                getDimensionPixelSize(R.dimen.clock_padding_start) +
                     getDimensionPixelSize(R.dimen.below_clock_padding_start)
-        }
+            }
 
-        val endPadding: Int = context.resources
-                .getDimensionPixelSize(R.dimen.below_clock_padding_end)
+        val endPadding: Int =
+            context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end)
 
         smartSpaceView?.let {
             it.setPaddingRelative(startPadding, topPadding, endPadding, 0)
             it.isClickable = false
 
             parentView.addView(
-                    it,
-                    FrameLayout.LayoutParams(
-                            FrameLayout.LayoutParams.MATCH_PARENT,
-                            FrameLayout.LayoutParams.WRAP_CONTENT,
-                    ),
+                it,
+                FrameLayout.LayoutParams(
+                    FrameLayout.LayoutParams.MATCH_PARENT,
+                    FrameLayout.LayoutParams.WRAP_CONTENT,
+                ),
             )
         }
+
+        smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f
     }
 
     private fun setUpBottomArea(parentView: ViewGroup) {
@@ -234,6 +253,33 @@
         )
     }
 
+    private fun setUpUdfps(parentView: ViewGroup) {
+        val sensorBounds = udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds
+
+        // If sensorBounds are default rect, then there is no UDFPS
+        if (sensorBounds == Rect()) {
+            return
+        }
+
+        // Place the UDFPS view in the proper sensor location
+        val fingerprintLayoutParams =
+            FrameLayout.LayoutParams(sensorBounds.width(), sensorBounds.height())
+        fingerprintLayoutParams.setMarginsRelative(
+            sensorBounds.left,
+            sensorBounds.top,
+            sensorBounds.right,
+            sensorBounds.bottom
+        )
+        val finger =
+            LayoutInflater.from(context)
+                .inflate(
+                    R.layout.udfps_keyguard_view_internal,
+                    parentView,
+                    false,
+                ) as View
+        parentView.addView(finger, fingerprintLayoutParams)
+    }
+
     private fun setUpClock(parentView: ViewGroup) {
         val clockChangeListener =
             object : ClockRegistry.ClockChangeListener {
@@ -252,8 +298,10 @@
         val receiver =
             object : BroadcastReceiver() {
                 override fun onReceive(context: Context?, intent: Intent?) {
-                    clockController.clock?.smallClock?.events?.onTimeTick()
-                    clockController.clock?.largeClock?.events?.onTimeTick()
+                    clockController.clock?.run {
+                        smallClock.events.onTimeTick()
+                        largeClock.events.onTimeTick()
+                    }
                 }
             }
         broadcastDispatcher.registerReceiver(
@@ -266,48 +314,35 @@
         disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) })
 
         onClockChanged(parentView)
-
-        updateSmartspaceWithSetupClock()
     }
 
     private fun onClockChanged(parentView: ViewGroup) {
-        clockController.clock = clockRegistry.createCurrentClock()
+        val clock = clockRegistry.createCurrentClock()
+        clockController.clock = clock
 
+        colorOverride?.let { clock.events.onSeedColorChanged(it) }
         if (!shouldHideClock) {
-            val largeClock = clockController.clock?.largeClock
-
-            largeClock
-                ?.events
-                ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
+            clock.largeClock.events.onTargetRegionChanged(
+                KeyguardClockSwitch.getLargeClockRegion(parentView)
+            )
 
             clockView?.let { parentView.removeView(it) }
-            clockView = largeClock?.view?.apply {
-                if (shouldHighlightSelectedAffordance) {
-                    alpha = DIM_ALPHA
+            clockView =
+                clock.largeClock.view.apply {
+                    if (shouldHighlightSelectedAffordance) {
+                        alpha = DIM_ALPHA
+                    }
+                    parentView.addView(this)
+                    visibility = View.VISIBLE
                 }
-                parentView.addView(this)
-                visibility = View.VISIBLE
-            }
         } else {
             clockView?.visibility = View.GONE
         }
-    }
 
-    /**
-     * Updates smart space after clock is set up. Used to show or hide smartspace with the right
-     * opacity based on the clock after setup.
-     */
-    private fun updateSmartspaceWithSetupClock() {
+        // Hide smart space if the clock has weather display; otherwise show it
         val hasCustomWeatherDataDisplay =
-                clockController
-                        .clock
-                        ?.largeClock
-                        ?.config
-                        ?.hasCustomWeatherDataDisplay == true
-
+            clock.largeClock.config.hasCustomWeatherDataDisplay == true
         hideSmartspace(hasCustomWeatherDataDisplay)
-
-        smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index 3869b23..79712f9c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -115,16 +115,21 @@
 
             when (message.what) {
                 KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
-                    message.data
-                        .getString(
-                            KeyguardPreviewConstants.KEY_SLOT_ID,
-                        )
-                        ?.let { slotId -> renderer.onSlotSelected(slotId = slotId) }
+                    message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId ->
+                        renderer.onSlotSelected(slotId = slotId)
+                    }
                 }
                 KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE -> {
-                    message.data
-                        .getBoolean(KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE)
-                        .let { hide -> renderer.hideSmartspace(hide) }
+                    renderer.hideSmartspace(
+                        message.data.getBoolean(KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE)
+                    )
+                }
+                KeyguardPreviewConstants.MESSAGE_ID_COLOR_OVERRIDE -> {
+                    renderer.onColorOverridden(
+                        message.data
+                            .getString(KeyguardPreviewConstants.KEY_COLOR_OVERRIDE)
+                            ?.toIntOrNull()
+                    )
                 }
                 else -> requestDestruction(this)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index 8d6545a4..2c9a9b3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
index f16827d..c135786 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
index bc9dc4f..c6187dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index a60665a..d3ea89c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index ddce516..6845c55 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index df93d23..68810f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index efd3ad6..fefa1b2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -6,9 +6,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.log.LogLevel.DEBUG
 import com.android.systemui.log.dagger.FaceAuthLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
 import javax.inject.Inject
 
 private const val TAG = "DeviceEntryFaceAuthRepositoryLog"
@@ -261,4 +260,8 @@
             { "Attempting face auth again because of HW error: retry attempt $int1" }
         )
     }
+
+    fun watchdogScheduled() {
+        logBuffer.log(TAG, DEBUG, "FaceManager Biometric watchdog scheduled.")
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
index d6e29e0..e4465ac 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -19,9 +19,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogcatEchoTracker
-
 import javax.inject.Inject
 
 @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
index edc278d..f727784 100644
--- a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
@@ -21,10 +21,11 @@
 import android.graphics.RectF
 import androidx.core.graphics.toRectF
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
 import com.android.systemui.log.dagger.ScreenDecorationsLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
 
 private const val TAG = "ScreenDecorationsLog"
@@ -131,4 +132,36 @@
     fun onSensorLocationChanged() {
         logBuffer.log(TAG, DEBUG, "AuthControllerCallback in ScreenDecorations triggered")
     }
+
+    fun cameraProtectionShownOrHidden(
+        faceDetectionRunning: Boolean,
+        biometricPromptShown: Boolean,
+        requestedState: Boolean,
+        currentlyShowing: Boolean
+    ) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                bool1 = faceDetectionRunning
+                bool2 = biometricPromptShown
+                bool3 = requestedState
+                bool4 = currentlyShowing
+            },
+            {
+                "isFaceDetectionRunning: $bool1, " +
+                    "isBiometricPromptShowing: $bool2, " +
+                    "requestedState: $bool3, " +
+                    "currentState: $bool4"
+            }
+        )
+    }
+
+    fun biometricEvent(@CompileTimeConstant info: String) {
+        logBuffer.log(TAG, DEBUG, info)
+    }
+
+    fun cameraProtectionEvent(@CompileTimeConstant cameraProtectionEvent: String) {
+        logBuffer.log(TAG, DEBUG, cameraProtectionEvent)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricLog.java
index 4b774d3..233f7a12 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricLog.java
@@ -23,7 +23,7 @@
 import javax.inject.Qualifier;
 
 /**
- * A {@link com.android.systemui.plugins.log.LogBuffer} for BiometricMessages processing such as
+ * A {@link com.android.systemui.log.LogBuffer} for BiometricMessages processing such as
  * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral}
  */
 @Qualifier
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java
index 5cca1ab..7d1f1c2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
index 1d016d8..9ca0293 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java
index c9f78bc..7c5f402 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt
index 9f563fe4..8732ef5 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt
@@ -18,19 +18,19 @@
 
 import javax.inject.Qualifier
 
-/** A [com.android.systemui.plugins.log.LogBuffer] for keyguard clock logs. */
+/** A [com.android.systemui.log.LogBuffer] for keyguard clock logs. */
 @Qualifier
 @MustBeDocumented
 @Retention(AnnotationRetention.RUNTIME)
 annotation class KeyguardClockLog
 
-/** A [com.android.systemui.plugins.log.LogBuffer] for small keyguard clock logs. */
+/** A [com.android.systemui.log.LogBuffer] for small keyguard clock logs. */
 @Qualifier
 @MustBeDocumented
 @Retention(AnnotationRetention.RUNTIME)
 annotation class KeyguardSmallClockLog
 
-/** A [com.android.systemui.plugins.log.LogBuffer] for large keyguard clock logs. */
+/** A [com.android.systemui.log.LogBuffer] for large keyguard clock logs. */
 @Qualifier
 @MustBeDocumented
 @Retention(AnnotationRetention.RUNTIME)
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java
index 76d20be..08d969b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 658f6a0..9be18ac 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -22,13 +22,13 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.LogBufferFactory;
+import com.android.systemui.log.LogcatEchoTracker;
+import com.android.systemui.log.LogcatEchoTrackerDebug;
+import com.android.systemui.log.LogcatEchoTrackerProd;
 import com.android.systemui.log.table.TableLogBuffer;
 import com.android.systemui.log.table.TableLogBufferFactory;
-import com.android.systemui.plugins.log.LogBuffer;
-import com.android.systemui.plugins.log.LogcatEchoTracker;
-import com.android.systemui.plugins.log.LogcatEchoTrackerDebug;
-import com.android.systemui.plugins.log.LogcatEchoTrackerProd;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.util.Compile;
 import com.android.systemui.util.wakelock.WakeLockLog;
@@ -209,7 +209,7 @@
     @SysUISingleton
     @CollapsedSbFragmentLog
     public static LogBuffer provideCollapsedSbFragmentLogBuffer(LogBufferFactory factory) {
-        return factory.create("CollapsedSbFragmentLog", 20);
+        return factory.create("CollapsedSbFragmentLog", 40);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
index af43347..1c00c93 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
index f4dac6e..86a916e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java
index 73690ab..c67d8be 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
index 0c2cd92..98e6556 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
index 5b7f4bb..dde0ee0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java
index 6d91f0c..b1c6dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
index 26af496..20fc6ff 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java
index 61daf9c..fcc184a 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java
index a59afa0..760fbf3 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLockscreenLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLockscreenLog.java
index a2d381e..f1646a8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLockscreenLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLockscreenLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java
index 6f8ea7f..a0b6864 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
index 835d349..8c8753a 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
index 6e2bd7b..7259eeb 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
index 77b1bf5..e96e532 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java
index 295bf88..973f650 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
index 9fd166b..557a254 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java
index dd168ba..dd5010c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
index d24bfcb..bd0d298 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeWindowLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeWindowLog.java
index 1d2b68c..4aa2b1d 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeWindowLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeWindowLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java
index af0f7c5..f26b316 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeUpLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeUpLog.java
index d58b538..4d797c1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeUpLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeUpLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
index ba8b27c..8671dbf 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/UnseenNotificationLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/UnseenNotificationLog.java
index 5c2321b..dbb6e8c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/UnseenNotificationLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/UnseenNotificationLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
index 42fdd68..592044e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
@@ -27,19 +27,27 @@
  */
 data class TableChange(
     var timestamp: Long = 0,
-    var columnPrefix: String = "",
-    var columnName: String = "",
-    var isInitial: Boolean = false,
-    var type: DataType = DataType.EMPTY,
-    var bool: Boolean = false,
-    var int: Int? = null,
-    var str: String? = null,
+    private var columnPrefix: String = "",
+    private var columnName: String = "",
+    private var isInitial: Boolean = false,
+    private var type: DataType = DataType.EMPTY,
+    private var bool: Boolean = false,
+    private var int: Int? = null,
+    private var str: String? = null,
 ) {
+    init {
+        // Truncate any strings that were passed into the constructor. [reset] and [set] will take
+        // care of the rest of the truncation.
+        this.columnPrefix = columnPrefix.take(MAX_STRING_LENGTH)
+        this.columnName = columnName.take(MAX_STRING_LENGTH)
+        this.str = str?.take(MAX_STRING_LENGTH)
+    }
+
     /** Resets to default values so that the object can be recycled. */
     fun reset(timestamp: Long, columnPrefix: String, columnName: String, isInitial: Boolean) {
         this.timestamp = timestamp
-        this.columnPrefix = columnPrefix
-        this.columnName = columnName
+        this.columnPrefix = columnPrefix.take(MAX_STRING_LENGTH)
+        this.columnName = columnName.take(MAX_STRING_LENGTH)
         this.isInitial = isInitial
         this.type = DataType.EMPTY
         this.bool = false
@@ -50,7 +58,7 @@
     /** Sets this to store a string change. */
     fun set(value: String?) {
         type = DataType.STRING
-        str = value
+        str = value?.take(MAX_STRING_LENGTH)
     }
 
     /** Sets this to store a boolean change. */
@@ -89,6 +97,8 @@
         }
     }
 
+    fun getColumnName() = columnName
+
     fun getVal(): String {
         val value =
             when (type) {
@@ -109,5 +119,8 @@
 
     companion object {
         @VisibleForTesting const val IS_INITIAL_PREFIX = "**"
+        // Don't allow any strings larger than this length so that we have a hard upper limit on the
+        // size of the data stored by the buffer.
+        @VisibleForTesting const val MAX_STRING_LENGTH = 500
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 9d883cc..1d785ae 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -20,8 +20,8 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.common.buffer.RingBuffer
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogcatEchoTracker
 import com.android.systemui.util.time.SystemClock
 import java.io.PrintWriter
 import java.text.SimpleDateFormat
@@ -35,7 +35,7 @@
  * A logger that logs changes in table format.
  *
  * Some parts of System UI maintain a lot of pieces of state at once.
- * [com.android.systemui.plugins.log.LogBuffer] allows us to easily log change events:
+ * [com.android.systemui.log.LogBuffer] allows us to easily log change events:
  * - 10-10 10:10:10.456: state2 updated to newVal2
  * - 10-10 10:11:00.000: stateN updated to StateN(val1=true, val2=1)
  * - 10-10 10:11:02.123: stateN updated to StateN(val1=true, val2=2)
@@ -291,7 +291,7 @@
     private fun echoToDesiredEndpoints(change: TableChange) {
         if (
             logcatEchoTracker.isBufferLoggable(bufferName = name, LogLevel.DEBUG) ||
-                logcatEchoTracker.isTagLoggable(change.columnName, LogLevel.DEBUG)
+                logcatEchoTracker.isTagLoggable(change.getColumnName(), LogLevel.DEBUG)
         ) {
             if (change.hasData()) {
                 localLogcat.d(name, change.logcatRepresentation())
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
index 42e742d..19e1124 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogcatEchoTracker
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index aec7b5f..c41f82b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -111,7 +111,7 @@
         if (featureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
             component.emptyStateProvider
         } else {
-            super.createBlockerEmptyStateProvider()
+            object : EmptyStateProvider {}
         }
 
     override fun createListController(userHandle: UserHandle): ResolverListController =
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index 28adfbb..c9c2ea2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -79,6 +79,9 @@
     // Indicates if user must review already-granted consent that the MediaProjection app is
     // attempting to re-use.
     private boolean mReviewGrantedConsentRequired = false;
+    // Indicates if the user has consented to record, but is continuing in another activity to
+    // select a particular task to capture.
+    private boolean mUserSelectingTask = false;
 
     @Inject
     public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
@@ -296,6 +299,7 @@
                 // Start activity from the current foreground user to avoid creating a separate
                 // SystemUI process without access to recent tasks because it won't have
                 // WM Shell running inside.
+                mUserSelectingTask = true;
                 startActivityAsUser(intent, UserHandle.of(ActivityManager.getCurrentUser()));
             }
         } catch (RemoteException e) {
@@ -316,7 +320,10 @@
     @Override
     public void finish() {
         // Default to cancelling recording when user needs to review consent.
-        finish(RECORD_CANCEL, /* projection= */ null);
+        // Don't send cancel if the user has moved on to the next activity.
+        if (!mUserSelectingTask) {
+            finish(RECORD_CANCEL, /* projection= */ null);
+        }
     }
 
     private void finish(@ReviewGrantedConsentResult int consentResult,
@@ -328,7 +335,7 @@
 
     private void onDialogDismissedOrCancelled(DialogInterface dialogInterface) {
         if (!isFinishing()) {
-            finish(RECORD_CANCEL, /* projection= */ null);
+            finish();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
index 37d956b..e38abc2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
@@ -21,9 +21,9 @@
 import android.text.format.DateUtils
 import androidx.annotation.UiThread
 import androidx.lifecycle.Observer
+import com.android.app.animation.Interpolators
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.media.controls.ui.SquigglyProgress
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 9997730..fa42114 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -43,7 +43,6 @@
 import android.net.Uri
 import android.os.Parcelable
 import android.os.Process
-import android.os.RemoteException
 import android.os.UserHandle
 import android.provider.Settings
 import android.service.notification.StatusBarNotification
@@ -53,7 +52,6 @@
 import android.util.Pair as APair
 import androidx.media.utils.MediaConstants
 import com.android.internal.logging.InstanceId
-import com.android.internal.statusbar.IStatusBarService
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.Dumpable
 import com.android.systemui.R
@@ -139,8 +137,6 @@
         expiryTimeMs = 0,
     )
 
-const val MEDIA_TITLE_ERROR_MESSAGE = "Invalid media data: title is null or blank."
-
 fun isMediaNotification(sbn: StatusBarNotification): Boolean {
     return sbn.notification.isMediaNotification()
 }
@@ -185,7 +181,6 @@
     private val logger: MediaUiEventLogger,
     private val smartspaceManager: SmartspaceManager,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val statusBarService: IStatusBarService,
 ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
 
     companion object {
@@ -257,7 +252,6 @@
         mediaFlags: MediaFlags,
         logger: MediaUiEventLogger,
         smartspaceManager: SmartspaceManager,
-        statusBarService: IStatusBarService,
         keyguardUpdateMonitor: KeyguardUpdateMonitor,
     ) : this(
         context,
@@ -283,7 +277,6 @@
         logger,
         smartspaceManager,
         keyguardUpdateMonitor,
-        statusBarService,
     )
 
     private val appChangeReceiver =
@@ -385,21 +378,21 @@
 
     fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
         if (useQsMediaPlayer && isMediaNotification(sbn)) {
-            var isNewlyActiveEntry = false
+            var logEvent = false
             Assert.isMainThread()
             val oldKey = findExistingEntry(key, sbn.packageName)
             if (oldKey == null) {
                 val instanceId = logger.getNewInstanceId()
                 val temp = LOADING.copy(packageName = sbn.packageName, instanceId = instanceId)
                 mediaEntries.put(key, temp)
-                isNewlyActiveEntry = true
+                logEvent = true
             } else if (oldKey != key) {
                 // Resume -> active conversion; move to new key
                 val oldData = mediaEntries.remove(oldKey)!!
-                isNewlyActiveEntry = true
+                logEvent = true
                 mediaEntries.put(key, oldData)
             }
-            loadMediaData(key, sbn, oldKey, isNewlyActiveEntry)
+            loadMediaData(key, sbn, oldKey, logEvent)
         } else {
             onNotificationRemoved(key)
         }
@@ -482,9 +475,9 @@
         key: String,
         sbn: StatusBarNotification,
         oldKey: String?,
-        isNewlyActiveEntry: Boolean = false,
+        logEvent: Boolean = false
     ) {
-        backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) }
+        backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, logEvent) }
     }
 
     /** Add a listener for changes in this class */
@@ -608,11 +601,9 @@
         }
     }
 
-    private fun removeEntry(key: String, logEvent: Boolean = true) {
+    private fun removeEntry(key: String) {
         mediaEntries.remove(key)?.let {
-            if (logEvent) {
-                logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
-            }
+            logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
         }
         notifyMediaDataRemoved(key)
     }
@@ -760,7 +751,7 @@
         key: String,
         sbn: StatusBarNotification,
         oldKey: String?,
-        isNewlyActiveEntry: Boolean = false,
+        logEvent: Boolean = false
     ) {
         val token =
             sbn.notification.extras.getParcelable(
@@ -774,34 +765,6 @@
         val metadata = mediaController.metadata
         val notif: Notification = sbn.notification
 
-        // Song name
-        var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
-        if (song == null) {
-            song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
-        }
-        if (song == null) {
-            song = HybridGroupManager.resolveTitle(notif)
-        }
-        // Media data must have a title.
-        if (song.isNullOrBlank()) {
-            try {
-                statusBarService.onNotificationError(
-                    sbn.packageName,
-                    sbn.tag,
-                    sbn.id,
-                    sbn.uid,
-                    sbn.initialPid,
-                    MEDIA_TITLE_ERROR_MESSAGE,
-                    sbn.user.identifier
-                )
-            } catch (e: RemoteException) {
-                Log.e(TAG, "cancelNotification failed: $e")
-            }
-            // Only add log for media removed if active media is updated with invalid title.
-            foregroundExecutor.execute { removeEntry(key, !isNewlyActiveEntry) }
-            return
-        }
-
         val appInfo =
             notif.extras.getParcelable(
                 Notification.EXTRA_BUILDER_APPLICATION_INFO,
@@ -830,6 +793,15 @@
         // App Icon
         val smallIcon = sbn.notification.smallIcon
 
+        // Song name
+        var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
+        if (song == null) {
+            song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
+        }
+        if (song == null) {
+            song = HybridGroupManager.resolveTitle(notif)
+        }
+
         // Explicit Indicator
         var isExplicit = false
         if (mediaFlags.isExplicitIndicatorEnabled()) {
@@ -901,7 +873,7 @@
         val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
         val appUid = appInfo?.uid ?: Process.INVALID_UID
 
-        if (isNewlyActiveEntry) {
+        if (logEvent) {
             logSingleVsMultipleMediaAdded(appUid, sbn.packageName, instanceId)
             logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation)
         } else if (playbackLocation != currentEntry?.playbackLocation) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
index f731dc0..e2e269d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
@@ -18,9 +18,9 @@
 
 import android.media.session.PlaybackState
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.MediaTimeoutListenerLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "MediaTimeout"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
index 095cf09..9e53d77 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
@@ -18,9 +18,9 @@
 
 import android.content.ComponentName
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.MediaBrowserLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
 /** A logger for events in [ResumeMediaBrowser]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
index 3669493..b46ebb2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
@@ -34,10 +34,10 @@
 import android.util.MathUtils
 import android.view.View
 import androidx.annotation.Keep
+import com.android.app.animation.Interpolators
 import com.android.internal.graphics.ColorUtils
 import com.android.internal.graphics.ColorUtils.blendARGB
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 import org.xmlpull.v1.XmlPullParser
 
 private const val BACKGROUND_ANIM_DURATION = 370L
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
index dd5c2bf..937a618 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
@@ -35,9 +35,9 @@
 import android.util.AttributeSet
 import android.util.MathUtils.lerp
 import androidx.annotation.Keep
+import com.android.app.animation.Interpolators
 import com.android.internal.graphics.ColorUtils
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 import org.xmlpull.v1.XmlPullParser
 
 private const val RIPPLE_ANIM_DURATION = 800L
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
index 9af11b9..0ed2434 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.media.controls.ui
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.MediaCarouselControllerLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
 /** A debug logger for [MediaCarouselController]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 40027a1..f9d3094 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -72,6 +72,7 @@
 import androidx.annotation.UiThread;
 import androidx.constraintlayout.widget.ConstraintSet;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -82,7 +83,6 @@
 import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.GhostedViewLaunchAnimatorController;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.bluetooth.BroadcastDialogController;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.dagger.qualifiers.Background;
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 49e1665..fe8ebaf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -33,9 +33,9 @@
 import android.view.ViewGroup
 import android.view.ViewGroupOverlay
 import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
 import com.android.keyguard.KeyguardViewController
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dreams.DreamOverlayStateController
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
index fdac33a..c781b76 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.media.controls.ui
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.MediaViewLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "MediaView"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
index e9b2cf2..583c626 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
@@ -31,8 +31,8 @@
 import android.util.MathUtils.lerpInv
 import android.util.MathUtils.lerpInvSat
 import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
 import com.android.internal.graphics.ColorUtils
-import com.android.systemui.animation.Interpolators
 import kotlin.math.abs
 import kotlin.math.cos
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 9ae4577..46efac5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -17,6 +17,7 @@
 package com.android.systemui.media.dagger;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.LogBufferFactory;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
@@ -30,16 +31,15 @@
 import com.android.systemui.media.taptotransfer.MediaTttFlags;
 import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogBuffer;
 import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogBuffer;
-import com.android.systemui.plugins.log.LogBuffer;
-
-import java.util.Optional;
-
-import javax.inject.Named;
 
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.Optional;
+
+import javax.inject.Named;
+
 /** Dagger module for the media package. */
 @Module(subcomponents = {
         MediaComplicationComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
index 5ace3ea..bbcf259 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
@@ -2,8 +2,8 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.dagger.MediaMuteAwaitLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import javax.inject.Inject
 
 /** Log messages for [MediaMuteAwaitConnectionManager]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
index 78408fc..66399d5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
@@ -2,8 +2,8 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.dagger.NearbyMediaDevicesLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import javax.inject.Inject
 
 /** Log messages for [NearbyMediaDevicesManager]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
index 0e839c6..eeda1027 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.media.taptotransfer.common
 
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 
 /** A helper for logging media tap-to-transfer events. */
 object MediaTttLoggerUtils {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 78082c3..77ff036 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -36,7 +36,7 @@
 import android.view.View.ACCESSIBILITY_LIVE_REGION_NONE
 import com.android.internal.widget.CachingIconView
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.ui.binder.TintedIconViewBinder
 import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogBuffer.java b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogBuffer.java
index 67e464c..31ccb9a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogBuffer.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
index b0c6257..1502df7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
@@ -18,8 +18,8 @@
 
 import android.app.StatusBarManager
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.media.taptotransfer.common.MediaTttLoggerUtils
-import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.temporarydisplay.TemporaryViewLogger
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogBuffer.java b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogBuffer.java
index a262e97..edee4a8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogBuffer.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
index 964a95b..03bcfc8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
@@ -18,9 +18,9 @@
 
 import android.app.StatusBarManager
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.media.taptotransfer.common.MediaTttLoggerUtils
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
 /** A logger for all events related to the media tap-to-transfer sender experience. */
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index 219629b..1d8fe72 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -19,7 +19,6 @@
 import android.content.ComponentName
 import android.os.UserHandle
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
 import javax.inject.Inject
@@ -59,13 +58,7 @@
      * Removes all recent tasks that are different from the profile of the host app to avoid any
      * cross-profile sharing
      */
-    private fun List<RecentTask>.filterDevicePolicyRestrictedTasks(): List<RecentTask> =
-        if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
-            // TODO(b/263950746): filter tasks based on the enterprise policies
-            this
-        } else {
-            filter { UserHandle.of(it.userId) == hostUserHandle }
-        }
+    private fun List<RecentTask>.filterDevicePolicyRestrictedTasks(): List<RecentTask> = this
 
     private fun List<RecentTask>.filterAppSelector(): List<RecentTask> = filter {
         // Only take tasks that is not the app selector
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 94f01b8..146b5f5 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -58,11 +58,11 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.Utils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
 import com.android.systemui.navigationbar.buttons.ContextualButton;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
index 0218016..10084bd 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.navigationbar.buttons;
 
-import static com.android.systemui.animation.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.LINEAR;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -24,9 +24,6 @@
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
 
-import com.android.systemui.Dependency;
-import com.android.systemui.assist.AssistManager;
-
 import java.util.ArrayList;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 590efbb..ff22398 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -48,10 +48,10 @@
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.util.LatencyTracker;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
 import com.android.systemui.settings.DisplayTracker;
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivity.kt
new file mode 100644
index 0000000..c209a00
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivity.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import javax.inject.Inject
+
+/**
+ * An internal proxy activity that starts the notes role setting.
+ *
+ * This activity is introduced mainly for the error handling of the notes app lock screen shortcut
+ * picker, which only supports package + action but not extras. See
+ * [KeyguardQuickAffordanceConfig.PickerScreenState.Disabled.actionComponentName].
+ */
+class LaunchNotesRoleSettingsTrampolineActivity
+@Inject
+constructor(
+    private val controller: NoteTaskController,
+) : ComponentActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val entryPoint =
+            if (intent?.action == ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE) {
+                NoteTaskEntryPoint.QUICK_AFFORDANCE
+            } else {
+                null
+            }
+        controller.startNotesRoleSetting(this, entryPoint)
+        finish()
+    }
+
+    companion object {
+        const val ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE =
+            "com.android.systemui.action.MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index d4052f5..7e9b346 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -31,15 +31,14 @@
 import android.content.pm.PackageManager
 import android.content.pm.ShortcutManager
 import android.graphics.drawable.Icon
-import android.os.Build
 import android.os.UserHandle
 import android.os.UserManager
-import android.util.Log
 import android.widget.Toast
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
+import com.android.systemui.log.DebugLogger.debugLog
 import com.android.systemui.notetask.NoteTaskRoleManagerExt.createNoteShortcutInfoAsUser
 import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolderAsUser
 import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
@@ -92,10 +91,10 @@
         if (info.launchMode != NoteTaskLaunchMode.AppBubble) return
 
         if (isExpanding) {
-            logDebug { "onBubbleExpandChanged - expanding: $info" }
+            debugLog { "onBubbleExpandChanged - expanding: $info" }
             eventLogger.logNoteTaskOpened(info)
         } else {
-            logDebug { "onBubbleExpandChanged - collapsing: $info" }
+            debugLog { "onBubbleExpandChanged - collapsing: $info" }
             eventLogger.logNoteTaskClosed(info)
         }
     }
@@ -112,6 +111,43 @@
         )
     }
 
+    /** Starts the notes role setting. */
+    fun startNotesRoleSetting(activityContext: Context, entryPoint: NoteTaskEntryPoint?) {
+        val user =
+            if (entryPoint == null) {
+                userTracker.userHandle
+            } else {
+                getUserForHandlingNotesTaking(entryPoint)
+            }
+        activityContext.startActivityAsUser(
+            Intent(Intent.ACTION_MANAGE_DEFAULT_APP).apply {
+                putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NOTES)
+            },
+            user
+        )
+    }
+
+    /**
+     * Returns the [UserHandle] of an android user that should handle the notes taking [entryPoint].
+     *
+     * On company owned personally enabled (COPE) devices, if the given [entryPoint] is in the
+     * [FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES] list, the default notes app in the work
+     * profile user will always be launched.
+     *
+     * On non managed devices or devices with other management modes, the current [UserHandle] is
+     * returned.
+     */
+    fun getUserForHandlingNotesTaking(entryPoint: NoteTaskEntryPoint): UserHandle =
+        if (
+            entryPoint in FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES &&
+                devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile
+        ) {
+            userTracker.userProfiles.firstOrNull { userManager.isManagedProfile(it.id) }?.userHandle
+                ?: userTracker.userHandle
+        } else {
+            userTracker.userHandle
+        }
+
     /**
      * Shows a note task. How the task is shown will depend on when the method is invoked.
      *
@@ -122,30 +158,13 @@
      * bubble is already opened.
      *
      * That will let users open other apps in full screen, and take contextual notes.
-     *
-     * On company owned personally enabled (COPE) devices, if the given [entryPoint] is in the
-     * [FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES] list, the default notes app in the work
-     * profile user will always be launched.
      */
     fun showNoteTask(
         entryPoint: NoteTaskEntryPoint,
     ) {
         if (!isEnabled) return
 
-        val user: UserHandle =
-            if (
-                entryPoint in FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES &&
-                    devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile
-            ) {
-                userTracker.userProfiles
-                    .firstOrNull { userManager.isManagedProfile(it.id) }
-                    ?.userHandle
-                    ?: userTracker.userHandle
-            } else {
-                userTracker.userHandle
-            }
-
-        showNoteTaskAsUser(entryPoint, user)
+        showNoteTaskAsUser(entryPoint, getUserForHandlingNotesTaking(entryPoint))
     }
 
     /** A variant of [showNoteTask] which launches note task in the given [user]. */
@@ -168,14 +187,14 @@
             isKeyguardLocked &&
                 devicePolicyManager.areKeyguardShortcutsDisabled(userId = user.identifier)
         ) {
-            logDebug { "Enterprise policy disallows launching note app when the screen is locked." }
+            debugLog { "Enterprise policy disallows launching note app when the screen is locked." }
             return
         }
 
         val info = resolver.resolveInfo(entryPoint, isKeyguardLocked, user)
 
         if (info == null) {
-            logDebug { "Default notes app isn't set" }
+            debugLog { "Default notes app isn't set" }
             showNoDefaultNotesAppToast()
             return
         }
@@ -184,7 +203,7 @@
 
         try {
             // TODO(b/266686199): We should handle when app not available. For now, we log.
-            logDebug { "onShowNoteTask - start: $info on user#${user.identifier}" }
+            debugLog { "onShowNoteTask - start: $info on user#${user.identifier}" }
             when (info.launchMode) {
                 is NoteTaskLaunchMode.AppBubble -> {
                     val intent = createNoteTaskIntent(info)
@@ -192,7 +211,7 @@
                         Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
                     bubbles.showOrHideAppBubble(intent, user, icon)
                     // App bubble logging happens on `onBubbleExpandChanged`.
-                    logDebug { "onShowNoteTask - opened as app bubble: $info" }
+                    debugLog { "onShowNoteTask - opened as app bubble: $info" }
                 }
                 is NoteTaskLaunchMode.Activity -> {
                     if (activityManager.isInForeground(info.packageName)) {
@@ -200,20 +219,20 @@
                         val intent = createHomeIntent()
                         context.startActivityAsUser(intent, user)
                         eventLogger.logNoteTaskClosed(info)
-                        logDebug { "onShowNoteTask - closed as activity: $info" }
+                        debugLog { "onShowNoteTask - closed as activity: $info" }
                     } else {
                         val intent = createNoteTaskIntent(info)
                         context.startActivityAsUser(intent, user)
                         eventLogger.logNoteTaskOpened(info)
-                        logDebug { "onShowNoteTask - opened as activity: $info" }
+                        debugLog { "onShowNoteTask - opened as activity: $info" }
                     }
                 }
             }
-            logDebug { "onShowNoteTask - success: $info" }
+            debugLog { "onShowNoteTask - success: $info" }
         } catch (e: ActivityNotFoundException) {
-            logDebug { "onShowNoteTask - failed: $info" }
+            debugLog { "onShowNoteTask - failed: $info" }
         }
-        logDebug { "onShowNoteTask - completed: $info" }
+        debugLog { "onShowNoteTask - completed: $info" }
     }
 
     @VisibleForTesting
@@ -253,7 +272,7 @@
             PackageManager.DONT_KILL_APP,
         )
 
-        logDebug { "setNoteTaskShortcutEnabled - completed: $isEnabled" }
+        debugLog { "setNoteTaskShortcutEnabled - completed: $isEnabled" }
     }
 
     /**
@@ -352,11 +371,6 @@
         }
     }
 
-/** [Log.println] a [Log.DEBUG] message, only when [Build.IS_DEBUGGABLE]. */
-private inline fun Any.logDebug(message: () -> String) {
-    if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message())
-}
-
 /** Creates an [Intent] which forces the current app to background by calling home. */
 private fun createHomeIntent(): Intent =
     Intent(Intent.ACTION_MAIN).apply {
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index 2c62ffd..4d5173a 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -45,6 +45,10 @@
     @[Binds IntoMap ClassKey(LaunchNoteTaskManagedProfileProxyActivity::class)]
     fun LaunchNoteTaskManagedProfileProxyActivity.bindNoteTaskLauncherProxyActivity(): Activity
 
+    @[Binds IntoMap ClassKey(LaunchNotesRoleSettingsTrampolineActivity::class)]
+    fun LaunchNotesRoleSettingsTrampolineActivity.bindLaunchNotesRoleSettingsTrampolineActivity():
+        Activity
+
     companion object {
 
         @[Provides NoteTaskEnabledKey]
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
index 2da5b76..444407c 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
@@ -16,9 +16,12 @@
 
 package com.android.systemui.notetask.quickaffordance
 
+import android.app.role.OnRoleHoldersChangedListener
+import android.app.role.RoleManager
 import android.content.Context
 import android.hardware.input.InputSettings
 import android.os.Build
+import android.os.UserHandle
 import android.os.UserManager
 import android.util.Log
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -27,17 +30,22 @@
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.PickerScreenState
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE
 import com.android.systemui.notetask.NoteTaskController
 import com.android.systemui.notetask.NoteTaskEnabledKey
-import com.android.systemui.notetask.NoteTaskEntryPoint
+import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
+import com.android.systemui.notetask.NoteTaskInfoResolver
+import com.android.systemui.shared.customization.data.content.CustomizationProviderContract.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR
 import com.android.systemui.stylus.StylusManager
 import dagger.Lazy
+import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.channels.trySendBlocking
@@ -49,13 +57,16 @@
 class NoteTaskQuickAffordanceConfig
 @Inject
 constructor(
-    context: Context,
+    private val context: Context,
     private val controller: NoteTaskController,
+    private val noteTaskInfoResolver: NoteTaskInfoResolver,
     private val stylusManager: StylusManager,
+    private val roleManager: RoleManager,
     private val keyguardMonitor: KeyguardUpdateMonitor,
     private val userManager: UserManager,
     private val lazyRepository: Lazy<KeyguardQuickAffordanceRepository>,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
+    @Background private val backgroundExecutor: Executor,
 ) : KeyguardQuickAffordanceConfig {
 
     override val key = BuiltInKeyguardQuickAffordanceKeys.CREATE_NOTE
@@ -73,15 +84,24 @@
         val configSelectedFlow = repository.createConfigSelectedFlow(key)
         val stylusEverUsedFlow = stylusManager.createStylusEverUsedFlow(context)
         val userUnlockedFlow = userManager.createUserUnlockedFlow(keyguardMonitor)
-        combine(userUnlockedFlow, stylusEverUsedFlow, configSelectedFlow) {
+        val defaultNotesAppFlow =
+            roleManager.createNotesRoleFlow(backgroundExecutor, controller, noteTaskInfoResolver)
+        combine(userUnlockedFlow, stylusEverUsedFlow, configSelectedFlow, defaultNotesAppFlow) {
                 isUserUnlocked,
                 isStylusEverUsed,
-                isConfigSelected ->
+                isConfigSelected,
+                isDefaultNotesAppSet ->
                 logDebug { "lockScreenState:isUserUnlocked=$isUserUnlocked" }
                 logDebug { "lockScreenState:isStylusEverUsed=$isStylusEverUsed" }
                 logDebug { "lockScreenState:isConfigSelected=$isConfigSelected" }
+                logDebug { "lockScreenState:isDefaultNotesAppSet=$isDefaultNotesAppSet" }
 
-                if (isEnabled && isUserUnlocked && (isConfigSelected || isStylusEverUsed)) {
+                if (
+                    isEnabled &&
+                        isUserUnlocked &&
+                        isDefaultNotesAppSet &&
+                        (isConfigSelected || isStylusEverUsed)
+                ) {
                     val contentDescription = ContentDescription.Resource(pickerNameResourceId)
                     val icon = Icon.Resource(pickerIconResourceId, contentDescription)
                     LockScreenState.Visible(icon)
@@ -92,15 +112,34 @@
             .onEach { state -> logDebug { "lockScreenState=$state" } }
     }
 
-    override suspend fun getPickerScreenState() =
-        if (isEnabled) {
-            PickerScreenState.Default()
-        } else {
-            PickerScreenState.UnavailableOnDevice
+    override suspend fun getPickerScreenState(): PickerScreenState {
+        val isDefaultNotesAppSet =
+            noteTaskInfoResolver.resolveInfo(
+                QUICK_AFFORDANCE,
+                user = controller.getUserForHandlingNotesTaking(QUICK_AFFORDANCE)
+            ) != null
+        return when {
+            isEnabled && isDefaultNotesAppSet -> PickerScreenState.Default()
+            isEnabled -> {
+                PickerScreenState.Disabled(
+                    listOf(
+                        context.getString(
+                            R.string.keyguard_affordance_enablement_dialog_notes_app_instruction
+                        )
+                    ),
+                    context.getString(
+                        R.string.keyguard_affordance_enablement_dialog_notes_app_action
+                    ),
+                    "${context.packageName}$COMPONENT_NAME_SEPARATOR" +
+                        "$ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE",
+                )
+            }
+            else -> PickerScreenState.UnavailableOnDevice
         }
+    }
 
     override fun onTriggered(expandable: Expandable?): OnTriggeredResult {
-        controller.showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+        controller.showNoteTask(entryPoint = QUICK_AFFORDANCE)
         return OnTriggeredResult.Handled
     }
 }
@@ -129,6 +168,27 @@
     awaitClose { unregisterCallback(callback) }
 }
 
+private fun RoleManager.createNotesRoleFlow(
+    executor: Executor,
+    noteTaskController: NoteTaskController,
+    noteTaskInfoResolver: NoteTaskInfoResolver,
+) = callbackFlow {
+    fun isDefaultNotesAppSetForUser() =
+        noteTaskInfoResolver.resolveInfo(
+            QUICK_AFFORDANCE,
+            user = noteTaskController.getUserForHandlingNotesTaking(QUICK_AFFORDANCE)
+        ) != null
+
+    trySendBlocking(isDefaultNotesAppSetForUser())
+    val callback = OnRoleHoldersChangedListener { roleName, _ ->
+        if (roleName == RoleManager.ROLE_NOTES) {
+            trySendBlocking(isDefaultNotesAppSetForUser())
+        }
+    }
+    addOnRoleHoldersChangedListenerAsUser(executor, callback, UserHandle.ALL)
+    awaitClose { removeOnRoleHoldersChangedListenerAsUser(callback, UserHandle.ALL) }
+}
+
 private fun KeyguardQuickAffordanceRepository.createConfigSelectedFlow(key: String) =
     selections.map { selected ->
         selected.values.flatten().any { selectedConfig -> selectedConfig.key == key }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
index 0f38d32..8ca13b9 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -18,12 +18,11 @@
 
 import android.content.Context
 import android.content.Intent
-import android.os.Build
 import android.os.Bundle
 import android.os.UserHandle
 import android.os.UserManager
-import android.util.Log
 import androidx.activity.ComponentActivity
+import com.android.systemui.log.DebugLogger.debugLog
 import com.android.systemui.notetask.NoteTaskController
 import com.android.systemui.notetask.NoteTaskEntryPoint
 import com.android.systemui.settings.UserTracker
@@ -68,7 +67,7 @@
         val mainUser: UserHandle? = userManager.mainUser
         if (userManager.isManagedProfile) {
             if (mainUser == null) {
-                logDebug { "Can't find the main user. Skipping the notes app launch." }
+                debugLog { "Can't find the main user. Skipping the notes app launch." }
             } else {
                 controller.startNoteTaskProxyActivityForUser(mainUser)
             }
@@ -89,8 +88,3 @@
         }
     }
 }
-
-/** [Log.println] a [Log.DEBUG] message, only when [Build.IS_DEBUGGABLE]. */
-private inline fun Any.logDebug(message: () -> String) {
-    if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message())
-}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
index 03503fd..f9e1adf 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
@@ -18,9 +18,9 @@
 
 import android.permission.PermissionGroupUsage
 import com.android.systemui.log.dagger.PrivacyLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogMessage
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogMessage
 import com.android.systemui.privacy.PrivacyDialog
 import com.android.systemui.privacy.PrivacyItem
 import java.text.SimpleDateFormat
diff --git a/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java
index 80fbf911..b6a5ad6 100644
--- a/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java
@@ -16,11 +16,14 @@
 
 package com.android.systemui.process.condition;
 
+import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.process.ProcessWrapper;
 import com.android.systemui.shared.condition.Condition;
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.CoroutineScope;
+
 /**
  * {@link SystemProcessCondition} checks to make sure the current process is being ran by the
  * System User.
@@ -29,8 +32,9 @@
     private final ProcessWrapper mProcessWrapper;
 
     @Inject
-    public SystemProcessCondition(ProcessWrapper processWrapper) {
-        super();
+    public SystemProcessCondition(@Application CoroutineScope scope,
+            ProcessWrapper processWrapper) {
+        super(scope);
         mProcessWrapper = processWrapper;
     }
 
@@ -42,4 +46,9 @@
     @Override
     protected void stop() {
     }
+
+    @Override
+    protected int getStartStrategy() {
+        return START_EAGERLY;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index a7aac5a..463c79c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -26,7 +26,7 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.qs.QSTile;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index b7f9f6b..1afc885 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -38,7 +38,9 @@
  */
 public class QSContainerImpl extends FrameLayout implements Dumpable {
 
+    private int mFancyClippingLeftInset;
     private int mFancyClippingTop;
+    private int mFancyClippingRightInset;
     private int mFancyClippingBottom;
     private final float[] mFancyClippingRadii = new float[] {0, 0, 0, 0, 0, 0, 0, 0};
     private  final Path mFancyClippingPath = new Path();
@@ -53,6 +55,7 @@
     private boolean mQsDisabled;
     private int mContentHorizontalPadding = -1;
     private boolean mClippingEnabled;
+    private boolean mIsFullWidth;
 
     public QSContainerImpl(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -237,7 +240,8 @@
     /**
      * Clip QS bottom using a concave shape.
      */
-    public void setFancyClipping(int top, int bottom, int radius, boolean enabled) {
+    public void setFancyClipping(int leftInset, int top, int rightInset, int bottom, int radius,
+            boolean enabled, boolean fullWidth) {
         boolean updatePath = false;
         if (mFancyClippingRadii[0] != radius) {
             mFancyClippingRadii[0] = radius;
@@ -246,10 +250,18 @@
             mFancyClippingRadii[3] = radius;
             updatePath = true;
         }
+        if (mFancyClippingLeftInset != leftInset) {
+            mFancyClippingLeftInset = leftInset;
+            updatePath = true;
+        }
         if (mFancyClippingTop != top) {
             mFancyClippingTop = top;
             updatePath = true;
         }
+        if (mFancyClippingRightInset != rightInset) {
+            mFancyClippingRightInset = rightInset;
+            updatePath = true;
+        }
         if (mFancyClippingBottom != bottom) {
             mFancyClippingBottom = bottom;
             updatePath = true;
@@ -258,6 +270,10 @@
             mClippingEnabled = enabled;
             updatePath = true;
         }
+        if (mIsFullWidth != fullWidth) {
+            mIsFullWidth = fullWidth;
+            updatePath = true;
+        }
 
         if (updatePath) {
             updateClippingPath();
@@ -281,15 +297,21 @@
             return;
         }
 
-        mFancyClippingPath.addRoundRect(0, mFancyClippingTop, getWidth(),
+        int clippingLeft = mIsFullWidth ? -mFancyClippingLeftInset : 0;
+        int clippingRight = mIsFullWidth ? getWidth() + mFancyClippingRightInset : getWidth();
+        mFancyClippingPath.addRoundRect(clippingLeft, mFancyClippingTop, clippingRight,
                 mFancyClippingBottom, mFancyClippingRadii, Path.Direction.CW);
         invalidate();
     }
 
     @Override
     public void dump(PrintWriter pw, String[] args) {
-        pw.println(getClass().getSimpleName() + " updateClippingPath: top("
-                + mFancyClippingTop + ") bottom(" + mFancyClippingBottom  + ") mClippingEnabled("
-                + mClippingEnabled + ")");
+        pw.println(getClass().getSimpleName() + " updateClippingPath: "
+                + "leftInset(" + mFancyClippingLeftInset + ") "
+                + "top(" + mFancyClippingTop + ") "
+                + "rightInset(" + mFancyClippingRightInset + ") "
+                + "bottom(" + mFancyClippingBottom  + ") "
+                + "mClippingEnabled(" + mClippingEnabled + ") "
+                + "mIsFullWidth(" + mIsFullWidth + ")");
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
index 0d29a1a..85f557b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -27,6 +27,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.retail.domain.interactor.RetailModeInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.ViewController;
 
@@ -45,18 +46,22 @@
     private final View mEditButton;
     private final FalsingManager mFalsingManager;
     private final ActivityStarter mActivityStarter;
+    private final RetailModeInteractor mRetailModeInteractor;
 
     @Inject
     QSFooterViewController(QSFooterView view,
             UserTracker userTracker,
             FalsingManager falsingManager,
             ActivityStarter activityStarter,
-            QSPanelController qsPanelController) {
+            QSPanelController qsPanelController,
+            RetailModeInteractor retailModeInteractor
+    ) {
         super(view);
         mUserTracker = userTracker;
         mQsPanelController = qsPanelController;
         mFalsingManager = falsingManager;
         mActivityStarter = activityStarter;
+        mRetailModeInteractor = retailModeInteractor;
 
         mBuildText = mView.findViewById(R.id.build);
         mPageIndicator = mView.findViewById(R.id.footer_page_indicator);
@@ -96,6 +101,8 @@
     @Override
     public void setVisibility(int visibility) {
         mView.setVisibility(visibility);
+        mEditButton
+                .setVisibility(mRetailModeInteractor.isInRetailMode() ? View.GONE : View.VISIBLE);
         mEditButton.setClickable(visibility == View.VISIBLE);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index d806afa..b8c2fad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -18,9 +18,10 @@
 
 import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
 import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
-import static com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
+import static com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.content.res.Configuration;
@@ -43,10 +44,10 @@
 import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.LifecycleRegistry;
 
+import com.android.app.animation.Interpolators;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.compose.ComposeFacade;
 import com.android.systemui.dump.DumpManager;
@@ -395,9 +396,11 @@
     }
 
     @Override
-    public void setFancyClipping(int top, int bottom, int cornerRadius, boolean visible) {
+    public void setFancyClipping(int leftInset, int top, int rightInset, int bottom,
+            int cornerRadius, boolean visible, boolean fullWidth) {
         if (getView() instanceof QSContainerImpl) {
-            ((QSContainerImpl) getView()).setFancyClipping(top, bottom, cornerRadius, visible);
+            ((QSContainerImpl) getView()).setFancyClipping(leftInset, top, rightInset, bottom,
+                    cornerRadius, visible, fullWidth);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
index 025fb22..cd52ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
@@ -1,8 +1,8 @@
 package com.android.systemui.qs
 
 import com.android.systemui.log.dagger.QSFragmentDisableLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
index 0bce1f7..b70b94b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
@@ -719,7 +719,8 @@
                 String name = vpnName != null ? vpnName : vpnNameWorkProfile;
                 String namedVp = mDpm.getResources().getString(
                         QS_DIALOG_MANAGEMENT_NAMED_VPN,
-                        () -> mContext.getString(R.string.monitoring_description_named_vpn, name),
+                        () -> mContext.getString(
+                                R.string.monitoring_description_managed_device_named_vpn, name),
                         name);
                 message.append(namedVp);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 5b461a6..c00a81c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -21,14 +21,14 @@
 import android.content.res.Configuration.Orientation
 import android.service.quicksettings.Tile
 import android.view.View
+import com.android.systemui.log.ConstantStringsLogger
+import com.android.systemui.log.ConstantStringsLoggerImpl
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.VERBOSE
 import com.android.systemui.log.dagger.QSConfigLog
 import com.android.systemui.log.dagger.QSLog
-import com.android.systemui.plugins.log.ConstantStringsLogger
-import com.android.systemui.plugins.log.ConstantStringsLoggerImpl
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.ERROR
-import com.android.systemui.plugins.log.LogLevel.VERBOSE
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.statusbar.StatusBarState
 import com.google.errorprone.annotations.CompileTimeConstant
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index e85440c..a066242 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -18,8 +18,8 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
 import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index 595b29a9..3b2362f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -20,6 +20,7 @@
 import android.content.res.Resources
 import android.database.ContentObserver
 import android.provider.Settings
+import com.android.systemui.R
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -27,12 +28,16 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.retail.data.repository.RetailModeRepository
 import com.android.systemui.util.settings.SecureSettings
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -84,6 +89,9 @@
  * [Settings.Secure.QS_TILES].
  *
  * All operations against [Settings] will be performed in a background thread.
+ *
+ * If the device is in retail mode, the tiles are fixed to the value of
+ * [R.string.quick_settings_tiles_retail_mode].
  */
 @SysUISingleton
 class TileSpecSettingsRepository
@@ -92,9 +100,31 @@
     private val secureSettings: SecureSettings,
     @Main private val resources: Resources,
     private val logger: QSPipelineLogger,
+    private val retailModeRepository: RetailModeRepository,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) : TileSpecRepository {
+
+    private val retailModeTiles by lazy {
+        resources
+            .getString(R.string.quick_settings_tiles_retail_mode)
+            .split(DELIMITER)
+            .map(TileSpec::create)
+            .filter { it !is TileSpec.Invalid }
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
     override fun tilesSpecs(userId: Int): Flow<List<TileSpec>> {
+        return retailModeRepository.retailMode.flatMapLatest { inRetailMode ->
+            if (inRetailMode) {
+                logger.logUsingRetailTiles()
+                flowOf(retailModeTiles)
+            } else {
+                settingsTiles(userId)
+            }
+        }
+    }
+
+    private fun settingsTiles(userId: Int): Flow<List<TileSpec>> {
         return conflatedCallbackFlow {
                 val observer =
                     object : ContentObserver(null) {
@@ -157,6 +187,10 @@
     }
 
     private suspend fun storeTiles(@UserIdInt forUser: Int, tiles: List<TileSpec>) {
+        if (retailModeRepository.inRetailMode) {
+            // No storing tiles when in retail mode
+            return
+        }
         val toStore =
             tiles
                 .filter { it !is TileSpec.Invalid }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index 767ce91..ff7d206 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.qs.pipeline.shared.logging
 
 import android.annotation.UserIdInt
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.qs.pipeline.dagger.QSTileListLog
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
@@ -120,6 +120,10 @@
         )
     }
 
+    fun logUsingRetailTiles() {
+        tileListLogBuffer.log(TILE_LIST_TAG, LogLevel.DEBUG, {}, { "Using retail tiles" })
+    }
+
     /** Reasons for destroying an existing tile. */
     enum class TileDestroyedReason(val readable: String) {
         TILE_REMOVED("Tile removed from  current set"),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 7f7f8ad6..2d9f7dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -68,6 +68,7 @@
     private final SensorPrivacyManager mPrivacyManager;
     private final BatteryController mBatteryController;
     private final SettingObserver mSetting;
+    private final boolean mAllowRotationResolver;
 
     @Inject
     public RotationLockTile(
@@ -105,6 +106,8 @@
             }
         };
         mBatteryController.observe(getLifecycle(), this);
+        mAllowRotationResolver = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_allowRotationResolver);
     }
 
     @Override
@@ -145,7 +148,7 @@
 
         final boolean powerSave = mBatteryController.isPowerSave();
         final boolean cameraLocked = mPrivacyManager.isSensorPrivacyEnabled(CAMERA);
-        final boolean cameraRotation =
+        final boolean cameraRotation = mAllowRotationResolver &&
                 !powerSave && !cameraLocked && hasSufficientPermission(mContext)
                         && mController.isCameraRotationEnabled();
         state.value = !rotationLocked;
diff --git a/packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt b/packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt
new file mode 100644
index 0000000..e863949
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.retail.dagger
+
+import com.android.systemui.retail.data.repository.RetailModeRepository
+import com.android.systemui.retail.data.repository.RetailModeSettingsRepository
+import com.android.systemui.retail.domain.interactor.RetailModeInteractor
+import com.android.systemui.retail.domain.interactor.RetailModeInteractorImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+abstract class RetailModeModule {
+
+    @Binds
+    abstract fun bindsRetailModeRepository(impl: RetailModeSettingsRepository): RetailModeRepository
+
+    @Binds
+    abstract fun bindsRetailModeInteractor(impl: RetailModeInteractorImpl): RetailModeInteractor
+}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt b/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
new file mode 100644
index 0000000..3c0aa38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.retail.data.repository
+
+import android.database.ContentObserver
+import android.provider.Settings
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.settings.GlobalSettings
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository to track if the device is in Retail mode */
+interface RetailModeRepository {
+    /** Flow of whether the device is currently in retail mode. */
+    val retailMode: StateFlow<Boolean>
+
+    /** Last value of whether the device is in retail mode. */
+    val inRetailMode: Boolean
+        get() = retailMode.value
+}
+
+/**
+ * Tracks [Settings.Global.DEVICE_DEMO_MODE].
+ *
+ * @see UserManager.isDeviceInDemoMode
+ */
+@SysUISingleton
+class RetailModeSettingsRepository
+@Inject
+constructor(
+    globalSettings: GlobalSettings,
+    @Background backgroundDispatcher: CoroutineDispatcher,
+    @Application scope: CoroutineScope,
+) : RetailModeRepository {
+    override val retailMode =
+        conflatedCallbackFlow {
+                val observer =
+                    object : ContentObserver(null) {
+                        override fun onChange(selfChange: Boolean) {
+                            trySend(Unit)
+                        }
+                    }
+
+                globalSettings.registerContentObserver(RETAIL_MODE_SETTING, observer)
+
+                awaitClose { globalSettings.unregisterContentObserver(observer) }
+            }
+            .onStart { emit(Unit) }
+            .map { globalSettings.getInt(RETAIL_MODE_SETTING, 0) != 0 }
+            .flowOn(backgroundDispatcher)
+            .stateIn(scope, SharingStarted.Eagerly, false)
+
+    companion object {
+        private const val RETAIL_MODE_SETTING = Settings.Global.DEVICE_DEMO_MODE
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
new file mode 100644
index 0000000..eea452c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.retail.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.retail.data.repository.RetailModeRepository
+import javax.inject.Inject
+
+/** Interactor to determine if the device is currently in retail mode */
+interface RetailModeInteractor {
+    /** Whether the device is currently in retail mode */
+    val isInRetailMode: Boolean
+}
+
+@SysUISingleton
+class RetailModeInteractorImpl
+@Inject
+constructor(
+    private val repository: RetailModeRepository,
+) : RetailModeInteractor {
+    override val isInRetailMode: Boolean
+        get() = repository.inRetailMode
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 69008cc..84f358c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -33,6 +33,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
@@ -60,11 +61,10 @@
     public static final int REQUEST_CODE = 2;
 
     private static final int USER_ID_NOT_SPECIFIED = -1;
-    private static final int NOTIFICATION_RECORDING_ID = 4274;
-    private static final int NOTIFICATION_PROCESSING_ID = 4275;
-    private static final int NOTIFICATION_VIEW_ID = 4273;
+    private static final int NOTIF_BASE_ID = 4273;
     private static final String TAG = "RecordingService";
     private static final String CHANNEL_ID = "screen_record";
+    private static final String GROUP_KEY = "screen_record_saved";
     private static final String EXTRA_RESULT_CODE = "extra_resultCode";
     private static final String EXTRA_PATH = "extra_path";
     private static final String EXTRA_AUDIO_SOURCE = "extra_useAudio";
@@ -89,6 +89,7 @@
     private final UiEventLogger mUiEventLogger;
     private final NotificationManager mNotificationManager;
     private final UserContextProvider mUserContextTracker;
+    private int mNotificationId = NOTIF_BASE_ID;
 
     @Inject
     public RecordingService(RecordingController controller, @LongRunning Executor executor,
@@ -134,14 +135,23 @@
         }
         String action = intent.getAction();
         Log.d(TAG, "onStartCommand " + action);
+        NotificationChannel channel = new NotificationChannel(
+                CHANNEL_ID,
+                getString(R.string.screenrecord_title),
+                NotificationManager.IMPORTANCE_DEFAULT);
+        channel.setDescription(getString(R.string.screenrecord_channel_description));
+        channel.enableVibration(true);
+        mNotificationManager.createNotificationChannel(channel);
 
         int currentUserId = mUserContextTracker.getUserContext().getUserId();
         UserHandle currentUser = new UserHandle(currentUserId);
         switch (action) {
             case ACTION_START:
+                // Get a unique ID for this recording's notifications
+                mNotificationId = NOTIF_BASE_ID + (int) SystemClock.uptimeMillis();
                 mAudioSource = ScreenRecordingAudioSource
                         .values()[intent.getIntExtra(EXTRA_AUDIO_SOURCE, 0)];
-                Log.d(TAG, "recording with audio source" + mAudioSource);
+                Log.d(TAG, "recording with audio source " + mAudioSource);
                 mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false);
                 MediaProjectionCaptureTarget captureTarget =
                         intent.getParcelableExtra(EXTRA_CAPTURE_TARGET,
@@ -169,7 +179,7 @@
                 } else {
                     updateState(false);
                     createErrorNotification();
-                    stopForeground(true);
+                    stopForeground(STOP_FOREGROUND_DETACH);
                     stopSelf();
                     return Service.START_NOT_STICKY;
                 }
@@ -200,7 +210,7 @@
                     startActivity(Intent.createChooser(shareIntent, shareLabel)
                             .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
                     // Remove notification
-                    mNotificationManager.cancelAsUser(null, NOTIFICATION_VIEW_ID, currentUser);
+                    mNotificationManager.cancelAsUser(null, mNotificationId, currentUser);
                     return false;
                 }, false, false);
 
@@ -260,14 +270,6 @@
     @VisibleForTesting
     protected void createErrorNotification() {
         Resources res = getResources();
-        NotificationChannel channel = new NotificationChannel(
-                CHANNEL_ID,
-                getString(R.string.screenrecord_title),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        channel.setDescription(getString(R.string.screenrecord_channel_description));
-        channel.enableVibration(true);
-        mNotificationManager.createNotificationChannel(channel);
-
         Bundle extras = new Bundle();
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
                 res.getString(R.string.screenrecord_title));
@@ -277,7 +279,7 @@
                 .setSmallIcon(R.drawable.ic_screenrecord)
                 .setContentTitle(notificationTitle)
                 .addExtras(extras);
-        startForeground(NOTIFICATION_RECORDING_ID, builder.build());
+        startForeground(mNotificationId, builder.build());
     }
 
     @VisibleForTesting
@@ -288,14 +290,6 @@
     @VisibleForTesting
     protected void createRecordingNotification() {
         Resources res = getResources();
-        NotificationChannel channel = new NotificationChannel(
-                CHANNEL_ID,
-                getString(R.string.screenrecord_title),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        channel.setDescription(getString(R.string.screenrecord_channel_description));
-        channel.enableVibration(true);
-        mNotificationManager.createNotificationChannel(channel);
-
         Bundle extras = new Bundle();
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
                 res.getString(R.string.screenrecord_title));
@@ -323,7 +317,7 @@
                 .setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE)
                 .addAction(stopAction)
                 .addExtras(extras);
-        startForeground(NOTIFICATION_RECORDING_ID, builder.build());
+        startForeground(mNotificationId, builder.build());
     }
 
     @VisibleForTesting
@@ -337,11 +331,12 @@
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
                 res.getString(R.string.screenrecord_title));
 
-        Notification.Builder builder = new Notification.Builder(getApplicationContext(), CHANNEL_ID)
+        Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
                 .setContentTitle(notificationTitle)
                 .setContentText(
                         getResources().getString(R.string.screenrecord_background_processing_label))
                 .setSmallIcon(R.drawable.ic_screenrecord)
+                .setGroup(GROUP_KEY)
                 .addExtras(extras);
         return builder.build();
     }
@@ -378,6 +373,7 @@
                         PendingIntent.FLAG_IMMUTABLE))
                 .addAction(shareAction)
                 .setAutoCancel(true)
+                .setGroup(GROUP_KEY)
                 .addExtras(extras);
 
         // Add thumbnail if available
@@ -391,6 +387,24 @@
         return builder.build();
     }
 
+    /**
+     * Adds a group notification so that save notifications from multiple recordings are
+     * grouped together, and the foreground service recording notification is not
+     */
+    private void postGroupNotification(UserHandle currentUser) {
+        Bundle extras = new Bundle();
+        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
+                getResources().getString(R.string.screenrecord_title));
+        Notification groupNotif = new Notification.Builder(this, CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_screenrecord)
+                .setContentTitle(getResources().getString(R.string.screenrecord_save_title))
+                .setGroup(GROUP_KEY)
+                .setGroupSummary(true)
+                .setExtras(extras)
+                .build();
+        mNotificationManager.notifyAsUser(TAG, NOTIF_BASE_ID, groupNotif, currentUser);
+    }
+
     private void stopService() {
         stopService(USER_ID_NOT_SPECIFIED);
     }
@@ -423,27 +437,26 @@
             Log.e(TAG, "stopRecording called, but recorder was null");
         }
         updateState(false);
+        stopForeground(STOP_FOREGROUND_DETACH);
         stopSelf();
     }
 
     private void saveRecording(int userId) {
         UserHandle currentUser = new UserHandle(userId);
-        mNotificationManager.notifyAsUser(null, NOTIFICATION_PROCESSING_ID,
+        mNotificationManager.notifyAsUser(null, mNotificationId,
                 createProcessingNotification(), currentUser);
 
         mLongExecutor.execute(() -> {
             try {
                 Log.d(TAG, "saving recording");
                 Notification notification = createSaveNotification(getRecorder().save());
-                if (!mController.isRecording()) {
-                    mNotificationManager.notifyAsUser(null, NOTIFICATION_VIEW_ID, notification,
-                            currentUser);
-                }
+                postGroupNotification(currentUser);
+                mNotificationManager.notifyAsUser(null, mNotificationId,  notification,
+                        currentUser);
             } catch (IOException e) {
                 Log.e(TAG, "Error saving screen recording: " + e.getMessage());
                 showErrorToast(R.string.screenrecord_delete_error);
-            } finally {
-                mNotificationManager.cancelAsUser(null, NOTIFICATION_PROCESSING_ID, currentUser);
+                mNotificationManager.cancelAsUser(null, mNotificationId, currentUser);
             }
         });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 5117915..926ede9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -22,10 +22,10 @@
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
+import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
+import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
-import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
-import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
 import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
 import static com.android.systemui.classifier.Classifier.GENERIC;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -88,6 +88,7 @@
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
@@ -111,7 +112,6 @@
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.LaunchAnimator;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.classifier.Classifier;
@@ -1513,6 +1513,8 @@
                 mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
                 mKeyguardStatusViewController.isClockTopAligned());
         mClockPositionAlgorithm.run(mClockPositionResult);
+        mKeyguardStatusViewController.setLockscreenClockY(
+                mClockPositionAlgorithm.getExpandedPreferredClockY());
         mKeyguardBottomAreaInteractor.setClockPosition(
                 mClockPositionResult.clockX, mClockPositionResult.clockY);
         boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
@@ -4474,7 +4476,7 @@
         mDisplayTopInset = combinedInsets.top;
         mDisplayRightInset = combinedInsets.right;
         mDisplayLeftInset = combinedInsets.left;
-        mQsController.setDisplayInsets(mDisplayRightInset, mDisplayLeftInset);
+        mQsController.setDisplayInsets(mDisplayLeftInset, mDisplayRightInset);
 
         mNavigationBarBottomHeight = insets.getStableInsetBottom();
         updateMaxHeadsUpTranslation();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index fb7c5c2..31b361f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -18,7 +18,6 @@
 
 import android.view.View
 import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.view.WindowInsets
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.widget.ConstraintSet
@@ -62,6 +61,7 @@
     private var isQSCustomizing = false
     private var isQSCustomizerAnimating = false
 
+    private var shadeHeaderHeight = 0
     private var largeScreenShadeHeaderHeight = 0
     private var largeScreenShadeHeaderActive = false
     private var notificationsBottomMargin = 0
@@ -146,6 +146,8 @@
                 R.dimen.notification_panel_margin_bottom)
         largeScreenShadeHeaderHeight =
                 resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
+        shadeHeaderHeight =
+                resources.getDimensionPixelSize(R.dimen.qs_header_height)
         panelMarginHorizontal = resources.getDimensionPixelSize(
                 R.dimen.notification_panel_margin_horizontal)
         topMargin = if (largeScreenShadeHeaderActive) {
@@ -245,7 +247,7 @@
         if (largeScreenShadeHeaderActive) {
             constraintSet.constrainHeight(R.id.split_shade_status_bar, largeScreenShadeHeaderHeight)
         } else {
-            constraintSet.constrainHeight(R.id.split_shade_status_bar, WRAP_CONTENT)
+            constraintSet.constrainHeight(R.id.split_shade_status_bar, shadeHeaderHeight)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 4012736..abdd1a9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -50,6 +50,7 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
@@ -59,7 +60,6 @@
 import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
@@ -85,11 +85,13 @@
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
+import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.LargeScreenUtils;
 
@@ -116,6 +118,7 @@
     private final PulseExpansionHandler mPulseExpansionHandler;
     private final ShadeExpansionStateManager mShadeExpansionStateManager;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private final LightBarController mLightBarController;
     private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     private final NotificationShadeDepthController mDepthController;
@@ -135,6 +138,7 @@
     private final LockscreenGestureLogger mLockscreenGestureLogger;
     private final ShadeLogger mShadeLog;
     private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+    private final CastController mCastController;
     private final FeatureFlags mFeatureFlags;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final ShadeRepository mShadeRepository;
@@ -302,6 +306,7 @@
             NotificationRemoteInputManager remoteInputManager,
             ShadeExpansionStateManager shadeExpansionStateManager,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            LightBarController lightBarController,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
             LockscreenShadeTransitionController lockscreenShadeTransitionController,
             NotificationShadeDepthController notificationShadeDepthController,
@@ -324,7 +329,8 @@
             InteractionJankMonitor interactionJankMonitor,
             ShadeLogger shadeLog,
             KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
-            ShadeRepository shadeRepository
+            ShadeRepository shadeRepository,
+            CastController castController
     ) {
         mPanelViewControllerLazy = panelViewControllerLazy;
         mPanelView = panelView;
@@ -343,6 +349,7 @@
         mRemoteInputManager = remoteInputManager;
         mShadeExpansionStateManager = shadeExpansionStateManager;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mLightBarController = lightBarController;
         mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
         mDepthController = notificationShadeDepthController;
@@ -364,6 +371,7 @@
         mMetricsLogger = metricsLogger;
         mShadeLog = shadeLog;
         mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+        mCastController = castController;
         mFeatureFlags = featureFlags;
         mInteractionJankMonitor = interactionJankMonitor;
         mShadeRepository = shadeRepository;
@@ -879,7 +887,9 @@
     }
 
     void setOverScrollAmount(int overExpansion) {
-        mQs.setOverScrollAmount(overExpansion);
+        if (mQs != null) {
+            mQs.setOverScrollAmount(overExpansion);
+        }
     }
 
     private void setOverScrolling(boolean overscrolling) {
@@ -1014,6 +1024,9 @@
         mShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
         mShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
         mShadeHeaderController.setQsVisible(mVisible);
+
+        // Update the light bar
+        mLightBarController.setQsExpanded(mFullyExpanded);
     }
 
     float getLockscreenShadeDragProgress() {
@@ -1188,7 +1201,9 @@
         mLastClipBounds.set(left, top, right, bottom);
         if (mIsFullWidth) {
             clipStatusView = qsVisible;
-            float screenCornerRadius = mRecordingController.isRecording() ? 0 : mScreenCornerRadius;
+            float screenCornerRadius =
+                    mRecordingController.isRecording() || mCastController.hasConnectedCastDevice()
+                            ? 0 : mScreenCornerRadius;
             radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
                     Math.min(top / (float) mScrimCornerRadius, 1f));
             mScrimController.setNotificationBottomRadius(radius);
@@ -1217,10 +1232,13 @@
             mVisible = qsVisible;
             mQs.setQsVisible(qsVisible);
             mQs.setFancyClipping(
+                    mDisplayLeftInset,
                     clipTop,
+                    mDisplayRightInset,
                     clipBottom,
                     radius,
-                    qsVisible && !mSplitShadeEnabled);
+                    qsVisible && !mSplitShadeEnabled,
+                    mIsFullWidth);
 
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 86ae4ec..f080d3d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -34,10 +34,10 @@
 import android.widget.TextView
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.motion.widget.MotionLayout
+import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
 import com.android.systemui.Dumpable
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 1839e13..25073c1b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -18,8 +18,8 @@
 
 import android.view.MotionEvent
 import com.android.systemui.log.dagger.ShadeLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.shade.ShadeViewController.Companion.FLING_COLLAPSE
 import com.android.systemui.shade.ShadeViewController.Companion.FLING_EXPAND
 import com.android.systemui.shade.ShadeViewController.Companion.FLING_HIDE
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
index 9851625..d8d4279 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
@@ -18,12 +18,12 @@
 
 import android.view.WindowManager
 import com.android.systemui.log.dagger.ShadeWindowLog
-import com.android.systemui.plugins.log.ConstantStringsLogger
-import com.android.systemui.plugins.log.ConstantStringsLoggerImpl
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogMessage
+import com.android.systemui.log.ConstantStringsLogger
+import com.android.systemui.log.ConstantStringsLoggerImpl
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogMessage
 import javax.inject.Inject
 
 private const val TAG = "systemui.shadewindow"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
index 90c52bd..e008ec0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
@@ -18,8 +18,8 @@
 
 import android.app.PendingIntent
 import com.android.systemui.log.dagger.NotifInteractionLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index 07b6869..b6970ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -20,6 +20,7 @@
 import android.content.res.Resources
 import android.os.SystemProperties
 import android.os.Trace
+import android.os.Trace.TRACE_TAG_APP
 import android.util.IndentingPrintWriter
 import android.util.MathUtils
 import android.view.CrossWindowBlurListeners
@@ -43,8 +44,8 @@
 ) : Dumpable {
     val minBlurRadius = resources.getDimensionPixelSize(R.dimen.min_window_blur_radius)
     val maxBlurRadius = resources.getDimensionPixelSize(R.dimen.max_window_blur_radius)
-    private val traceCookie = System.identityHashCode(this)
     private var lastAppliedBlur = 0
+    private var earlyWakeupEnabled = false
 
     init {
         dumpManager.registerDumpable(javaClass.name, this)
@@ -72,6 +73,26 @@
     }
 
     /**
+     * This method should be called before [applyBlur] so that, if needed, we can set the
+     * early-wakeup flag in SurfaceFlinger.
+     */
+    fun prepareBlur(viewRootImpl: ViewRootImpl?, radius: Int) {
+        if (viewRootImpl == null || !viewRootImpl.surfaceControl.isValid ||
+            !supportsBlursOnWindows() || earlyWakeupEnabled
+        ) {
+            return
+        }
+        if (lastAppliedBlur == 0 && radius != 0) {
+            Trace.asyncTraceForTrackBegin(TRACE_TAG_APP, TRACK_NAME, EARLY_WAKEUP_SLICE_NAME, 0)
+            earlyWakeupEnabled = true
+            createTransaction().use {
+                it.setEarlyWakeupStart()
+                it.apply()
+            }
+        }
+    }
+
+    /**
      * Applies background blurs to a {@link ViewRootImpl}.
      *
      * @param viewRootImpl The window root.
@@ -85,14 +106,20 @@
         createTransaction().use {
             if (supportsBlursOnWindows()) {
                 it.setBackgroundBlurRadius(viewRootImpl.surfaceControl, radius)
-                if (lastAppliedBlur == 0 && radius != 0) {
-                    Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME,
-                            EARLY_WAKEUP_SLICE_NAME, traceCookie)
+                if (!earlyWakeupEnabled && lastAppliedBlur == 0 && radius != 0) {
+                    Trace.asyncTraceForTrackBegin(
+                        TRACE_TAG_APP,
+                        TRACK_NAME,
+                        EARLY_WAKEUP_SLICE_NAME,
+                        0
+                    )
                     it.setEarlyWakeupStart()
+                    earlyWakeupEnabled = true
                 }
-                if (lastAppliedBlur != 0 && radius == 0) {
+                if (earlyWakeupEnabled && lastAppliedBlur != 0 && radius == 0) {
                     it.setEarlyWakeupEnd()
-                    Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, traceCookie)
+                    Trace.asyncTraceForTrackEnd(TRACE_TAG_APP, TRACK_NAME, 0)
+                    earlyWakeupEnabled = false
                 }
                 lastAppliedBlur = radius
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index 63179da..c1ebf12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -18,8 +18,8 @@
 
 import android.view.View;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
index 54b341f..1a32d70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
@@ -38,8 +38,8 @@
 import android.view.animation.Interpolator;
 import android.widget.ImageView;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index ea5a1c0..0ea2570 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -41,8 +41,8 @@
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
 import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
+import static com.android.systemui.log.LogLevel.ERROR;
 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
-import static com.android.systemui.plugins.log.LogLevel.ERROR;
 
 import android.app.AlarmManager;
 import android.app.admin.DevicePolicyManager;
@@ -95,8 +95,8 @@
 import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.log.LogLevel;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.log.LogLevel;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 9421524..823bb35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -16,7 +16,7 @@
 import android.util.MathUtils.lerp
 import android.view.View
 import android.view.animation.PathInterpolator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold
 import com.android.systemui.util.getColorWithAlpha
 import com.android.systemui.util.leak.RotationUtils
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 7f016f3..b61f243 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -18,7 +18,7 @@
 import com.android.systemui.ExpandHelper
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.biometrics.UdfpsKeyguardViewController
 import com.android.systemui.classifier.Classifier
 import com.android.systemui.classifier.FalsingCollector
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 72ae16e..fb88a96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -44,9 +44,9 @@
 import android.view.View;
 import android.widget.ImageView;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 8dc7842..0e20df6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -33,7 +33,7 @@
 import androidx.dynamicanimation.animation.SpringAnimation
 import androidx.dynamicanimation.animation.SpringForce
 import com.android.systemui.Dumpable
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
@@ -189,12 +189,7 @@
             scheduleUpdate()
         }
 
-    /**
-     * Callback that updates the window blur value and is called only once per frame.
-     */
-    @VisibleForTesting
-    val updateBlurCallback = Choreographer.FrameCallback {
-        updateScheduled = false
+    private fun computeBlurAndZoomOut(): Pair<Int, Float> {
         val animationRadius = MathUtils.constrain(shadeAnimation.radius,
                 blurUtils.minBlurRadius.toFloat(), blurUtils.maxBlurRadius.toFloat())
         val expansionRadius = blurUtils.blurRadiusOfRatio(
@@ -232,6 +227,16 @@
         // Brightness slider removes blur, but doesn't affect zooms
         blur = (blur * (1f - brightnessMirrorSpring.ratio)).toInt()
 
+        return Pair(blur, zoomOut)
+    }
+
+    /**
+     * Callback that updates the window blur value and is called only once per frame.
+     */
+    @VisibleForTesting
+    val updateBlurCallback = Choreographer.FrameCallback {
+        updateScheduled = false
+        val (blur, zoomOut) = computeBlurAndZoomOut()
         val opaque = scrimsVisible && !blursDisabledForAppLaunch
         Trace.traceCounter(Trace.TRACE_TAG_APP, "shade_blur_radius", blur)
         blurUtils.applyBlur(root.viewRootImpl, blur, opaque)
@@ -441,6 +446,8 @@
             return
         }
         updateScheduled = true
+        val (blur, _) = computeBlurAndZoomOut()
+        blurUtils.prepareBlur(root.viewRootImpl, blur)
         choreographer.postFrameCallback(updateBlurCallback)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 49c7950..9d7f3be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -35,10 +35,10 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 821a172..fc6eaa8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -31,7 +31,7 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
index 575f354..f1e51e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
@@ -4,7 +4,7 @@
 import android.content.res.Configuration
 import android.util.MathUtils
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import dagger.assisted.Assisted
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
index 572c0e0..3d574ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
@@ -8,7 +8,7 @@
 import android.view.animation.PathInterpolator
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 7755003..91c08a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -52,10 +52,10 @@
 
 import androidx.core.graphics.ColorUtils;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.util.drawable.DrawableSize;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 79d01b4a..d6a14604 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -42,6 +42,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -49,7 +50,6 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
index 2fa27ee..67ab060 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -25,8 +25,8 @@
 import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.TransformState;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 6e74542..2465c21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -73,9 +73,9 @@
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogLevel;
 import com.android.systemui.log.dagger.StatusBarNetworkControllerLog;
-import com.android.systemui.plugins.log.LogBuffer;
-import com.android.systemui.plugins.log.LogLevel;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
@@ -87,6 +87,8 @@
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.CarrierConfigTracker;
 
+import kotlin.Unit;
+
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -101,8 +103,6 @@
 
 import javax.inject.Inject;
 
-import kotlin.Unit;
-
 /** Platform implementation of the network controller. **/
 @SysUISingleton
 public class NetworkControllerImpl extends BroadcastReceiver
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index bfc4e9c..eddb683 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -26,7 +26,7 @@
 import android.widget.FrameLayout
 import com.android.internal.annotations.GuardedBy
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
index 9ce6b02..a67c26c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
@@ -18,8 +18,8 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.dagger.SwipeUpLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import javax.inject.Inject
 
 /** Log messages for [SwipeUpGestureHandler]. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
index 0446165..b09b9f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
@@ -21,8 +21,8 @@
 import android.view.View;
 import android.widget.ImageView;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.notification.row.HybridNotificationView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
index c22dbf6..785e65d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
@@ -3,7 +3,7 @@
 import android.util.MathUtils
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.animation.LaunchAnimator
 import kotlin.math.min
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
index c22cd1b..5a14200 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -23,13 +23,13 @@
 import android.view.ViewGroup;
 import android.widget.TextView;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.widget.IMessagingLayout;
 import com.android.internal.widget.MessagingGroup;
 import com.android.internal.widget.MessagingImageMessage;
 import com.android.internal.widget.MessagingLinearLayout;
 import com.android.internal.widget.MessagingMessage;
 import com.android.internal.widget.MessagingPropertyAnimator;
-import com.android.systemui.animation.Interpolators;
 
 import java.util.ArrayList;
 import java.util.HashMap;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
index 3058fbb..a3a72d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.notification
 
 import com.android.systemui.log.dagger.NotifInteractionLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
index 3fc7b13..a045698 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
@@ -24,8 +24,8 @@
 import android.view.View;
 import android.widget.ImageView;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
 import java.util.function.Consumer;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index fe0b28d..9ba2199 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -21,8 +21,8 @@
 import androidx.annotation.VisibleForTesting
 import androidx.core.animation.ObjectAnimator
 import com.android.systemui.Dumpable
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.animation.InterpolatorsAndroidX
+import com.android.app.animation.Interpolators
+import com.android.app.animation.InterpolatorsAndroidX
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
index dd3c2a9..f7679ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
@@ -13,9 +13,9 @@
 
 package com.android.systemui.statusbar.notification
 
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
 import com.android.systemui.log.dagger.NotificationLockscreenLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
 import com.android.systemui.statusbar.StatusBarState
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 5d07cac..57d20246 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -24,7 +24,7 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
-import com.android.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
 import com.android.systemui.statusbar.notification.stack.AnimationFilter;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ViewState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
index 9582dfad..487a5f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.statusbar.notification
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
 import com.android.systemui.log.dagger.NotificationRemoteInputLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
 import javax.inject.Inject
 
 /** Logger class for [RemoteInputController]. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index 9f9fba4..90eb630 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -23,11 +23,11 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.widget.MessagingImageMessage;
 import com.android.internal.widget.MessagingPropertyAnimator;
 import com.android.internal.widget.ViewClippingUtil;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.ViewTransformationHelper;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
index dc16274..16f1a45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
@@ -22,7 +22,7 @@
 import android.view.View
 import android.view.ViewGroup
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 
 /**
  * Class to help with fading of view groups without fading one subview
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
index 68d1319..39d0833 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.notification.collection.coalescer
 
 import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import javax.inject.Inject
 
 class GroupCoalescerLogger @Inject constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
index 2919def..79c63e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
@@ -1,8 +1,8 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.statusbar.notification.row.NotificationGuts
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index 32c3c66..e17ce5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -3,8 +3,8 @@
 import android.util.Log
 
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "HeadsUpCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
index 6503a64..1f8ec34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator
 
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.UnseenNotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "KeyguardCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
index 9558f47..6271d38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
index d804454..1f4861a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "ShadeEventCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 4adc90a..f13ff68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.statusbar.notification.collection.listbuilder
 
 import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.INFO
-import com.android.systemui.plugins.log.LogLevel.WARNING
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.WARNING
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index 911a2d0..20de785 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -21,12 +21,12 @@
 import android.service.notification.NotificationListenerService.RankingMap
 import android.service.notification.StatusBarNotification
 import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.ERROR
-import com.android.systemui.plugins.log.LogLevel.INFO
-import com.android.systemui.plugins.log.LogLevel.WARNING
-import com.android.systemui.plugins.log.LogLevel.WTF
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.LogLevel.WTF
 import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason
 import com.android.systemui.statusbar.notification.collection.NotifCollection.FutureDismissal
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
index 9c71e5c..07fd349 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.notification.collection.render
 
 import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
 import com.android.systemui.util.Compile
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index 1e22c2c..a880b71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.notification.collection.render
 
 import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import java.lang.RuntimeException
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
index d4f11fc..0b31265 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
@@ -1,8 +1,8 @@
 package com.android.systemui.statusbar.notification.interruption
 
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 115e0502..5bac2a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -19,10 +19,10 @@
 import android.util.Log
 
 import com.android.systemui.log.dagger.NotificationInterruptLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.INFO
-import com.android.systemui.plugins.log.LogLevel.WARNING
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.WARNING
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import com.android.systemui.util.Compile
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
index 10197a3..fe03b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.notification.logging
 
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
 import com.android.systemui.log.dagger.NotificationRenderLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.INFO
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.notification.stack.NotificationSection
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 66d4c3a9..8af488e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -31,12 +31,12 @@
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
 import com.android.settingslib.Utils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -733,12 +733,16 @@
         super.dump(pw, args);
         if (DUMP_VERBOSE) {
             DumpUtilsKt.withIncreasedIndent(pw, () -> {
-                pw.println("mBackgroundNormal: " + mBackgroundNormal);
-                if (mBackgroundNormal != null) {
-                    DumpUtilsKt.withIncreasedIndent(pw, () -> {
-                        mBackgroundNormal.dump(pw, args);
-                    });
-                }
+                dumpBackgroundView(pw, args);
+            });
+        }
+    }
+
+    protected void dumpBackgroundView(IndentingPrintWriter pw, String[] args) {
+        pw.println("Background View: " + mBackgroundNormal);
+        if (DUMP_VERBOSE && mBackgroundNormal != null) {
+            DumpUtilsKt.withIncreasedIndent(pw, () -> {
+                mBackgroundNormal.dump(pw, args);
             });
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 5978133..ba8a5f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -64,6 +64,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -72,7 +73,6 @@
 import com.android.internal.widget.CachingIconView;
 import com.android.internal.widget.CallLayout;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -3593,6 +3593,7 @@
         // Skip super call; dump viewState ourselves
         pw.println("Notification: " + mEntry.getKey());
         DumpUtilsKt.withIncreasedIndent(pw, () -> {
+            pw.println(this);
             pw.print("visibility: " + getVisibility());
             pw.print(", alpha: " + getAlpha());
             pw.print(", translation: " + getTranslation());
@@ -3612,6 +3613,7 @@
                 pw.println("no viewState!!!");
             }
             pw.println(getRoundableState().debugString());
+            dumpBackgroundView(pw, args);
 
             int transientViewCount = mChildrenContainer == null
                     ? 0 : mChildrenContainer.getTransientViewCount();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index b56bae1..7a2bee9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -45,10 +45,10 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 5edff5f..f0e15c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -32,14 +32,15 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.Roundable;
 import com.android.systemui.statusbar.notification.RoundableState;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.util.Compile;
 import com.android.systemui.util.DumpUtilsKt;
 
 import java.io.PrintWriter;
@@ -52,7 +53,8 @@
 public abstract class ExpandableView extends FrameLayout implements Dumpable, Roundable {
     private static final String TAG = "ExpandableView";
     /** whether the dump() for this class should include verbose details */
-    protected static final boolean DUMP_VERBOSE = false;
+    protected static final boolean DUMP_VERBOSE =
+            Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
 
     private RoundableState mRoundableState = null;
     protected OnHeightChangedListener mOnHeightChangedListener;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
index 46fef3f..45be0b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.notification.row
 
 import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index da8d2d5..647505c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static com.android.systemui.util.ColorUtilKt.hexColorString;
+
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Canvas;
@@ -27,6 +29,9 @@
 import android.util.AttributeSet;
 import android.view.View;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.util.ArrayUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
@@ -44,6 +49,7 @@
     private int mClipTopAmount;
     private int mClipBottomAmount;
     private int mTintColor;
+    @Nullable private Integer mRippleColor;
     private final float[] mCornerRadii = new float[8];
     private boolean mBottomIsRounded;
     private boolean mBottomAmountClips = true;
@@ -127,6 +133,7 @@
             unscheduleDrawable(mBackground);
         }
         mBackground = background;
+        mRippleColor = null;
         mBackground.mutate();
         if (mBackground != null) {
             mBackground.setCallback(this);
@@ -215,6 +222,9 @@
         if (mBackground instanceof RippleDrawable) {
             RippleDrawable ripple = (RippleDrawable) mBackground;
             ripple.setColor(ColorStateList.valueOf(color));
+            mRippleColor = color;
+        } else {
+            mRippleColor = null;
         }
     }
 
@@ -290,7 +300,7 @@
     }
 
     @Override
-    public void dump(PrintWriter pw, String[] args) {
+    public void dump(PrintWriter pw, @NonNull String[] args) {
         pw.println("mDontModifyCorners: " + mDontModifyCorners);
         pw.println("mClipTopAmount: " + mClipTopAmount);
         pw.println("mClipBottomAmount: " + mClipBottomAmount);
@@ -299,5 +309,8 @@
         pw.println("mBottomAmountClips: " + mBottomAmountClips);
         pw.println("mActualWidth: " + mActualWidth);
         pw.println("mActualHeight: " + mActualHeight);
+        pw.println("mTintColor: " + hexColorString(mTintColor));
+        pw.println("mRippleColor: " + hexColorString(mRippleColor));
+        pw.println("mBackground: " + mBackground);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 4522e41..b4bfded 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -966,7 +966,7 @@
 
             @Override
             public ApplicationInfo getApplicationInfo() {
-                ApplicationInfo applicationInfo = super.getApplicationInfo();
+                ApplicationInfo applicationInfo = new ApplicationInfo(super.getApplicationInfo());
                 applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_RTL;
                 return applicationInfo;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index f21db0b..9bc0333 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -25,7 +25,7 @@
 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
 
-import static com.android.systemui.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
index 596bdc0..047db20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
@@ -33,9 +33,9 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 8a50f2f..99a7755 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -20,7 +20,7 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 
-import static com.android.systemui.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index bafc474..5a129fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -39,9 +39,9 @@
 import android.widget.FrameLayout;
 import android.widget.FrameLayout.LayoutParams;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
index ce11be3..c3dd92a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
@@ -17,9 +17,9 @@
 
 package com.android.systemui.statusbar.notification.row
 
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index 5f4c926..d5d7f75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -45,11 +45,11 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
index 8a5d29a..684a276 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.notification.row
 
 import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 5aaf63f..b24cec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -23,8 +23,8 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.animation.Interpolators;
 
 import java.util.function.Consumer;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 2c59c2e..ef5e86f06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -34,10 +34,10 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.widget.CachingIconView;
 import com.android.internal.widget.NotificationExpandButton;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.ViewTransformationHelper;
 import com.android.systemui.statusbar.notification.CustomInterpolatorTransformation;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
index 7f3381c..d73bbeb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
@@ -22,8 +22,8 @@
 import android.animation.ValueAnimator;
 import android.view.View;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
index 0b435fe..9a33a94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
@@ -26,7 +26,7 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
-import com.android.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
 import com.android.systemui.statusbar.notification.ShadeViewRefactor;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
index b61c55e..f953187 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
@@ -18,8 +18,8 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.dagger.NotificationSectionLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "NotifSections"
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 edff877..cf051fb 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
@@ -75,6 +75,7 @@
 import android.widget.OverScroller;
 import android.widget.ScrollView;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -86,7 +87,6 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ActivityStarter;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 64dd6dc..5b0ec1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -1,9 +1,9 @@
 package com.android.systemui.statusbar.notification.stack
 
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.INFO
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index ee72943..f07dd00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -22,9 +22,9 @@
 import android.util.Property;
 import android.view.View;
 
+import com.android.app.animation.Interpolators;
 import com.android.keyguard.KeyguardSliceView;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.shared.clocks.AnimatableClockView;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarIconView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index f5de678..cca84b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -1,8 +1,8 @@
 package com.android.systemui.statusbar.notification.stack
 
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
index d07da38..f4605be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
@@ -26,9 +26,9 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.NotificationFadeAware.FadeOptimizedNotification;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
index 9dce332..4590712 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -33,9 +33,9 @@
 import android.util.Log;
 import android.view.View;
 
+import com.android.app.animation.Interpolators;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 89c3946..618120d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -37,6 +37,7 @@
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -56,10 +57,12 @@
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
 /**
  * Implementation of DozeHost for SystemUI.
  */
-@SysUISingleton
+@ExperimentalCoroutinesApi @SysUISingleton
 public final class DozeServiceHost implements DozeHost {
     private static final String TAG = "DozeServiceHost";
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
@@ -89,6 +92,7 @@
     private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     private final AuthController mAuthController;
     private final NotificationIconAreaController mNotificationIconAreaController;
+    private final BurnInInteractor mBurnInInteractor;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private ShadeViewController mNotificationPanel;
     private View mAmbientIndicationContainer;
@@ -110,7 +114,8 @@
             NotificationShadeWindowController notificationShadeWindowController,
             NotificationWakeUpCoordinator notificationWakeUpCoordinator,
             AuthController authController,
-            NotificationIconAreaController notificationIconAreaController) {
+            NotificationIconAreaController notificationIconAreaController,
+            BurnInInteractor burnInInteractor) {
         super();
         mDozeLog = dozeLog;
         mPowerManager = powerManager;
@@ -129,6 +134,7 @@
         mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
         mAuthController = authController;
         mNotificationIconAreaController = notificationIconAreaController;
+        mBurnInInteractor = burnInInteractor;
         mHeadsUpManagerPhone.addListener(mOnHeadsUpChangedListener);
     }
 
@@ -304,6 +310,7 @@
         if (mAmbientIndicationContainer instanceof DozeReceiver) {
             ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
         }
+        mBurnInInteractor.dozeTimeTick();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 90a6d0f..561bd91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -23,10 +23,10 @@
 import android.content.res.Resources;
 import android.util.MathUtils;
 
+import com.android.app.animation.Interpolators;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
 
@@ -239,7 +239,11 @@
         }
     }
 
-    private int getExpandedPreferredClockY() {
+    /**
+     * give the static topMargin, used for lockscreen clocks to get the initial translationY
+     * to do counter translation
+     */
+    public int getExpandedPreferredClockY() {
         if (mIsSplitShade) {
             return mSplitShadeTargetTopMargin;
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index 9d30cb4..61c1cc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -30,9 +30,9 @@
 
 import androidx.annotation.StyleRes;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.keyguard.KeyguardIndication;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 13566ef..720eeba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -43,9 +43,9 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.app.animation.Interpolators;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index e835c5ce..4d716c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -37,15 +37,15 @@
 import androidx.core.animation.AnimatorListenerAdapter;
 import androidx.core.animation.ValueAnimator;
 
+import com.android.app.animation.InterpolatorsAndroidX;
 import com.android.keyguard.CarrierTextController;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.R;
-import com.android.systemui.animation.InterpolatorsAndroidX;
 import com.android.systemui.battery.BatteryMeterViewController;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.log.LogLevel;
+import com.android.systemui.log.LogLevel;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeViewStateProvider;
 import com.android.systemui.statusbar.CommandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
index 4839fe6..5c357d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
@@ -20,8 +20,8 @@
 import android.view.View
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent
 import com.android.systemui.log.dagger.LSShadeTransitionLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 534edb9..a058bf8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -25,6 +25,7 @@
 import android.annotation.ColorInt;
 import android.content.Context;
 import android.graphics.Rect;
+import android.util.Log;
 import android.view.InsetsFlags;
 import android.view.ViewDebug;
 import android.view.WindowInsetsController.Appearance;
@@ -35,13 +36,17 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.util.Compile;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Date;
 
 import javax.inject.Inject;
 
@@ -51,10 +56,14 @@
 @SysUISingleton
 public class LightBarController implements BatteryController.BatteryStateChangeCallback, Dumpable {
 
+    private static final String TAG = "LightBarController";
+    private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
+
     private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
 
     private final SysuiDarkIconDispatcher mStatusBarIconController;
     private final BatteryController mBatteryController;
+    private final boolean mUseNewLightBarLogic;
     private BiometricUnlockController mBiometricUnlockController;
 
     private LightBarTransitionsController mNavigationBarController;
@@ -67,13 +76,17 @@
     private final int mLightIconColor;
 
     /**
-     * Whether the navigation bar should be light factoring in already how much alpha the scrim has
+     * Whether the navigation bar should be light factoring in already how much alpha the scrim has.
+     * "Light" refers to the background color of the navigation bar, so when this is true,
+     * it's referring to a state where the navigation bar icons are tinted dark.
      */
     private boolean mNavigationLight;
 
     /**
-     * Whether the flags indicate that a light status bar is requested. This doesn't factor in the
-     * scrim alpha yet.
+     * Whether the flags indicate that a light navigation bar is requested.
+     * "Light" refers to the background color of the navigation bar, so when this is true,
+     * it's referring to a state where the navigation bar icons would be tinted dark.
+     * This doesn't factor in the scrim alpha yet.
      */
     private boolean mHasLightNavigationBar;
 
@@ -82,22 +95,34 @@
      * {@link #mNavigationLight} {@code false}.
      */
     private boolean mForceDarkForScrim;
+    /**
+     * {@code true} if {@link #mHasLightNavigationBar} should be ignored and forcefully make
+     * {@link #mNavigationLight} {@code true}.
+     */
+    private boolean mForceLightForScrim;
 
     private boolean mQsCustomizing;
+    private boolean mQsExpanded;
+    private boolean mGlobalActionsVisible;
 
     private boolean mDirectReplying;
     private boolean mNavbarColorManagedByIme;
 
     private boolean mIsCustomizingForBackNav;
 
+    private String mLastSetScrimStateLog;
+    private String mLastNavigationBarAppearanceChangedLog;
+
     @Inject
     public LightBarController(
             Context ctx,
             DarkIconDispatcher darkIconDispatcher,
             BatteryController batteryController,
             NavigationModeController navModeController,
+            FeatureFlags featureFlags,
             DumpManager dumpManager,
             DisplayTracker displayTracker) {
+        mUseNewLightBarLogic = featureFlags.isEnabled(Flags.NEW_LIGHT_BAR_LOGIC);
         mDarkIconColor = ctx.getColor(R.color.dark_mode_icon_color_single_tone);
         mLightIconColor = ctx.getColor(R.color.light_mode_icon_color_single_tone);
         mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
@@ -159,9 +184,42 @@
             final boolean last = mNavigationLight;
             mHasLightNavigationBar = isLight(appearance, navigationBarMode,
                     APPEARANCE_LIGHT_NAVIGATION_BARS);
-            mNavigationLight = mHasLightNavigationBar
-                    && (mDirectReplying && mNavbarColorManagedByIme || !mForceDarkForScrim)
-                    && !mQsCustomizing;
+            if (mUseNewLightBarLogic) {
+                final boolean ignoreScrimForce = mDirectReplying && mNavbarColorManagedByIme;
+                final boolean darkForScrim = mForceDarkForScrim && !ignoreScrimForce;
+                final boolean lightForScrim = mForceLightForScrim && !ignoreScrimForce;
+                final boolean darkForQs = mQsCustomizing || mQsExpanded || mGlobalActionsVisible;
+                mNavigationLight =
+                        ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForQs;
+                mLastNavigationBarAppearanceChangedLog = "onNavigationBarAppearanceChanged()"
+                        + " appearance=" + appearance
+                        + " nbModeChanged=" + nbModeChanged
+                        + " navigationBarMode=" + navigationBarMode
+                        + " navbarColorManagedByIme=" + navbarColorManagedByIme
+                        + " mHasLightNavigationBar=" + mHasLightNavigationBar
+                        + " ignoreScrimForce=" + ignoreScrimForce
+                        + " darkForScrim=" + darkForScrim
+                        + " lightForScrim=" + lightForScrim
+                        + " darkForQs=" + darkForQs
+                        + " mNavigationLight=" + mNavigationLight
+                        + " last=" + last
+                        + " timestamp=" + new Date();
+                if (DEBUG) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
+            } else {
+                mNavigationLight = mHasLightNavigationBar
+                        && (mDirectReplying && mNavbarColorManagedByIme || !mForceDarkForScrim)
+                        && !mQsCustomizing;
+                mLastNavigationBarAppearanceChangedLog = "onNavigationBarAppearanceChanged()"
+                        + " appearance=" + appearance
+                        + " nbModeChanged=" + nbModeChanged
+                        + " navigationBarMode=" + navigationBarMode
+                        + " navbarColorManagedByIme=" + navbarColorManagedByIme
+                        + " mHasLightNavigationBar=" + mHasLightNavigationBar
+                        + " mNavigationLight=" + mNavigationLight
+                        + " last=" + last
+                        + " timestamp=" + new Date();
+                if (DEBUG) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
+            }
             if (mNavigationLight != last) {
                 updateNavigation();
             }
@@ -188,6 +246,20 @@
         reevaluate();
     }
 
+    /** Set if Quick Settings is fully expanded, which affects notification scrim visibility */
+    public void setQsExpanded(boolean expanded) {
+        if (mQsExpanded == expanded) return;
+        mQsExpanded = expanded;
+        reevaluate();
+    }
+
+    /** Set if Global Actions dialog is visible, which requires dark mode (light buttons) */
+    public void setGlobalActionsVisible(boolean visible) {
+        if (mGlobalActionsVisible == visible) return;
+        mGlobalActionsVisible = visible;
+        reevaluate();
+    }
+
     /**
      * Controls the light status bar temporarily for back navigation.
      * @param appearance the custmoized appearance.
@@ -225,16 +297,52 @@
 
     public void setScrimState(ScrimState scrimState, float scrimBehindAlpha,
             GradientColors scrimInFrontColor) {
-        boolean forceDarkForScrimLast = mForceDarkForScrim;
-        // For BOUNCER/BOUNCER_SCRIMMED cases, we assume that alpha is always below threshold.
-        // This enables IMEs to control the navigation bar color.
-        // For other cases, scrim should be able to veto the light navigation bar.
-        mForceDarkForScrim = scrimState != ScrimState.BOUNCER
-                && scrimState != ScrimState.BOUNCER_SCRIMMED
-                && scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD
-                && !scrimInFrontColor.supportsDarkText();
-        if (mHasLightNavigationBar && (mForceDarkForScrim != forceDarkForScrimLast)) {
-            reevaluate();
+        if (mUseNewLightBarLogic) {
+            boolean forceDarkForScrimLast = mForceDarkForScrim;
+            boolean forceLightForScrimLast = mForceLightForScrim;
+            final boolean forceForScrim =
+                    scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD;
+            final boolean scrimColorIsLight = scrimInFrontColor.supportsDarkText();
+
+            mForceDarkForScrim = forceForScrim && !scrimColorIsLight;
+            mForceLightForScrim = forceForScrim && scrimColorIsLight;
+            if (mHasLightNavigationBar) {
+                if (mForceDarkForScrim != forceDarkForScrimLast) reevaluate();
+            } else {
+                if (mForceLightForScrim != forceLightForScrimLast) reevaluate();
+            }
+            mLastSetScrimStateLog = "setScrimState()"
+                    + " scrimState=" + scrimState
+                    + " scrimBehindAlpha=" + scrimBehindAlpha
+                    + " scrimInFrontColor=" + scrimInFrontColor
+                    + " forceForScrim=" + forceForScrim
+                    + " scrimColorIsLight=" + scrimColorIsLight
+                    + " mHasLightNavigationBar=" + mHasLightNavigationBar
+                    + " mForceDarkForScrim=" + mForceDarkForScrim
+                    + " mForceLightForScrim=" + mForceLightForScrim
+                    + " timestamp=" + new Date();
+            if (DEBUG) Log.d(TAG, mLastSetScrimStateLog);
+        } else {
+            boolean forceDarkForScrimLast = mForceDarkForScrim;
+            // For BOUNCER/BOUNCER_SCRIMMED cases, we assume that alpha is always below threshold.
+            // This enables IMEs to control the navigation bar color.
+            // For other cases, scrim should be able to veto the light navigation bar.
+            // NOTE: this was also wrong for S and has been removed in the new logic.
+            mForceDarkForScrim = scrimState != ScrimState.BOUNCER
+                    && scrimState != ScrimState.BOUNCER_SCRIMMED
+                    && scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD
+                    && !scrimInFrontColor.supportsDarkText();
+            if (mHasLightNavigationBar && (mForceDarkForScrim != forceDarkForScrimLast)) {
+                reevaluate();
+            }
+            mLastSetScrimStateLog = "setScrimState()"
+                    + " scrimState=" + scrimState
+                    + " scrimBehindAlpha=" + scrimBehindAlpha
+                    + " scrimInFrontColor=" + scrimInFrontColor
+                    + " mHasLightNavigationBar=" + mHasLightNavigationBar
+                    + " mForceDarkForScrim=" + mForceDarkForScrim
+                    + " timestamp=" + new Date();
+            if (DEBUG) Log.d(TAG, mLastSetScrimStateLog);
         }
     }
 
@@ -309,16 +417,24 @@
             pw.print(mAppearanceRegions[i].toString()); pw.print(" isLight="); pw.println(isLight);
         }
 
-        pw.print(" mNavigationLight="); pw.print(mNavigationLight);
+        pw.print(" mNavigationLight="); pw.println(mNavigationLight);
         pw.print(" mHasLightNavigationBar="); pw.println(mHasLightNavigationBar);
-
+        pw.println();
         pw.print(" mStatusBarMode="); pw.print(mStatusBarMode);
         pw.print(" mNavigationBarMode="); pw.println(mNavigationBarMode);
-
-        pw.print(" mForceDarkForScrim="); pw.print(mForceDarkForScrim);
-        pw.print(" mQsCustomizing="); pw.print(mQsCustomizing);
+        pw.println();
+        pw.print(" mForceDarkForScrim="); pw.println(mForceDarkForScrim);
+        pw.print(" mForceLightForScrim="); pw.println(mForceLightForScrim);
+        pw.println();
+        pw.print(" mQsCustomizing="); pw.println(mQsCustomizing);
+        pw.print(" mQsExpanded="); pw.println(mQsExpanded);
+        pw.print(" mGlobalActionsVisible="); pw.println(mGlobalActionsVisible);
         pw.print(" mDirectReplying="); pw.println(mDirectReplying);
         pw.print(" mNavbarColorManagedByIme="); pw.println(mNavbarColorManagedByIme);
+        pw.println();
+        pw.println(" Recent Calculation Logs:");
+        pw.print("   "); pw.println(mLastSetScrimStateLog);
+        pw.print("   "); pw.println(mLastNavigationBarAppearanceChangedLog);
 
         pw.println();
 
@@ -344,6 +460,7 @@
         private final DarkIconDispatcher mDarkIconDispatcher;
         private final BatteryController mBatteryController;
         private final NavigationModeController mNavModeController;
+        private final FeatureFlags mFeatureFlags;
         private final DumpManager mDumpManager;
         private final DisplayTracker mDisplayTracker;
 
@@ -352,12 +469,14 @@
                 DarkIconDispatcher darkIconDispatcher,
                 BatteryController batteryController,
                 NavigationModeController navModeController,
+                FeatureFlags featureFlags,
                 DumpManager dumpManager,
                 DisplayTracker displayTracker) {
 
             mDarkIconDispatcher = darkIconDispatcher;
             mBatteryController = batteryController;
             mNavModeController = navModeController;
+            mFeatureFlags = featureFlags;
             mDumpManager = dumpManager;
             mDisplayTracker = displayTracker;
         }
@@ -365,7 +484,7 @@
         /** Create an {@link LightBarController} */
         public LightBarController create(Context context) {
             return new LightBarController(context, mDarkIconDispatcher, mBatteryController,
-                    mNavModeController, mDumpManager, mDisplayTracker);
+                    mNavModeController, mFeatureFlags, mDumpManager, mDisplayTracker);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index 6bf5443..7bc4fc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -24,21 +24,21 @@
 import android.util.MathUtils;
 import android.util.TimeUtils;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.Dumpable;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
 import dagger.assisted.AssistedInject;
 
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+
 /**
  * Class to control all aspects about light bar changes.
  */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index cc4f901..46a2457 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -31,9 +31,9 @@
 import android.util.SparseArray;
 import android.view.ViewTreeObserver.OnPreDrawListener;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.graphics.ColorUtils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 55dc188..560ea8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -15,11 +15,11 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.collection.ArrayMap;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 006a029d..bef422c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -36,10 +36,10 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.collection.ArrayMap;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.stack.AnimationFilter;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 51c56a0..fdb772b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -42,6 +42,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.graphics.ColorUtils;
+import com.android.internal.util.ContrastColorUtil;
 import com.android.internal.util.function.TriConsumer;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -249,6 +250,7 @@
     private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
     private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
     private final FeatureFlags mFeatureFlags;
+    private final boolean mUseNewLightBarLogic;
     private Consumer<Integer> mScrimVisibleListener;
     private boolean mBlankScreen;
     private boolean mScreenBlankingCallbackCalled;
@@ -306,6 +308,7 @@
         mScrimStateListener = lightBarController::setScrimState;
         mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
         mFeatureFlags = featureFlags;
+        mUseNewLightBarLogic = featureFlags.isEnabled(Flags.NEW_LIGHT_BAR_LOGIC);
         mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
 
         mKeyguardStateController = keyguardStateController;
@@ -1159,7 +1162,13 @@
         if (mClipsQsScrim && mQsBottomVisible) {
             alpha = mNotificationsAlpha;
         }
-        mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors());
+        if (mUseNewLightBarLogic) {
+            mScrimStateListener.accept(mState, alpha, mColors);
+        } else {
+            // NOTE: This wasn't wrong, but it implied that each scrim might have different colors,
+            //  when in fact they all share the same GradientColors instance, which we own.
+            mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors());
+        }
     }
 
     private void dispatchScrimsVisible() {
@@ -1487,8 +1496,15 @@
         int accent = Utils.getColorAccent(mScrimBehind.getContext()).getDefaultColor();
         mColors.setMainColor(background);
         mColors.setSecondaryColor(accent);
-        mColors.setSupportsDarkText(
-                ColorUtils.calculateContrast(mColors.getMainColor(), Color.WHITE) > 4.5);
+        if (mUseNewLightBarLogic) {
+            final boolean isBackgroundLight = !ContrastColorUtil.isColorDark(background);
+            mColors.setSupportsDarkText(isBackgroundLight);
+        } else {
+            // NOTE: This was totally backward, but LightBarController was flipping it back.
+            // There may be other consumers of this which would struggle though
+            mColors.setSupportsDarkText(
+                    ColorUtils.calculateContrast(mColors.getMainColor(), Color.WHITE) > 4.5);
+        }
         mNeedsDrawableColorUpdate = true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
index 5e5317d7..07a6d0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
@@ -29,7 +29,7 @@
 import android.view.animation.AnimationUtils;
 import android.widget.Button;
 
-import com.android.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
 
 public class SettingsButton extends AlphaOptimizedImageView {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
index 1f0b96a..12f023b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -18,11 +18,11 @@
 
 import android.app.PendingIntent
 import com.android.systemui.log.dagger.NotifInteractionLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.ERROR
-import com.android.systemui.plugins.log.LogLevel.INFO
-import com.android.systemui.plugins.log.LogLevel.WARNING
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.WARNING
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 8fa803e..cdf6652 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -15,7 +15,7 @@
 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF
 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
 import com.android.systemui.DejankUtils
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.WakefulnessLifecycle
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 453dd1b..5af8932 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -14,11 +14,7 @@
 
 package com.android.systemui.statusbar.phone.fragment;
 
-import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
-import static android.app.StatusBarManager.DISABLE_CLOCK;
-import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS;
-import static android.app.StatusBarManager.DISABLE_ONGOING_CALL_CHIP;
-import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
+
 
 import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
 import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT;
@@ -44,10 +40,10 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.core.animation.Animator;
 
+import com.android.app.animation.Interpolators;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
@@ -112,8 +108,13 @@
     private View mClockView;
     private View mOngoingCallChip;
     private View mNotificationIconAreaInner;
-    private int mDisabled1;
-    private int mDisabled2;
+    // Visibilities come in from external system callers via disable flags, but we also sometimes
+    // modify the visibilities internally. We need to store both so that we don't accidentally
+    // propagate our internally modified flags for too long.
+    private StatusBarVisibilityModel mLastSystemVisibility =
+            StatusBarVisibilityModel.createDefaultModel();
+    private StatusBarVisibilityModel mLastModifiedVisibility =
+            StatusBarVisibilityModel.createDefaultModel();
     private DarkIconManager mDarkIconManager;
     private final StatusBarFragmentComponent.Factory mStatusBarFragmentComponentFactory;
     private final CommandQueue mCommandQueue;
@@ -141,7 +142,7 @@
     private final OngoingCallListener mOngoingCallListener = new OngoingCallListener() {
         @Override
         public void onOngoingCallStateChanged(boolean animate) {
-            disable(getContext().getDisplayId(), mDisabled1, mDisabled2, animate);
+            updateStatusBarVisibilities(animate);
         }
     };
     private OperatorNameViewController mOperatorNameViewController;
@@ -388,8 +389,7 @@
         }
         notificationIconArea.addView(mNotificationIconAreaInner);
 
-        // #disable should have already been called, so use the disable values to set visibility.
-        updateNotificationIconAreaAndCallChip(mDisabled1, false);
+        updateNotificationIconAreaAndCallChip(/* animate= */ false);
     }
 
     /**
@@ -408,49 +408,50 @@
         if (displayId != getContext().getDisplayId()) {
             return;
         }
+        mCollapsedStatusBarFragmentLogger
+                .logDisableFlagChange(new DisableState(state1, state2));
+        mLastSystemVisibility =
+                StatusBarVisibilityModel.createModelFromFlags(state1, state2);
+        updateStatusBarVisibilities(animate);
+    }
 
-        int state1BeforeAdjustment = state1;
-        state1 = adjustDisableFlags(state1);
+    private void updateStatusBarVisibilities(boolean animate) {
+        StatusBarVisibilityModel previousModel = mLastModifiedVisibility;
+        StatusBarVisibilityModel newModel = calculateInternalModel(mLastSystemVisibility);
+        mCollapsedStatusBarFragmentLogger.logVisibilityModel(newModel);
+        mLastModifiedVisibility = newModel;
 
-        mCollapsedStatusBarFragmentLogger.logDisableFlagChange(
-                /* new= */ new DisableState(state1BeforeAdjustment, state2),
-                /* newAfterLocalModification= */ new DisableState(state1, state2));
-
-        final int old1 = mDisabled1;
-        final int diff1 = state1 ^ old1;
-        final int old2 = mDisabled2;
-        final int diff2 = state2 ^ old2;
-        mDisabled1 = state1;
-        mDisabled2 = state2;
-        if ((diff1 & DISABLE_SYSTEM_INFO) != 0 || ((diff2 & DISABLE2_SYSTEM_ICONS) != 0)) {
-            if ((state1 & DISABLE_SYSTEM_INFO) != 0 || ((state2 & DISABLE2_SYSTEM_ICONS) != 0)) {
-                hideEndSideContent(animate);
-                hideOperatorName(animate);
-            } else {
+        if (newModel.getShowSystemInfo() != previousModel.getShowSystemInfo()) {
+            if (newModel.getShowSystemInfo()) {
                 showEndSideContent(animate);
                 showOperatorName(animate);
+            } else {
+                hideEndSideContent(animate);
+                hideOperatorName(animate);
             }
         }
 
         // The ongoing call chip and notification icon visibilities are intertwined, so update both
         // if either change.
-        if (((diff1 & DISABLE_ONGOING_CALL_CHIP) != 0)
-                || ((diff1 & DISABLE_NOTIFICATION_ICONS) != 0)) {
-            updateNotificationIconAreaAndCallChip(state1, animate);
+        if (newModel.getShowNotificationIcons() != previousModel.getShowNotificationIcons()
+                || newModel.getShowOngoingCallChip() != previousModel.getShowOngoingCallChip()) {
+            updateNotificationIconAreaAndCallChip(animate);
         }
 
         // The clock may have already been hidden, but we might want to shift its
         // visibility to GONE from INVISIBLE or vice versa
-        if ((diff1 & DISABLE_CLOCK) != 0 || mClockView.getVisibility() != clockHiddenMode()) {
-            if ((state1 & DISABLE_CLOCK) != 0) {
-                hideClock(animate);
-            } else {
+        if (newModel.getShowClock() != previousModel.getShowClock()
+                || mClockView.getVisibility() != clockHiddenMode()) {
+            if (newModel.getShowClock()) {
                 showClock(animate);
+            } else {
+                hideClock(animate);
             }
         }
     }
 
-    protected int adjustDisableFlags(int state) {
+    private StatusBarVisibilityModel calculateInternalModel(
+            StatusBarVisibilityModel externalModel) {
         boolean headsUpVisible =
                 mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible();
 
@@ -459,34 +460,31 @@
                 && shouldHideNotificationIcons()
                 && !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD
                         && headsUpVisible)) {
-            state |= DISABLE_NOTIFICATION_ICONS;
-            state |= DISABLE_SYSTEM_INFO;
-            state |= DISABLE_CLOCK;
+            // Hide everything
+            return new StatusBarVisibilityModel(
+                    /* showClock= */ false,
+                    /* showNotificationIcons= */ false,
+                    /* showOngoingCallChip= */ false,
+                    /* showSystemInfo= */ false);
         }
 
-        if (mOngoingCallController.hasOngoingCall()) {
-            state &= ~DISABLE_ONGOING_CALL_CHIP;
-        } else {
-            state |= DISABLE_ONGOING_CALL_CHIP;
-        }
-
-        if (headsUpVisible) {
-            // Disable everything on the left side of the status bar, since the app name for the
-            // heads up notification appears there instead.
-            state |= DISABLE_CLOCK;
-            state |= DISABLE_ONGOING_CALL_CHIP;
-        }
-
-        return state;
+        boolean showClock = externalModel.getShowClock() && !headsUpVisible;
+        boolean showOngoingCallChip = mOngoingCallController.hasOngoingCall() && !headsUpVisible;
+        return new StatusBarVisibilityModel(
+                showClock,
+                externalModel.getShowNotificationIcons(),
+                showOngoingCallChip,
+                externalModel.getShowSystemInfo());
     }
 
     /**
      * Updates the visibility of the notification icon area and ongoing call chip based on disabled1
      * state.
      */
-    private void updateNotificationIconAreaAndCallChip(int state1, boolean animate) {
-        boolean disableNotifications = (state1 & DISABLE_NOTIFICATION_ICONS) != 0;
-        boolean hasOngoingCall = (state1 & DISABLE_ONGOING_CALL_CHIP) == 0;
+    private void updateNotificationIconAreaAndCallChip(boolean animate) {
+        StatusBarVisibilityModel visibilityModel = mLastModifiedVisibility;
+        boolean disableNotifications = !visibilityModel.getShowNotificationIcons();
+        boolean hasOngoingCall = visibilityModel.getShowOngoingCallChip();
 
         // Hide notifications if the disable flag is set or we have an ongoing call.
         if (disableNotifications || hasOngoingCall) {
@@ -683,7 +681,7 @@
 
     @Override
     public void onDozingChanged(boolean isDozing) {
-        disable(getContext().getDisplayId(), mDisabled1, mDisabled2, false /* animate */);
+        updateStatusBarVisibilities(/* animate= */ false);
     }
 
     @Nullable
@@ -698,10 +696,6 @@
         return mSystemEventAnimator.onSystemEventAnimationFinish(hasPersistentDot);
     }
 
-    private boolean isSystemIconAreaDisabled() {
-        return (mDisabled1 & DISABLE_SYSTEM_INFO) != 0 || (mDisabled2 & DISABLE2_SYSTEM_ICONS) != 0;
-    }
-
     private void updateStatusBarLocation(int left, int right) {
         int leftMargin = left - mStatusBar.getLeft();
         int rightMargin = mStatusBar.getRight() - right;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
index d64bc58..8c19fb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.phone.fragment
 
 import com.android.systemui.log.dagger.CollapsedSbFragmentLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
 import javax.inject.Inject
 
@@ -37,7 +37,6 @@
      */
     fun logDisableFlagChange(
         new: DisableFlagsLogger.DisableState,
-        newAfterLocalModification: DisableFlagsLogger.DisableState
     ) {
         buffer.log(
                 TAG,
@@ -45,19 +44,34 @@
                 {
                     int1 = new.disable1
                     int2 = new.disable2
-                    long1 = newAfterLocalModification.disable1.toLong()
-                    long2 = newAfterLocalModification.disable2.toLong()
                 },
                 {
                     disableFlagsLogger.getDisableFlagsString(
                         old = null,
                         new = DisableFlagsLogger.DisableState(int1, int2),
-                        newAfterLocalModification =
-                            DisableFlagsLogger.DisableState(long1.toInt(), long2.toInt())
                     )
                 }
         )
     }
+
+    fun logVisibilityModel(model: StatusBarVisibilityModel) {
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                bool1 = model.showClock
+                bool2 = model.showNotificationIcons
+                bool3 = model.showOngoingCallChip
+                bool4 = model.showSystemInfo
+            },
+            { "New visibilities calculated internally. " +
+                    "showClock=$bool1 " +
+                    "showNotificationIcons=$bool2 " +
+                    "showOngoingCallChip=$bool3 " +
+                    "showSystemInfo=$bool4"
+            }
+        )
+    }
 }
 
 private const val TAG = "CollapsedSbFragment"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModel.kt
new file mode 100644
index 0000000..cf54cb7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModel.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.fragment
+
+import android.app.StatusBarManager.DISABLE2_NONE
+import android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS
+import android.app.StatusBarManager.DISABLE_CLOCK
+import android.app.StatusBarManager.DISABLE_NONE
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS
+import android.app.StatusBarManager.DISABLE_ONGOING_CALL_CHIP
+import android.app.StatusBarManager.DISABLE_SYSTEM_INFO
+
+/** A model for which parts of the status bar should be visible or not visible. */
+data class StatusBarVisibilityModel(
+    val showClock: Boolean,
+    val showNotificationIcons: Boolean,
+    val showOngoingCallChip: Boolean,
+    val showSystemInfo: Boolean,
+) {
+    companion object {
+        /** Creates the default model. */
+        @JvmStatic
+        fun createDefaultModel(): StatusBarVisibilityModel {
+            return createModelFromFlags(DISABLE_NONE, DISABLE2_NONE)
+        }
+
+        /**
+         * Given a set of disabled flags, converts them into the correct visibility statuses.
+         *
+         * See [CommandQueue.Callbacks.disable].
+         */
+        @JvmStatic
+        fun createModelFromFlags(disabled1: Int, disabled2: Int): StatusBarVisibilityModel {
+            return StatusBarVisibilityModel(
+                showClock = (disabled1 and DISABLE_CLOCK) == 0,
+                showNotificationIcons = (disabled1 and DISABLE_NOTIFICATION_ICONS) == 0,
+                // TODO(b/279899176): [CollapsedStatusBarFragment] always overwrites this with the
+                //  value of [OngoingCallController]. Do we need to process the flag here?
+                showOngoingCallChip = (disabled1 and DISABLE_ONGOING_CALL_CHIP) == 0,
+                showSystemInfo =
+                    (disabled1 and DISABLE_SYSTEM_INFO) == 0 &&
+                        (disabled2 and DISABLE2_SYSTEM_ICONS) == 0
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index b3d2461..19c77e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
-import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 68cbbce..b3a1c40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -22,8 +22,8 @@
 import com.android.settingslib.SignalIcon
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.MobileInputLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
index f2f9143..7e0c145 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
@@ -20,8 +20,8 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.MobileViewLog
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
 import java.io.PrintWriter
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
index f67bc8f..507549b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
@@ -19,8 +19,8 @@
 import android.view.View
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.VerboseMobileViewLog
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging
 import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
index 82492ba..051f43f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
@@ -19,8 +19,8 @@
 import android.net.Network
 import android.net.NetworkCapabilities
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.SharedConnectivityInputLog
 import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
index a96e8ff..328d901 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
@@ -18,8 +18,8 @@
 
 import android.net.Network
 import android.net.NetworkCapabilities
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 
 /** Helper object for logs that are shared between wifi and mobile. */
 object LoggerHelper {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt
index 2a02687..058eda4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.pipeline.shared.data.model
 
 import android.net.NetworkCapabilities
-import com.android.systemui.plugins.log.LogMessage
+import com.android.systemui.log.LogMessage
 
 /**
  * A model for all of the current default connections(s).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
index bb0b166..4a9ceac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
@@ -19,8 +19,8 @@
 import android.net.Network
 import android.net.NetworkCapabilities
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.WifiInputLog
 import com.android.systemui.statusbar.pipeline.shared.LoggerHelper
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
index 98cde2a..abedd3220 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
@@ -28,6 +28,11 @@
     void startCasting(CastDevice device);
     void stopCasting(CastDevice device);
 
+    /**
+     * @return whether we have a connected device.
+     */
+    boolean hasConnectedCastDevice();
+
     public interface Callback {
         void onCastDevicesChanged();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index 7290a1a..f7b601b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -217,6 +217,12 @@
         }
     }
 
+    @Override
+    public boolean hasConnectedCastDevice() {
+        return getCastDevices().stream().anyMatch(
+                castDevice -> castDevice.state == CastDevice.STATE_CONNECTED);
+    }
+
     private void setProjection(MediaProjectionInfo projection, boolean started) {
         boolean changed = false;
         final MediaProjectionInfo oldProjection = mProjection;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
index f61f3b7..6ba2a81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
@@ -21,9 +21,9 @@
 import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED
 import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED
 import com.android.internal.R
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.VERBOSE
 import com.android.systemui.log.dagger.DeviceStateAutoRotationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.VERBOSE
 import javax.inject.Inject
 
 class DeviceStateRotationLockSettingControllerLogger
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index df1e80b..06ed1fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.statusbar.policy
 
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.INFO
-import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.VERBOSE
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
index 4dd63be..e1ec94f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
@@ -24,9 +24,9 @@
 
 import androidx.core.graphics.ColorUtils;
 
+import com.android.app.animation.Interpolators;
 import com.android.keyguard.KeyguardConstants;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.qs.tiles.UserDetailItemView;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index 928e011..66b5256 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -31,6 +31,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.app.animation.Interpolators;
 import com.android.keyguard.KeyguardConstants;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -38,7 +39,6 @@
 import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
 import com.android.settingslib.drawable.CircleFramedDrawable;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
index 850a4b4..363b06a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
@@ -21,11 +21,11 @@
 import android.util.Log;
 import android.view.View;
 
+import com.android.app.animation.Interpolators;
 import com.android.keyguard.AlphaOptimizedLinearLayout;
 import com.android.keyguard.KeyguardConstants;
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.settingslib.animation.DisappearAnimationUtils;
-import com.android.systemui.animation.Interpolators;
 
 /**
  * The container for the user switcher on Keyguard.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 403a7e8..e311bad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -73,6 +73,7 @@
 import androidx.core.animation.ObjectAnimator;
 import androidx.core.animation.ValueAnimator;
 
+import com.android.app.animation.InterpolatorsAndroidX;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
 import com.android.internal.logging.UiEvent;
@@ -80,7 +81,6 @@
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.animation.InterpolatorsAndroidX;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
index b563d86..21d0338 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -311,7 +311,7 @@
                             setBounds(0, 0, newIconSize, newIconSize)
                         }
                 // Add the action icon to the Smart Action button.
-                setCompoundDrawables(iconDrawable, null, null, null)
+                setCompoundDrawablesRelative(iconDrawable, null, null, null)
 
                 val onClickListener = View.OnClickListener {
                     onSmartActionClick(entry, smartActions, actionIndex, action)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 9e88ceb..fb6ba85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -588,15 +588,15 @@
     }
 
     /**
-     * Returns the combined width of the left drawable (the action icon) and the padding between the
-     * drawable and the button text.
+     * Returns the combined width of the start drawable (the action icon) and the padding between
+     * the drawable and the button text.
      */
-    private int getLeftCompoundDrawableWidthWithPadding(Button button) {
-        Drawable[] drawables = button.getCompoundDrawables();
-        Drawable leftDrawable = drawables[0];
-        if (leftDrawable == null) return 0;
+    private int getStartCompoundDrawableWidthWithPadding(Button button) {
+        Drawable[] drawables = button.getCompoundDrawablesRelative();
+        Drawable startDrawable = drawables[0];
+        if (startDrawable == null) return 0;
 
-        return leftDrawable.getBounds().width() + button.getCompoundDrawablePadding();
+        return startDrawable.getBounds().width() + button.getCompoundDrawablePadding();
     }
 
     private int squeezeButtonToTextWidth(Button button, int heightMeasureSpec, int textWidth) {
@@ -605,8 +605,8 @@
         // Re-measure the squeezed smart reply button.
         clearLayoutLineCount(button);
         final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(
-                button.getPaddingLeft() + button.getPaddingRight() + textWidth
-                      + getLeftCompoundDrawableWidthWithPadding(button), MeasureSpec.AT_MOST);
+                button.getPaddingStart() + button.getPaddingEnd() + textWidth
+                      + getStartCompoundDrawableWidthWithPadding(button), MeasureSpec.AT_MOST);
         button.measure(widthMeasureSpec, heightMeasureSpec);
         if (button.getLayout() == null) {
             Log.wtf(TAG, "Button layout is null after measure.");
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index 412b315..27aaa68 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -22,7 +22,6 @@
 import android.hardware.BatteryState
 import android.hardware.input.InputManager
 import android.hardware.input.InputSettings
-import android.os.Build
 import android.os.Handler
 import android.util.ArrayMap
 import android.util.Log
@@ -35,6 +34,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.log.DebugLogger.debugLog
 import com.android.systemui.shared.hardware.hasInputDevice
 import com.android.systemui.shared.hardware.isInternalStylusSource
 import java.util.concurrent.CopyOnWriteArrayList
@@ -81,7 +81,7 @@
     fun startListener() {
         handler.post {
             if (hasStarted) return@post
-            logDebug { "Listener has started." }
+            debugLog { "Listener has started." }
 
             hasStarted = true
             isInUsiSession =
@@ -109,7 +109,7 @@
 
         val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
         if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
-        logDebug {
+        debugLog {
             "Stylus InputDevice added: $deviceId ${device.name}, " +
                 "External: ${device.isExternal}"
         }
@@ -134,7 +134,7 @@
 
         val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
         if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
-        logDebug { "Stylus InputDevice changed: $deviceId ${device.name}" }
+        debugLog { "Stylus InputDevice changed: $deviceId ${device.name}" }
 
         val currAddress: String? = device.bluetoothAddress
         val prevAddress: String? = inputDeviceAddressMap[deviceId]
@@ -155,7 +155,7 @@
         if (!hasStarted) return
 
         if (!inputDeviceAddressMap.contains(deviceId)) return
-        logDebug { "Stylus InputDevice removed: $deviceId" }
+        debugLog { "Stylus InputDevice removed: $deviceId" }
 
         unregisterBatteryListener(deviceId)
 
@@ -180,7 +180,7 @@
 
             val isCharging = String(value) == "true"
 
-            logDebug {
+            debugLog {
                 "Charging state metadata changed for device $inputDeviceId " +
                     "${device.address}: $isCharging"
             }
@@ -199,7 +199,7 @@
         handler.post {
             if (!hasStarted) return@post
 
-            logDebug {
+            debugLog {
                 "Battery state changed for $deviceId. " +
                     "batteryState present: ${batteryState.isPresent}, " +
                     "capacity: ${batteryState.capacity}"
@@ -247,7 +247,7 @@
         if (!featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)) return
         if (InputSettings.isStylusEverUsed(context)) return
 
-        logDebug { "Stylus used for the first time." }
+        debugLog { "Stylus used for the first time." }
         InputSettings.setStylusEverUsed(context, true)
         executeStylusCallbacks { cb -> cb.onStylusFirstUsed() }
     }
@@ -264,7 +264,7 @@
         val hasBtConnection = if (inputDeviceBtSessionIdMap.isEmpty()) 0 else 1
 
         if (batteryStateValid && usiSessionId == null) {
-            logDebug { "USI battery newly present, entering new USI session: $deviceId" }
+            debugLog { "USI battery newly present, entering new USI session: $deviceId" }
             usiSessionId = instanceIdSequence.newInstanceId()
             uiEventLogger.logWithInstanceIdAndPosition(
                 StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
@@ -274,7 +274,7 @@
                 hasBtConnection,
             )
         } else if (!batteryStateValid && usiSessionId != null) {
-            logDebug { "USI battery newly absent, exiting USI session: $deviceId" }
+            debugLog { "USI battery newly absent, exiting USI session: $deviceId" }
             uiEventLogger.logWithInstanceIdAndPosition(
                 StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED,
                 0,
@@ -291,7 +291,7 @@
         btAddress: String,
         btConnected: Boolean
     ) {
-        logDebug {
+        debugLog {
             "Bluetooth stylus ${if (btConnected) "connected" else "disconnected"}:" +
                 " $deviceId $btAddress"
         }
@@ -386,9 +386,3 @@
         val TAG = StylusManager::class.simpleName.orEmpty()
     }
 }
-
-private inline fun logDebug(message: () -> String) {
-    if (Build.IS_DEBUGGABLE) {
-        Log.d(StylusManager.TAG, message())
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
index 21b0efa..6eddd9e 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
@@ -26,7 +26,6 @@
 import android.content.IntentFilter
 import android.hardware.BatteryState
 import android.hardware.input.InputManager
-import android.os.Build
 import android.os.Bundle
 import android.os.Handler
 import android.os.UserHandle
@@ -40,6 +39,7 @@
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.DebugLogger.debugLog
 import com.android.systemui.shared.hardware.hasInputDevice
 import com.android.systemui.shared.hardware.isAnyStylusSource
 import com.android.systemui.util.NotificationChannels
@@ -110,7 +110,7 @@
 
             inputDeviceId = deviceId
             batteryCapacity = batteryState.capacity
-            logDebug {
+            debugLog {
                 "Updating notification battery state to $batteryCapacity " +
                     "for InputDevice $deviceId."
             }
@@ -130,14 +130,14 @@
         handler.post updateSuppressed@{
             if (suppressed == suppress) return@updateSuppressed
 
-            logDebug { "Updating notification suppression to $suppress." }
+            debugLog { "Updating notification suppression to $suppress." }
             suppressed = suppress
             refresh()
         }
     }
 
     private fun hideNotification() {
-        logDebug { "Cancelling USI low battery notification." }
+        debugLog { "Cancelling USI low battery notification." }
         instanceId = null
         notificationManager.cancel(USI_NOTIFICATION_ID)
     }
@@ -160,7 +160,7 @@
                 .setAutoCancel(true)
                 .build()
 
-        logDebug { "Show or update USI low battery notification at $batteryCapacity." }
+        debugLog { "Show or update USI low battery notification at $batteryCapacity." }
         logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_SHOWN)
         notificationManager.notify(USI_NOTIFICATION_ID, notification)
     }
@@ -188,12 +188,12 @@
             override fun onReceive(context: Context, intent: Intent) {
                 when (intent.action) {
                     ACTION_DISMISSED_LOW_BATTERY -> {
-                        logDebug { "USI low battery notification dismissed." }
+                        debugLog { "USI low battery notification dismissed." }
                         logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_DISMISSED)
                         updateSuppression(true)
                     }
                     ACTION_CLICKED_LOW_BATTERY -> {
-                        logDebug { "USI low battery notification clicked." }
+                        debugLog { "USI low battery notification clicked." }
                         logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_CLICKED)
                         updateSuppression(true)
                         if (inputDeviceId == null) return
@@ -263,9 +263,3 @@
         @VisibleForTesting const val KEY_SETTINGS_FRAGMENT_ARGS = ":settings:show_fragment_args"
     }
 }
-
-private inline fun logDebug(message: () -> String) {
-    if (Build.IS_DEBUGGABLE) {
-        Log.d(StylusUsiPowerUI.TAG, message())
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index 667e22a..066ac04 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.temporarydisplay
 
 import android.view.View
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 
 /** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */
 open class TemporaryViewLogger<T : TemporaryViewInfo>(
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
index 1612388..46954b5 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
@@ -18,7 +18,7 @@
 
 import android.view.View
 import android.view.ViewGroup
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 import com.android.systemui.animation.ViewHierarchyAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.util.children
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index e819f94..4fbbc89 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -34,10 +34,10 @@
 import androidx.annotation.DimenRes
 import androidx.annotation.IdRes
 import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
 import com.android.internal.widget.CachingIconView
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Text.Companion.loadText
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
index f239428..d55751b 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.temporarydisplay.chipbar
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import com.android.systemui.temporarydisplay.TemporaryViewLogger
 import com.android.systemui.temporarydisplay.dagger.ChipbarLog
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
index b1be404..cae1308 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
@@ -18,9 +18,9 @@
 
 import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.media.taptotransfer.MediaTttFlags
-import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.settings.DisplayTracker
 import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger
 import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler
diff --git a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
index f0fa779..58f2246 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
+++ b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
@@ -51,6 +51,7 @@
                 Pair.create("surface_variant", MDC.surfaceVariant()),
                 Pair.create("on_surface_variant", MDC.onSurfaceVariant()),
                 Pair.create("outline", MDC.outline()),
+                Pair.create("outline_variant", MDC.outlineVariant()),
                 Pair.create("error", MDC.error()),
                 Pair.create("on_error", MDC.onError()),
                 Pair.create("error_container", MDC.errorContainer()),
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 43d15bc..4b73d61 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -659,6 +659,10 @@
                     == mColorScheme.getNeutral1().getS500()
                     && res.getColor(android.R.color.system_neutral2_500, theme)
                     == mColorScheme.getNeutral2().getS500()
+                    && res.getColor(android.R.color.system_outline_variant_dark, theme)
+                    == MaterialDynamicColors.outlineVariant().getArgb(mDynamicSchemeDark)
+                    && res.getColor(android.R.color.system_outline_variant_light, theme)
+                    == MaterialDynamicColors.outlineVariant().getArgb(mDynamicSchemeLight)
                     && res.getColor(android.R.color.system_primary_container_dark, theme)
                     == MaterialDynamicColors.primaryContainer().getArgb(mDynamicSchemeDark)
                     && res.getColor(android.R.color.system_primary_container_light, theme)
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
index fda5114..dfe748a 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.toast
 
 import com.android.systemui.log.dagger.ToastLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogMessage
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogMessage
 import javax.inject.Inject
 
 private const val TAG = "ToastLog"
diff --git a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto
index eb23b9d..d940a6b 100644
--- a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto
+++ b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto
@@ -16,8 +16,6 @@
 
 syntax = "proto2";
 
-import "frameworks/base/libs/WindowManager/Shell/proto/wm_shell_trace.proto";
-
 package com.android.systemui.tracing;
 
 option java_multiple_files = true;
@@ -25,7 +23,6 @@
 message SystemUiTraceProto {
 
     optional EdgeBackGestureHandlerProto edge_back_gesture_handler = 1;
-    optional com.android.wm.shell.WmShellTraceProto wm_shell = 2;
 }
 
 message EdgeBackGestureHandlerProto {
diff --git a/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt b/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt
index 27a53bf..41b3145 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt
@@ -19,6 +19,7 @@
 import android.content.res.TypedArray
 import android.graphics.Color
 import android.view.ContextThemeWrapper
+import androidx.annotation.ColorInt
 
 /** Returns an ARGB color version of [color] at the given [alpha]. */
 fun getColorWithAlpha(color: Int, alpha: Float): Int =
@@ -35,8 +36,11 @@
  * otherwise, returns the color from the private attribute {@param privAttrId}.
  */
 fun getPrivateAttrColorIfUnset(
-    ctw: ContextThemeWrapper, attrArray: TypedArray,
-    attrIndex: Int, defColor: Int, privAttrId: Int
+    ctw: ContextThemeWrapper,
+    attrArray: TypedArray,
+    attrIndex: Int,
+    defColor: Int,
+    privAttrId: Int
 ): Int {
     // If the index is specified, use that value
     var a = attrArray
@@ -51,3 +55,8 @@
     a.recycle()
     return color
 }
+
+/** Returns the color as a HTML hex color (or null) */
+fun hexColorString(@ColorInt color: Int?): String = color
+    ?.let { String.format("#%08x", it) }
+    ?: "null"
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
index 5d80292..db4ab7e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
@@ -19,7 +19,7 @@
 import android.animation.ValueAnimator
 import android.graphics.PointF
 import android.util.MathUtils
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
 
 /**
  * The fraction after which we start fading in when going from a gone widget to a visible one
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLog.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLog.java
index 59cb052..9cebc33 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLog.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLog.java
@@ -18,7 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt
index 951903d..0926800 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.util.wakelock
 
 import android.os.PowerManager
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
 import javax.inject.Inject
 
 class WakeLockLogger @Inject constructor(@WakeLockLog private val buffer: LogBuffer) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/wrapper/DisplayUtilsWrapper.kt b/packages/SystemUI/src/com/android/systemui/util/wrapper/DisplayUtilsWrapper.kt
new file mode 100644
index 0000000..cfca7f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/wrapper/DisplayUtilsWrapper.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.wrapper
+
+import android.util.DisplayUtils
+import android.view.Display
+import javax.inject.Inject
+
+/** Injectable wrapper around `DisplayUtils` functions */
+class DisplayUtilsWrapper @Inject constructor() {
+    fun getPhysicalPixelDisplaySizeRatio(
+        physicalWidth: Int,
+        physicalHeight: Int,
+        currentWidth: Int,
+        currentHeight: Int
+    ): Float {
+        return DisplayUtils.getPhysicalPixelDisplaySizeRatio(
+            physicalWidth,
+            physicalHeight,
+            currentWidth,
+            currentHeight
+        )
+    }
+
+    fun getMaximumResolutionDisplayMode(modes: Array<Display.Mode>?): Display.Mode? {
+        return DisplayUtils.getMaximumResolutionDisplayMode(modes)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 77210b7..91078dc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -109,6 +109,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -119,7 +120,6 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index b3e7cb0..5144d19 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -33,6 +33,7 @@
 import android.graphics.Rect;
 import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
+import android.view.Display;
 import android.view.KeyEvent;
 
 import androidx.annotation.NonNull;
@@ -347,19 +348,20 @@
     void initDesktopMode(DesktopMode desktopMode) {
         desktopMode.addVisibleTasksListener(
                 new DesktopModeTaskRepository.VisibleTasksListener() {
-            @Override
-            public void onVisibilityChanged(boolean hasFreeformTasks) {
-                mSysUiState.setFlag(SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE, hasFreeformTasks)
-                        .commitUpdate(mDisplayTracker.getDefaultDisplayId());
-            }
-        }, mSysUiMainExecutor);
+                    @Override
+                    public void onVisibilityChanged(int displayId, boolean hasFreeformTasks) {
+                        if (displayId == Display.DEFAULT_DISPLAY) {
+                            mSysUiState.setFlag(SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE,
+                                            hasFreeformTasks)
+                                    .commitUpdate(mDisplayTracker.getDefaultDisplayId());
+                        }
+                        // TODO(b/278084491): update sysui state for changes on other displays
+                    }
+                }, mSysUiMainExecutor);
     }
 
     @Override
     public void writeToProto(SystemUiTraceProto proto) {
-        if (proto.wmShell == null) {
-            proto.wmShell = new WmShellTraceProto();
-        }
         // Dump to WMShell proto here
         // TODO: Figure out how we want to synchronize while dumping to proto
     }
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index e2b568c..080be6d 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -196,6 +196,17 @@
             android:exported="false"
             android:permission="com.android.systemui.permission.SELF"
             android:excludeFromRecents="true" />
+
+        <activity
+            android:name="com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity"
+            android:exported="false"
+            android:permission="com.android.systemui.permission.SELF"
+            android:excludeFromRecents="true" >
+            <intent-filter>
+                <action android:name="com.android.systemui.action.MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
     </application>
 
     <instrumentation android:name="android.testing.TestableInstrumentation"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 8f4b320..19d5278 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -35,7 +35,7 @@
 import com.android.systemui.plugins.ClockFaceConfig
 import com.android.systemui.plugins.ClockFaceEvents
 import com.android.systemui.plugins.ClockTickRate
-import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 95db0c0..fb73845 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.log.LogBuffer;
 import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.plugins.ClockEvents;
@@ -52,7 +53,6 @@
 import com.android.systemui.plugins.ClockFaceController;
 import com.android.systemui.plugins.ClockFaceEvents;
 import com.android.systemui.plugins.ClockTickRate;
-import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.clocks.AnimatableClockView;
 import com.android.systemui.shared.clocks.ClockRegistry;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index f4df26d..8a05a37 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -682,8 +682,7 @@
     }
 
     @Test
-    public void testReinflateViewFlipper_asyncBouncerFlagOn() {
-        when(mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER)).thenReturn(true);
+    public void testReinflateViewFlipper() {
         KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedCallback =
                 controller -> {
                 };
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 2962c14..ddd9a08 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -151,7 +151,9 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
 import org.mockito.internal.util.reflection.FieldSetter;
@@ -2737,6 +2739,36 @@
         verifyFingerprintAuthenticateCall();
     }
 
+    @Test
+    public void onTrustChangedCallbacksCalledBeforeOnTrustGrantedForCurrentUserCallback() {
+        // GIVEN device is interactive
+        deviceIsInteractive();
+
+        // GIVEN callback is registered
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        // WHEN onTrustChanged enabled=true
+        mKeyguardUpdateMonitor.onTrustChanged(
+                true /* enabled */,
+                true /* newlyUnlocked */,
+                getCurrentUser() /* userId */,
+                TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
+                null /* trustGrantedMessages */);
+
+        // THEN onTrustChanged is called FIRST
+        final InOrder inOrder = Mockito.inOrder(callback);
+        inOrder.verify(callback).onTrustChanged(eq(getCurrentUser()));
+
+        // AND THEN onTrustGrantedForCurrentUser callback called
+        inOrder.verify(callback).onTrustGrantedForCurrentUser(
+                eq(true) /* dismissKeyguard */,
+                eq(true) /* newlyUnlocked */,
+                eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD)),
+                eq(null) /* message */
+        );
+    }
+
     private void verifyFingerprintAuthenticateNeverCalled() {
         verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
         verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
deleted file mode 100644
index 2c680be..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.animation
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import java.lang.reflect.Modifier
-import junit.framework.Assert.assertEquals
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@SmallTest
-@RunWith(JUnit4::class)
-class InterpolatorsAndroidXTest : SysuiTestCase() {
-
-    @Test
-    fun testInterpolatorsAndInterpolatorsAndroidXPublicMethodsAreEqual() {
-        assertEquals(
-            Interpolators::class.java.getPublicMethods(),
-            InterpolatorsAndroidX::class.java.getPublicMethods()
-        )
-    }
-
-    @Test
-    fun testInterpolatorsAndInterpolatorsAndroidXPublicFieldsAreEqual() {
-        assertEquals(
-            Interpolators::class.java.getPublicFields(),
-            InterpolatorsAndroidX::class.java.getPublicFields()
-        )
-    }
-
-    private fun <T> Class<T>.getPublicMethods() =
-        declaredMethods
-            .filter { Modifier.isPublic(it.modifiers) }
-            .map { it.toString().replace(name, "") }
-            .toSet()
-
-    private fun <T> Class<T>.getPublicFields() =
-        fields.filter { Modifier.isPublic(it.modifiers) }.map { it.name }.toSet()
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
index d7aa6e0..14ad3ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
@@ -39,10 +39,6 @@
 
 import kotlin.math.ceil
 
-private val PAINT = TextPaint().apply {
-    textSize = 32f
-}
-
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class TextAnimatorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt
index 063757a..f6fcd16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt
@@ -49,7 +49,7 @@
 private fun Font.toTypeface() =
         Typeface.CustomFallbackBuilder(FontFamily.Builder(this).build()).build()
 
-private val PAINT = TextPaint().apply {
+internal val PAINT = TextPaint().apply {
     typeface = Font.Builder(VF_FONT).setFontVariationSettings("'wght' 400").build().toTypeface()
     textSize = 32f
 }
@@ -79,7 +79,7 @@
 
     @Before
     fun setup() {
-        typefaceCache = TypefaceVariantCacheImpl()
+        typefaceCache = TypefaceVariantCacheImpl(PAINT.typeface)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index 6ab54a3..da9ceb4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -19,6 +19,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import com.android.app.animation.Interpolators
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index a20875b..a361bbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -40,14 +40,19 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
 import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
 import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
+import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
 import org.junit.After
 import org.junit.Ignore
 import org.junit.Rule
@@ -63,7 +68,6 @@
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
 
-@Ignore("b/279650412")
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
 @SmallTest
@@ -87,13 +91,26 @@
     @Mock
     lateinit var interactionJankMonitor: InteractionJankMonitor
 
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val fakeExecutor = FakeExecutor(FakeSystemClock())
     private val biometricPromptRepository = FakePromptRepository()
+    private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
     private val credentialInteractor = FakeCredentialInteractor()
     private val bpCredentialInteractor = BiometricPromptCredentialInteractor(
         Dispatchers.Main.immediate,
         biometricPromptRepository,
         credentialInteractor
     )
+    private val displayStateInteractor = DisplayStateInteractorImpl(
+        testScope.backgroundScope,
+        mContext,
+        fakeExecutor,
+        rearDisplayStateRepository
+    )
+
+    private val authBiometricFingerprintViewModel = AuthBiometricFingerprintViewModel(
+        displayStateInteractor
+    )
     private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
 
     private var authContainer: TestAuthContainerView? = null
@@ -263,6 +280,7 @@
         assertThat(authContainer!!.parent).isNull()
     }
 
+    @Ignore("b/279650412")
     @Test
     fun testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() {
         val container = initializeFingerprintContainer(
@@ -469,9 +487,10 @@
         lockPatternUtils,
         interactionJankMonitor,
         { bpCredentialInteractor },
+        { authBiometricFingerprintViewModel },
         { credentialViewModel },
         Handler(TestableLooper.get(this).looper),
-        FakeExecutor(FakeSystemClock())
+        fakeExecutor
     ) {
         override fun postOnAnimation(runnable: Runnable) {
             runnable.run()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 4f24b3a..a326cc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -93,6 +93,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
+import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel;
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.CommandQueue;
@@ -172,6 +173,8 @@
     @Mock
     private BiometricPromptCredentialInteractor mBiometricPromptCredentialInteractor;
     @Mock
+    private AuthBiometricFingerprintViewModel mAuthBiometricFingerprintViewModel;
+    @Mock
     private CredentialViewModel mCredentialViewModel;
     @Mock
     private UdfpsUtils mUdfpsUtils;
@@ -995,8 +998,9 @@
                     () -> mSideFpsController, mDisplayManager, mWakefulnessLifecycle,
                     mPanelInteractionDetector, mUserManager, mLockPatternUtils, mUdfpsLogger,
                     mLogContextInteractor, () -> mBiometricPromptCredentialInteractor,
-                    () -> mCredentialViewModel, mInteractionJankMonitor, mHandler,
-                    mBackgroundExecutor, mVibratorHelper, mUdfpsUtils);
+                    () -> mAuthBiometricFingerprintViewModel, () -> mCredentialViewModel,
+                    mInteractionJankMonitor, mHandler, mBackgroundExecutor, mVibratorHelper,
+                    mUdfpsUtils);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index e6334cf..40d9009 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -55,6 +55,9 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.SysuiTestableContext
+import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
@@ -66,7 +69,9 @@
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.TestScope
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -90,6 +95,8 @@
 private const val DISPLAY_ID = 2
 private const val SENSOR_ID = 1
 
+private const val REAR_DISPLAY_MODE_DEVICE_STATE = 3
+
 @SmallTest
 @RoboPilotTest
 @RunWith(AndroidJUnit4::class)
@@ -112,7 +119,12 @@
 
     private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
     private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+    private lateinit var displayStateInteractor: DisplayStateInteractor
+
     private val executor = FakeExecutor(FakeSystemClock())
+    private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
+    private val testScope = TestScope(StandardTestDispatcher())
+
     private lateinit var overlayController: ISidefpsController
     private lateinit var sideFpsController: SideFpsController
 
@@ -142,6 +154,13 @@
                 FakeDeviceEntryFingerprintAuthRepository(),
                 FakeSystemClock(),
             )
+        displayStateInteractor =
+            DisplayStateInteractorImpl(
+                testScope.backgroundScope,
+                context,
+                executor,
+                rearDisplayStateRepository
+            )
 
         context.addMockSystemService(DisplayManager::class.java, displayManager)
         context.addMockSystemService(WindowManager::class.java, windowManager)
@@ -168,6 +187,7 @@
         isReverseDefaultRotation: Boolean = false,
         initInfo: DisplayInfo.() -> Unit = {},
         windowInsets: WindowInsets = insetsForSmallNavbar(),
+        inRearDisplayMode: Boolean = false,
         block: () -> Unit
     ) {
         this.deviceConfig = deviceConfig
@@ -228,6 +248,13 @@
             isReverseDefaultRotation
         )
 
+        val rearDisplayDeviceStates =
+            if (inRearDisplayMode) intArrayOf(REAR_DISPLAY_MODE_DEVICE_STATE) else intArrayOf()
+        sideFpsControllerContext.orCreateTestableResources.addOverride(
+            com.android.internal.R.array.config_rearDisplayDeviceStates,
+            rearDisplayDeviceStates
+        )
+
         sideFpsController =
             SideFpsController(
                 sideFpsControllerContext,
@@ -237,12 +264,14 @@
                 activityTaskManager,
                 overviewProxyService,
                 displayManager,
+                displayStateInteractor,
                 executor,
                 handler,
                 alternateBouncerInteractor,
                 TestCoroutineScope(),
-                dumpManager,
+                dumpManager
             )
+        rearDisplayStateRepository.setIsInRearDisplayMode(inRearDisplayMode)
 
         overlayController =
             ArgumentCaptor.forClass(ISidefpsController::class.java)
@@ -584,10 +613,62 @@
             verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true)
         }
 
+    @Test
+    fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_0() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_0 },
+            inRearDisplayMode = true,
+        ) {
+            verifySfpsIndicator_notAdded_InRearDisplayMode()
+        }
+
+    @Test
+    fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_90() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_90 },
+            inRearDisplayMode = true,
+        ) {
+            verifySfpsIndicator_notAdded_InRearDisplayMode()
+        }
+
+    @Test
+    fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_180 },
+            inRearDisplayMode = true,
+        ) {
+            verifySfpsIndicator_notAdded_InRearDisplayMode()
+        }
+
+    @Test
+    fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_270() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_270 },
+            inRearDisplayMode = true,
+        ) {
+            verifySfpsIndicator_notAdded_InRearDisplayMode()
+        }
+
     private fun verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible: Boolean) {
         sideFpsController.overlayOffsets = sensorLocation
     }
 
+    private fun verifySfpsIndicator_notAdded_InRearDisplayMode() {
+        sideFpsController.overlayOffsets = sensorLocation
+        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+        executor.runAllReady()
+
+        verify(windowManager, never()).addView(any(), any())
+    }
+
     fun alternateBouncerVisibility_showAndHideSideFpsUI() = testWithDisplay {
         // WHEN alternate bouncer is visible
         keyguardBouncerRepository.setAlternateVisible(true)
@@ -624,7 +705,7 @@
      * in other rotations have been omitted.
      */
     @Test
-    fun verifiesIndicatorPlacementForXAlignedSensor_0() {
+    fun verifiesIndicatorPlacementForXAlignedSensor_0() =
         testWithDisplay(
             deviceConfig = DeviceConfig.X_ALIGNED,
             isReverseDefaultRotation = false,
@@ -641,7 +722,6 @@
             assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
             assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
         }
-    }
 
     /**
      * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
@@ -650,7 +730,7 @@
      * correctly, tests for indicator placement in other rotations have been omitted.
      */
     @Test
-    fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() {
+    fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() =
         testWithDisplay(
             deviceConfig = DeviceConfig.X_ALIGNED,
             isReverseDefaultRotation = true,
@@ -667,7 +747,6 @@
             assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
             assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
         }
-    }
 
     /**
      * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 2747e83..8a62ea0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -25,7 +25,6 @@
 import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
 import android.hardware.fingerprint.FingerprintManager
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import android.testing.TestableLooper.RunWithLooper
 import android.view.LayoutInflater
 import android.view.MotionEvent
@@ -35,6 +34,7 @@
 import android.view.View
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.settingslib.udfps.UdfpsOverlayParams
@@ -69,8 +69,8 @@
 import org.mockito.Mock
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
 
 private const val REQUEST_ID = 2L
 
@@ -340,4 +340,22 @@
             assertThat(lp.height).isEqualTo(overlayParams.sensorBounds.height())
         }
     }
+
+    @Test
+    fun fullScreenOverlayWithNewTouchDetectionEnabled() = withRotation(ROTATION_0) {
+        withReason(REASON_AUTH_KEYGUARD) {
+            whenever(featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true)
+
+            controllerOverlay.show(udfpsController, overlayParams)
+            verify(windowManager).addView(
+                    eq(controllerOverlay.overlayView),
+                    layoutParamsCaptor.capture()
+            )
+
+            // Layout params should use natural display width and height
+            val lp = layoutParamsCaptor.value
+            assertThat(lp.width).isEqualTo(overlayParams.naturalDisplayWidth)
+            assertThat(lp.height).isEqualTo(overlayParams.naturalDisplayHeight)
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index da71188..5536d83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -1388,7 +1388,7 @@
     }
 
     @Test
-    public void onTouch_withNewTouchDetection_doNotPilferWhenPullingUpBouncer()
+    public void onTouch_withNewTouchDetection_doNotProcessTouchWhenPullingUpBouncer()
             throws RemoteException {
         final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
                 0L);
@@ -1427,8 +1427,10 @@
         mBiometricExecutor.runAllReady();
         moveEvent.recycle();
 
-        // THEN the touch is NOT pilfered
-        verify(mInputManager, never()).pilferPointers(any());
+        // THEN the touch is NOT processed
+        verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(),
+                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(),
+                anyBoolean());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 8bf32cf..8f6017c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -21,22 +21,23 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
+import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.RoboPilotTest
 import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.time.SystemClock
@@ -94,7 +95,8 @@
                 mock(DismissCallbackRegistry::class.java),
                 context,
                 mKeyguardUpdateMonitor,
-                mock(KeyguardBypassController::class.java),
+                mock(TrustRepository::class.java),
+                FakeFeatureFlags(),
             )
         mAlternateBouncerInteractor =
             AlternateBouncerInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepositoryTest.kt
new file mode 100644
index 0000000..dfe8d36
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepositoryTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.hardware.devicestate.DeviceStateManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository
+import com.android.systemui.biometrics.data.repository.RearDisplayStateRepositoryImpl
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+private const val NORMAL_DISPLAY_MODE_DEVICE_STATE = 2
+private const val REAR_DISPLAY_MODE_DEVICE_STATE = 3
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class RearDisplayStateRepositoryTest : SysuiTestCase() {
+    @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
+    @Mock private lateinit var deviceStateManager: DeviceStateManager
+    private lateinit var underTest: RearDisplayStateRepository
+
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val fakeExecutor = FakeExecutor(FakeSystemClock())
+
+    @Captor
+    private lateinit var callbackCaptor: ArgumentCaptor<DeviceStateManager.DeviceStateCallback>
+
+    @Before
+    fun setUp() {
+        val rearDisplayDeviceStates = intArrayOf(REAR_DISPLAY_MODE_DEVICE_STATE)
+        mContext.orCreateTestableResources.addOverride(
+            com.android.internal.R.array.config_rearDisplayDeviceStates,
+            rearDisplayDeviceStates
+        )
+
+        underTest =
+            RearDisplayStateRepositoryImpl(
+                testScope.backgroundScope,
+                mContext,
+                deviceStateManager,
+                fakeExecutor
+            )
+    }
+
+    @Test
+    fun updatesIsInRearDisplayMode_whenRearDisplayStateChanges() =
+        testScope.runTest {
+            val isInRearDisplayMode = collectLastValue(underTest.isInRearDisplayMode)
+            runCurrent()
+
+            val callback = deviceStateManager.captureCallback()
+
+            callback.onStateChanged(NORMAL_DISPLAY_MODE_DEVICE_STATE)
+            assertThat(isInRearDisplayMode()).isFalse()
+
+            callback.onStateChanged(REAR_DISPLAY_MODE_DEVICE_STATE)
+            assertThat(isInRearDisplayMode()).isTrue()
+        }
+}
+
+private fun DeviceStateManager.captureCallback() =
+    withArgCaptor<DeviceStateManager.DeviceStateCallback> {
+        verify(this@captureCallback).registerCallback(any(), capture())
+    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
new file mode 100644
index 0000000..2217c5c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
@@ -0,0 +1,84 @@
+package com.android.systemui.biometrics.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
+import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class DisplayStateInteractorImplTest : SysuiTestCase() {
+
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+    private val fakeExecutor = FakeExecutor(FakeSystemClock())
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
+
+    @Mock private lateinit var screenSizeFoldProvider: ScreenSizeFoldProvider
+    private lateinit var interactor: DisplayStateInteractorImpl
+
+    @Before
+    fun setup() {
+        interactor =
+            DisplayStateInteractorImpl(
+                testScope.backgroundScope,
+                mContext,
+                fakeExecutor,
+                rearDisplayStateRepository
+            )
+        interactor.setScreenSizeFoldProvider(screenSizeFoldProvider)
+    }
+
+    @Test
+    fun isInRearDisplayModeChanges() =
+        testScope.runTest {
+            val isInRearDisplayMode = collectLastValue(interactor.isInRearDisplayMode)
+
+            rearDisplayStateRepository.setIsInRearDisplayMode(false)
+            assertThat(isInRearDisplayMode()).isFalse()
+
+            rearDisplayStateRepository.setIsInRearDisplayMode(true)
+            assertThat(isInRearDisplayMode()).isTrue()
+        }
+
+    @Test
+    fun isFoldedChanges() =
+        testScope.runTest {
+            val isFolded = collectLastValue(interactor.isFolded)
+            runCurrent()
+            val callback = screenSizeFoldProvider.captureCallback()
+
+            callback.onFoldUpdated(isFolded = true)
+            assertThat(isFolded()).isTrue()
+
+            callback.onFoldUpdated(isFolded = false)
+            assertThat(isFolded()).isFalse()
+        }
+}
+
+private fun FoldProvider.captureCallback() =
+    withArgCaptor<FoldProvider.FoldCallback> {
+        verify(this@captureCallback).registerCallback(capture(), any())
+    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModelTest.kt
new file mode 100644
index 0000000..0c210e5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModelTest.kt
@@ -0,0 +1,69 @@
+package com.android.systemui.biometrics.ui.viewmodel
+
+import android.content.res.Configuration
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class AuthBiometricFingerprintViewModelTest : SysuiTestCase() {
+
+    private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val fakeExecutor = FakeExecutor(FakeSystemClock())
+
+    private lateinit var interactor: DisplayStateInteractor
+    private lateinit var viewModel: AuthBiometricFingerprintViewModel
+
+    @Before
+    fun setup() {
+        interactor =
+            DisplayStateInteractorImpl(
+                testScope.backgroundScope,
+                mContext,
+                fakeExecutor,
+                rearDisplayStateRepository
+            )
+        viewModel = AuthBiometricFingerprintViewModel(interactor)
+    }
+
+    @Test
+    fun iconUpdates_onConfigurationChanged() {
+        testScope.runTest {
+            runCurrent()
+            val testConfig = Configuration()
+            val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1
+            val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1
+            val currentIcon = collectLastValue(viewModel.iconAsset)
+
+            testConfig.smallestScreenWidthDp = folded
+            viewModel.onConfigurationChanged(testConfig)
+            val foldedIcon = currentIcon()
+
+            testConfig.smallestScreenWidthDp = unfolded
+            viewModel.onConfigurationChanged(testConfig)
+            val unfoldedIcon = currentIcon()
+
+            assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
+        }
+    }
+}
+
+internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
new file mode 100644
index 0000000..21516d49
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.clipboardoverlay
+
+import android.content.ContentResolver
+import android.content.Context
+import android.net.Uri
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.whenever
+import java.io.IOException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class ClipboardImageLoaderTest : SysuiTestCase() {
+    @Mock private lateinit var mockContext: Context
+
+    @Mock private lateinit var mockContentResolver: ContentResolver
+
+    private lateinit var clipboardImageLoader: ClipboardImageLoader
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun test_imageLoadSuccess() = runTest {
+        val testDispatcher = StandardTestDispatcher(this.testScheduler)
+        clipboardImageLoader =
+            ClipboardImageLoader(mockContext, testDispatcher, CoroutineScope(testDispatcher))
+        val testUri = Uri.parse("testUri")
+        whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
+        whenever(mockContext.resources).thenReturn(context.resources)
+
+        clipboardImageLoader.load(testUri)
+
+        verify(mockContentResolver).loadThumbnail(eq(testUri), any(), any())
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    @Throws(IOException::class)
+    fun test_imageLoadFailure() = runTest {
+        val testDispatcher = StandardTestDispatcher(this.testScheduler)
+        clipboardImageLoader =
+            ClipboardImageLoader(mockContext, testDispatcher, CoroutineScope(testDispatcher))
+        val testUri = Uri.parse("testUri")
+        whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
+        whenever(mockContext.resources).thenReturn(context.resources)
+
+        val res = clipboardImageLoader.load(testUri)
+
+        verify(mockContentResolver).loadThumbnail(eq(testUri), any(), any())
+        assertNull(res)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index fe5fa1f..39fb7b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -25,6 +25,7 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_EXPANDED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
+import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -90,6 +91,8 @@
     @Mock
     private ClipboardOverlayUtils mClipboardUtils;
     @Mock
+    private ClipboardImageLoader mClipboardImageLoader;
+    @Mock
     private UiEventLogger mUiEventLogger;
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
     private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@@ -120,6 +123,7 @@
         mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
                 new ClipData.Item("Test Item"));
 
+        mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, true); // turned off for legacy tests
 
         mOverlayController = new ClipboardOverlayController(
                 mContext,
@@ -131,6 +135,7 @@
                 mFeatureFlags,
                 mClipboardUtils,
                 mExecutor,
+                mClipboardImageLoader,
                 mUiEventLogger);
         verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
         mCallbacks = mOverlayCallbacksCaptor.getValue();
@@ -142,6 +147,69 @@
     }
 
     @Test
+    public void test_setClipData_invalidImageData_legacy() {
+        ClipData clipData = new ClipData("", new String[]{"image/png"},
+                new ClipData.Item(Uri.parse("")));
+        mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+
+        mOverlayController.setClipData(clipData, "");
+
+        verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+        verify(mClipboardOverlayView, times(1)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_nonImageUri_legacy() {
+        ClipData clipData = new ClipData("", new String[]{"resource/png"},
+                new ClipData.Item(Uri.parse("")));
+        mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+
+        mOverlayController.setClipData(clipData, "");
+
+        verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+        verify(mClipboardOverlayView, times(1)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_textData_legacy() {
+        mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+        mOverlayController.setClipData(mSampleClipData, "abc");
+
+        verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHOWN_EXPANDED, 0, "abc");
+        verify(mClipboardOverlayView, times(1)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_sensitiveTextData_legacy() {
+        mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+        ClipDescription description = mSampleClipData.getDescription();
+        PersistableBundle b = new PersistableBundle();
+        b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
+        description.setExtras(b);
+        ClipData data = new ClipData(description, mSampleClipData.getItemAt(0));
+        mOverlayController.setClipData(data, "");
+
+        verify(mClipboardOverlayView, times(1)).showTextPreview("••••••", true);
+        verify(mClipboardOverlayView, times(1)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_repeatedCalls_legacy() {
+        when(mAnimator.isRunning()).thenReturn(true);
+        mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+
+        mOverlayController.setClipData(mSampleClipData, "");
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
     public void test_setClipData_invalidImageData() {
         ClipData clipData = new ClipData("", new String[]{"image/png"},
                 new ClipData.Item(Uri.parse("")));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt
new file mode 100644
index 0000000..a308c8e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.common.ui.data.repository
+
+import android.content.res.Configuration
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.wrapper.DisplayUtilsWrapper
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ConfigurationRepositoryImplTest : SysuiTestCase() {
+    private var displaySizeRatio = 0f
+    @Mock private lateinit var configurationController: ConfigurationController
+    @Mock private lateinit var displayUtils: DisplayUtilsWrapper
+
+    private lateinit var testScope: TestScope
+    private lateinit var underTest: ConfigurationRepositoryImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        setPhysicalPixelDisplaySizeRatio(displaySizeRatio)
+
+        testScope = TestScope()
+        underTest =
+            ConfigurationRepositoryImpl(
+                configurationController,
+                context,
+                testScope.backgroundScope,
+                displayUtils,
+            )
+    }
+
+    @Test
+    fun onAnyConfigurationChange_updatesOnUiModeChanged() =
+        testScope.runTest {
+            val lastAnyConfigurationChange by collectLastValue(underTest.onAnyConfigurationChange)
+            assertThat(lastAnyConfigurationChange).isNull()
+
+            val configurationCallback = withArgCaptor {
+                verify(configurationController).addCallback(capture())
+            }
+
+            configurationCallback.onUiModeChanged()
+            runCurrent()
+            assertThat(lastAnyConfigurationChange).isNotNull()
+        }
+
+    @Test
+    fun onAnyConfigurationChange_updatesOnThemeChanged() =
+        testScope.runTest {
+            val lastAnyConfigurationChange by collectLastValue(underTest.onAnyConfigurationChange)
+            assertThat(lastAnyConfigurationChange).isNull()
+
+            val configurationCallback = withArgCaptor {
+                verify(configurationController).addCallback(capture())
+            }
+
+            configurationCallback.onThemeChanged()
+            runCurrent()
+            assertThat(lastAnyConfigurationChange).isNotNull()
+        }
+
+    @Test
+    fun onAnyConfigurationChange_updatesOnConfigChanged() =
+        testScope.runTest {
+            val lastAnyConfigurationChange by collectLastValue(underTest.onAnyConfigurationChange)
+            assertThat(lastAnyConfigurationChange).isNull()
+
+            val configurationCallback = withArgCaptor {
+                verify(configurationController).addCallback(capture())
+            }
+
+            configurationCallback.onConfigChanged(mock(Configuration::class.java))
+            runCurrent()
+            assertThat(lastAnyConfigurationChange).isNotNull()
+        }
+
+    @Test
+    fun onResolutionScale_updatesOnConfigurationChange() =
+        testScope.runTest {
+            val scaleForResolution by collectLastValue(underTest.scaleForResolution)
+            assertThat(scaleForResolution).isEqualTo(displaySizeRatio)
+
+            val configurationCallback = withArgCaptor {
+                verify(configurationController).addCallback(capture())
+            }
+
+            setPhysicalPixelDisplaySizeRatio(2f)
+            configurationCallback.onConfigChanged(mock(Configuration::class.java))
+            assertThat(scaleForResolution).isEqualTo(displaySizeRatio)
+
+            setPhysicalPixelDisplaySizeRatio(.21f)
+            configurationCallback.onConfigChanged(mock(Configuration::class.java))
+            assertThat(scaleForResolution).isEqualTo(displaySizeRatio)
+        }
+
+    @Test
+    fun onResolutionScale_nullMaxResolution() =
+        testScope.runTest {
+            val scaleForResolution by collectLastValue(underTest.scaleForResolution)
+            runCurrent()
+
+            givenNullMaxResolutionDisplayMode()
+            val configurationCallback = withArgCaptor {
+                verify(configurationController).addCallback(capture())
+            }
+            configurationCallback.onConfigChanged(mock(Configuration::class.java))
+            assertThat(scaleForResolution).isEqualTo(1f)
+        }
+
+    @Test
+    fun getResolutionScale_nullMaxResolutionDisplayMode() {
+        givenNullMaxResolutionDisplayMode()
+        assertThat(underTest.getResolutionScale()).isEqualTo(1f)
+    }
+
+    @Test
+    fun getResolutionScale_infiniteDisplayRatios() {
+        setPhysicalPixelDisplaySizeRatio(Float.POSITIVE_INFINITY)
+        assertThat(underTest.getResolutionScale()).isEqualTo(1f)
+    }
+
+    @Test
+    fun getResolutionScale_differentDisplayRatios() {
+        setPhysicalPixelDisplaySizeRatio(.5f)
+        assertThat(underTest.getResolutionScale()).isEqualTo(displaySizeRatio)
+
+        setPhysicalPixelDisplaySizeRatio(.283f)
+        assertThat(underTest.getResolutionScale()).isEqualTo(displaySizeRatio)
+
+        setPhysicalPixelDisplaySizeRatio(3.58f)
+        assertThat(underTest.getResolutionScale()).isEqualTo(displaySizeRatio)
+
+        setPhysicalPixelDisplaySizeRatio(0f)
+        assertThat(underTest.getResolutionScale()).isEqualTo(displaySizeRatio)
+
+        setPhysicalPixelDisplaySizeRatio(1f)
+        assertThat(underTest.getResolutionScale()).isEqualTo(displaySizeRatio)
+    }
+
+    private fun givenNullMaxResolutionDisplayMode() {
+        whenever(displayUtils.getMaximumResolutionDisplayMode(any())).thenReturn(null)
+    }
+
+    private fun setPhysicalPixelDisplaySizeRatio(ratio: Float) {
+        displaySizeRatio = ratio
+        whenever(displayUtils.getMaximumResolutionDisplayMode(any()))
+            .thenReturn(Display.Mode(0, 0, 0, 90f))
+        whenever(
+                displayUtils.getPhysicalPixelDisplaySizeRatio(
+                    anyInt(),
+                    anyInt(),
+                    anyInt(),
+                    anyInt()
+                )
+            )
+            .thenReturn(ratio)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
new file mode 100644
index 0000000..b2a1668
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.common.ui.data.repository
+
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.receiveAsFlow
+
+class FakeConfigurationRepository : ConfigurationRepository {
+    private val onAnyConfigurationChangeChannel = Channel<Unit>()
+    override val onAnyConfigurationChange: Flow<Unit> =
+        onAnyConfigurationChangeChannel.receiveAsFlow()
+
+    private val _scaleForResolution = MutableStateFlow(1f)
+    override val scaleForResolution: Flow<Float> = _scaleForResolution.asStateFlow()
+
+    suspend fun onAnyConfigurationChange() {
+        onAnyConfigurationChangeChannel.send(Unit)
+    }
+
+    fun setScaleForResolution(scale: Float) {
+        _scaleForResolution.value = scale
+    }
+
+    override fun getResolutionScale(): Float {
+        return _scaleForResolution.value
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
index ef1061f..07cb5d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
@@ -42,6 +42,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import kotlinx.coroutines.CoroutineScope;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class AssistantAttentionConditionTest extends SysuiTestCase {
@@ -51,6 +53,8 @@
     AssistUtils mAssistUtils;
     @Mock
     DreamOverlayStateController mDreamOverlayStateController;
+    @Mock
+    CoroutineScope mScope;
 
     private AssistantAttentionCondition mAssistantAttentionCondition;
 
@@ -59,7 +63,7 @@
         MockitoAnnotations.initMocks(this);
 
         mAssistantAttentionCondition =
-                new AssistantAttentionCondition(mDreamOverlayStateController, mAssistUtils);
+                new AssistantAttentionCondition(mScope, mDreamOverlayStateController, mAssistUtils);
         // Adding a callback also starts the condition.
         mAssistantAttentionCondition.addCallback(mCallback);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
index e1c54976..68c7965 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
@@ -25,7 +25,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.DreamManager;
-import android.content.Context;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
@@ -42,13 +41,12 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import kotlinx.coroutines.CoroutineScope;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class DreamConditionTest extends SysuiTestCase {
     @Mock
-    Context mContext;
-
-    @Mock
     Condition.Callback mCallback;
 
     @Mock
@@ -57,6 +55,9 @@
     @Mock
     KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
+    @Mock
+    CoroutineScope mScope;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -68,7 +69,8 @@
     @Test
     public void testInitialDreamingState() {
         when(mDreamManager.isDreaming()).thenReturn(true);
-        final DreamCondition condition = new DreamCondition(mDreamManager, mKeyguardUpdateMonitor);
+        final DreamCondition condition = new DreamCondition(mScope, mDreamManager,
+                mKeyguardUpdateMonitor);
         condition.addCallback(mCallback);
 
         verify(mCallback).onConditionChanged(eq(condition));
@@ -81,7 +83,8 @@
     @Test
     public void testInitialNonDreamingState() {
         when(mDreamManager.isDreaming()).thenReturn(false);
-        final DreamCondition condition = new DreamCondition(mDreamManager, mKeyguardUpdateMonitor);
+        final DreamCondition condition = new DreamCondition(mScope, mDreamManager,
+                mKeyguardUpdateMonitor);
         condition.addCallback(mCallback);
 
         verify(mCallback, never()).onConditionChanged(eq(condition));
@@ -96,7 +99,8 @@
         final ArgumentCaptor<KeyguardUpdateMonitorCallback> callbackCaptor =
                 ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
         when(mDreamManager.isDreaming()).thenReturn(true);
-        final DreamCondition condition = new DreamCondition(mDreamManager, mKeyguardUpdateMonitor);
+        final DreamCondition condition = new DreamCondition(mScope, mDreamManager,
+                mKeyguardUpdateMonitor);
         condition.addCallback(mCallback);
         verify(mKeyguardUpdateMonitor).registerCallback(callbackCaptor.capture());
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
index 19135d0..e8cbdf3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.ProtoDumpable
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
index 0c5a74c..02555cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
@@ -19,7 +19,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.Dumpable
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.util.mockito.any
 import java.io.PrintWriter
 import org.junit.Before
@@ -60,16 +60,14 @@
 
         // WHEN a dumpable is dumped explicitly
         val args = arrayOf<String>()
-        dumpManager.dumpTarget("dumpable2", pw, arrayOf(), tailLength = 0)
+        dumpManager.dumpTarget("dumpable2", pw, args, tailLength = 0)
 
         // THEN only the requested one has their dump() method called
-        verify(dumpable1, never())
-            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+        verify(dumpable1, never()).dump(any(), any())
         verify(dumpable2).dump(pw, args)
-        verify(dumpable3, never())
-            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
-        verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
-        verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+        verify(dumpable3, never()).dump(any(), any())
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2, never()).dump(any(), anyInt())
     }
 
     @Test
@@ -82,17 +80,15 @@
         dumpManager.registerBuffer("buffer2", buffer2)
 
         // WHEN a buffer is dumped explicitly
-        dumpManager.dumpTarget("buffer1", pw, arrayOf(), tailLength = 14)
+        val args = arrayOf<String>()
+        dumpManager.dumpTarget("buffer1", pw, args, tailLength = 14)
 
         // THEN only the requested one has their dump() method called
-        verify(dumpable1, never())
-            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
-        verify(dumpable2, never())
-            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
-        verify(dumpable2, never())
-            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2, never()).dump(any(), any())
+        verify(dumpable3, never()).dump(any(), any())
         verify(buffer1).dump(pw, tailLength = 14)
-        verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+        verify(buffer2, never()).dump(any(), anyInt())
     }
 
     @Test
@@ -109,6 +105,122 @@
     }
 
     @Test
+    fun testDumpTarget_selectsShortestNamedDumpable() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("first-dumpable", dumpable1)
+        dumpManager.registerCriticalDumpable("scnd-dumpable", dumpable2)
+        dumpManager.registerCriticalDumpable("third-dumpable", dumpable3)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf<String>()
+        dumpManager.dumpTarget("dumpable", pw, args, tailLength = 0)
+
+        // THEN the matching dumpable with the shorter name is dumped
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2).dump(pw, args)
+        verify(dumpable3, never()).dump(any(), any())
+    }
+
+    @Test
+    fun testDumpTarget_selectsShortestNamedBuffer() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerBuffer("first-buffer", buffer1)
+        dumpManager.registerBuffer("scnd-buffer", buffer2)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf<String>()
+        dumpManager.dumpTarget("buffer", pw, args, tailLength = 14)
+
+        // THEN the matching buffer with the shorter name is dumped
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2).dump(pw, tailLength = 14)
+    }
+
+    @Test
+    fun testDumpTarget_selectsShortestNamedMatch_dumpable() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("big-buffer1", buffer1)
+        dumpManager.registerBuffer("big-buffer2", buffer2)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf<String>()
+        dumpManager.dumpTarget("2", pw, args, tailLength = 14)
+
+        // THEN the matching dumpable with the shorter name is dumped
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2).dump(pw, args)
+        verify(dumpable3, never()).dump(any(), any())
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2, never()).dump(any(), anyInt())
+    }
+
+    @Test
+    fun testDumpTarget_selectsShortestNamedMatch_buffer() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf<String>()
+        dumpManager.dumpTarget("2", pw, args, tailLength = 14)
+
+        // THEN the matching buffer with the shorter name is dumped
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2, never()).dump(any(), any())
+        verify(dumpable3, never()).dump(any(), any())
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2).dump(pw, tailLength = 14)
+    }
+
+    @Test
+    fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_dumpable() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("d1x", dumpable1)
+        dumpManager.registerCriticalDumpable("d2x", dumpable2)
+        dumpManager.registerCriticalDumpable("a3x", dumpable3)
+        dumpManager.registerBuffer("ab1x", buffer1)
+        dumpManager.registerBuffer("b2x", buffer2)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf<String>()
+        dumpManager.dumpTarget("x", pw, args, tailLength = 14)
+
+        // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2, never()).dump(any(), any())
+        verify(dumpable3).dump(pw, args)
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2, never()).dump(any(), anyInt())
+    }
+
+    @Test
+    fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_buffer() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("d1x", dumpable1)
+        dumpManager.registerCriticalDumpable("d2x", dumpable2)
+        dumpManager.registerCriticalDumpable("az1x", dumpable3)
+        dumpManager.registerBuffer("b1x", buffer1)
+        dumpManager.registerBuffer("b2x", buffer2)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf<String>()
+        dumpManager.dumpTarget("x", pw, args, tailLength = 14)
+
+        // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2, never()).dump(any(), any())
+        verify(dumpable3, never()).dump(any(), any())
+        verify(buffer1).dump(pw, tailLength = 14)
+        verify(buffer2, never()).dump(any(), anyInt())
+    }
+
+    @Test
     fun testDumpDumpables() {
         // GIVEN a variety of registered dumpables and buffers
         dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
@@ -125,8 +237,8 @@
         verify(dumpable1).dump(pw, args)
         verify(dumpable2).dump(pw, args)
         verify(dumpable3).dump(pw, args)
-        verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
-        verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2, never()).dump(any(), anyInt())
     }
 
     @Test
@@ -142,12 +254,9 @@
         dumpManager.dumpBuffers(pw, tailLength = 1)
 
         // THEN all buffers are dumped (and no dumpables)
-        verify(dumpable1, never())
-            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
-        verify(dumpable2, never())
-            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
-        verify(dumpable3, never())
-            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2, never()).dump(any(), any())
+        verify(dumpable3, never()).dump(any(), any())
         verify(buffer1).dump(pw, tailLength = 1)
         verify(buffer2).dump(pw, tailLength = 1)
     }
@@ -168,10 +277,9 @@
         // THEN only critical modules are dumped (and no buffers)
         verify(dumpable1).dump(pw, args)
         verify(dumpable2).dump(pw, args)
-        verify(dumpable3, never())
-            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
-        verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
-        verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+        verify(dumpable3, never()).dump(any(), any())
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2, never()).dump(any(), anyInt())
     }
 
     @Test
@@ -188,10 +296,8 @@
         dumpManager.dumpNormal(pw, args, tailLength = 2)
 
         // THEN the normal module and all buffers are dumped
-        verify(dumpable1, never())
-            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
-        verify(dumpable2, never())
-            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2, never()).dump(any(), any())
         verify(dumpable3).dump(pw, args)
         verify(buffer1).dump(pw, tailLength = 2)
         verify(buffer2).dump(pw, tailLength = 2)
@@ -213,9 +319,7 @@
 
         // THEN the unregistered dumpables (both normal and critical) are not dumped
         verify(dumpable1).dump(pw, args)
-        verify(dumpable2, never())
-            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
-        verify(dumpable3, never())
-            .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+        verify(dumpable2, never()).dump(any(), any())
+        verify(dumpable3, never()).dump(any(), any())
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
index 64547f4..bd029a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.dump
 
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogcatEchoTracker
 
 /**
  * Creates a LogBuffer that will echo everything to logcat, which is useful for debugging tests.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 8795ac0..c9ee1e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -70,6 +70,7 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -115,6 +116,7 @@
     @Mock private MetricsLogger mMetricsLogger;
     @Mock private SysuiColorExtractor mColorExtractor;
     @Mock private IStatusBarService mStatusBarService;
+    @Mock private LightBarController mLightBarController;
     @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
     @Mock private IWindowManager mWindowManager;
     @Mock private Executor mBackgroundExecutor;
@@ -166,6 +168,7 @@
                 mMetricsLogger,
                 mColorExtractor,
                 mStatusBarService,
+                mLightBarController,
                 mNotificationShadeWindowController,
                 mWindowManager,
                 mBackgroundExecutor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
index b9cfc65..e981d62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
@@ -25,13 +25,11 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.IActivityTaskManager;
 import android.app.IApplicationThread;
 import android.app.ProfilerInfo;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
@@ -59,13 +57,14 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class WorkLockActivityControllerTest extends SysuiTestCase {
-    private static final int USER_ID = 333;
+    private static final int TASK_USER_ID = 333;
+    private static final int PROFILE_USER_ID = 555;
     private static final int TASK_ID = 444;
     private static final ActivityManager.RunningTaskInfo TASK_INFO =
             new ActivityManager.RunningTaskInfo();
 
     static {
-        TASK_INFO.userId = USER_ID;
+        TASK_INFO.userId = TASK_USER_ID;
         TASK_INFO.taskId = TASK_ID;
     }
 
@@ -101,10 +100,10 @@
         setActivityStartCode(TASK_ID, true /*taskOverlay*/, ActivityManager.START_SUCCESS);
 
         // And the controller receives a message saying the profile is locked,
-        mTaskStackListener.onTaskProfileLocked(TASK_INFO);
+        mTaskStackListener.onTaskProfileLocked(TASK_INFO, PROFILE_USER_ID);
 
         // The overlay should start and the task the activity started in should not be removed.
-        verifyStartActivity(TASK_ID, true /*taskOverlay*/);
+        verifyStartActivity(TASK_ID, true /*taskOverlay*/, PROFILE_USER_ID);
         verify(mIActivityTaskManager, never()).removeTask(anyInt() /*taskId*/);
     }
 
@@ -114,11 +113,11 @@
         setActivityStartCode(TASK_ID, true /*taskOverlay*/, ActivityManager.START_CLASS_NOT_FOUND);
 
         // And the controller receives a message saying the profile is locked,
-        mTaskStackListener.onTaskProfileLocked(TASK_INFO);
+        mTaskStackListener.onTaskProfileLocked(TASK_INFO, PROFILE_USER_ID);
 
         // The task the activity started in should be removed to prevent the locked task from
         // being shown.
-        verifyStartActivity(TASK_ID, true /*taskOverlay*/);
+        verifyStartActivity(TASK_ID, true /*taskOverlay*/, PROFILE_USER_ID);
         verify(mIActivityTaskManager).removeTask(TASK_ID);
     }
 
@@ -141,12 +140,13 @@
                 eq(ActivityManager.getCurrentUser()));
     }
 
-    private void verifyStartActivity(int taskId, boolean taskOverlay) throws Exception {
+    private void verifyStartActivity(int taskId, boolean taskOverlay, int profileUserId)
+            throws Exception {
         verify(mIActivityTaskManager).startActivityAsUser(
                 eq((IApplicationThread) null),
                 eq((String) null),
                 eq((String) null),
-                any(Intent.class),
+                argThat(hasUserId(profileUserId)),
                 eq((String) null),
                 eq((IBinder) null),
                 eq((String) null),
@@ -157,24 +157,15 @@
                 eq(ActivityManager.getCurrentUser()));
     }
 
-    private static ArgumentMatcher<Intent> hasComponent(final Context context,
-            final Class<? extends Activity> activityClass) {
-        return new ArgumentMatcher<Intent>() {
-            @Override
-            public boolean matches(Intent intent) {
-                return new ComponentName(context, activityClass).equals(intent.getComponent());
-            }
-        };
+    private static ArgumentMatcher<Intent> hasUserId(int userId) {
+        return intent -> intent.getIntExtra(Intent.EXTRA_USER_ID, -1) == userId;
     }
 
     private static ArgumentMatcher<Bundle> hasOptions(final int taskId, final boolean overlay) {
-        return new ArgumentMatcher<Bundle>() {
-            @Override
-            public boolean matches(Bundle item) {
-                final ActivityOptions options = ActivityOptions.fromBundle(item);
-                return (options.getLaunchTaskId() == taskId)
-                        && (options.getTaskOverlay() == overlay);
-            }
+        return item -> {
+            final ActivityOptions options = ActivityOptions.fromBundle(item);
+            return (options.getLaunchTaskId() == taskId)
+                    && (options.getTaskOverlay() == overlay);
         };
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 1d0b58a..d73c2c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -54,6 +54,7 @@
 import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
@@ -234,6 +235,7 @@
             faceDetectBuffer,
             faceAuthBuffer,
             keyguardTransitionInteractor,
+            featureFlags,
             dumpManager,
         )
     }
@@ -612,6 +614,7 @@
             authStatus()
             detectStatus()
             authRunning()
+            bypassEnabled()
             lockedOut()
             canFaceAuthRun()
             authenticated()
@@ -847,7 +850,11 @@
     fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromDozing() =
         testScope.runTest {
             keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(from = KeyguardState.DOZING, to = KeyguardState.GONE)
+                TransitionStep(
+                    from = KeyguardState.DOZING,
+                    to = KeyguardState.GONE,
+                    transitionState = TransitionState.FINISHED
+                )
             )
 
             runCurrent()
@@ -858,7 +865,11 @@
     fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromAod() =
         testScope.runTest {
             keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(from = KeyguardState.AOD, to = KeyguardState.GONE)
+                TransitionStep(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.GONE,
+                    transitionState = TransitionState.FINISHED
+                )
             )
 
             runCurrent()
@@ -869,7 +880,11 @@
     fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromLockscreen() =
         testScope.runTest {
             keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    transitionState = TransitionState.FINISHED
+                )
             )
 
             runCurrent()
@@ -880,7 +895,11 @@
     fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromBouncer() =
         testScope.runTest {
             keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE)
+                TransitionStep(
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                    transitionState = TransitionState.FINISHED
+                )
             )
 
             runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index d0bfaa9..5afc405 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -23,8 +23,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
index bf3c73a..c2195c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
@@ -24,8 +24,8 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogcatEchoTracker
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
new file mode 100644
index 0000000..069a486
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BurnInInteractorTest : SysuiTestCase() {
+    private val burnInOffset = 7
+    private var burnInProgress = 0f
+
+    @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+
+    private lateinit var configurationRepository: FakeConfigurationRepository
+    private lateinit var systemClock: FakeSystemClock
+    private lateinit var testScope: TestScope
+    private lateinit var underTest: BurnInInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        configurationRepository = FakeConfigurationRepository()
+        systemClock = FakeSystemClock()
+
+        whenever(burnInHelperWrapper.burnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset)
+        setBurnInProgress(.65f)
+
+        testScope = TestScope()
+        underTest =
+            BurnInInteractor(
+                context,
+                burnInHelperWrapper,
+                testScope.backgroundScope,
+                configurationRepository,
+                systemClock,
+            )
+    }
+
+    @Test
+    fun dozeTimeTick_updatesOnDozeTimeTick() =
+        testScope.runTest {
+            // Initial state set to 0
+            val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick)
+            assertEquals(0L, lastDozeTimeTick)
+
+            // WHEN dozeTimeTick updated
+            incrementUptimeMillis()
+            underTest.dozeTimeTick()
+
+            // THEN listeners were updated to the latest uptime millis
+            assertThat(systemClock.uptimeMillis()).isEqualTo(lastDozeTimeTick)
+        }
+
+    @Test
+    fun udfpsBurnInOffset_updatesOnResolutionScaleChange() =
+        testScope.runTest {
+            val udfpsBurnInOffsetX by collectLastValue(underTest.udfpsBurnInXOffset)
+            val udfpsBurnInOffsetY by collectLastValue(underTest.udfpsBurnInYOffset)
+            assertThat(udfpsBurnInOffsetX).isEqualTo(burnInOffset)
+            assertThat(udfpsBurnInOffsetY).isEqualTo(burnInOffset)
+
+            configurationRepository.setScaleForResolution(3f)
+            assertThat(udfpsBurnInOffsetX).isEqualTo(burnInOffset * 3)
+            assertThat(udfpsBurnInOffsetY).isEqualTo(burnInOffset * 3)
+
+            configurationRepository.setScaleForResolution(.5f)
+            assertThat(udfpsBurnInOffsetX).isEqualTo(burnInOffset / 2)
+            assertThat(udfpsBurnInOffsetY).isEqualTo(burnInOffset / 2)
+        }
+
+    @Test
+    fun udfpsBurnInProgress_updatesOnDozeTimeTick() =
+        testScope.runTest {
+            val udfpsBurnInProgress by collectLastValue(underTest.udfpsBurnInProgress)
+            assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
+
+            setBurnInProgress(.88f)
+            incrementUptimeMillis()
+            underTest.dozeTimeTick()
+            assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
+
+            setBurnInProgress(.92f)
+            incrementUptimeMillis()
+            underTest.dozeTimeTick()
+            assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
+
+            setBurnInProgress(.32f)
+            incrementUptimeMillis()
+            underTest.dozeTimeTick()
+            assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
+        }
+
+    private fun incrementUptimeMillis() {
+        systemClock.setUptimeMillis(systemClock.uptimeMillis() + 5)
+    }
+
+    private fun setBurnInProgress(progress: Float) {
+        burnInProgress = progress
+        whenever(burnInHelperWrapper.burnInProgressOffset()).thenReturn(burnInProgress)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 5da1a84..e261982 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -35,12 +35,12 @@
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -100,7 +100,8 @@
                     mock(DismissCallbackRegistry::class.java),
                     context,
                     keyguardUpdateMonitor,
-                    mock(KeyguardBypassController::class.java),
+                    mock(TrustRepository::class.java),
+                    FakeFeatureFlags(),
                 ),
                 AlternateBouncerInteractor(
                     mock(StatusBarStateController::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 24a47b0..eed1e739 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import android.os.Looper
+import android.hardware.biometrics.BiometricSourceType
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.testing.TestableResources
@@ -28,15 +28,17 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.BouncerViewDelegate
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
@@ -67,16 +69,23 @@
     @Mock private lateinit var mPrimaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
-    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    private val mainHandler = FakeHandler(Looper.getMainLooper())
+    private lateinit var mainHandler: FakeHandler
     private lateinit var underTest: PrimaryBouncerInteractor
     private lateinit var resources: TestableResources
+    private lateinit var trustRepository: FakeTrustRepository
+    private lateinit var featureFlags: FakeFeatureFlags
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
+            .thenReturn(KeyguardSecurityModel.SecurityMode.PIN)
+
         DejankUtils.setImmediate(true)
+        mainHandler = FakeHandler(android.os.Looper.getMainLooper())
+        trustRepository = FakeTrustRepository()
+        featureFlags = FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, false) }
         underTest =
             PrimaryBouncerInteractor(
                 repository,
@@ -89,7 +98,8 @@
                 dismissCallbackRegistry,
                 context,
                 keyguardUpdateMonitor,
-                keyguardBypassController,
+                trustRepository,
+                featureFlags,
             )
         whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
         whenever(repository.primaryBouncerShow.value).thenReturn(false)
@@ -383,6 +393,55 @@
         verify(repository).setSideFpsShowing(false)
     }
 
+    @Test
+    fun delayBouncerWhenFaceAuthPossible() {
+        mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+        // GIVEN bouncer should be delayed due to face auth
+        featureFlags.apply { set(Flags.DELAY_BOUNCER, true) }
+        whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
+        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+            .thenReturn(true)
+
+        // WHEN bouncer show is requested
+        underTest.show(true)
+
+        // THEN primary show & primary showing soon aren't updated immediately
+        verify(repository, never()).setPrimaryShow(true)
+        verify(repository, never()).setPrimaryShowingSoon(false)
+
+        // WHEN all queued messages are dispatched
+        mainHandler.dispatchQueuedMessages()
+
+        // THEN primary show & primary showing soon are updated
+        verify(repository).setPrimaryShow(true)
+        verify(repository).setPrimaryShowingSoon(false)
+    }
+
+    @Test
+    fun delayBouncerWhenActiveUnlockPossible() {
+        mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+        // GIVEN bouncer should be delayed due to active unlock
+        featureFlags.apply { set(Flags.DELAY_BOUNCER, true) }
+        trustRepository.setCurrentUserActiveUnlockAvailable(true)
+        whenever(keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()).thenReturn(true)
+
+        // WHEN bouncer show is requested
+        underTest.show(true)
+
+        // THEN primary show & primary showing soon were scheduled to update
+        verify(repository, never()).setPrimaryShow(true)
+        verify(repository, never()).setPrimaryShowingSoon(false)
+
+        // WHEN all queued messages are dispatched
+        mainHandler.dispatchQueuedMessages()
+
+        // THEN primary show & primary showing soon are updated
+        verify(repository).setPrimaryShow(true)
+        verify(repository).setPrimaryShowingSoon(false)
+    }
+
     private fun updateSideFpsVisibilityParameters(
         isVisible: Boolean,
         sfpsEnabled: Boolean,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index e35e971..5056b43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -25,9 +25,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.utils.os.FakeHandler
@@ -37,6 +39,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -71,7 +74,8 @@
                 dismissCallbackRegistry,
                 context,
                 keyguardUpdateMonitor,
-                keyguardBypassController,
+                Mockito.mock(TrustRepository::class.java),
+                FakeFeatureFlags(),
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
index a5b78b74..3efe382 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.keyguard.ui
 
 import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index 0e9c99e..e9f1ac1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -24,9 +24,11 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
@@ -42,6 +44,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -77,7 +80,8 @@
                 dismissCallbackRegistry,
                 context,
                 keyguardUpdateMonitor,
-                keyguardBypassController,
+                Mockito.mock(TrustRepository::class.java),
+                FakeFeatureFlags(),
             )
         underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
     }
diff --git a/packages/SystemUI/plugin/tests/log/LogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
similarity index 98%
rename from packages/SystemUI/plugin/tests/log/LogBufferTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
index a39b856..0cf6d3d 100644
--- a/packages/SystemUI/plugin/tests/log/LogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
@@ -2,7 +2,6 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.log.LogBuffer
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
index a003e1d..43e430f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
@@ -19,7 +19,9 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX
+import com.android.systemui.log.table.TableChange.Companion.MAX_STRING_LENGTH
 import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertTrue
 import org.junit.Test
 
 @SmallTest
@@ -326,4 +328,152 @@
 
         assertThat(underTest.getVal()).doesNotContain(IS_INITIAL_PREFIX)
     }
+
+    @Test
+    fun constructor_columnAndValueTooLong_truncated() {
+        val underTest =
+            TableChange(
+                columnPrefix = "P".repeat(MAX_STRING_LENGTH + 10),
+                columnName = "N".repeat(MAX_STRING_LENGTH + 10),
+                type = TableChange.DataType.STRING,
+                str = "V".repeat(MAX_STRING_LENGTH + 10),
+            )
+
+        assertThat(underTest.getName()).contains("P".repeat(MAX_STRING_LENGTH))
+        assertThat(underTest.getName()).doesNotContain("P".repeat(MAX_STRING_LENGTH + 1))
+        assertThat(underTest.getName()).contains("N".repeat(MAX_STRING_LENGTH))
+        assertThat(underTest.getName()).doesNotContain("N".repeat(MAX_STRING_LENGTH + 1))
+        assertThat(underTest.getVal()).isEqualTo("V".repeat(MAX_STRING_LENGTH))
+    }
+
+    @Test
+    fun constructor_columnNameNotTooLong_noReallocation() {
+        val inputColumnName = "fakeName"
+        val inputValue = "fakeValue"
+        val underTest =
+            TableChange(
+                columnPrefix = "",
+                columnName = inputColumnName,
+                type = TableChange.DataType.STRING,
+                str = inputValue,
+            )
+
+        // Use referential equality to verify we didn't reallocate a new string when the string is
+        // *not* too long.
+        assertTrue(underTest.getColumnName() === inputColumnName)
+    }
+
+    @Test
+    fun reset_columnPrefixTooLong_truncated() {
+        val underTest = TableChange()
+
+        underTest.reset(
+            timestamp = 1L,
+            columnPrefix = "P".repeat(MAX_STRING_LENGTH + 10),
+            columnName = "name",
+            isInitial = false,
+        )
+
+        assertThat(underTest.getName()).contains("P".repeat(MAX_STRING_LENGTH))
+        assertThat(underTest.getName()).doesNotContain("P".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun reset_columnNameTooLong_truncated() {
+        val underTest = TableChange()
+
+        underTest.reset(
+            timestamp = 1L,
+            columnPrefix = "prefix",
+            columnName = "N".repeat(MAX_STRING_LENGTH + 10),
+            isInitial = false,
+        )
+
+        assertThat(underTest.getName()).contains("N".repeat(MAX_STRING_LENGTH))
+        assertThat(underTest.getName()).doesNotContain("N".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun reset_columnNameNotTooLong_noReallocation() {
+        val underTest = TableChange()
+        val shortColumnName = "shortColumnName"
+
+        underTest.reset(
+            timestamp = 1L,
+            columnPrefix = "prefix",
+            columnName = shortColumnName,
+            isInitial = false,
+        )
+
+        // Use referential equality to verify we didn't reallocate a new string when the string is
+        // *not* too long.
+        assertTrue(underTest.getColumnName() === shortColumnName)
+    }
+
+    @Test
+    fun setString_valueTooLong_truncated() {
+        val underTest = TableChange()
+
+        underTest.set("V".repeat(MAX_STRING_LENGTH + 1))
+
+        assertThat(underTest.getVal()).isEqualTo("V".repeat(MAX_STRING_LENGTH))
+    }
+
+    @Test
+    fun updateTo_newColumnPrefixTooLong_truncated() {
+        val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+        underTest.set(42)
+
+        val new =
+            TableChange(
+                columnPrefix = "P".repeat(MAX_STRING_LENGTH + 10),
+                columnName = "name",
+            )
+        underTest.updateTo(new)
+
+        assertThat(underTest.getName()).contains("P".repeat(MAX_STRING_LENGTH))
+        assertThat(underTest.getName()).doesNotContain("P".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun updateTo_newColumnNameTooLong_truncated() {
+        val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+        underTest.set(42)
+
+        val new =
+            TableChange(
+                columnPrefix = "prefix",
+                columnName = "N".repeat(MAX_STRING_LENGTH + 10),
+            )
+        underTest.updateTo(new)
+
+        assertThat(underTest.getName()).contains("N".repeat(MAX_STRING_LENGTH))
+        assertThat(underTest.getName()).doesNotContain("N".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun updateTo_columnNameNotTooLong_noReallocation() {
+        val underTest = TableChange()
+        val shortColumnName = "shortColumnName"
+        val new = TableChange(columnPrefix = "prefix", columnName = shortColumnName)
+
+        underTest.updateTo(new)
+
+        // Use referential equality to verify we didn't reallocate a new string when the string is
+        // *not* too long.
+        assertTrue(underTest.getColumnName() === shortColumnName)
+    }
+
+    @Test
+    fun updateTo_newValTooLong_truncated() {
+        val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+        underTest.set("value")
+
+        val new = TableChange()
+        new.set("V".repeat(MAX_STRING_LENGTH + 10))
+
+        underTest.updateTo(new)
+
+        assertThat(underTest.getVal()).isEqualTo("V".repeat(MAX_STRING_LENGTH))
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
index a2b2322..12f4689 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
@@ -18,9 +18,10 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogcatEchoTracker
 import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.table.TableChange.Companion.MAX_STRING_LENGTH
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
@@ -576,6 +577,112 @@
     }
 
     @Test
+    fun dumpChanges_tooLongColumnPrefix_viaLogChange_truncated() {
+        underTest.logChange(
+            prefix = "P".repeat(MAX_STRING_LENGTH + 10),
+            columnName = "name",
+            value = true,
+        )
+
+        val dumpedString = dumpChanges()
+
+        assertThat(dumpedString).contains("P".repeat(MAX_STRING_LENGTH))
+        assertThat(dumpedString).doesNotContain("P".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun dumpChanges_tooLongColumnPrefix_viaLogDiffs_truncated() {
+        val prevDiffable = object : TestDiffable() {}
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    row.logChange("status", "value")
+                }
+            }
+
+        // WHEN the column prefix is too large
+        underTest.logDiffs(
+            columnPrefix = "P".repeat(MAX_STRING_LENGTH + 10),
+            prevDiffable,
+            nextDiffable,
+        )
+
+        val dumpedString = dumpChanges()
+
+        // THEN it's truncated to the max length
+        assertThat(dumpedString).contains("P".repeat(MAX_STRING_LENGTH))
+        assertThat(dumpedString).doesNotContain("P".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun dumpChanges_tooLongColumnName_viaLogChange_truncated() {
+        underTest.logChange(
+            prefix = "prefix",
+            columnName = "N".repeat(MAX_STRING_LENGTH + 10),
+            value = 10,
+        )
+
+        val dumpedString = dumpChanges()
+
+        assertThat(dumpedString).contains("N".repeat(MAX_STRING_LENGTH))
+        assertThat(dumpedString).doesNotContain("N".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun dumpChanges_tooLongColumnName_viaLogDiffs_truncated() {
+        val prevDiffable = object : TestDiffable() {}
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    // WHEN the column name is too large
+                    row.logChange(columnName = "N".repeat(MAX_STRING_LENGTH + 10), "value")
+                }
+            }
+
+        underTest.logDiffs(columnPrefix = "prefix", prevDiffable, nextDiffable)
+
+        val dumpedString = dumpChanges()
+
+        // THEN it's truncated to the max length
+        assertThat(dumpedString).contains("N".repeat(MAX_STRING_LENGTH))
+        assertThat(dumpedString).doesNotContain("N".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun dumpChanges_tooLongValue_viaLogChange_truncated() {
+        underTest.logChange(
+            prefix = "prefix",
+            columnName = "name",
+            value = "V".repeat(MAX_STRING_LENGTH + 10),
+        )
+
+        val dumpedString = dumpChanges()
+
+        assertThat(dumpedString).contains("V".repeat(MAX_STRING_LENGTH))
+        assertThat(dumpedString).doesNotContain("V".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
+    fun dumpChanges_tooLongValue_viaLogDiffs_truncated() {
+        val prevDiffable = object : TestDiffable() {}
+        val nextDiffable =
+            object : TestDiffable() {
+                override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+                    // WHEN the value is too large
+                    row.logChange("columnName", value = "V".repeat(MAX_STRING_LENGTH + 10))
+                }
+            }
+
+        underTest.logDiffs(columnPrefix = "prefix", prevDiffable, nextDiffable)
+
+        val dumpedString = dumpChanges()
+
+        // THEN it's truncated to the max length
+        assertThat(dumpedString).contains("V".repeat(MAX_STRING_LENGTH))
+        assertThat(dumpedString).doesNotContain("V".repeat(MAX_STRING_LENGTH + 1))
+    }
+
+    @Test
     fun dumpChanges_rotatesIfBufferIsFull() {
         lateinit var valToDump: String
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 0a1db60..d428db7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -40,7 +40,6 @@
 import androidx.media.utils.MediaConstants
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.InstanceId
-import com.android.internal.statusbar.IStatusBarService
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.InstanceIdSequenceFake
 import com.android.systemui.R
@@ -131,7 +130,6 @@
     @Mock lateinit var activityStarter: ActivityStarter
     @Mock lateinit var smartspaceManager: SmartspaceManager
     @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    @Mock lateinit var statusBarService: IStatusBarService
     lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
     @Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
     @Mock private lateinit var mediaRecommendationItem: SmartspaceAction
@@ -194,8 +192,7 @@
                 mediaFlags = mediaFlags,
                 logger = logger,
                 smartspaceManager = smartspaceManager,
-                keyguardUpdateMonitor = keyguardUpdateMonitor,
-                statusBarService = statusBarService,
+                keyguardUpdateMonitor = keyguardUpdateMonitor
             )
         verify(tunerService)
             .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -520,136 +517,19 @@
     }
 
     @Test
-    fun testOnNotificationAdded_emptyTitle_notLoaded() {
-        // GIVEN that the manager has a notification with an empty title.
+    fun testOnNotificationRemoved_emptyTitle_notConverted() {
+        // GIVEN that the manager has a notification with a resume action and empty title.
         whenever(controller.metadata)
             .thenReturn(
                 metadataBuilder
                     .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
                     .build()
             )
-        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
-
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(statusBarService)
-            .onNotificationError(
-                eq(PACKAGE_NAME),
-                eq(mediaNotification.tag),
-                eq(mediaNotification.id),
-                eq(mediaNotification.uid),
-                eq(mediaNotification.initialPid),
-                eq(MEDIA_TITLE_ERROR_MESSAGE),
-                eq(mediaNotification.user.identifier)
-            )
-        verify(listener, never())
-            .onMediaDataLoaded(
-                eq(KEY),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
-        verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
-        verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any())
-    }
-
-    @Test
-    fun testOnNotificationAdded_blankTitle_notLoaded() {
-        // GIVEN that the manager has a notification with a blank title.
-        whenever(controller.metadata)
-            .thenReturn(
-                metadataBuilder
-                    .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
-                    .build()
-            )
-        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
-
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(statusBarService)
-            .onNotificationError(
-                eq(PACKAGE_NAME),
-                eq(mediaNotification.tag),
-                eq(mediaNotification.id),
-                eq(mediaNotification.uid),
-                eq(mediaNotification.initialPid),
-                eq(MEDIA_TITLE_ERROR_MESSAGE),
-                eq(mediaNotification.user.identifier)
-            )
-        verify(listener, never())
-            .onMediaDataLoaded(
-                eq(KEY),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
-        verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
-        verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any())
-    }
-
-    @Test
-    fun testOnNotificationUpdated_invalidTitle_logMediaRemoved() {
-        addNotificationAndLoad()
-        val data = mediaDataCaptor.value
-
-        verify(listener)
-            .onMediaDataLoaded(
-                eq(KEY),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
-
-        reset(listener)
-        whenever(controller.metadata)
-            .thenReturn(
-                metadataBuilder
-                    .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
-                    .build()
-            )
-        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(statusBarService)
-            .onNotificationError(
-                eq(PACKAGE_NAME),
-                eq(mediaNotification.tag),
-                eq(mediaNotification.id),
-                eq(mediaNotification.uid),
-                eq(mediaNotification.initialPid),
-                eq(MEDIA_TITLE_ERROR_MESSAGE),
-                eq(mediaNotification.user.identifier)
-            )
-        verify(listener, never())
-            .onMediaDataLoaded(
-                eq(KEY),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
-        verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
-    }
-
-    @Test
-    fun testOnNotificationRemoved_emptyTitle_notConverted() {
-        // GIVEN that the manager has a notification with a resume action and empty title.
         addNotificationAndLoad()
         val data = mediaDataCaptor.value
         val instanceId = data.instanceId
         assertThat(data.resumption).isFalse()
-        mediaDataManager.onMediaDataLoaded(
-            KEY,
-            null,
-            data.copy(song = SESSION_EMPTY_TITLE, resumeAction = Runnable {})
-        )
+        mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
 
         // WHEN the notification is removed
         reset(listener)
@@ -674,15 +554,17 @@
     @Test
     fun testOnNotificationRemoved_blankTitle_notConverted() {
         // GIVEN that the manager has a notification with a resume action and blank title.
+        whenever(controller.metadata)
+            .thenReturn(
+                metadataBuilder
+                    .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
+                    .build()
+            )
         addNotificationAndLoad()
         val data = mediaDataCaptor.value
         val instanceId = data.instanceId
         assertThat(data.resumption).isFalse()
-        mediaDataManager.onMediaDataLoaded(
-            KEY,
-            null,
-            data.copy(song = SESSION_BLANK_TITLE, resumeAction = Runnable {})
-        )
+        mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
 
         // WHEN the notification is removed
         reset(listener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt
index 8da1c64..b322bb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt
@@ -19,9 +19,9 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogcatEchoTracker
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt
index 95df484..64f3fd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt
@@ -19,9 +19,9 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogcatEchoTracker
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt
index 0033757..2287da5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt
@@ -19,9 +19,9 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogcatEchoTracker
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 4977775..f1bbd84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -132,7 +132,7 @@
     }
 
     @Test
-    fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesDisabled_bindsOnlyTasksWithHostProfile() {
+    fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesDisabled_bindsAllTasks() {
         givenEnterprisePoliciesFeatureFlag(enabled = false)
 
         val tasks =
@@ -147,11 +147,15 @@
 
         controller.init()
 
+        // TODO (b/263950746): Cross-profile filtering is removed for now. This should be brought
+        // back with the future fix
         verify(view)
             .bind(
                 listOf(
                     createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+                    createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
                     createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+                    createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
                     createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
                 )
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
new file mode 100644
index 0000000..36b913f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import android.content.Context
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.intercepting.SingleActivityFactory
+import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE
+import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class LaunchNotesRoleSettingsTrampolineActivityTest : SysuiTestCase() {
+
+    @Mock lateinit var noteTaskController: NoteTaskController
+
+    @Rule
+    @JvmField
+    val activityRule =
+        ActivityTestRule<LaunchNotesRoleSettingsTrampolineActivity>(
+            /* activityFactory= */ object :
+                SingleActivityFactory<LaunchNotesRoleSettingsTrampolineActivity>(
+                    LaunchNotesRoleSettingsTrampolineActivity::class.java
+                ) {
+                override fun create(intent: Intent?) =
+                    LaunchNotesRoleSettingsTrampolineActivity(noteTaskController)
+            },
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
+        )
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @After
+    fun tearDown() {
+        activityRule.finishActivity()
+    }
+
+    @Test
+    fun startActivity_noAction_shouldLaunchNotesRoleSettingTaskWithNullEntryPoint() {
+        activityRule.launchActivity(/* startIntent= */ null)
+
+        verify(noteTaskController).startNotesRoleSetting(any(Context::class.java), eq(null))
+    }
+
+    @Test
+    fun startActivity_quickAffordanceAction_shouldLaunchNotesRoleSettingTaskWithQuickAffordanceEntryPoint() { // ktlint-disable max-line-length
+        activityRule.launchActivity(Intent(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE))
+
+        verify(noteTaskController)
+            .startNotesRoleSetting(any(Context::class.java), eq(QUICK_AFFORDANCE))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 5dbcd33..5f89705 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -47,6 +47,9 @@
 import com.android.systemui.notetask.NoteTaskController.Companion.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE
 import com.android.systemui.notetask.NoteTaskController.Companion.SETTINGS_CREATE_NOTE_TASK_SHORTCUT_COMPONENT
 import com.android.systemui.notetask.NoteTaskController.Companion.SHORTCUT_ID
+import com.android.systemui.notetask.NoteTaskEntryPoint.APP_CLIPS
+import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
+import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
 import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
 import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
 import com.android.systemui.settings.FakeUserTracker
@@ -493,7 +496,7 @@
             )
             .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
 
-        createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+        createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
 
         verifyZeroInteractions(context, bubbles, eventLogger)
     }
@@ -509,7 +512,7 @@
             )
             .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
 
-        createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+        createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
 
         verifyZeroInteractions(context, bubbles, eventLogger)
     }
@@ -525,7 +528,7 @@
             )
             .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
 
-        createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+        createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
 
         verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle)
     }
@@ -541,7 +544,7 @@
             )
             .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
 
-        createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+        createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
 
         verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle)
     }
@@ -553,7 +556,7 @@
         whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
         userTracker.set(listOf(mainUserInfo), mainAndWorkProfileUsers.indexOf(mainUserInfo))
 
-        createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+        createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
 
         verifyNoteTaskOpenInBubbleInUser(mainUserInfo.userHandle)
     }
@@ -563,7 +566,7 @@
         whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
         userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
 
-        createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+        createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
 
         verifyNoteTaskOpenInBubbleInUser(workUserInfo.userHandle)
     }
@@ -734,6 +737,129 @@
     }
     // endregion
 
+    // region getUserForHandlingNotesTaking
+    @Test
+    fun getUserForHandlingNotesTaking_cope_quickAffordance_shouldReturnWorkProfileUser() {
+        whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        val user = createNoteTaskController().getUserForHandlingNotesTaking(QUICK_AFFORDANCE)
+
+        assertThat(user).isEqualTo(UserHandle.of(workUserInfo.id))
+    }
+
+    @Test
+    fun getUserForHandlingNotesTaking_cope_tailButton_shouldReturnWorkProfileUser() {
+        whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        val user = createNoteTaskController().getUserForHandlingNotesTaking(TAIL_BUTTON)
+
+        assertThat(user).isEqualTo(UserHandle.of(workUserInfo.id))
+    }
+
+    @Test
+    fun getUserForHandlingNotesTaking_cope_appClip_shouldReturnCurrentUser() {
+        whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        val user = createNoteTaskController().getUserForHandlingNotesTaking(APP_CLIPS)
+
+        assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id))
+    }
+
+    @Test
+    fun getUserForHandlingNotesTaking_noManagement_quickAffordance_shouldReturnCurrentUser() {
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        val user = createNoteTaskController().getUserForHandlingNotesTaking(QUICK_AFFORDANCE)
+
+        assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id))
+    }
+
+    @Test
+    fun getUserForHandlingNotesTaking_noManagement_tailButton_shouldReturnCurrentUser() {
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        val user = createNoteTaskController().getUserForHandlingNotesTaking(TAIL_BUTTON)
+
+        assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id))
+    }
+
+    @Test
+    fun getUserForHandlingNotesTaking_noManagement_appClip_shouldReturnCurrentUser() {
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        val user = createNoteTaskController().getUserForHandlingNotesTaking(APP_CLIPS)
+
+        assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id))
+    }
+    // endregion
+
+    // startregion startNotesRoleSetting
+    @Test
+    fun startNotesRoleSetting_cope_quickAffordance_shouldStartNoteRoleIntentWithWorkProfileUser() {
+        whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        createNoteTaskController().startNotesRoleSetting(context, QUICK_AFFORDANCE)
+
+        val intentCaptor = argumentCaptor<Intent>()
+        val userCaptor = argumentCaptor<UserHandle>()
+        verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP)
+        }
+        assertThat(userCaptor.value).isEqualTo(UserHandle.of(workUserInfo.id))
+    }
+
+    @Test
+    fun startNotesRoleSetting_cope_nullEntryPoint_shouldStartNoteRoleIntentWithCurrentUser() {
+        whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        createNoteTaskController().startNotesRoleSetting(context, entryPoint = null)
+
+        val intentCaptor = argumentCaptor<Intent>()
+        val userCaptor = argumentCaptor<UserHandle>()
+        verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP)
+        }
+        assertThat(userCaptor.value).isEqualTo(UserHandle.of(mainUserInfo.id))
+    }
+
+    @Test
+    fun startNotesRoleSetting_noManagement_quickAffordance_shouldStartNoteRoleIntentWithCurrentUser() { // ktlint-disable max-line-length
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        createNoteTaskController().startNotesRoleSetting(context, QUICK_AFFORDANCE)
+
+        val intentCaptor = argumentCaptor<Intent>()
+        val userCaptor = argumentCaptor<UserHandle>()
+        verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP)
+        }
+        assertThat(userCaptor.value).isEqualTo(UserHandle.of(mainUserInfo.id))
+    }
+
+    @Test
+    fun startNotesRoleSetting_noManagement_nullEntryPoint_shouldStartNoteRoleIntentWithCurrentUser() { // ktlint-disable max-line-length
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        createNoteTaskController().startNotesRoleSetting(context, entryPoint = null)
+
+        val intentCaptor = argumentCaptor<Intent>()
+        val userCaptor = argumentCaptor<UserHandle>()
+        verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP)
+        }
+        assertThat(userCaptor.value).isEqualTo(UserHandle.of(mainUserInfo.id))
+    }
+    // endregion
+
     private companion object {
         const val NOTE_TASK_SHORT_LABEL = "Notetaking"
         const val NOTE_TASK_ACTIVITY_NAME = "NoteTaskActivity"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index 42ef2b5..4526580 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -18,7 +18,12 @@
 
 package com.android.systemui.notetask.quickaffordance
 
+import android.app.role.RoleManager
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
 import android.hardware.input.InputSettings
+import android.os.UserHandle
 import android.os.UserManager
 import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
@@ -31,11 +36,18 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE
 import com.android.systemui.notetask.NoteTaskController
 import com.android.systemui.notetask.NoteTaskEntryPoint
+import com.android.systemui.notetask.NoteTaskInfoResolver
+import com.android.systemui.shared.customization.data.content.CustomizationProviderContract.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR
 import com.android.systemui.stylus.StylusManager
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -45,6 +57,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.anyString
 import org.mockito.Mockito.verify
 import org.mockito.MockitoSession
 import org.mockito.quality.Strictness
@@ -58,6 +71,8 @@
     @Mock lateinit var stylusManager: StylusManager
     @Mock lateinit var repository: KeyguardQuickAffordanceRepository
     @Mock lateinit var userManager: UserManager
+    @Mock lateinit var roleManager: RoleManager
+    @Mock lateinit var packageManager: PackageManager
 
     private lateinit var mockitoSession: MockitoSession
 
@@ -69,6 +84,23 @@
                 .mockStatic(InputSettings::class.java)
                 .strictness(Strictness.LENIENT)
                 .startMocking()
+
+        whenever(
+                packageManager.getApplicationInfoAsUser(
+                    anyString(),
+                    any(ApplicationInfoFlags::class.java),
+                    any(UserHandle::class.java)
+                )
+            )
+            .thenReturn(ApplicationInfo())
+        whenever(controller.getUserForHandlingNotesTaking(any())).thenReturn(UserHandle.SYSTEM)
+        whenever(
+                roleManager.getRoleHoldersAsUser(
+                    eq(RoleManager.ROLE_NOTES),
+                    any(UserHandle::class.java)
+                )
+            )
+            .thenReturn(listOf("com.google.test.notes"))
     }
 
     @After
@@ -85,6 +117,9 @@
             keyguardMonitor = mock(),
             lazyRepository = { repository },
             isEnabled = isEnabled,
+            backgroundExecutor = FakeExecutor(FakeSystemClock()),
+            roleManager = roleManager,
+            noteTaskInfoResolver = NoteTaskInfoResolver(roleManager, packageManager)
         )
 
     private fun createLockScreenStateVisible(): LockScreenState =
@@ -112,6 +147,27 @@
     }
 
     @Test
+    fun lockScreenState_stylusUsed_userUnlocked_isSelected_noDefaultNotesAppSet_shouldEmitHidden() =
+        runTest {
+            TestConfig()
+                .setStylusEverUsed(true)
+                .setUserUnlocked(true)
+                .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>())
+            whenever(
+                    roleManager.getRoleHoldersAsUser(
+                        eq(RoleManager.ROLE_NOTES),
+                        any(UserHandle::class.java)
+                    )
+                )
+                .thenReturn(emptyList())
+
+            val underTest = createUnderTest()
+            val actual by collectLastValue(underTest.lockScreenState)
+
+            assertThat(actual).isEqualTo(LockScreenState.Hidden)
+        }
+
+    @Test
     fun lockScreenState_stylusUnused_userUnlocked_isSelected_shouldEmitHidden() = runTest {
         TestConfig()
             .setStylusEverUsed(false)
@@ -217,6 +273,39 @@
         verify(controller).showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
     }
 
+    // region getPickerScreenState
+    @Test
+    fun getPickerScreenState_defaultNoteAppSet_shouldReturnDefault() = runTest {
+        val underTest = createUnderTest(isEnabled = true)
+
+        assertThat(underTest.getPickerScreenState())
+            .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default())
+    }
+
+    @Test
+    fun getPickerScreenState_nodefaultNoteAppSet_shouldReturnDisable() = runTest {
+        val underTest = createUnderTest(isEnabled = true)
+        whenever(
+                roleManager.getRoleHoldersAsUser(
+                    eq(RoleManager.ROLE_NOTES),
+                    any(UserHandle::class.java)
+                )
+            )
+            .thenReturn(emptyList())
+
+        assertThat(underTest.getPickerScreenState())
+            .isEqualTo(
+                KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
+                    listOf("Select a default notes app to use the notetaking shortcut"),
+                    actionText = "Select app",
+                    actionComponentName =
+                        "${context.packageName}$COMPONENT_NAME_SEPARATOR" +
+                            "$ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE"
+                )
+            )
+    }
+    // endregion
+
     private inner class TestConfig {
 
         fun setStylusEverUsed(value: Boolean) = also {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java
index fb71977..ff60fcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java
@@ -37,6 +37,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import kotlinx.coroutines.CoroutineScope;
+
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
@@ -47,6 +49,9 @@
     @Mock
     Monitor.Callback mCallback;
 
+    @Mock
+    CoroutineScope mScope;
+
     private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
     @Before
@@ -61,7 +66,7 @@
     @Test
     public void testConditionFailsWithNonSystemProcess() {
 
-        final Condition condition = new SystemProcessCondition(mProcessWrapper);
+        final Condition condition = new SystemProcessCondition(mScope, mProcessWrapper);
         when(mProcessWrapper.isSystemUser()).thenReturn(false);
 
         final Monitor monitor = new Monitor(mExecutor);
@@ -82,7 +87,7 @@
     @Test
     public void testConditionSucceedsWithSystemProcess() {
 
-        final Condition condition = new SystemProcessCondition(mProcessWrapper);
+        final Condition condition = new SystemProcessCondition(mScope, mProcessWrapper);
         when(mProcessWrapper.isSystemUser()).thenReturn(true);
 
         final Monitor monitor = new Monitor(mExecutor);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
index 26aa37d..d9db60d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
@@ -37,6 +37,8 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.retail.data.repository.FakeRetailModeRepository;
+import com.android.systemui.retail.domain.interactor.RetailModeInteractorImpl;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
@@ -67,6 +69,8 @@
     @Mock
     private ActivityStarter mActivityStarter;
 
+    private FakeRetailModeRepository mRetailModeRepository;
+
     private QSFooterViewController mController;
     private View mEditButton;
 
@@ -74,6 +78,9 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        mRetailModeRepository = new FakeRetailModeRepository();
+        mRetailModeRepository.setRetailMode(false);
+
         mEditButton = new View(mContext);
 
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
@@ -89,7 +96,8 @@
         when(mView.findViewById(android.R.id.edit)).thenReturn(mEditButton);
 
         mController = new QSFooterViewController(mView, mUserTracker, mFalsingManager,
-                mActivityStarter, mQSPanelController);
+                mActivityStarter, mQSPanelController,
+                new RetailModeInteractorImpl(mRetailModeRepository));
 
         mController.init();
     }
@@ -132,4 +140,20 @@
         captor.getValue().run();
         verify(mQSPanelController).showEdit(mEditButton);
     }
+
+    @Test
+    public void testEditButton_notRetailMode_visible() {
+        mRetailModeRepository.setRetailMode(false);
+
+        mController.setVisibility(View.VISIBLE);
+        assertThat(mEditButton.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testEditButton_retailMode_notVisible() {
+        mRetailModeRepository.setRetailMode(true);
+
+        mController.setVisibility(View.VISIBLE);
+        assertThat(mEditButton.getVisibility()).isEqualTo(View.GONE);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
index 68c10f2..aacc695 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogcatEchoTracker
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 88d7e9c..2dc78a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -587,14 +587,14 @@
         assertEquals(addLink(mContext.getString(R.string.monitoring_description_two_named_vpns,
                                  VPN_PACKAGE, VPN_PACKAGE_2)),
                 mFooterUtils.getVpnMessage(false, true, VPN_PACKAGE, VPN_PACKAGE_2));
-        assertEquals(addLink(mContext.getString(R.string.monitoring_description_named_vpn,
-                                 VPN_PACKAGE)),
+        assertEquals(addLink(mContext.getString(
+                R.string.monitoring_description_managed_device_named_vpn, VPN_PACKAGE)),
                 mFooterUtils.getVpnMessage(true, false, VPN_PACKAGE, null));
         assertEquals(addLink(mContext.getString(R.string.monitoring_description_named_vpn,
                                  VPN_PACKAGE)),
                 mFooterUtils.getVpnMessage(false, false, VPN_PACKAGE, null));
-        assertEquals(addLink(mContext.getString(R.string.monitoring_description_named_vpn,
-                                 VPN_PACKAGE_2)),
+        assertEquals(addLink(mContext.getString(
+                R.string.monitoring_description_managed_device_named_vpn, VPN_PACKAGE_2)),
                 mFooterUtils.getVpnMessage(true, true, null, VPN_PACKAGE_2));
         assertEquals(addLink(mContext.getString(
                                  R.string.monitoring_description_managed_profile_named_vpn,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index 50a8d26..fda63ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.retail.data.repository.FakeRetailModeRepository
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,6 +45,7 @@
 class TileSpecSettingsRepositoryTest : SysuiTestCase() {
 
     private lateinit var secureSettings: FakeSettings
+    private lateinit var retailModeRepository: FakeRetailModeRepository
 
     @Mock private lateinit var logger: QSPipelineLogger
 
@@ -57,9 +59,12 @@
         MockitoAnnotations.initMocks(this)
 
         secureSettings = FakeSettings()
+        retailModeRepository = FakeRetailModeRepository()
+        retailModeRepository.setRetailMode(false)
 
         with(context.orCreateTestableResources) {
             addOverride(R.string.quick_settings_tiles_default, DEFAULT_TILES)
+            addOverride(R.string.quick_settings_tiles_retail_mode, RETAIL_TILES)
         }
 
         underTest =
@@ -67,6 +72,7 @@
                 secureSettings,
                 context.resources,
                 logger,
+                retailModeRepository,
                 testDispatcher,
             )
     }
@@ -346,6 +352,26 @@
             assertThat(tiles).isEqualTo("b".toTileSpecs())
         }
 
+    @Test
+    fun retailMode_usesRetailTiles() =
+        testScope.runTest {
+            retailModeRepository.setRetailMode(true)
+
+            val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+            assertThat(tiles).isEqualTo(RETAIL_TILES.toTileSpecs())
+        }
+
+    @Test
+    fun retailMode_cannotModifyTiles() =
+        testScope.runTest {
+            retailModeRepository.setRetailMode(true)
+
+            underTest.setTiles(0, DEFAULT_TILES.toTileSpecs())
+
+            assertThat(loadTilesForUser(0)).isNull()
+        }
+
     private fun getDefaultTileSpecs(): List<TileSpec> {
         return QSHost.getDefaultSpecs(context.resources).map(TileSpec::create)
     }
@@ -360,6 +386,7 @@
 
     companion object {
         private const val DEFAULT_TILES = "a,b,c"
+        private const val RETAIL_TILES = "d"
         private const val SETTING = Settings.Secure.QS_TILES
 
         private fun String.toTileSpecs(): List<TileSpec> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index e106741..41545fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -30,6 +30,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.testing.TestableResources;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
@@ -94,14 +95,18 @@
     private RotationLockController mController;
     private TestableLooper mTestableLooper;
     private RotationLockTile mLockTile;
+    private TestableResources mTestableResources;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
+        mTestableResources = mContext.getOrCreateTestableResources();
 
         when(mHost.getContext()).thenReturn(mContext);
         when(mHost.getUserContext()).thenReturn(mContext);
+        mTestableResources.addOverride(com.android.internal.R.bool.config_allowRotationResolver,
+                true);
 
         mController = new RotationLockControllerImpl(mRotationPolicyWrapper,
                 mDeviceStateRotationLockSettingController, DEFAULT_SETTINGS);
@@ -208,6 +213,32 @@
     }
 
     @Test
+    public void testSecondaryString_rotationResolverDisabled_isEmpty() {
+        mTestableResources.addOverride(com.android.internal.R.bool.config_allowRotationResolver,
+                false);
+        mLockTile = new RotationLockTile(
+                mHost,
+                mUiEventLogger,
+                mTestableLooper.getLooper(),
+                new Handler(mTestableLooper.getLooper()),
+                new FalsingManagerFake(),
+                mMetricsLogger,
+                mStatusBarStateController,
+                mActivityStarter,
+                mQSLogger,
+                mController,
+                mPrivacyManager,
+                mBatteryController,
+                new FakeSettings()
+        );
+
+        mLockTile.refreshState();
+        mTestableLooper.processAllMessages();
+
+        assertEquals("", mLockTile.getState().secondaryLabel.toString());
+    }
+
+    @Test
     public void testIcon_whenDisabled_isOffState() {
         QSTile.BooleanState state = new QSTile.BooleanState();
         disableAutoRotation();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
new file mode 100644
index 0000000..d7682b2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.retail.data.repository
+
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class RetailModeSettingsRepositoryTest : SysuiTestCase() {
+
+    private val globalSettings = FakeSettings()
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private val underTest =
+        RetailModeSettingsRepository(
+            globalSettings,
+            backgroundDispatcher = testDispatcher,
+            scope = testScope.backgroundScope,
+        )
+
+    @Test
+    fun retailMode_defaultFalse() =
+        testScope.runTest {
+            val value by collectLastValue(underTest.retailMode)
+
+            assertThat(value).isFalse()
+            assertThat(underTest.inRetailMode).isFalse()
+        }
+
+    @Test
+    fun retailMode_false() =
+        testScope.runTest {
+            val value by collectLastValue(underTest.retailMode)
+
+            globalSettings.putInt(SETTING, 0)
+
+            assertThat(value).isFalse()
+            assertThat(underTest.inRetailMode).isFalse()
+        }
+
+    @Test
+    fun retailMode_true() =
+        testScope.runTest {
+            val value by collectLastValue(underTest.retailMode)
+
+            globalSettings.putInt(SETTING, 1)
+
+            assertThat(value).isTrue()
+            assertThat(underTest.inRetailMode).isTrue()
+        }
+
+    companion object {
+        private const val SETTING = Settings.Global.DEVICE_DEMO_MODE
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
new file mode 100644
index 0000000..8f13169
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.retail.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.retail.data.repository.FakeRetailModeRepository
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class RetailModeInteractorImplTest : SysuiTestCase() {
+
+    private val retailModeRepository = FakeRetailModeRepository()
+
+    private val underTest = RetailModeInteractorImpl(retailModeRepository)
+
+    @Test
+    fun retailMode_false() {
+        retailModeRepository.setRetailMode(false)
+
+        assertThat(underTest.isInRetailMode).isFalse()
+    }
+
+    @Test
+    fun retailMode_true() {
+        retailModeRepository.setRetailMode(true)
+
+        assertThat(underTest.isInRetailMode).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index a1d78cb..7c30843b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
@@ -94,6 +93,7 @@
         doReturn(mContext.getUserId()).when(mRecordingService).getUserId();
         doReturn(mContext.getPackageName()).when(mRecordingService).getPackageName();
         doReturn(mContext.getContentResolver()).when(mRecordingService).getContentResolver();
+        doReturn(mContext.getResources()).when(mRecordingService).getResources();
 
         // Mock notifications
         doNothing().when(mRecordingService).createRecordingNotification();
@@ -101,7 +101,7 @@
         doReturn(mNotification).when(mRecordingService).createSaveNotification(any());
         doNothing().when(mRecordingService).createErrorNotification();
         doNothing().when(mRecordingService).showErrorToast(anyInt());
-        doNothing().when(mRecordingService).stopForeground(anyBoolean());
+        doNothing().when(mRecordingService).stopForeground(anyInt());
 
         doNothing().when(mRecordingService).startForeground(anyInt(), any());
         doReturn(mScreenMediaRecorder).when(mRecordingService).getRecorder();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
index 31a33d4..cbd9dba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
@@ -30,6 +30,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -127,6 +128,9 @@
 
     @Before
     public void setUp() {
+        assumeFalse("Skip test: does not apply to watches",
+            mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
+
         MockitoAnnotations.initMocks(this);
         mMainHandler = mContext.getMainThreadHandler();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 1edc63c..9a8ec88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -151,6 +151,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
+import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ScrimController;
@@ -158,6 +159,7 @@
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.phone.TapAgainViewController;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -239,6 +241,7 @@
     @Mock protected KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent;
     @Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController;
     @Mock protected KeyguardStatusBarViewController mKeyguardStatusBarViewController;
+    @Mock protected LightBarController mLightBarController;
     @Mock protected NotificationStackScrollLayoutController
             mNotificationStackScrollLayoutController;
     @Mock protected NotificationShadeDepthController mNotificationShadeDepthController;
@@ -306,6 +309,7 @@
     @Mock protected ActivityStarter mActivityStarter;
     @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     @Mock protected ShadeRepository mShadeRepository;
+    @Mock private CastController mCastController;
 
     protected final int mMaxUdfpsBurnInOffsetY = 5;
     protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
@@ -653,6 +657,7 @@
                 mNotificationRemoteInputManager,
                 mShadeExpansionStateManager,
                 mStatusBarKeyguardViewManager,
+                mLightBarController,
                 mNotificationStackScrollLayoutController,
                 mLockscreenShadeTransitionController,
                 mNotificationShadeDepthController,
@@ -675,7 +680,8 @@
                 mInteractionJankMonitor,
                 mShadeLog,
                 mKeyguardFaceAuthInteractor,
-                mShadeRepository
+                mShadeRepository,
+                mCastController
         );
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
new file mode 100644
index 0000000..d4751c8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableResources
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.fragments.FragmentService
+import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class NotificationsQSContainerControllerTest : SysuiTestCase() {
+
+    @Mock lateinit var view: NotificationsQuickSettingsContainer
+    @Mock lateinit var navigationModeController: NavigationModeController
+    @Mock lateinit var overviewProxyService: OverviewProxyService
+    @Mock lateinit var shadeHeaderController: ShadeHeaderController
+    @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+    @Mock lateinit var fragmentService: FragmentService
+
+    lateinit var underTest: NotificationsQSContainerController
+
+    private lateinit var fakeResources: TestableResources
+
+    private val delayableExecutor: DelayableExecutor = FakeExecutor(FakeSystemClock())
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        fakeResources = TestableResources(context.resources)
+
+        whenever(view.resources).thenReturn(fakeResources.resources)
+
+        underTest =
+            NotificationsQSContainerController(
+                view,
+                navigationModeController,
+                overviewProxyService,
+                shadeHeaderController,
+                shadeExpansionStateManager,
+                fragmentService,
+                delayableExecutor,
+            )
+    }
+
+    @Test
+    fun testSmallScreen_updateResources_splitShadeHeightIsSet() {
+        with(fakeResources) {
+            addOverride(R.bool.config_use_large_screen_shade_header, false)
+            addOverride(R.dimen.qs_header_height, 1)
+            addOverride(R.dimen.large_screen_shade_header_height, 2)
+        }
+
+        underTest.updateResources()
+
+        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+        verify(view).applyConstraints(capture(captor))
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(1)
+    }
+
+    @Test
+    fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
+        with(fakeResources) {
+            addOverride(R.bool.config_use_large_screen_shade_header, true)
+            addOverride(R.dimen.qs_header_height, 1)
+            addOverride(R.dimen.large_screen_shade_header_height, 2)
+        }
+
+        underTest.updateResources()
+
+        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+        verify(view).applyConstraints(capture(captor))
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(2)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
index 8a9161e..1cf3873 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
@@ -83,10 +83,12 @@
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
+import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import dagger.Lazy;
@@ -132,6 +134,7 @@
     @Mock private PulseExpansionHandler mPulseExpansionHandler;
     @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager;
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    @Mock private LightBarController mLightBarController;
     @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
     @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     @Mock private NotificationShadeDepthController mNotificationShadeDepthController;
@@ -155,6 +158,7 @@
     @Mock private ShadeLogger mShadeLogger;
     @Mock private DumpManager mDumpManager;
     @Mock private UiEventLogger mUiEventLogger;
+    @Mock private CastController mCastController;
 
     private SysuiStatusBarStateController mStatusBarStateController;
 
@@ -221,6 +225,7 @@
                 mNotificationRemoteInputManager,
                 mShadeExpansionStateManager,
                 mStatusBarKeyguardViewManager,
+                mLightBarController,
                 mNotificationStackScrollLayoutController,
                 mLockscreenShadeTransitionController,
                 mNotificationShadeDepthController,
@@ -243,7 +248,8 @@
                 mInteractionJankMonitor,
                 mShadeLogger,
                 mock(KeyguardFaceAuthInteractor.class),
-                mock(ShadeRepository.class)
+                mock(ShadeRepository.class),
+                mCastController
         );
 
         mFragmentListener = mQsController.getQsFragmentListener();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index 9fe75ab..20da8a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -33,9 +33,9 @@
 import androidx.constraintlayout.motion.widget.MotionLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt
new file mode 100644
index 0000000..1a0e932
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.condition
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.condition.Condition.START_EAGERLY
+import com.android.systemui.shared.condition.Condition.START_LAZILY
+import com.android.systemui.shared.condition.Condition.START_WHEN_NEEDED
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CombinedConditionTest : SysuiTestCase() {
+
+    class FakeCondition
+    constructor(
+        scope: CoroutineScope,
+        initialValue: Boolean?,
+        overriding: Boolean = false,
+        @StartStrategy private val startStrategy: Int = START_WHEN_NEEDED,
+    ) : Condition(scope, initialValue, overriding) {
+        private var _started = false
+        val started: Boolean
+            get() = _started
+
+        override fun start() {
+            _started = true
+        }
+
+        override fun stop() {
+            _started = false
+        }
+
+        override fun getStartStrategy(): Int {
+            return startStrategy
+        }
+
+        fun setValue(value: Boolean?) {
+            value?.also { updateCondition(value) } ?: clearCondition()
+        }
+    }
+
+    @Test
+    fun testOrOperatorWithMixedConditions() = runSelfCancelingTest {
+        val startWhenNeededCondition =
+            FakeCondition(scope = this, initialValue = false, startStrategy = START_WHEN_NEEDED)
+        val eagerCondition =
+            FakeCondition(scope = this, initialValue = false, startStrategy = START_EAGERLY)
+        val lazyCondition =
+            FakeCondition(scope = this, initialValue = false, startStrategy = START_LAZILY)
+
+        val combinedCondition =
+            CombinedCondition(
+                scope = this,
+                conditions =
+                    listOf(
+                        eagerCondition,
+                        lazyCondition,
+                        startWhenNeededCondition,
+                    ),
+                operand = Evaluator.OP_OR
+            )
+
+        val callback = Condition.Callback {}
+        combinedCondition.addCallback(callback)
+
+        assertThat(combinedCondition.isConditionMet).isFalse()
+        assertThat(eagerCondition.started).isTrue()
+        assertThat(lazyCondition.started).isTrue()
+        assertThat(startWhenNeededCondition.started).isTrue()
+
+        eagerCondition.setValue(true)
+        assertThat(combinedCondition.isConditionMet).isTrue()
+        assertThat(eagerCondition.started).isTrue()
+        assertThat(lazyCondition.started).isTrue()
+        assertThat(startWhenNeededCondition.started).isFalse()
+
+        startWhenNeededCondition.setValue(true)
+        assertThat(combinedCondition.isConditionMet).isTrue()
+        assertThat(eagerCondition.started).isTrue()
+        assertThat(lazyCondition.started).isTrue()
+        assertThat(startWhenNeededCondition.started).isFalse()
+
+        startWhenNeededCondition.setValue(false)
+        eagerCondition.setValue(false)
+        assertThat(combinedCondition.isConditionMet).isFalse()
+        assertThat(eagerCondition.started).isTrue()
+        assertThat(lazyCondition.started).isTrue()
+        assertThat(startWhenNeededCondition.started).isTrue()
+    }
+
+    @Test
+    fun testAndOperatorWithMixedConditions() = runSelfCancelingTest {
+        val startWhenNeededCondition =
+            FakeCondition(scope = this, initialValue = false, startStrategy = START_WHEN_NEEDED)
+        val eagerCondition =
+            FakeCondition(scope = this, initialValue = false, startStrategy = START_EAGERLY)
+        val lazyCondition =
+            FakeCondition(scope = this, initialValue = false, startStrategy = START_LAZILY)
+
+        val combinedCondition =
+            CombinedCondition(
+                scope = this,
+                conditions =
+                    listOf(
+                        startWhenNeededCondition,
+                        lazyCondition,
+                        eagerCondition,
+                    ),
+                operand = Evaluator.OP_AND
+            )
+
+        val callback = Condition.Callback {}
+        combinedCondition.addCallback(callback)
+
+        assertThat(combinedCondition.isConditionMet).isFalse()
+        assertThat(eagerCondition.started).isTrue()
+        assertThat(lazyCondition.started).isFalse()
+        assertThat(startWhenNeededCondition.started).isFalse()
+
+        eagerCondition.setValue(true)
+        assertThat(combinedCondition.isConditionMet).isFalse()
+        assertThat(eagerCondition.started).isTrue()
+        assertThat(startWhenNeededCondition.started).isTrue()
+        assertThat(lazyCondition.started).isFalse()
+
+        startWhenNeededCondition.setValue(true)
+        assertThat(combinedCondition.isConditionMet).isFalse()
+        assertThat(eagerCondition.started).isTrue()
+        assertThat(startWhenNeededCondition.started).isFalse()
+        assertThat(lazyCondition.started).isTrue()
+
+        startWhenNeededCondition.setValue(false)
+        assertThat(combinedCondition.isConditionMet).isFalse()
+        assertThat(eagerCondition.started).isTrue()
+        assertThat(startWhenNeededCondition.started).isFalse()
+        assertThat(lazyCondition.started).isTrue()
+
+        startWhenNeededCondition.setValue(true)
+        lazyCondition.setValue(true)
+        assertThat(combinedCondition.isConditionMet).isTrue()
+        assertThat(eagerCondition.started).isTrue()
+        assertThat(startWhenNeededCondition.started).isTrue()
+        assertThat(lazyCondition.started).isTrue()
+    }
+
+    @Test
+    fun testAndOperatorWithStartWhenNeededConditions() = runSelfCancelingTest {
+        val conditions =
+            0.rangeTo(2)
+                .map {
+                    FakeCondition(
+                        scope = this,
+                        initialValue = false,
+                        startStrategy = START_WHEN_NEEDED
+                    )
+                }
+                .toList()
+
+        val combinedCondition =
+            CombinedCondition(scope = this, conditions = conditions, operand = Evaluator.OP_AND)
+
+        val callback = Condition.Callback {}
+        combinedCondition.addCallback(callback)
+        assertThat(combinedCondition.isConditionMet).isFalse()
+        // Only the first condition should be started
+        assertThat(areStarted(conditions)).containsExactly(true, false, false).inOrder()
+
+        conditions[0].setValue(true)
+        assertThat(combinedCondition.isConditionMet).isFalse()
+        assertThat(areStarted(conditions)).containsExactly(false, true, false).inOrder()
+
+        conditions[1].setValue(true)
+        assertThat(combinedCondition.isConditionMet).isFalse()
+        assertThat(areStarted(conditions)).containsExactly(false, false, true).inOrder()
+
+        conditions[2].setValue(true)
+        assertThat(combinedCondition.isConditionMet).isTrue()
+        assertThat(areStarted(conditions)).containsExactly(true, true, true).inOrder()
+
+        conditions[0].setValue(false)
+        assertThat(combinedCondition.isConditionMet).isFalse()
+        assertThat(areStarted(conditions)).containsExactly(true, false, false).inOrder()
+    }
+
+    @Test
+    fun testOrOperatorWithStartWhenNeededConditions() = runSelfCancelingTest {
+        val conditions =
+            0.rangeTo(2)
+                .map {
+                    FakeCondition(
+                        scope = this,
+                        initialValue = false,
+                        startStrategy = START_WHEN_NEEDED
+                    )
+                }
+                .toList()
+
+        val combinedCondition =
+            CombinedCondition(scope = this, conditions = conditions, operand = Evaluator.OP_OR)
+
+        val callback = Condition.Callback {}
+        combinedCondition.addCallback(callback)
+        // Default is to monitor all conditions when false
+        assertThat(combinedCondition.isConditionMet).isFalse()
+        assertThat(areStarted(conditions)).containsExactly(true, true, true).inOrder()
+
+        // Condition 2 is true, so we should only monitor condition 2
+        conditions[1].setValue(true)
+        assertThat(combinedCondition.isConditionMet).isTrue()
+        assertThat(areStarted(conditions)).containsExactly(false, true, false).inOrder()
+
+        // Condition 2 becomes false, so we go back to monitoring all conditions
+        conditions[1].setValue(false)
+        assertThat(combinedCondition.isConditionMet).isFalse()
+        assertThat(areStarted(conditions)).containsExactly(true, true, true).inOrder()
+
+        // Condition 3 becomes true, so we only monitor condition 3
+        conditions[2].setValue(true)
+        assertThat(combinedCondition.isConditionMet).isTrue()
+        assertThat(areStarted(conditions)).containsExactly(false, false, true).inOrder()
+
+        // Condition 2 becomes true, but we are still only monitoring condition 3
+        conditions[1].setValue(true)
+        assertThat(combinedCondition.isConditionMet).isTrue()
+        assertThat(areStarted(conditions)).containsExactly(false, false, true).inOrder()
+
+        // Condition 3 becomes false, so we should now only be monitoring condition 2
+        conditions[2].setValue(false)
+        assertThat(combinedCondition.isConditionMet).isTrue()
+        assertThat(areStarted(conditions)).containsExactly(false, true, false).inOrder()
+    }
+
+    @Test
+    fun testRemovingCallbackWillStopMonitoringAllConditions() = runSelfCancelingTest {
+        val conditions =
+            0.rangeTo(2)
+                .map {
+                    FakeCondition(
+                        scope = this,
+                        initialValue = false,
+                        startStrategy = START_WHEN_NEEDED
+                    )
+                }
+                .toList()
+
+        val combinedCondition =
+            CombinedCondition(scope = this, conditions = conditions, operand = Evaluator.OP_OR)
+
+        val callback = Condition.Callback {}
+        combinedCondition.addCallback(callback)
+        assertThat(areStarted(conditions)).containsExactly(true, true, true)
+
+        combinedCondition.removeCallback(callback)
+        assertThat(areStarted(conditions)).containsExactly(false, false, false)
+    }
+
+    @Test
+    fun testOverridingConditionSkipsOtherConditions() = runSelfCancelingTest {
+        val startWhenNeededCondition =
+            FakeCondition(scope = this, initialValue = false, startStrategy = START_WHEN_NEEDED)
+        val eagerCondition =
+            FakeCondition(scope = this, initialValue = false, startStrategy = START_EAGERLY)
+        val lazyCondition =
+            FakeCondition(scope = this, initialValue = false, startStrategy = START_LAZILY)
+        val overridingCondition1 =
+            FakeCondition(scope = this, initialValue = null, overriding = true)
+        val overridingCondition2 =
+            FakeCondition(scope = this, initialValue = null, overriding = true)
+
+        val combinedCondition =
+            CombinedCondition(
+                scope = this,
+                conditions =
+                    listOf(
+                        eagerCondition,
+                        overridingCondition1,
+                        lazyCondition,
+                        startWhenNeededCondition,
+                        overridingCondition2
+                    ),
+                operand = Evaluator.OP_OR
+            )
+
+        val callback = Condition.Callback {}
+        combinedCondition.addCallback(callback)
+        assertThat(combinedCondition.isConditionMet).isFalse()
+        assertThat(eagerCondition.started).isTrue()
+        assertThat(lazyCondition.started).isTrue()
+        assertThat(startWhenNeededCondition.started).isTrue()
+        assertThat(overridingCondition1.started).isTrue()
+
+        // Overriding condition is true, so we should stop monitoring all other conditions
+        overridingCondition1.setValue(true)
+        assertThat(combinedCondition.isConditionMet).isTrue()
+        assertThat(eagerCondition.started).isFalse()
+        assertThat(lazyCondition.started).isFalse()
+        assertThat(startWhenNeededCondition.started).isFalse()
+        assertThat(overridingCondition1.started).isTrue()
+        assertThat(overridingCondition2.started).isFalse()
+
+        // Overriding condition is false, so we should only monitor other overriding conditions
+        overridingCondition1.setValue(false)
+        eagerCondition.setValue(true)
+        assertThat(combinedCondition.isConditionMet).isFalse()
+        assertThat(eagerCondition.started).isFalse()
+        assertThat(lazyCondition.started).isFalse()
+        assertThat(startWhenNeededCondition.started).isFalse()
+        assertThat(overridingCondition1.started).isTrue()
+        assertThat(overridingCondition2.started).isTrue()
+
+        // Second overriding condition is true, condition 1 is still true
+        overridingCondition2.setValue(true)
+        assertThat(combinedCondition.isConditionMet).isTrue()
+        assertThat(eagerCondition.started).isFalse()
+        assertThat(lazyCondition.started).isFalse()
+        assertThat(startWhenNeededCondition.started).isFalse()
+        assertThat(overridingCondition1.started).isFalse()
+        assertThat(overridingCondition2.started).isTrue()
+
+        // Overriding condition is cleared, condition 1 is still true
+        overridingCondition1.setValue(null)
+        overridingCondition2.setValue(null)
+        assertThat(combinedCondition.isConditionMet).isTrue()
+        assertThat(eagerCondition.started).isTrue()
+        assertThat(lazyCondition.started).isFalse()
+        assertThat(startWhenNeededCondition.started).isFalse()
+        assertThat(overridingCondition1.started).isTrue()
+        assertThat(overridingCondition2.started).isTrue()
+    }
+
+    @Test
+    fun testAndOperatorCorrectlyHandlesUnknownValues() = runSelfCancelingTest {
+        val conditions =
+            0.rangeTo(2)
+                .map {
+                    FakeCondition(scope = this, initialValue = false, startStrategy = START_EAGERLY)
+                }
+                .toList()
+
+        val combinedCondition =
+            CombinedCondition(scope = this, conditions = conditions, operand = Evaluator.OP_AND)
+
+        val callback = Condition.Callback {}
+        combinedCondition.addCallback(callback)
+
+        conditions[0].setValue(null)
+        conditions[1].setValue(true)
+        conditions[2].setValue(false)
+        assertThat(combinedCondition.isConditionMet).isFalse()
+        assertThat(combinedCondition.isConditionSet).isTrue()
+
+        // The condition should not be set since the value is unknown
+        conditions[2].setValue(true)
+        assertThat(combinedCondition.isConditionMet).isFalse()
+        assertThat(combinedCondition.isConditionSet).isFalse()
+
+        conditions[0].setValue(true)
+        assertThat(combinedCondition.isConditionMet).isTrue()
+        assertThat(combinedCondition.isConditionSet).isTrue()
+    }
+
+    @Test
+    fun testOrOperatorCorrectlyHandlesUnknownValues() = runSelfCancelingTest {
+        val conditions =
+            0.rangeTo(2)
+                .map {
+                    FakeCondition(scope = this, initialValue = false, startStrategy = START_EAGERLY)
+                }
+                .toList()
+
+        val combinedCondition =
+            CombinedCondition(scope = this, conditions = conditions, operand = Evaluator.OP_OR)
+
+        val callback = Condition.Callback {}
+        combinedCondition.addCallback(callback)
+
+        conditions[0].setValue(null)
+        conditions[1].setValue(true)
+        conditions[2].setValue(false)
+        assertThat(combinedCondition.isConditionMet).isTrue()
+        assertThat(combinedCondition.isConditionSet).isTrue()
+
+        conditions[1].setValue(false)
+        assertThat(combinedCondition.isConditionMet).isFalse()
+        // The condition should not be set since the value is unknown
+        assertThat(combinedCondition.isConditionSet).isFalse()
+    }
+
+    @Test
+    fun testEmptyConditions() = runSelfCancelingTest {
+        for (operand in intArrayOf(Evaluator.OP_OR, Evaluator.OP_AND)) {
+            val combinedCondition =
+                CombinedCondition(
+                    scope = this,
+                    conditions = emptyList(),
+                    operand = operand,
+                )
+
+            val callback = Condition.Callback {}
+            combinedCondition.addCallback(callback)
+            assertThat(combinedCondition.isConditionMet).isFalse()
+            assertThat(combinedCondition.isConditionSet).isFalse()
+        }
+    }
+
+    private fun areStarted(conditions: List<FakeCondition>): List<Boolean> {
+        return conditions.map { it.started }
+    }
+
+    /**
+     * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
+     * is then automatically canceled and cleaned-up.
+     */
+    private fun runSelfCancelingTest(
+        block: suspend CoroutineScope.() -> Unit,
+    ) =
+        runBlocking(IMMEDIATE) {
+            val scope = CoroutineScope(coroutineContext + Job())
+            block(scope)
+            scope.cancel()
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
index 2b4a7fb..0a8210d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
@@ -32,7 +32,7 @@
     fun flowInitiallyTrue() =
         testScope.runTest {
             val flow = flowOf(true)
-            val condition = flow.toCondition(this)
+            val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
 
             runCurrent()
             assertThat(condition.isConditionSet).isFalse()
@@ -47,7 +47,7 @@
     fun flowInitiallyFalse() =
         testScope.runTest {
             val flow = flowOf(false)
-            val condition = flow.toCondition(this)
+            val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
 
             runCurrent()
             assertThat(condition.isConditionSet).isFalse()
@@ -62,7 +62,7 @@
     fun emptyFlowWithNoInitialValue() =
         testScope.runTest {
             val flow = emptyFlow<Boolean>()
-            val condition = flow.toCondition(this)
+            val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
             condition.start()
 
             runCurrent()
@@ -74,7 +74,12 @@
     fun emptyFlowWithInitialValueOfTrue() =
         testScope.runTest {
             val flow = emptyFlow<Boolean>()
-            val condition = flow.toCondition(scope = this, initialValue = true)
+            val condition =
+                flow.toCondition(
+                    scope = this,
+                    strategy = Condition.START_EAGERLY,
+                    initialValue = true
+                )
             condition.start()
 
             runCurrent()
@@ -86,7 +91,12 @@
     fun emptyFlowWithInitialValueOfFalse() =
         testScope.runTest {
             val flow = emptyFlow<Boolean>()
-            val condition = flow.toCondition(scope = this, initialValue = false)
+            val condition =
+                flow.toCondition(
+                    scope = this,
+                    strategy = Condition.START_EAGERLY,
+                    initialValue = false
+                )
             condition.start()
 
             runCurrent()
@@ -98,7 +108,7 @@
     fun conditionUpdatesWhenFlowEmitsNewValue() =
         testScope.runTest {
             val flow = MutableStateFlow(false)
-            val condition = flow.toCondition(this)
+            val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY)
             condition.start()
 
             runCurrent()
@@ -120,7 +130,7 @@
     fun stoppingConditionUnsubscribesFromFlow() =
         testScope.runTest {
             val flow = MutableSharedFlow<Boolean>()
-            val condition = flow.toCondition(this)
+            val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY)
             runCurrent()
             assertThat(flow.subscriptionCount.value).isEqualTo(0)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
index aa1636d..de5824d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
@@ -39,12 +39,15 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.Arrays;
 import java.util.HashSet;
 
+import kotlinx.coroutines.CoroutineScope;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class ConditionMonitorTest extends SysuiTestCase {
@@ -54,15 +57,18 @@
     private HashSet<Condition> mConditions;
     private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
+    @Mock
+    private CoroutineScope mScope;
+
     private Monitor mConditionMonitor;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        mCondition1 = spy(new FakeCondition());
-        mCondition2 = spy(new FakeCondition());
-        mCondition3 = spy(new FakeCondition());
+        mCondition1 = spy(new FakeCondition(mScope));
+        mCondition2 = spy(new FakeCondition(mScope));
+        mCondition3 = spy(new FakeCondition(mScope));
         mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3));
 
         mConditionMonitor = new Monitor(mExecutor);
@@ -396,7 +402,7 @@
 
     @Test
     public void unsetCondition_shouldNotAffectValue() {
-        final FakeCondition settableCondition = new FakeCondition(null, false);
+        final FakeCondition settableCondition = new FakeCondition(mScope, null, false);
         mCondition1.fakeUpdateCondition(true);
         mCondition2.fakeUpdateCondition(true);
         mCondition3.fakeUpdateCondition(true);
@@ -414,7 +420,7 @@
 
     @Test
     public void setUnsetCondition_shouldAffectValue() {
-        final FakeCondition settableCondition = new FakeCondition(null, false);
+        final FakeCondition settableCondition = new FakeCondition(mScope, null, false);
         mCondition1.fakeUpdateCondition(true);
         mCondition2.fakeUpdateCondition(true);
         mCondition3.fakeUpdateCondition(true);
@@ -443,7 +449,7 @@
 
     @Test
     public void clearingOverridingCondition_shouldBeExcluded() {
-        final FakeCondition overridingCondition = new FakeCondition(true, true);
+        final FakeCondition overridingCondition = new FakeCondition(mScope, true, true);
         mCondition1.fakeUpdateCondition(false);
         mCondition2.fakeUpdateCondition(false);
         mCondition3.fakeUpdateCondition(false);
@@ -466,7 +472,7 @@
 
     @Test
     public void settingUnsetOverridingCondition_shouldBeIncluded() {
-        final FakeCondition overridingCondition = new FakeCondition(null, true);
+        final FakeCondition overridingCondition = new FakeCondition(mScope, null, true);
         mCondition1.fakeUpdateCondition(false);
         mCondition2.fakeUpdateCondition(false);
         mCondition3.fakeUpdateCondition(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java
index 8443221..6efade9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java
@@ -34,15 +34,23 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import kotlinx.coroutines.CoroutineScope;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class ConditionTest extends SysuiTestCase {
+    @Mock
+    CoroutineScope mScope;
+
     private FakeCondition mCondition;
 
     @Before
     public void setup() {
-        mCondition = spy(new FakeCondition());
+        MockitoAnnotations.initMocks(this);
+        mCondition = spy(new FakeCondition(mScope));
     }
 
     @Test
@@ -152,168 +160,4 @@
         mCondition.clearCondition();
         assertThat(mCondition.isConditionSet()).isFalse();
     }
-
-    @Test
-    public void combineConditionsWithOr_allFalse_reportsNotMet() {
-        mCondition.fakeUpdateCondition(false);
-
-        final Condition combinedCondition = mCondition.or(
-                new FakeCondition(/* initialValue= */ false));
-
-        final Condition.Callback callback = mock(
-                Condition.Callback.class);
-        combinedCondition.addCallback(callback);
-
-        assertThat(combinedCondition.isConditionSet()).isTrue();
-        assertThat(combinedCondition.isConditionMet()).isFalse();
-        verify(callback, times(1)).onConditionChanged(combinedCondition);
-    }
-
-    @Test
-    public void combineConditionsWithOr_allTrue_reportsMet() {
-        mCondition.fakeUpdateCondition(true);
-
-        final Condition combinedCondition = mCondition.or(
-                new FakeCondition(/* initialValue= */ true));
-
-        final Condition.Callback callback = mock(
-                Condition.Callback.class);
-        combinedCondition.addCallback(callback);
-
-        assertThat(combinedCondition.isConditionSet()).isTrue();
-        assertThat(combinedCondition.isConditionMet()).isTrue();
-        verify(callback, times(1)).onConditionChanged(combinedCondition);
-    }
-
-    @Test
-    public void combineConditionsWithOr_singleTrue_reportsMet() {
-        mCondition.fakeUpdateCondition(false);
-
-        final Condition combinedCondition = mCondition.or(
-                new FakeCondition(/* initialValue= */ true));
-
-        final Condition.Callback callback = mock(
-                Condition.Callback.class);
-        combinedCondition.addCallback(callback);
-
-        assertThat(combinedCondition.isConditionSet()).isTrue();
-        assertThat(combinedCondition.isConditionMet()).isTrue();
-        verify(callback, times(1)).onConditionChanged(combinedCondition);
-    }
-
-    @Test
-    public void combineConditionsWithOr_unknownAndTrue_reportsMet() {
-        mCondition.fakeUpdateCondition(true);
-
-        // Combine with an unset condition.
-        final Condition combinedCondition = mCondition.or(
-                new FakeCondition(/* initialValue= */ null));
-
-        final Condition.Callback callback = mock(
-                Condition.Callback.class);
-        combinedCondition.addCallback(callback);
-
-        assertThat(combinedCondition.isConditionSet()).isTrue();
-        assertThat(combinedCondition.isConditionMet()).isTrue();
-        verify(callback, times(1)).onConditionChanged(combinedCondition);
-    }
-
-    @Test
-    public void combineConditionsWithOr_unknownAndFalse_reportsNotMet() {
-        mCondition.fakeUpdateCondition(false);
-
-        // Combine with an unset condition.
-        final Condition combinedCondition = mCondition.or(
-                new FakeCondition(/* initialValue= */ null));
-
-        final Condition.Callback callback = mock(
-                Condition.Callback.class);
-        combinedCondition.addCallback(callback);
-
-        assertThat(combinedCondition.isConditionSet()).isFalse();
-        assertThat(combinedCondition.isConditionMet()).isFalse();
-        verify(callback, never()).onConditionChanged(combinedCondition);
-    }
-
-    @Test
-    public void combineConditionsWithAnd_allFalse_reportsNotMet() {
-        mCondition.fakeUpdateCondition(false);
-
-        final Condition combinedCondition = mCondition.and(
-                new FakeCondition(/* initialValue= */ false));
-
-        final Condition.Callback callback = mock(
-                Condition.Callback.class);
-        combinedCondition.addCallback(callback);
-
-        assertThat(combinedCondition.isConditionSet()).isTrue();
-        assertThat(combinedCondition.isConditionMet()).isFalse();
-        verify(callback, times(1)).onConditionChanged(combinedCondition);
-    }
-
-    @Test
-    public void combineConditionsWithAnd_allTrue_reportsMet() {
-        mCondition.fakeUpdateCondition(true);
-
-        final Condition combinedCondition = mCondition.and(
-                new FakeCondition(/* initialValue= */ true));
-
-        final Condition.Callback callback = mock(
-                Condition.Callback.class);
-        combinedCondition.addCallback(callback);
-
-        assertThat(combinedCondition.isConditionSet()).isTrue();
-        assertThat(combinedCondition.isConditionMet()).isTrue();
-        verify(callback, times(1)).onConditionChanged(combinedCondition);
-    }
-
-    @Test
-    public void combineConditionsWithAnd_singleTrue_reportsNotMet() {
-        mCondition.fakeUpdateCondition(true);
-
-        final Condition combinedCondition = mCondition.and(
-                new FakeCondition(/* initialValue= */ false));
-
-        final Condition.Callback callback = mock(
-                Condition.Callback.class);
-        combinedCondition.addCallback(callback);
-
-        assertThat(combinedCondition.isConditionSet()).isTrue();
-        assertThat(combinedCondition.isConditionMet()).isFalse();
-        verify(callback, times(1)).onConditionChanged(combinedCondition);
-    }
-
-    @Test
-    public void combineConditionsWithAnd_unknownAndTrue_reportsNotMet() {
-        mCondition.fakeUpdateCondition(true);
-
-        // Combine with an unset condition.
-        final Condition combinedCondition = mCondition.and(
-                new FakeCondition(/* initialValue= */ null));
-
-        final Condition.Callback callback = mock(
-                Condition.Callback.class);
-        combinedCondition.addCallback(callback);
-
-        assertThat(combinedCondition.isConditionSet()).isFalse();
-        assertThat(combinedCondition.isConditionMet()).isFalse();
-        verify(callback, never()).onConditionChanged(combinedCondition);
-    }
-
-    @Test
-    public void combineConditionsWithAnd_unknownAndFalse_reportsMet() {
-        mCondition.fakeUpdateCondition(false);
-
-        // Combine with an unset condition.
-        final Condition combinedCondition = mCondition.and(
-                new FakeCondition(/* initialValue= */ null));
-
-        final Condition.Callback callback = mock(
-                Condition.Callback.class);
-        combinedCondition.addCallback(callback);
-
-        assertThat(combinedCondition.isConditionSet()).isTrue();
-        assertThat(combinedCondition.isConditionMet()).isFalse();
-        verify(callback, times(1)).onConditionChanged(combinedCondition);
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java
index 55a6d39..a325cbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java
@@ -16,21 +16,19 @@
 
 package com.android.systemui.shared.condition;
 
+import kotlinx.coroutines.CoroutineScope;
+
 /**
  * Fake implementation of {@link Condition}, and provides a way for tests to update
  * condition fulfillment.
  */
 public class FakeCondition extends Condition {
-    FakeCondition() {
-        super();
+    FakeCondition(CoroutineScope scope) {
+        super(scope);
     }
 
-    FakeCondition(Boolean initialValue) {
-        super(initialValue, false);
-    }
-
-    FakeCondition(Boolean initialValue, boolean overriding) {
-        super(initialValue, overriding);
+    FakeCondition(CoroutineScope scope, Boolean initialValue, boolean overriding) {
+        super(scope, initialValue, overriding);
     }
 
     @Override
@@ -41,6 +39,11 @@
     public void stop() {
     }
 
+    @Override
+    protected int getStartStrategy() {
+        return START_EAGERLY;
+    }
+
     public void fakeUpdateCondition(boolean isConditionMet) {
         updateCondition(isConditionMet);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
index 5fc0ffe..8cb530c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
@@ -4,7 +4,7 @@
 import android.util.DisplayMetrics
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.phone.LSShadeTransitionLogger
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index beaf300..c49f179 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -356,6 +356,7 @@
     @Test
     fun ignoreShadeBlurUntilHidden_schedulesFrame() {
         notificationShadeDepthController.blursDisabledForAppLaunch = true
+        verify(blurUtils).prepareBlur(any(), anyInt())
         verify(choreographer)
             .postFrameCallback(eq(notificationShadeDepthController.updateBlurCallback))
     }
@@ -419,6 +420,7 @@
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
         verify(notificationShadeWindowController).setBackgroundBlurRadius(eq(0))
         verify(wallpaperController).setNotificationShadeZoom(eq(1f))
+        verify(blurUtils).prepareBlur(any(), eq(0))
         verify(blurUtils).applyBlur(eq(viewRootImpl), eq(0), eq(false))
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 5431eba..c7ea09c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -70,7 +70,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index 9441d49..d5689dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -48,7 +48,7 @@
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.CarrierConfigTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index 4c1f0a8..35b9814 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -43,7 +43,7 @@
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.R;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.CarrierConfigTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
index bef9fcb..a37c386 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
@@ -19,9 +19,9 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogcatEchoTracker
 import com.android.systemui.statusbar.StatusBarState
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index a1168f8..f0abf2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -32,9 +32,9 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
+import com.android.app.animation.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.stack.AnimationFilter;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ViewState;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 2831d2f..163369f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -41,6 +41,7 @@
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -92,6 +93,7 @@
     @Mock private BiometricUnlockController mBiometricUnlockController;
     @Mock private AuthController mAuthController;
     @Mock private DozeHost.Callback mCallback;
+    @Mock private BurnInInteractor mBurnInInteractor;
 
     @Before
     public void setup() {
@@ -102,7 +104,8 @@
                 () -> mAssistManager, mDozeScrimController,
                 mKeyguardUpdateMonitor, mPulseExpansionHandler,
                 mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
-                mAuthController, mNotificationIconAreaController);
+                mAuthController, mNotificationIconAreaController,
+                mBurnInInteractor);
 
         mDozeServiceHost.initialize(
                 mCentralSurfaces,
@@ -213,4 +216,12 @@
         assertFalse(mDozeServiceHost.isPulsePending());
         verify(mDozeScrimController).pulseOutNow();
     }
+    @Test
+    public void dozeTimeTickSentTBurnInInteractor() {
+        // WHEN dozeTimeTick
+        mDozeServiceHost.dozeTimeTick();
+
+        // THEN burnInInteractor's dozeTimeTick is updated
+        verify(mBurnInInteractor).dozeTimeTick();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 529519a..a501556 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -22,21 +22,31 @@
 
 import static junit.framework.Assert.assertTrue;
 
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.graphics.Color;
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.annotation.ColorInt;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.colorextraction.ColorExtractor.GradientColors;
+import com.android.internal.util.ContrastColorUtil;
 import com.android.internal.view.AppearanceRegion;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -53,13 +63,25 @@
 @TestableLooper.RunWithLooper
 public class LightBarControllerTest extends SysuiTestCase {
 
+    private static final GradientColors COLORS_LIGHT = makeColors(Color.WHITE);
+    private static final GradientColors COLORS_DARK = makeColors(Color.BLACK);
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     private LightBarTransitionsController mLightBarTransitionsController;
+    private LightBarTransitionsController mNavBarController;
     private SysuiDarkIconDispatcher mStatusBarIconController;
     private LightBarController mLightBarController;
 
+    /** Allow testing with NEW_LIGHT_BAR_LOGIC flag in different states */
+    protected boolean testNewLightBarLogic() {
+        return false;
+    }
+
     @Before
     public void setup() {
+        mFeatureFlags.set(Flags.NEW_LIGHT_BAR_LOGIC, testNewLightBarLogic());
         mStatusBarIconController = mock(SysuiDarkIconDispatcher.class);
+        mNavBarController = mock(LightBarTransitionsController.class);
+        when(mNavBarController.supportsIconTintForNavMode(anyInt())).thenReturn(true);
         mLightBarTransitionsController = mock(LightBarTransitionsController.class);
         when(mStatusBarIconController.getTransitionsController()).thenReturn(
                 mLightBarTransitionsController);
@@ -68,10 +90,19 @@
                 mStatusBarIconController,
                 mock(BatteryController.class),
                 mock(NavigationModeController.class),
+                mFeatureFlags,
                 mock(DumpManager.class),
                 new FakeDisplayTracker(mContext));
     }
 
+    private static GradientColors makeColors(@ColorInt int bgColor) {
+        GradientColors colors = new GradientColors();
+        colors.setMainColor(bgColor);
+        colors.setSecondaryColor(bgColor);
+        colors.setSupportsDarkText(!ContrastColorUtil.isColorDark(bgColor));
+        return colors;
+    }
+
     @Test
     public void testOnStatusBarAppearanceChanged_multipleStacks_allStacksLight() {
         final Rect firstBounds = new Rect(0, 0, 1, 1);
@@ -177,4 +208,54 @@
                 false /* navbarColorManagedByIme */);
         verify(mLightBarTransitionsController).setIconsDark(eq(false), anyBoolean());
     }
+
+    @Test
+    public void validateNavBarChangesUpdateIcons() {
+        assumeTrue(testNewLightBarLogic());  // Only run in the new suite
+
+        // On the launcher in dark mode buttons are light
+        mLightBarController.setScrimState(ScrimState.UNLOCKED, 0f, COLORS_DARK);
+        mLightBarController.onNavigationBarAppearanceChanged(
+                0, /* nbModeChanged = */ true,
+                MODE_TRANSPARENT, /* navbarColorManagedByIme = */ false);
+        verifyNavBarIconsUnchanged(); // no changes yet; not attached
+
+        // Initial state is set when controller is set
+        mLightBarController.setNavigationBar(mNavBarController);
+        verifyNavBarIconsDarkSetTo(false);
+
+        // Changing the color of the transparent scrim has no effect
+        mLightBarController.setScrimState(ScrimState.UNLOCKED, 0f, COLORS_LIGHT);
+        verifyNavBarIconsUnchanged(); // still light
+
+        // Showing the notification shade with white scrim requires dark icons
+        mLightBarController.setScrimState(ScrimState.UNLOCKED, 1f, COLORS_LIGHT);
+        verifyNavBarIconsDarkSetTo(true);
+
+        // Expanded QS always provides a black background, so icons become light again
+        mLightBarController.setQsExpanded(true);
+        verifyNavBarIconsDarkSetTo(false);
+
+        // Tapping the QS tile to change to dark theme has no effect in this state
+        mLightBarController.setScrimState(ScrimState.UNLOCKED, 1f, COLORS_DARK);
+        verifyNavBarIconsUnchanged(); // still light
+
+        // collapsing QS in dark mode doesn't affect button color
+        mLightBarController.setQsExpanded(false);
+        verifyNavBarIconsUnchanged(); // still light
+
+        // Closing the shade has no affect
+        mLightBarController.setScrimState(ScrimState.UNLOCKED, 0f, COLORS_DARK);
+        verifyNavBarIconsUnchanged(); // still light
+    }
+
+    private void verifyNavBarIconsUnchanged() {
+        verify(mNavBarController, never()).setIconsDark(anyBoolean(), anyBoolean());
+    }
+
+    private void verifyNavBarIconsDarkSetTo(boolean iconsDark) {
+        verify(mNavBarController).setIconsDark(eq(iconsDark), anyBoolean());
+        verify(mNavBarController, never()).setIconsDark(eq(!iconsDark), anyBoolean());
+        clearInvocations(mNavBarController);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt
new file mode 100644
index 0000000..d9c2cfa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.flags.Flags.NEW_LIGHT_BAR_LOGIC
+
+/**
+ * This file only needs to live as long as [NEW_LIGHT_BAR_LOGIC] does. When we delete that flag, we
+ * can roll this back into the old test.
+ */
+@SmallTest
+class LightBarControllerWithNewLogicTest : LightBarControllerTest() {
+    override fun testNewLightBarLogic(): Boolean = true
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
index 3a0a94d..9bc49ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogcatEchoTracker
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
@@ -43,15 +43,39 @@
     fun logDisableFlagChange_bufferHasStates() {
         val state = DisableFlagsLogger.DisableState(0, 1)
 
-        logger.logDisableFlagChange(state, state)
+        logger.logDisableFlagChange(state)
 
         val stringWriter = StringWriter()
         buffer.dump(PrintWriter(stringWriter), tailLength = 0)
         val actualString = stringWriter.toString()
-        val expectedLogString = disableFlagsLogger.getDisableFlagsString(
-            old = null, new = state, newAfterLocalModification = state
-        )
+        val expectedLogString =
+            disableFlagsLogger.getDisableFlagsString(
+                old = null,
+                new = state,
+                newAfterLocalModification = null,
+            )
 
         assertThat(actualString).contains(expectedLogString)
     }
+
+    @Test
+    fun logVisibilityModel_bufferCorrect() {
+        logger.logVisibilityModel(
+            StatusBarVisibilityModel(
+                showClock = false,
+                showNotificationIcons = true,
+                showOngoingCallChip = false,
+                showSystemInfo = true,
+            )
+        )
+
+        val stringWriter = StringWriter()
+        buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+        val actualString = stringWriter.toString()
+
+        assertThat(actualString).contains("showClock=false")
+        assertThat(actualString).contains("showNotificationIcons=true")
+        assertThat(actualString).contains("showOngoingCallChip=false")
+        assertThat(actualString).contains("showSystemInfo=true")
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 2a3c775..2176992 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -16,6 +16,8 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
+import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
 import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
 import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
 import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
@@ -52,9 +54,9 @@
 import com.android.systemui.SysuiBaseFragmentTest;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogcatEchoTracker;
 import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.log.LogBuffer;
-import com.android.systemui.plugins.log.LogcatEchoTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.ShadeViewController;
@@ -93,6 +95,7 @@
 public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
 
     private NotificationIconAreaController mMockNotificationAreaController;
+    private ShadeExpansionStateManager mShadeExpansionStateManager;
     private View mNotificationAreaInner;
     private OngoingCallController mOngoingCallController;
     private SystemStatusAnimationScheduler mAnimationScheduler;
@@ -173,6 +176,10 @@
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+
+        fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
+
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
     }
 
     @Test
@@ -278,6 +285,10 @@
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
         Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE));
+
+        fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
+
+        Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE));
     }
 
     @Test
@@ -291,6 +302,70 @@
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
         assertEquals(View.VISIBLE, getClockView().getVisibility());
+
+        fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_CLOCK, 0, false);
+
+        assertEquals(View.GONE, getClockView().getVisibility());
+    }
+
+    @Test
+    public void disable_shadeOpenAndShouldHide_everythingHidden() {
+        CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+        // WHEN the shade is open and configured to hide the status bar icons
+        mShadeExpansionStateManager.updateState(STATE_OPEN);
+        when(mShadeViewController.shouldHideStatusBarIconsWhenExpanded()).thenReturn(true);
+
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+        // THEN all views are hidden
+        assertEquals(View.INVISIBLE, getClockView().getVisibility());
+        Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE));
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
+    }
+
+    @Test
+    public void disable_shadeOpenButNotShouldHide_everythingShown() {
+        CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+        // WHEN the shade is open but *not* configured to hide the status bar icons
+        mShadeExpansionStateManager.updateState(STATE_OPEN);
+        when(mShadeViewController.shouldHideStatusBarIconsWhenExpanded()).thenReturn(false);
+
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+        // THEN all views are shown
+        assertEquals(View.VISIBLE, getClockView().getVisibility());
+        Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE));
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+    }
+
+    /** Regression test for b/279790651. */
+    @Test
+    public void disable_shadeOpenAndShouldHide_thenShadeNotOpenAndDozingUpdate_everythingShown() {
+        CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+        // WHEN the shade is open and configured to hide the status bar icons
+        mShadeExpansionStateManager.updateState(STATE_OPEN);
+        when(mShadeViewController.shouldHideStatusBarIconsWhenExpanded()).thenReturn(true);
+
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+        // THEN all views are hidden
+        assertEquals(View.INVISIBLE, getClockView().getVisibility());
+        Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE));
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
+
+        // WHEN the shade is updated to no longer be open
+        mShadeExpansionStateManager.updateState(STATE_CLOSED);
+
+        // AND we internally request an update via dozing change
+        fragment.onDozingChanged(true);
+
+        // THEN all views are shown
+        assertEquals(View.VISIBLE, getClockView().getVisibility());
+        Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE));
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
     @Test
@@ -323,7 +398,6 @@
         assertEquals(View.VISIBLE,
                 mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
         Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE));
-
     }
 
     @Test
@@ -356,20 +430,26 @@
     public void disable_ongoingCallEnded_chipHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
-        when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
-
         // Ongoing call started
+        when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
         assertEquals(View.VISIBLE,
                 mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
 
         // Ongoing call ended
         when(mOngoingCallController.hasOngoingCall()).thenReturn(false);
-
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
         assertEquals(View.GONE,
                 mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+
+        // Ongoing call started
+        when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+        assertEquals(View.VISIBLE,
+                mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
     }
 
     @Test
@@ -494,6 +574,8 @@
         when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
         mSecureSettings = mock(SecureSettings.class);
 
+        mShadeExpansionStateManager = new ShadeExpansionStateManager();
+
         setUpNotificationIconAreaController();
         return new CollapsedStatusBarFragment(
                 mStatusBarFragmentComponentFactory,
@@ -501,7 +583,7 @@
                 mAnimationScheduler,
                 mLocationPublisher,
                 mMockNotificationAreaController,
-                new ShadeExpansionStateManager(),
+                mShadeExpansionStateManager,
                 mock(FeatureFlags.class),
                 mStatusBarIconController,
                 mIconManagerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt
new file mode 100644
index 0000000..8e789cb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.fragment
+
+import android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS
+import android.app.StatusBarManager.DISABLE_CLOCK
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS
+import android.app.StatusBarManager.DISABLE_ONGOING_CALL_CHIP
+import android.app.StatusBarManager.DISABLE_SYSTEM_INFO
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.fragment.StatusBarVisibilityModel.Companion.createDefaultModel
+import com.android.systemui.statusbar.phone.fragment.StatusBarVisibilityModel.Companion.createModelFromFlags
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class StatusBarVisibilityModelTest : SysuiTestCase() {
+    @Test
+    fun createDefaultModel_everythingEnabled() {
+        val result = createDefaultModel()
+
+        val expected =
+            StatusBarVisibilityModel(
+                showClock = true,
+                showNotificationIcons = true,
+                showOngoingCallChip = true,
+                showSystemInfo = true,
+            )
+
+        assertThat(result).isEqualTo(expected)
+    }
+
+    @Test
+    fun createModelFromFlags_clockNotDisabled_showClockTrue() {
+        val result = createModelFromFlags(disabled1 = 0, disabled2 = 0)
+
+        assertThat(result.showClock).isTrue()
+    }
+
+    @Test
+    fun createModelFromFlags_clockDisabled_showClockFalse() {
+        val result = createModelFromFlags(disabled1 = DISABLE_CLOCK, disabled2 = 0)
+
+        assertThat(result.showClock).isFalse()
+    }
+
+    @Test
+    fun createModelFromFlags_notificationIconsNotDisabled_showNotificationIconsTrue() {
+        val result = createModelFromFlags(disabled1 = 0, disabled2 = 0)
+
+        assertThat(result.showNotificationIcons).isTrue()
+    }
+
+    @Test
+    fun createModelFromFlags_notificationIconsDisabled_showNotificationIconsFalse() {
+        val result = createModelFromFlags(disabled1 = DISABLE_NOTIFICATION_ICONS, disabled2 = 0)
+
+        assertThat(result.showNotificationIcons).isFalse()
+    }
+
+    @Test
+    fun createModelFromFlags_ongoingCallChipNotDisabled_showOngoingCallChipTrue() {
+        val result = createModelFromFlags(disabled1 = 0, disabled2 = 0)
+
+        assertThat(result.showOngoingCallChip).isTrue()
+    }
+
+    @Test
+    fun createModelFromFlags_ongoingCallChipDisabled_showOngoingCallChipFalse() {
+        val result = createModelFromFlags(disabled1 = DISABLE_ONGOING_CALL_CHIP, disabled2 = 0)
+
+        assertThat(result.showOngoingCallChip).isFalse()
+    }
+
+    @Test
+    fun createModelFromFlags_systemInfoAndIconsNotDisabled_showSystemInfoTrue() {
+        val result = createModelFromFlags(disabled1 = 0, disabled2 = 0)
+
+        assertThat(result.showSystemInfo).isTrue()
+    }
+
+    @Test
+    fun createModelFromFlags_disable1SystemInfoDisabled_showSystemInfoFalse() {
+        val result = createModelFromFlags(disabled1 = DISABLE_SYSTEM_INFO, disabled2 = 0)
+
+        assertThat(result.showSystemInfo).isFalse()
+    }
+
+    @Test
+    fun createModelFromFlags_disable2SystemIconsDisabled_showSystemInfoFalse() {
+        val result = createModelFromFlags(disabled1 = 0, disabled2 = DISABLE2_SYSTEM_ICONS)
+
+        assertThat(result.showSystemInfo).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt
index 03cd94f..c43778a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.log.LogMessageImpl
+import com.android.systemui.log.LogMessageImpl
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
index db50163..b8e4306 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
@@ -1,6 +1,7 @@
 package com.android.systemui.statusbar.policy;
 
 
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -125,4 +126,20 @@
             fail("Concurrent modification exception");
         }
     }
+
+    @Test
+    public void hasConnectedCastDevice_connected() {
+        CastController.CastDevice castDevice = new CastController.CastDevice();
+        castDevice.state = CastController.CastDevice.STATE_CONNECTED;
+        mController.startCasting(castDevice);
+        assertTrue(mController.hasConnectedCastDevice());
+    }
+
+    @Test
+    public void hasConnectedCastDevice_notConnected() {
+        CastController.CastDevice castDevice = new CastController.CastDevice();
+        castDevice.state = CastController.CastDevice.STATE_CONNECTING;
+        mController.startCasting(castDevice);
+        assertTrue(mController.hasConnectedCastDevice());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index d9d8b63..3b0d512 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -585,8 +585,6 @@
         // devices.
         layout.setBaselineAligned(false);
 
-        final boolean isRtl = mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
-
         // Add smart replies
         Button previous = null;
         SmartReplyView.SmartReplies smartReplies =
@@ -606,11 +604,7 @@
             if (previous != null) {
                 ViewGroup.MarginLayoutParams lp =
                         (ViewGroup.MarginLayoutParams) previous.getLayoutParams();
-                if (isRtl) {
-                    lp.leftMargin = mSpacing;
-                } else {
-                    lp.rightMargin = mSpacing;
-                }
+                lp.setMarginEnd(mSpacing);
             }
             layout.addView(current);
             previous = current;
@@ -634,11 +628,7 @@
             if (previous != null) {
                 ViewGroup.MarginLayoutParams lp =
                         (ViewGroup.MarginLayoutParams) previous.getLayoutParams();
-                if (isRtl) {
-                    lp.leftMargin = mSpacing;
-                } else {
-                    lp.rightMargin = mSpacing;
-                }
+                lp.setMarginEnd(mSpacing);
             }
             layout.addView(current);
             previous = current;
@@ -937,8 +927,8 @@
                 .collect(Collectors.toList());
         Button singleLineButton = buttons.get(0);
         Button doubleLineButton = buttons.get(1);
-        Drawable singleLineDrawable = singleLineButton.getCompoundDrawables()[0]; // left drawable
-        Drawable doubleLineDrawable = doubleLineButton.getCompoundDrawables()[0]; // left drawable
+        Drawable singleLineDrawable = singleLineButton.getCompoundDrawablesRelative()[0]; // start
+        Drawable doubleLineDrawable = doubleLineButton.getCompoundDrawablesRelative()[0]; // start
         assertEquals(singleLineDrawable.getBounds().width(),
                      doubleLineDrawable.getBounds().width());
         assertEquals(singleLineDrawable.getBounds().height(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
index 2e66b20..4514249 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
@@ -19,9 +19,9 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogcatEchoTracker
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
index 5b431e7..0983041 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
@@ -14,6 +14,8 @@
 
 package com.android.systemui.animation
 
+import com.android.app.animation.Interpolators
+
 /** A [LaunchAnimator] to be used in tests. */
 fun fakeLaunchAnimator(): LaunchAnimator {
     return LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeRearDisplayStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeRearDisplayStateRepository.kt
new file mode 100644
index 0000000..fd91391
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeRearDisplayStateRepository.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeRearDisplayStateRepository : RearDisplayStateRepository {
+    private val _isInRearDisplayMode = MutableStateFlow<Boolean>(false)
+    override val isInRearDisplayMode: StateFlow<Boolean> = _isInRearDisplayMode.asStateFlow()
+
+    fun setIsInRearDisplayMode(isInRearDisplayMode: Boolean) {
+        _isInRearDisplayMode.value = isInRearDisplayMode
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
index 6690de8..f0dbc60 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
@@ -19,13 +19,23 @@
 
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 
 class FakeTrustRepository : TrustRepository {
     private val _isCurrentUserTrusted = MutableStateFlow(false)
     override val isCurrentUserTrusted: Flow<Boolean>
         get() = _isCurrentUserTrusted
 
+    private val _isCurrentUserActiveUnlockAvailable = MutableStateFlow(false)
+    override val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean> =
+        _isCurrentUserActiveUnlockAvailable.asStateFlow()
+
     fun setCurrentUserTrusted(trust: Boolean) {
         _isCurrentUserTrusted.value = trust
     }
+
+    fun setCurrentUserActiveUnlockAvailable(available: Boolean) {
+        _isCurrentUserActiveUnlockAvailable.value = available
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/retail/data/repository/FakeRetailModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/retail/data/repository/FakeRetailModeRepository.kt
new file mode 100644
index 0000000..75b29ca
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/retail/data/repository/FakeRetailModeRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.retail.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeRetailModeRepository : RetailModeRepository {
+
+    private val _retailMode = MutableStateFlow(false)
+    override val retailMode: StateFlow<Boolean> = _retailMode.asStateFlow()
+
+    private var _retailModeValue = false
+    override val inRetailMode: Boolean
+        get() = _retailModeValue
+
+    fun setRetailMode(value: Boolean) {
+        _retailMode.value = value
+        _retailModeValue = value
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java
index f6b24da..84ace7c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java
@@ -51,4 +51,9 @@
     public void stopCasting(CastDevice device) {
 
     }
+
+    @Override
+    public boolean hasConnectedCastDevice() {
+        return false;
+    }
 }
diff --git a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java b/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java
deleted file mode 100644
index 715697d..0000000
--- a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * 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.autofill;
-
-import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
-
-import static com.android.server.autofill.Helper.sVerbose;
-
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.AppGlobals;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.ICancellationSignal;
-import android.os.RemoteException;
-import android.service.autofill.Dataset;
-import android.service.autofill.FillResponse;
-import android.service.autofill.IFillCallback;
-import android.service.autofill.SaveInfo;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Slog;
-import android.view.autofill.AutofillId;
-import android.view.autofill.IAutoFillManagerClient;
-import android.view.inputmethod.InlineSuggestionsRequest;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.AndroidFuture;
-
-import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Maintains a client suggestions session with the
- * {@link android.view.autofill.AutofillRequestCallback} through the {@link IAutoFillManagerClient}.
- *
- */
-final class ClientSuggestionsSession {
-
-    private static final String TAG = "ClientSuggestionsSession";
-    private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 15 * DateUtils.SECOND_IN_MILLIS;
-
-    private final int mSessionId;
-    private final IAutoFillManagerClient mClient;
-    private final Handler mHandler;
-    private final ComponentName mComponentName;
-
-    private final RemoteFillService.FillServiceCallbacks mCallbacks;
-
-    private final Object mLock = new Object();
-    @GuardedBy("mLock")
-    private AndroidFuture<FillResponse> mPendingFillRequest;
-    @GuardedBy("mLock")
-    private int mPendingFillRequestId = INVALID_REQUEST_ID;
-
-    ClientSuggestionsSession(int sessionId, IAutoFillManagerClient client, Handler handler,
-            ComponentName componentName, RemoteFillService.FillServiceCallbacks callbacks) {
-        mSessionId = sessionId;
-        mClient = client;
-        mHandler = handler;
-        mComponentName = componentName;
-        mCallbacks = callbacks;
-    }
-
-    void onFillRequest(int requestId, InlineSuggestionsRequest inlineRequest, int flags) {
-        final AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
-        final AtomicReference<AndroidFuture<FillResponse>> futureRef = new AtomicReference<>();
-        final AndroidFuture<FillResponse> fillRequest = new AndroidFuture<>();
-
-        mHandler.post(() -> {
-            if (sVerbose) {
-                Slog.v(TAG, "calling onFillRequest() for id=" + requestId);
-            }
-
-            try {
-                mClient.requestFillFromClient(requestId, inlineRequest,
-                        new FillCallbackImpl(fillRequest, futureRef, cancellationSink));
-            } catch (RemoteException e) {
-                fillRequest.completeExceptionally(e);
-            }
-        });
-
-        fillRequest.orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
-        futureRef.set(fillRequest);
-
-        synchronized (mLock) {
-            mPendingFillRequest = fillRequest;
-            mPendingFillRequestId = requestId;
-        }
-
-        fillRequest.whenComplete((res, err) -> mHandler.post(() -> {
-            synchronized (mLock) {
-                mPendingFillRequest = null;
-                mPendingFillRequestId = INVALID_REQUEST_ID;
-            }
-            if (err == null) {
-                processAutofillId(res);
-                mCallbacks.onFillRequestSuccess(requestId, res,
-                        mComponentName.getPackageName(), flags);
-            } else {
-                Slog.e(TAG, "Error calling on  client fill request", err);
-                if (err instanceof TimeoutException) {
-                    dispatchCancellationSignal(cancellationSink.get());
-                    mCallbacks.onFillRequestTimeout(requestId);
-                } else if (err instanceof CancellationException) {
-                    dispatchCancellationSignal(cancellationSink.get());
-                } else {
-                    mCallbacks.onFillRequestFailure(requestId, err.getMessage());
-                }
-            }
-        }));
-    }
-
-    /**
-     * Gets the application info for the component.
-     */
-    @Nullable
-    static ApplicationInfo getAppInfo(ComponentName comp, @UserIdInt int userId) {
-        try {
-            ApplicationInfo si = AppGlobals.getPackageManager().getApplicationInfo(
-                    comp.getPackageName(),
-                    PackageManager.GET_META_DATA,
-                    userId);
-            if (si != null) {
-                return si;
-            }
-        } catch (RemoteException e) {
-        }
-        return null;
-    }
-
-    /**
-     * Gets the user-visible name of the application.
-     */
-    @Nullable
-    @GuardedBy("mLock")
-    static CharSequence getAppLabelLocked(Context context, ApplicationInfo appInfo) {
-        return appInfo == null ? null : appInfo.loadSafeLabel(
-                context.getPackageManager(), 0 /* do not ellipsize */,
-                TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
-    }
-
-    /**
-     * Gets the user-visible icon of the application.
-     */
-    @Nullable
-    @GuardedBy("mLock")
-    static Drawable getAppIconLocked(Context context, ApplicationInfo appInfo) {
-        return appInfo == null ? null : appInfo.loadIcon(context.getPackageManager());
-    }
-
-    int cancelCurrentRequest() {
-        synchronized (mLock) {
-            return mPendingFillRequest != null && mPendingFillRequest.cancel(false)
-                    ? mPendingFillRequestId
-                    : INVALID_REQUEST_ID;
-        }
-    }
-
-    /**
-     * The {@link AutofillId} which the client gets from its view is not contain the session id,
-     * but Autofill framework is using the {@link AutofillId} with a session id. So before using
-     * those ids in the Autofill framework, applies the current session id.
-     *
-     * @param res which response need to apply for a session id
-     */
-    private void processAutofillId(FillResponse res) {
-        if (res == null) {
-            return;
-        }
-
-        final List<Dataset> datasets = res.getDatasets();
-        if (datasets != null && !datasets.isEmpty()) {
-            for (int i = 0; i < datasets.size(); i++) {
-                final Dataset dataset = datasets.get(i);
-                if (dataset != null) {
-                    applySessionId(dataset.getFieldIds());
-                }
-            }
-        }
-
-        final SaveInfo saveInfo = res.getSaveInfo();
-        if (saveInfo != null) {
-            applySessionId(saveInfo.getOptionalIds());
-            applySessionId(saveInfo.getRequiredIds());
-            applySessionId(saveInfo.getSanitizerValues());
-            applySessionId(saveInfo.getTriggerId());
-        }
-    }
-
-    private void applySessionId(List<AutofillId> ids) {
-        if (ids == null || ids.isEmpty()) {
-            return;
-        }
-
-        for (int i = 0; i < ids.size(); i++) {
-            applySessionId(ids.get(i));
-        }
-    }
-
-    private void applySessionId(AutofillId[][] ids) {
-        if (ids == null) {
-            return;
-        }
-        for (int i = 0; i < ids.length; i++) {
-            applySessionId(ids[i]);
-        }
-    }
-
-    private void applySessionId(AutofillId[] ids) {
-        if (ids == null) {
-            return;
-        }
-        for (int i = 0; i < ids.length; i++) {
-            applySessionId(ids[i]);
-        }
-    }
-
-    private void applySessionId(AutofillId id) {
-        if (id == null) {
-            return;
-        }
-        id.setSessionId(mSessionId);
-    }
-
-    private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) {
-        if (signal == null) {
-            return;
-        }
-        try {
-            signal.cancel();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Error requesting a cancellation", e);
-        }
-    }
-
-    private class FillCallbackImpl extends IFillCallback.Stub {
-        final AndroidFuture<FillResponse> mFillRequest;
-        final AtomicReference<AndroidFuture<FillResponse>> mFutureRef;
-        final AtomicReference<ICancellationSignal> mCancellationSink;
-
-        FillCallbackImpl(AndroidFuture<FillResponse> fillRequest,
-                AtomicReference<AndroidFuture<FillResponse>> futureRef,
-                AtomicReference<ICancellationSignal> cancellationSink) {
-            mFillRequest = fillRequest;
-            mFutureRef = futureRef;
-            mCancellationSink = cancellationSink;
-        }
-
-        @Override
-        public void onCancellable(ICancellationSignal cancellation) {
-            AndroidFuture<FillResponse> future = mFutureRef.get();
-            if (future != null && future.isCancelled()) {
-                dispatchCancellationSignal(cancellation);
-            } else {
-                mCancellationSink.set(cancellation);
-            }
-        }
-
-        @Override
-        public void onSuccess(FillResponse response) {
-            mFillRequest.complete(response);
-        }
-
-        @Override
-        public void onFailure(int requestId, CharSequence message) {
-            String errorMessage = message == null ? "" : String.valueOf(message);
-            mFillRequest.completeExceptionally(
-                    new RuntimeException(errorMessage));
-        }
-    }
-}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java b/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java
index feae56e..b8bac61 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java
@@ -157,6 +157,8 @@
                                         if (sDebug) {
                                             Log.d(TAG, "onSuccess Response: " + response);
                                         }
+                                        fieldClassificationServiceCallbacks
+                                                .onClassificationRequestSuccess(response);
                                     }
 
                                     @Override
@@ -165,6 +167,8 @@
                                         if (sDebug) {
                                             Log.d(TAG, "onFailure");
                                         }
+                                        fieldClassificationServiceCallbacks
+                                                .onClassificationRequestFailure(0, null);
                                     }
 
                                     @Override
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index f83d734..9e46d73 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -36,7 +36,6 @@
 import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
 import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
 import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN;
-import static android.view.autofill.AutofillManager.FLAG_ENABLED_CLIENT_SUGGESTIONS;
 import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
 import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
 
@@ -205,6 +204,10 @@
         RemoteFieldClassificationService.FieldClassificationServiceCallbacks {
     private static final String TAG = "AutofillSession";
 
+    // This should never be true in production. This is only for local debugging.
+    // Otherwise it will spam logcat.
+    private static final boolean DBG = false;
+
     private static final String ACTION_DELAYED_FILL =
             "android.service.autofill.action.DELAYED_FILL";
     private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID";
@@ -446,9 +449,6 @@
      */
     private final PccAssistDataReceiverImpl mPccAssistReceiver = new PccAssistDataReceiverImpl();
 
-    @Nullable
-    private ClientSuggestionsSession mClientSuggestionsSession;
-
     private final ClassificationState mClassificationState = new ClassificationState();
 
     // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a
@@ -590,9 +590,6 @@
         /** Whether the current {@link FillResponse} is expired. */
         private boolean mExpiredResponse;
 
-        /** Whether the client is using {@link android.view.autofill.AutofillRequestCallback}. */
-        private boolean mClientSuggestionsEnabled;
-
         /** Whether the fill dialog UI is disabled. */
         private boolean mFillDialogDisabled;
     }
@@ -623,21 +620,14 @@
                     }
                     mWaitForInlineRequest = inlineSuggestionsRequest != null;
                     mPendingInlineSuggestionsRequest = inlineSuggestionsRequest;
-                    mWaitForInlineRequest = inlineSuggestionsRequest != null;
-                    maybeRequestFillFromServiceLocked();
+                    maybeRequestFillLocked();
                     viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
                 }
             } : null;
         }
 
-        void newAutofillRequestLocked(@Nullable InlineSuggestionsRequest inlineRequest) {
-            mPendingFillRequest = null;
-            mWaitForInlineRequest = inlineRequest != null;
-            mPendingInlineSuggestionsRequest = inlineRequest;
-        }
-
         @GuardedBy("mLock")
-        void maybeRequestFillFromServiceLocked() {
+        void maybeRequestFillLocked() {
             if (mPendingFillRequest == null) {
                 return;
             }
@@ -647,15 +637,13 @@
                     return;
                 }
 
-                if (mPendingInlineSuggestionsRequest.isServiceSupported()) {
-                    mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
-                            mPendingFillRequest.getFillContexts(),
-                            mPendingFillRequest.getHints(),
-                            mPendingFillRequest.getClientState(),
-                            mPendingFillRequest.getFlags(),
-                            mPendingInlineSuggestionsRequest,
-                            mPendingFillRequest.getDelayedFillIntentSender());
-                }
+                mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
+                        mPendingFillRequest.getFillContexts(),
+                        mPendingFillRequest.getHints(),
+                        mPendingFillRequest.getClientState(),
+                        mPendingFillRequest.getFlags(),
+                        mPendingInlineSuggestionsRequest,
+                        mPendingFillRequest.getDelayedFillIntentSender());
             }
             mLastFillRequest = mPendingFillRequest;
 
@@ -777,7 +765,7 @@
                             : mDelayedFillPendingIntent.getIntentSender());
 
                 mPendingFillRequest = request;
-                maybeRequestFillFromServiceLocked();
+                maybeRequestFillLocked();
             }
 
             if (mActivityToken != null) {
@@ -1099,39 +1087,30 @@
     }
 
     /**
-     * Cancels the last request sent to the {@link #mRemoteFillService} or the
-     * {@link #mClientSuggestionsSession}.
+     * Cancels the last request sent to the {@link #mRemoteFillService}.
      */
     @GuardedBy("mLock")
     private void cancelCurrentRequestLocked() {
-        if (mRemoteFillService == null && mClientSuggestionsSession == null) {
-            wtf(null, "cancelCurrentRequestLocked() called without a remote service or a "
-                    + "client suggestions session.  mForAugmentedAutofillOnly: %s",
-                    mSessionFlags.mAugmentedAutofillOnly);
+        if (mRemoteFillService == null) {
+            wtf(null, "cancelCurrentRequestLocked() called without a remote service. "
+                + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly);
             return;
         }
+        final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
 
-        if (mRemoteFillService != null) {
-            final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
+        // Remove the FillContext as there will never be a response for the service
+        if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
+            final int numContexts = mContexts.size();
 
-            // Remove the FillContext as there will never be a response for the service
-            if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
-                final int numContexts = mContexts.size();
-
-                // It is most likely the last context, hence search backwards
-                for (int i = numContexts - 1; i >= 0; i--) {
-                    if (mContexts.get(i).getRequestId() == canceledRequest) {
-                        if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
-                        mContexts.remove(i);
-                        break;
-                    }
+            // It is most likely the last context, hence search backwards
+            for (int i = numContexts - 1; i >= 0; i--) {
+                if (mContexts.get(i).getRequestId() == canceledRequest) {
+                    if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
+                    mContexts.remove(i);
+                    break;
                 }
             }
         }
-
-        if (mClientSuggestionsSession != null) {
-            mClientSuggestionsSession.cancelCurrentRequest();
-        }
     }
 
     private boolean isViewFocusedLocked(int flags) {
@@ -1225,30 +1204,17 @@
             requestAssistStructureForPccLocked(flags | FLAG_PCC_DETECTION);
         }
 
-        // Only ask IME to create inline suggestions request when
-        // 1. Autofill provider supports it or client enabled client suggestions.
-        // 2. The render service is available.
-        // 3. The view is focused. (The view may not be focused if the autofill is triggered
-        //    manually.)
+        // Only ask IME to create inline suggestions request if Autofill provider supports it and
+        // the render service is available except the autofill is triggered manually and the view
+        // is also not focused.
         final RemoteInlineSuggestionRenderService remoteRenderService =
                 mService.getRemoteInlineSuggestionRenderServiceLocked();
-        if ((mSessionFlags.mInlineSupportedByService || mSessionFlags.mClientSuggestionsEnabled)
-                && remoteRenderService != null
-                && (isViewFocusedLocked(flags) || (isRequestSupportFillDialog(flags)))) {
-            final Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer;
-            if (mSessionFlags.mClientSuggestionsEnabled) {
-                final int finalRequestId = requestId;
-                inlineSuggestionsRequestConsumer = (inlineSuggestionsRequest) -> {
-                    // Using client suggestions
-                    synchronized (mLock) {
-                        onClientFillRequestLocked(finalRequestId, inlineSuggestionsRequest);
-                    }
-                    viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
-                };
-            } else {
-                inlineSuggestionsRequestConsumer = mAssistReceiver.newAutofillRequestLocked(
-                        viewState, /* isInlineRequest= */ true);
-            }
+        if (mSessionFlags.mInlineSupportedByService
+            && remoteRenderService != null
+            && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) {
+            Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
+                mAssistReceiver.newAutofillRequestLocked(viewState,
+                    /* isInlineRequest= */ true);
             if (inlineSuggestionsRequestConsumer != null) {
                 final AutofillId focusedId = mCurrentViewId;
                 final int requestIdCopy = requestId;
@@ -1264,18 +1230,10 @@
                 );
                 viewState.setState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
             }
-        } else if (mSessionFlags.mClientSuggestionsEnabled) {
-            // Request client suggestions for the dropdown mode
-            onClientFillRequestLocked(requestId, null);
         } else {
             mAssistReceiver.newAutofillRequestLocked(viewState, /* isInlineRequest= */ false);
         }
 
-        if (mSessionFlags.mClientSuggestionsEnabled) {
-            // Using client suggestions, unnecessary request AssistStructure
-            return;
-        }
-
         // Now request the assist structure data.
         requestAssistStructureLocked(requestId, flags);
     }
@@ -1286,6 +1244,8 @@
 
     @GuardedBy("mLock")
     private void requestAssistStructureForPccLocked(int flags) {
+        if (!mClassificationState.shouldTriggerRequest()) return;
+        mClassificationState.updatePendingRequest();
         // Get request id
         int requestId;
         // TODO(b/158623971): Update this to prevent possible overflow
@@ -1380,11 +1340,6 @@
             mSessionFlags = new SessionFlags();
             mSessionFlags.mAugmentedAutofillOnly = forAugmentedAutofillOnly;
             mSessionFlags.mInlineSupportedByService = mService.isInlineSuggestionsEnabledLocked();
-            if (mContext.checkCallingPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
-                    == PackageManager.PERMISSION_GRANTED) {
-                mSessionFlags.mClientSuggestionsEnabled =
-                        (mFlags & FLAG_ENABLED_CLIENT_SUGGESTIONS) != 0;
-            }
             setClientLocked(client);
         }
 
@@ -1522,15 +1477,14 @@
                 if (requestLog != null) {
                     requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, -1);
                 }
-                processNullResponseOrFallbackLocked(requestId, requestFlags);
+                processNullResponseLocked(requestId, requestFlags);
                 return;
             }
 
             // TODO: Check if this is required. We can still present datasets to the user even if
             //  traditional field classification is disabled.
             fieldClassificationIds = response.getFieldClassificationIds();
-            if (!mSessionFlags.mClientSuggestionsEnabled && fieldClassificationIds != null
-                    && !mService.isFieldClassificationEnabledLocked()) {
+            if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) {
                 Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
                 processNullResponseLocked(requestId, requestFlags);
                 return;
@@ -1623,12 +1577,18 @@
         // TODO(b/266379948): Ideally wait for PCC request to finish for a while more
         // (say 100ms) before proceeding further on.
 
+        if (DBG) {
+            Slog.d(TAG, "DBG: Initial response: " + response);
+        }
         synchronized (mLock) {
             response = getEffectiveFillResponse(response);
             if (isEmptyResponse(response)) {
                 // Treat it as a null response.
                 processNullResponseLocked(requestId, requestFlags);
             }
+            if (DBG) {
+                Slog.d(TAG, "DBG: Processed response: " + response);
+            }
             processResponseLocked(response, null, requestFlags);
         }
     }
@@ -1643,9 +1603,7 @@
                         || (ArrayUtils.isEmpty(saveInfo.getOptionalIds())
                             && ArrayUtils.isEmpty(saveInfo.getRequiredIds())
                             && ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) == 0)))
-                    && (ArrayUtils.isEmpty(response.getFieldClassificationIds())
-                        || (!mSessionFlags.mClientSuggestionsEnabled
-                        && !mService.isFieldClassificationEnabledLocked())));
+                    && (ArrayUtils.isEmpty(response.getFieldClassificationIds())));
         }
     }
 
@@ -1655,12 +1613,25 @@
         DatasetComputationContainer autofillProviderContainer = new DatasetComputationContainer();
         computeDatasetsForProviderAndUpdateContainer(response, autofillProviderContainer);
 
+        if (DBG) {
+            Slog.d(TAG, "DBG: computeDatasetsForProviderAndUpdateContainer: "
+                    + autofillProviderContainer);
+        }
         if (!mService.getMaster().isPccClassificationEnabled())  {
+            if (sVerbose) {
+                Slog.v(TAG, "PCC classification is disabled");
+            }
             return createShallowCopy(response, autofillProviderContainer);
         }
         synchronized (mLock) {
             if (mClassificationState.mState != ClassificationState.STATE_RESPONSE
                     || mClassificationState.mLastFieldClassificationResponse == null) {
+                if (sVerbose) {
+                    Slog.v(TAG, "PCC classification no last response:"
+                            + (mClassificationState.mLastFieldClassificationResponse == null)
+                            +   " ,ineligible state="
+                            + (mClassificationState.mState != ClassificationState.STATE_RESPONSE));
+                }
                 return createShallowCopy(response, autofillProviderContainer);
             }
             if (!mClassificationState.processResponse()) return response;
@@ -1668,11 +1639,22 @@
         boolean preferAutofillProvider = mService.getMaster().preferProviderOverPcc();
         boolean shouldUseFallback = mService.getMaster().shouldUsePccFallback();
         if (preferAutofillProvider && !shouldUseFallback) {
+            if (sVerbose) {
+                Slog.v(TAG, "preferAutofillProvider but no fallback");
+            }
             return createShallowCopy(response, autofillProviderContainer);
         }
 
+        if (DBG) {
+            synchronized (mLock) {
+                Slog.d(TAG, "DBG: ClassificationState: " + mClassificationState);
+            }
+        }
         DatasetComputationContainer detectionPccContainer = new DatasetComputationContainer();
         computeDatasetsForPccAndUpdateContainer(response, detectionPccContainer);
+        if (DBG) {
+            Slog.d(TAG, "DBG: computeDatasetsForPccAndUpdateContainer: " + detectionPccContainer);
+        }
 
         DatasetComputationContainer resultContainer;
         if (preferAutofillProvider) {
@@ -1746,6 +1728,20 @@
         // FillResponse.
         Set<Dataset> mDatasets = new LinkedHashSet<>();
         ArrayMap<AutofillId, Set<Dataset>> mAutofillIdToDatasetMap = new ArrayMap<>();
+
+        public String toString() {
+            final StringBuilder builder = new StringBuilder("DatasetComputationContainer[");
+            if (mAutofillIds != null) {
+                builder.append(", autofillIds=").append(mAutofillIds);
+            }
+            if (mDatasets != null) {
+                builder.append(", mDatasets=").append(mDatasets);
+            }
+            if (mAutofillIdToDatasetMap != null) {
+                builder.append(", mAutofillIdToDatasetMap=").append(mAutofillIdToDatasetMap);
+            }
+            return builder.append(']').toString();
+        }
     }
 
     // Adds fallback datasets to the first container.
@@ -1897,7 +1893,6 @@
                 Dataset dataset = datasets.get(i);
                 if (dataset.getAutofillDatatypes() == null
                         || dataset.getAutofillDatatypes().isEmpty()) continue;
-                if (dataset.getFieldIds() != null && dataset.getFieldIds().size() > 0) continue;
 
                 ArrayList<AutofillId> fieldIds = new ArrayList<>();
                 ArrayList<AutofillValue> fieldValues = new ArrayList<>();
@@ -1906,9 +1901,10 @@
                 ArrayList<InlinePresentation> fieldInlinePresentations = new ArrayList<>();
                 ArrayList<InlinePresentation> fieldInlineTooltipPresentations = new ArrayList<>();
                 ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>();
+                Set<AutofillId> datasetAutofillIds = new ArraySet<>();
 
                 for (int j = 0; j < dataset.getAutofillDatatypes().size(); j++) {
-                    if (dataset.getAutofillDatatypes().get(0) == null) continue;
+                    if (dataset.getAutofillDatatypes().get(j) == null) continue;
                     String hint = dataset.getAutofillDatatypes().get(j);
 
                     if (hintsToAutofillIdMap.containsKey(hint)) {
@@ -1917,6 +1913,7 @@
 
                         for (AutofillId autofillId : tempIds) {
                             eligibleAutofillIds.add(autofillId);
+                            datasetAutofillIds.add(autofillId);
                             // For each of the field, copy over values.
                             fieldIds.add(autofillId);
                             fieldValues.add(dataset.getFieldValues().get(j));
@@ -1930,37 +1927,6 @@
                                     dataset.getFieldInlineTooltipPresentation(j));
                             fieldFilters.add(dataset.getFilter(j));
                         }
-
-                        Dataset newDataset =
-                                new Dataset(
-                                        fieldIds,
-                                        fieldValues,
-                                        fieldPresentations,
-                                        fieldDialogPresentations,
-                                        fieldInlinePresentations,
-                                        fieldInlineTooltipPresentations,
-                                        fieldFilters,
-                                        new ArrayList<>(),
-                                        dataset.getFieldContent(),
-                                        null,
-                                        null,
-                                        null,
-                                        null,
-                                        dataset.getId(),
-                                        dataset.getAuthentication());
-                        eligibleDatasets.add(newDataset);
-
-                        // Associate this dataset with all the ids that are represented with it.
-                        Set<Dataset> newDatasets;
-                        for (AutofillId autofillId : tempIds) {
-                            if (map.containsKey(autofillId)) {
-                                newDatasets = map.get(autofillId);
-                            } else {
-                                newDatasets = new ArraySet<>();
-                            }
-                            newDatasets.add(newDataset);
-                            map.put(autofillId, newDatasets);
-                        }
                     }
                     // TODO(b/266379948):  handle the case:
                     // groupHintsToAutofillIdMap.containsKey(hint))
@@ -1968,6 +1934,34 @@
                     // TODO(b/266379948):  also handle the case where there could be more types in
                     // the dataset, provided by the provider, however, they aren't applicable.
                 }
+                Dataset newDataset =
+                        new Dataset(
+                                fieldIds,
+                                fieldValues,
+                                fieldPresentations,
+                                fieldDialogPresentations,
+                                fieldInlinePresentations,
+                                fieldInlineTooltipPresentations,
+                                fieldFilters,
+                                new ArrayList<>(),
+                                dataset.getFieldContent(),
+                                null,
+                                null,
+                                null,
+                                null,
+                                dataset.getId(),
+                                dataset.getAuthentication());
+                eligibleDatasets.add(newDataset);
+                Set<Dataset> newDatasets;
+                for (AutofillId autofillId : datasetAutofillIds) {
+                    if (map.containsKey(autofillId)) {
+                        newDatasets = map.get(autofillId);
+                    } else {
+                        newDatasets = new ArraySet<>();
+                    }
+                    newDatasets.add(newDataset);
+                    map.put(autofillId, newDatasets);
+                }
             }
             container.mAutofillIds = eligibleAutofillIds;
             container.mDatasets = eligibleDatasets;
@@ -1975,40 +1969,6 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private void processNullResponseOrFallbackLocked(int requestId, int flags) {
-        if (!mSessionFlags.mClientSuggestionsEnabled) {
-            processNullResponseLocked(requestId, flags);
-            return;
-        }
-
-        // fallback to the default platform password manager
-        mSessionFlags.mClientSuggestionsEnabled = false;
-        mLastFillDialogTriggerIds = null;
-        // Log the existing FillResponse event.
-        mFillResponseEventLogger.logAndEndEvent();
-
-        final InlineSuggestionsRequest inlineRequest =
-                (mLastInlineSuggestionsRequest != null
-                        && mLastInlineSuggestionsRequest.first == requestId)
-                        ? mLastInlineSuggestionsRequest.second : null;
-
-        // Start a new FillRequest logger for client suggestion fallback.
-        mFillRequestEventLogger.startLogForNewRequest();
-        mRequestCount++;
-        mFillRequestEventLogger.maybeSetAppPackageUid(uid);
-        mFillRequestEventLogger.maybeSetFlags(
-            flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS);
-        mFillRequestEventLogger.maybeSetRequestTriggerReason(
-            TRIGGER_REASON_NORMAL_TRIGGER);
-        mFillRequestEventLogger.maybeSetIsClientSuggestionFallback(true);
-
-        mAssistReceiver.newAutofillRequestLocked(inlineRequest);
-        requestAssistStructureLocked(requestId,
-                flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS);
-        return;
-    }
-
     // FillServiceCallbacks
     @Override
     @SuppressWarnings("GuardedBy")
@@ -4205,22 +4165,13 @@
             filterText = value.getTextValue().toString();
         }
 
-        final CharSequence targetLabel;
-        final Drawable targetIcon;
-        synchronized (mLock) {
-            if (mSessionFlags.mClientSuggestionsEnabled) {
-                final ApplicationInfo appInfo = ClientSuggestionsSession.getAppInfo(mComponentName,
-                        mService.getUserId());
-                targetLabel = ClientSuggestionsSession.getAppLabelLocked(
-                        mService.getMaster().getContext(), appInfo);
-                targetIcon = ClientSuggestionsSession.getAppIconLocked(
-                        mService.getMaster().getContext(), appInfo);
-            } else {
-                targetLabel = mService.getServiceLabelLocked();
-                targetIcon = mService.getServiceIconLocked();
-            }
+        final CharSequence serviceLabel;
+        final Drawable serviceIcon;
+        synchronized (this.mService.mLock) {
+            serviceLabel = mService.getServiceLabelLocked();
+            serviceIcon = mService.getServiceIconLocked();
         }
-        if (targetLabel == null || targetIcon == null) {
+        if (serviceLabel == null || serviceIcon == null) {
             wtf(null, "onFillReady(): no service label or icon");
             return;
         }
@@ -4281,7 +4232,7 @@
 
         getUiForShowing().showFillUi(filledId, response, filterText,
                 mService.getServicePackageName(), mComponentName,
-                targetLabel, targetIcon, this, mContext, id, mCompatMode);
+                serviceLabel, serviceIcon, this, mContext, id, mCompatMode);
 
         synchronized (mLock) {
             mPresentationStatsEventLogger.maybeSetCountShown(
@@ -4477,17 +4428,6 @@
             return false;
         }
 
-        final InlineSuggestionsRequest request = inlineSuggestionsRequest.get();
-        if (mSessionFlags.mClientSuggestionsEnabled && !request.isClientSupported()
-                || !mSessionFlags.mClientSuggestionsEnabled && !request.isServiceSupported()) {
-            if (sDebug) {
-                Slog.d(TAG, "Inline suggestions not supported for "
-                        + (mSessionFlags.mClientSuggestionsEnabled ? "client" : "service")
-                        + ". Falling back to dropdown.");
-            }
-            return false;
-        }
-
         final RemoteInlineSuggestionRenderService remoteRenderService =
                 mService.getRemoteInlineSuggestionRenderServiceLocked();
         if (remoteRenderService == null) {
@@ -4502,8 +4442,8 @@
         }
 
         final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
-                new InlineFillUi.InlineFillUiInfo(request, focusedId,
-                        filterText, remoteRenderService, userId, id);
+            new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId,
+                filterText, remoteRenderService, userId, id);
         InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response,
                 new InlineFillUi.InlineSuggestionUiCallback() {
                     @Override
@@ -5154,26 +5094,6 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private void onClientFillRequestLocked(int requestId,
-            InlineSuggestionsRequest inlineSuggestionsRequest) {
-        if (mClientSuggestionsSession == null) {
-            mClientSuggestionsSession = new ClientSuggestionsSession(id, mClient, mHandler,
-                    mComponentName, this);
-        }
-
-        if (mContexts == null) {
-            mContexts = new ArrayList<>(1);
-        }
-        mContexts.add(new FillContext(requestId, new AssistStructure(), mCurrentViewId));
-
-        if (inlineSuggestionsRequest != null && !inlineSuggestionsRequest.isClientSupported()) {
-            inlineSuggestionsRequest = null;
-        }
-
-        mClientSuggestionsSession.onFillRequest(requestId, inlineSuggestionsRequest, mFlags);
-    }
-
     /**
      * The result of checking whether to show the save dialog, when session can be saved.
      *
@@ -5447,6 +5367,26 @@
             mState = STATE_PENDING_REQUEST;
             mPendingFieldClassificationRequest = null;
         }
+
+        @GuardedBy("mLock")
+        private boolean shouldTriggerRequest() {
+            return mState == STATE_INITIAL || mState == STATE_INVALIDATED;
+        }
+
+        @GuardedBy("mLock")
+        @Override
+        public String toString() {
+            return "ClassificationState: ["
+                    + "state=" + stateToString()
+                    + ", mPendingFieldClassificationRequest=" + mPendingFieldClassificationRequest
+                    + ", mLastFieldClassificationResponse=" + mLastFieldClassificationResponse
+                    + ", mClassificationHintsMap=" + mClassificationHintsMap
+                    + ", mClassificationGroupHintsMap=" + mClassificationGroupHintsMap
+                    + ", mHintsToAutofillIdMap=" + mHintsToAutofillIdMap
+                    + ", mGroupHintsToAutofillIdMap=" + mGroupHintsToAutofillIdMap
+                    + "]";
+        }
+
     }
 
     @Override
@@ -5971,7 +5911,7 @@
         return serviceInfo == null ? Process.INVALID_UID : serviceInfo.applicationInfo.uid;
     }
 
-    // DetectionServiceCallbacks
+    // FieldClassificationServiceCallbacks
     public void onClassificationRequestSuccess(@Nullable FieldClassificationResponse response) {
         mClassificationState.updateResponseReceived(response);
     }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index a3a0674..c8db662a 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -108,6 +108,9 @@
 import com.android.server.SystemService;
 import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
 import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
+import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall;
+import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
+import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback;
 import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
 import com.android.server.companion.transport.CompanionTransportManager;
 import com.android.server.pm.UserManagerInternal;
@@ -117,6 +120,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -200,6 +204,8 @@
     private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners =
             new RemoteCallbackList<>();
 
+    private CrossDeviceSyncController mCrossDeviceSyncController;
+
     public CompanionDeviceManagerService(Context context) {
         super(context);
 
@@ -239,6 +245,8 @@
         mTransportManager = new CompanionTransportManager(context, mAssociationStore);
         mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mAssociationStore,
                 mSystemDataTransferRequestStore, mTransportManager);
+        // TODO(b/279663946): move context sync to a dedicated system service
+        mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager);
 
         // Publish "binder" service.
         final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl();
@@ -320,12 +328,14 @@
         // Notify and bind the app after the phone is unlocked.
         final int userId = user.getUserIdentifier();
         final Set<BluetoothDevice> blueToothDevices =
-                mDevicePresenceMonitor.getPendingReportConnectedDevices().get(userId);
-        for (BluetoothDevice bluetoothDevice : blueToothDevices) {
-            for (AssociationInfo ai:
-                    mAssociationStore.getAssociationsByAddress(bluetoothDevice.getAddress())) {
-                Slog.i(TAG, "onUserUnlocked, device id( " + ai.getId() + " ) is connected");
-                mDevicePresenceMonitor.onBluetoothCompanionDeviceConnected(ai.getId());
+                mDevicePresenceMonitor.getPendingConnectedDevices().get(userId);
+        if (blueToothDevices != null) {
+            for (BluetoothDevice bluetoothDevice : blueToothDevices) {
+                for (AssociationInfo ai:
+                        mAssociationStore.getAssociationsByAddress(bluetoothDevice.getAddress())) {
+                    Slog.i(TAG, "onUserUnlocked, device id( " + ai.getId() + " ) is connected");
+                    mDevicePresenceMonitor.onBluetoothCompanionDeviceConnected(ai.getId());
+                }
             }
         }
     }
@@ -1369,6 +1379,39 @@
         public void removeInactiveSelfManagedAssociations() {
             CompanionDeviceManagerService.this.removeInactiveSelfManagedAssociations();
         }
+
+        @Override
+        public void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback) {
+            if (CompanionDeviceConfig.isEnabled(
+                    CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+                mCrossDeviceSyncController.registerCallMetadataSyncCallback(callback);
+            }
+        }
+
+        @Override
+        public void crossDeviceSync(int userId, Collection<CrossDeviceCall> calls) {
+            if (CompanionDeviceConfig.isEnabled(
+                    CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+                mCrossDeviceSyncController.syncToAllDevicesForUserId(userId, calls);
+            }
+        }
+
+        @Override
+        public void crossDeviceSync(AssociationInfo associationInfo,
+                Collection<CrossDeviceCall> calls) {
+            if (CompanionDeviceConfig.isEnabled(
+                    CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+                mCrossDeviceSyncController.syncToSingleDevice(associationInfo, calls);
+            }
+        }
+
+        @Override
+        public void sendCrossDeviceSyncMessage(int associationId, byte[] message) {
+            if (CompanionDeviceConfig.isEnabled(
+                    CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+                mCrossDeviceSyncController.syncMessageToDevice(associationId, message);
+            }
+        }
     }
 
     /**
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
index 3649240..3b108e6 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
@@ -16,12 +16,41 @@
 
 package com.android.server.companion;
 
+import android.companion.AssociationInfo;
+
+import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall;
+import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback;
+
+import java.util.Collection;
+
 /**
  * Companion Device Manager Local System Service Interface.
  */
-interface CompanionDeviceManagerServiceInternal {
+public interface CompanionDeviceManagerServiceInternal {
     /**
      * @see CompanionDeviceManagerService#removeInactiveSelfManagedAssociations
      */
     void removeInactiveSelfManagedAssociations();
+
+    /**
+     * Registers a callback from an InCallService / ConnectionService to CDM to process sync
+     * requests and perform call control actions.
+     */
+    void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback);
+
+    /**
+     * Requests a sync from an InCallService / ConnectionService to CDM, for the given association
+     * and message.
+     */
+    void sendCrossDeviceSyncMessage(int associationId, byte[] message);
+
+    /**
+     * Requests a sync from an InCallService to CDM, for the given user and call metadata.
+     */
+    void crossDeviceSync(int userId, Collection<CrossDeviceCall> calls);
+
+    /**
+     * Requests a sync from an InCallService to CDM, for the given association and call metadata.
+     */
+    void crossDeviceSync(AssociationInfo associationInfo, Collection<CrossDeviceCall> calls);
 }
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
index 6f99d86..e436e93 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
@@ -128,9 +128,9 @@
 
     private static final class CallMetadataSyncConnectionIdentifier {
         private final int mAssociationId;
-        private final long mCallId;
+        private final String mCallId;
 
-        CallMetadataSyncConnectionIdentifier(int associationId, long callId) {
+        CallMetadataSyncConnectionIdentifier(int associationId, String callId) {
             mAssociationId = associationId;
             mCallId = callId;
         }
@@ -139,7 +139,7 @@
             return mAssociationId;
         }
 
-        public long getCallId() {
+        public String getCallId() {
             return mCallId;
         }
 
@@ -161,9 +161,7 @@
 
     private abstract static class CallMetadataSyncConnectionCallback {
 
-        abstract void sendCallAction(int associationId, long callId, int action);
-
-        abstract void sendStateChange(int associationId, long callId, int newState);
+        abstract void sendCallAction(int associationId, String callId, int action);
     }
 
     private static class CallMetadataSyncConnection extends Connection {
@@ -184,7 +182,7 @@
             mCallback = callback;
         }
 
-        public long getCallId() {
+        public String getCallId() {
             return mCall.getId();
         }
 
@@ -205,22 +203,22 @@
             }
 
             final Bundle extras = new Bundle();
-            extras.putLong(CrossDeviceCall.EXTRA_CALL_ID, mCall.getId());
+            extras.putString(CrossDeviceCall.EXTRA_CALL_ID, mCall.getId());
             putExtras(extras);
 
             int capabilities = getConnectionCapabilities();
-            if (mCall.hasControl(android.companion.Telecom.Call.PUT_ON_HOLD)) {
+            if (mCall.hasControl(android.companion.Telecom.PUT_ON_HOLD)) {
                 capabilities |= CAPABILITY_HOLD;
             } else {
                 capabilities &= ~CAPABILITY_HOLD;
             }
-            if (mCall.hasControl(android.companion.Telecom.Call.MUTE)) {
+            if (mCall.hasControl(android.companion.Telecom.MUTE)) {
                 capabilities |= CAPABILITY_MUTE;
             } else {
                 capabilities &= ~CAPABILITY_MUTE;
             }
             mAudioManager.setMicrophoneMute(
-                    mCall.hasControl(android.companion.Telecom.Call.UNMUTE));
+                    mCall.hasControl(android.companion.Telecom.UNMUTE));
             if (capabilities != getConnectionCapabilities()) {
                 setConnectionCapabilities(capabilities);
             }
@@ -248,8 +246,8 @@
 
             int capabilities = getConnectionCapabilities();
             final boolean hasHoldControl = mCall.hasControl(
-                    android.companion.Telecom.Call.PUT_ON_HOLD)
-                    || mCall.hasControl(android.companion.Telecom.Call.TAKE_OFF_HOLD);
+                    android.companion.Telecom.PUT_ON_HOLD)
+                    || mCall.hasControl(android.companion.Telecom.TAKE_OFF_HOLD);
             if (hasHoldControl != ((getConnectionCapabilities() & CAPABILITY_HOLD)
                     == CAPABILITY_HOLD)) {
                 if (hasHoldControl) {
@@ -258,7 +256,7 @@
                     capabilities &= ~CAPABILITY_HOLD;
                 }
             }
-            final boolean hasMuteControl = mCall.hasControl(android.companion.Telecom.Call.MUTE);
+            final boolean hasMuteControl = mCall.hasControl(android.companion.Telecom.MUTE);
             if (hasMuteControl != ((getConnectionCapabilities() & CAPABILITY_MUTE)
                     == CAPABILITY_MUTE)) {
                 if (hasMuteControl) {
@@ -268,7 +266,7 @@
                 }
             }
             mAudioManager.setMicrophoneMute(
-                    mCall.hasControl(android.companion.Telecom.Call.UNMUTE));
+                    mCall.hasControl(android.companion.Telecom.UNMUTE));
             if (capabilities != getConnectionCapabilities()) {
                 setConnectionCapabilities(capabilities);
             }
@@ -276,12 +274,12 @@
 
         @Override
         public void onAnswer(int videoState) {
-            sendCallAction(android.companion.Telecom.Call.ACCEPT);
+            sendCallAction(android.companion.Telecom.ACCEPT);
         }
 
         @Override
         public void onReject() {
-            sendCallAction(android.companion.Telecom.Call.REJECT);
+            sendCallAction(android.companion.Telecom.REJECT);
         }
 
         @Override
@@ -296,33 +294,28 @@
 
         @Override
         public void onSilence() {
-            sendCallAction(android.companion.Telecom.Call.SILENCE);
+            sendCallAction(android.companion.Telecom.SILENCE);
         }
 
         @Override
         public void onHold() {
-            sendCallAction(android.companion.Telecom.Call.PUT_ON_HOLD);
+            sendCallAction(android.companion.Telecom.PUT_ON_HOLD);
         }
 
         @Override
         public void onUnhold() {
-            sendCallAction(android.companion.Telecom.Call.TAKE_OFF_HOLD);
+            sendCallAction(android.companion.Telecom.TAKE_OFF_HOLD);
         }
 
         @Override
         public void onMuteStateChanged(boolean isMuted) {
-            sendCallAction(isMuted ? android.companion.Telecom.Call.MUTE
-                    : android.companion.Telecom.Call.UNMUTE);
+            sendCallAction(isMuted ? android.companion.Telecom.MUTE
+                    : android.companion.Telecom.UNMUTE);
         }
 
         @Override
         public void onDisconnect() {
-            sendCallAction(android.companion.Telecom.Call.END);
-        }
-
-        @Override
-        public void onStateChanged(int state) {
-            mCallback.sendStateChange(mAssociationId, mCall.getId(), state);
+            sendCallAction(android.companion.Telecom.END);
         }
 
         private void sendCallAction(int action) {
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
index 1e4bb9a..5b0c745 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
@@ -33,14 +33,14 @@
 /** A read-only snapshot of an {@link ContextSyncMessage}. */
 class CallMetadataSyncData {
 
-    final Map<Long, CallMetadataSyncData.Call> mCalls = new HashMap<>();
+    final Map<String, CallMetadataSyncData.Call> mCalls = new HashMap<>();
     final List<CallMetadataSyncData.Call> mRequests = new ArrayList<>();
 
     public void addCall(CallMetadataSyncData.Call call) {
         mCalls.put(call.getId(), call);
     }
 
-    public boolean hasCall(long id) {
+    public boolean hasCall(String id) {
         return mCalls.containsKey(id);
     }
 
@@ -57,7 +57,7 @@
     }
 
     public static class Call implements Parcelable {
-        private long mId;
+        private String mId;
         private String mCallerId;
         private byte[] mAppIcon;
         private String mAppName;
@@ -67,7 +67,7 @@
 
         public static Call fromParcel(Parcel parcel) {
             final Call call = new Call();
-            call.setId(parcel.readLong());
+            call.setId(parcel.readString());
             call.setCallerId(parcel.readString());
             call.setAppIcon(parcel.readBlob());
             call.setAppName(parcel.readString());
@@ -82,7 +82,7 @@
 
         @Override
         public void writeToParcel(Parcel parcel, int parcelableFlags) {
-            parcel.writeLong(mId);
+            parcel.writeString(mId);
             parcel.writeString(mCallerId);
             parcel.writeBlob(mAppIcon);
             parcel.writeString(mAppName);
@@ -94,7 +94,7 @@
             }
         }
 
-        void setId(long id) {
+        void setId(String id) {
             mId = id;
         }
 
@@ -122,7 +122,7 @@
             mControls.add(control);
         }
 
-        long getId() {
+        String getId() {
             return mId;
         }
 
@@ -157,7 +157,7 @@
         @Override
         public boolean equals(Object other) {
             if (other instanceof CallMetadataSyncData.Call) {
-                return ((Call) other).getId() == getId();
+                return mId != null && mId.equals(((Call) other).getId());
             }
             return false;
         }
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
index ae4766a..0c23730 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
@@ -17,15 +17,20 @@
 package com.android.server.companion.datatransfer.contextsync;
 
 import android.annotation.Nullable;
+import android.companion.AssociationInfo;
 import android.telecom.Call;
 import android.telecom.InCallService;
 import android.telecom.TelecomManager;
+import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
 import com.android.server.companion.CompanionDeviceConfig;
+import com.android.server.companion.CompanionDeviceManagerServiceInternal;
 
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.stream.Collectors;
 
@@ -35,101 +40,142 @@
  */
 public class CallMetadataSyncInCallService extends InCallService {
 
-    private static final long NOT_VALID = -1L;
+    private static final String TAG = "CallMetadataIcs";
+
+    private CompanionDeviceManagerServiceInternal mCdmsi;
 
     @VisibleForTesting
     final Map<Call, CrossDeviceCall> mCurrentCalls = new HashMap<>();
-    @VisibleForTesting
-    boolean mShouldSync;
+    @VisibleForTesting int mNumberOfActiveSyncAssociations;
     final Call.Callback mTelecomCallback = new Call.Callback() {
         @Override
         public void onDetailsChanged(Call call, Call.Details details) {
-            mCurrentCalls.get(call).updateCallDetails(details);
-        }
-    };
-    final CallMetadataSyncCallback mCallMetadataSyncCallback = new CallMetadataSyncCallback() {
-        @Override
-        void processCallControlAction(int crossDeviceCallId, int callControlAction) {
-            final CrossDeviceCall crossDeviceCall = getCallForId(crossDeviceCallId,
-                    mCurrentCalls.values());
-            switch (callControlAction) {
-                case android.companion.Telecom.Call.ACCEPT:
-                    if (crossDeviceCall != null) {
-                        crossDeviceCall.doAccept();
-                    }
-                    break;
-                case android.companion.Telecom.Call.REJECT:
-                    if (crossDeviceCall != null) {
-                        crossDeviceCall.doReject();
-                    }
-                    break;
-                case android.companion.Telecom.Call.SILENCE:
-                    doSilence();
-                    break;
-                case android.companion.Telecom.Call.MUTE:
-                    doMute();
-                    break;
-                case android.companion.Telecom.Call.UNMUTE:
-                    doUnmute();
-                    break;
-                case android.companion.Telecom.Call.END:
-                    if (crossDeviceCall != null) {
-                        crossDeviceCall.doEnd();
-                    }
-                    break;
-                case android.companion.Telecom.Call.PUT_ON_HOLD:
-                    if (crossDeviceCall != null) {
-                        crossDeviceCall.doPutOnHold();
-                    }
-                    break;
-                case android.companion.Telecom.Call.TAKE_OFF_HOLD:
-                    if (crossDeviceCall != null) {
-                        crossDeviceCall.doTakeOffHold();
-                    }
-                    break;
-                default:
-            }
-        }
-
-        @Override
-        void requestCrossDeviceSync(int userId) {
-        }
-
-        @Override
-        void updateStatus(int userId, boolean shouldSyncCallMetadata) {
-            if (userId == getUserId()) {
-                mShouldSync = shouldSyncCallMetadata;
-                if (shouldSyncCallMetadata) {
-                    initializeCalls();
+            if (mNumberOfActiveSyncAssociations > 0) {
+                final CrossDeviceCall crossDeviceCall = mCurrentCalls.get(call);
+                if (crossDeviceCall != null) {
+                    crossDeviceCall.updateCallDetails(details);
+                    sync(getUserId());
                 } else {
-                    mCurrentCalls.clear();
+                    Slog.w(TAG, "Could not update details for nonexistent call");
                 }
             }
         }
     };
+    final CrossDeviceSyncControllerCallback
+            mCrossDeviceSyncControllerCallback = new CrossDeviceSyncControllerCallback() {
+                @Override
+                void processContextSyncMessage(int associationId,
+                        CallMetadataSyncData callMetadataSyncData) {
+                    final Iterator<CallMetadataSyncData.Call> iterator =
+                            callMetadataSyncData.getRequests().iterator();
+                    while (iterator.hasNext()) {
+                        final CallMetadataSyncData.Call call = iterator.next();
+                        if (call.getId() != null) {
+                            // The call is already assigned an id; treat as control invocations.
+                            for (int control : call.getControls()) {
+                                processCallControlAction(call.getId(), control);
+                            }
+                        }
+                        iterator.remove();
+                    }
+                }
+
+                private void processCallControlAction(String crossDeviceCallId,
+                        int callControlAction) {
+                    final CrossDeviceCall crossDeviceCall = getCallForId(crossDeviceCallId,
+                            mCurrentCalls.values());
+                    switch (callControlAction) {
+                        case android.companion.Telecom.ACCEPT:
+                            if (crossDeviceCall != null) {
+                                crossDeviceCall.doAccept();
+                            }
+                            break;
+                        case android.companion.Telecom.REJECT:
+                            if (crossDeviceCall != null) {
+                                crossDeviceCall.doReject();
+                            }
+                            break;
+                        case android.companion.Telecom.SILENCE:
+                            doSilence();
+                            break;
+                        case android.companion.Telecom.MUTE:
+                            doMute();
+                            break;
+                        case android.companion.Telecom.UNMUTE:
+                            doUnmute();
+                            break;
+                        case android.companion.Telecom.END:
+                            if (crossDeviceCall != null) {
+                                crossDeviceCall.doEnd();
+                            }
+                            break;
+                        case android.companion.Telecom.PUT_ON_HOLD:
+                            if (crossDeviceCall != null) {
+                                crossDeviceCall.doPutOnHold();
+                            }
+                            break;
+                        case android.companion.Telecom.TAKE_OFF_HOLD:
+                            if (crossDeviceCall != null) {
+                                crossDeviceCall.doTakeOffHold();
+                            }
+                            break;
+                        default:
+                    }
+                }
+
+                @Override
+                void requestCrossDeviceSync(AssociationInfo associationInfo) {
+                    if (associationInfo.getUserId() == getUserId()) {
+                        sync(associationInfo);
+                    }
+                }
+
+                @Override
+                void updateNumberOfActiveSyncAssociations(int userId, boolean added) {
+                    if (userId == getUserId()) {
+                        final boolean wasActivelySyncing = mNumberOfActiveSyncAssociations > 0;
+                        if (added) {
+                            mNumberOfActiveSyncAssociations++;
+                        } else {
+                            mNumberOfActiveSyncAssociations--;
+                        }
+                        if (!wasActivelySyncing && mNumberOfActiveSyncAssociations > 0) {
+                            initializeCalls();
+                        } else if (wasActivelySyncing && mNumberOfActiveSyncAssociations <= 0) {
+                            mCurrentCalls.clear();
+                        }
+                    }
+                }
+    };
 
     @Override
     public void onCreate() {
         super.onCreate();
-        initializeCalls();
+        if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+            mCdmsi = LocalServices.getService(CompanionDeviceManagerServiceInternal.class);
+            mCdmsi.registerCallMetadataSyncCallback(mCrossDeviceSyncControllerCallback);
+        }
     }
 
     private void initializeCalls() {
         if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)
-                && mShouldSync) {
+                && mNumberOfActiveSyncAssociations > 0) {
             mCurrentCalls.putAll(getCalls().stream().collect(Collectors.toMap(call -> call,
                     call -> new CrossDeviceCall(getPackageManager(), call, getCallAudioState()))));
+            mCurrentCalls.keySet().forEach(call -> call.registerCallback(mTelecomCallback,
+                    getMainThreadHandler()));
+            sync(getUserId());
         }
     }
 
     @Nullable
     @VisibleForTesting
-    CrossDeviceCall getCallForId(long crossDeviceCallId, Collection<CrossDeviceCall> calls) {
-        if (crossDeviceCallId == NOT_VALID) {
+    CrossDeviceCall getCallForId(String crossDeviceCallId, Collection<CrossDeviceCall> calls) {
+        if (crossDeviceCallId == null) {
             return null;
         }
         for (CrossDeviceCall crossDeviceCall : calls) {
-            if (crossDeviceCall.getId() == crossDeviceCallId) {
+            if (crossDeviceCallId.equals(crossDeviceCall.getId())) {
                 return crossDeviceCall;
             }
         }
@@ -139,33 +185,39 @@
     @Override
     public void onCallAdded(Call call) {
         if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)
-                && mShouldSync) {
+                && mNumberOfActiveSyncAssociations > 0) {
             mCurrentCalls.put(call,
                     new CrossDeviceCall(getPackageManager(), call, getCallAudioState()));
+            call.registerCallback(mTelecomCallback);
+            sync(getUserId());
         }
     }
 
     @Override
     public void onCallRemoved(Call call) {
         if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)
-                && mShouldSync) {
+                && mNumberOfActiveSyncAssociations > 0) {
             mCurrentCalls.remove(call);
+            call.unregisterCallback(mTelecomCallback);
+            sync(getUserId());
         }
     }
 
     @Override
     public void onMuteStateChanged(boolean isMuted) {
         if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)
-                && mShouldSync) {
+                && mNumberOfActiveSyncAssociations > 0) {
             mCurrentCalls.values().forEach(call -> call.updateMuted(isMuted));
+            sync(getUserId());
         }
     }
 
     @Override
     public void onSilenceRinger() {
         if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)
-                && mShouldSync) {
+                && mNumberOfActiveSyncAssociations > 0) {
             mCurrentCalls.values().forEach(call -> call.updateSilencedIfRinging());
+            sync(getUserId());
         }
     }
 
@@ -183,4 +235,12 @@
             telecomManager.silenceRinger();
         }
     }
+
+    private void sync(int userId) {
+        mCdmsi.crossDeviceSync(userId, mCurrentCalls.values());
+    }
+
+    private void sync(AssociationInfo associationInfo) {
+        mCdmsi.crossDeviceSync(associationInfo, mCurrentCalls.values());
+    }
 }
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
index ac981d4..168068e 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
@@ -23,7 +23,6 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.os.Bundle;
 import android.telecom.Call;
 import android.telecom.CallAudioState;
 import android.telecom.VideoProfile;
@@ -34,7 +33,7 @@
 import java.io.ByteArrayOutputStream;
 import java.util.HashSet;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
+import java.util.UUID;
 
 /** Data holder for a telecom call and additional metadata. */
 public class CrossDeviceCall {
@@ -45,9 +44,7 @@
             "com.android.companion.datatransfer.contextsync.extra.CALL_ID";
     private static final int APP_ICON_BITMAP_DIMENSION = 256;
 
-    private static final AtomicLong sNextId = new AtomicLong(1);
-
-    private final long mId;
+    private final String mId;
     private Call mCall;
     @VisibleForTesting boolean mIsEnterprise;
     @VisibleForTesting boolean mIsOtt;
@@ -64,14 +61,14 @@
             CallAudioState callAudioState) {
         this(packageManager, call.getDetails(), callAudioState);
         mCall = call;
-        final Bundle extras = new Bundle();
-        extras.putLong(EXTRA_CALL_ID, mId);
-        call.putExtras(extras);
+        call.putExtra(EXTRA_CALL_ID, mId);
     }
 
     CrossDeviceCall(PackageManager packageManager, Call.Details callDetails,
             CallAudioState callAudioState) {
-        mId = sNextId.getAndIncrement();
+        final String predefinedId = callDetails.getIntentExtras() != null
+                ? callDetails.getIntentExtras().getString(EXTRA_CALL_ID) : null;
+        mId = predefinedId != null ? predefinedId : UUID.randomUUID().toString();
         mCallingAppPackageName =
                 callDetails.getAccountHandle().getComponentName().getPackageName();
         mIsOtt = (callDetails.getCallCapabilities() & Call.Details.PROPERTY_SELF_MANAGED)
@@ -145,7 +142,7 @@
         if (mStatus == android.companion.Telecom.Call.RINGING) {
             mStatus = android.companion.Telecom.Call.RINGING_SILENCED;
         }
-        mControls.remove(android.companion.Telecom.Call.SILENCE);
+        mControls.remove(android.companion.Telecom.SILENCE);
     }
 
     @VisibleForTesting
@@ -156,26 +153,26 @@
         mControls.clear();
         if (mStatus == android.companion.Telecom.Call.RINGING
                 || mStatus == android.companion.Telecom.Call.RINGING_SILENCED) {
-            mControls.add(android.companion.Telecom.Call.ACCEPT);
-            mControls.add(android.companion.Telecom.Call.REJECT);
+            mControls.add(android.companion.Telecom.ACCEPT);
+            mControls.add(android.companion.Telecom.REJECT);
             if (mStatus == android.companion.Telecom.Call.RINGING) {
-                mControls.add(android.companion.Telecom.Call.SILENCE);
+                mControls.add(android.companion.Telecom.SILENCE);
             }
         }
         if (mStatus == android.companion.Telecom.Call.ONGOING
                 || mStatus == android.companion.Telecom.Call.ON_HOLD) {
-            mControls.add(android.companion.Telecom.Call.END);
+            mControls.add(android.companion.Telecom.END);
             if (callDetails.can(Call.Details.CAPABILITY_HOLD)) {
                 mControls.add(
                         mStatus == android.companion.Telecom.Call.ON_HOLD
-                                ? android.companion.Telecom.Call.TAKE_OFF_HOLD
-                                : android.companion.Telecom.Call.PUT_ON_HOLD);
+                                ? android.companion.Telecom.TAKE_OFF_HOLD
+                                : android.companion.Telecom.PUT_ON_HOLD);
             }
         }
         if (mStatus == android.companion.Telecom.Call.ONGOING && callDetails.can(
                 Call.Details.CAPABILITY_MUTE)) {
-            mControls.add(mIsMuted ? android.companion.Telecom.Call.UNMUTE
-                    : android.companion.Telecom.Call.MUTE);
+            mControls.add(mIsMuted ? android.companion.Telecom.UNMUTE
+                    : android.companion.Telecom.MUTE);
         }
     }
 
@@ -212,7 +209,7 @@
         }
     }
 
-    public long getId() {
+    public String getId() {
         return mId;
     }
 
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
index 3d8fb7a..e5ab963 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
@@ -16,27 +16,32 @@
 
 package com.android.server.companion.datatransfer.contextsync;
 
+import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_CONTEXT_SYNC;
+
 import android.app.admin.DevicePolicyManager;
 import android.companion.AssociationInfo;
 import android.companion.ContextSyncMessage;
+import android.companion.IOnMessageReceivedListener;
+import android.companion.IOnTransportsChangedListener;
 import android.companion.Telecom;
-import android.companion.Telecom.Call;
 import android.content.Context;
+import android.os.Binder;
 import android.os.UserHandle;
-import android.util.Pair;
 import android.util.Slog;
+import android.util.proto.ProtoInputStream;
 import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoParseException;
+import android.util.proto.ProtoUtils;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.companion.CompanionDeviceConfig;
+import com.android.server.companion.transport.CompanionTransportManager;
+
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 /**
@@ -45,149 +50,308 @@
 public class CrossDeviceSyncController {
 
     private static final String TAG = "CrossDeviceSyncController";
-    private static final int BYTE_ARRAY_SIZE = 64;
+
+    private static final int VERSION_1 = 1;
+    private static final int CURRENT_VERSION = VERSION_1;
 
     private final Context mContext;
-    private final Callback mCdmCallback;
-    private final Map<Integer, List<AssociationInfo>> mUserIdToAssociationInfo = new HashMap<>();
-    private final Map<Integer, Pair<InputStream, OutputStream>> mAssociationIdToStreams =
-            new HashMap<>();
+    private final CompanionTransportManager mCompanionTransportManager;
+    private final List<AssociationInfo> mConnectedAssociations = new ArrayList<>();
     private final Set<Integer> mBlocklist = new HashSet<>();
 
-    private CallMetadataSyncCallback mInCallServiceCallMetadataSyncCallback;
+    private CrossDeviceSyncControllerCallback mCrossDeviceSyncControllerCallback;
 
-    public CrossDeviceSyncController(Context context, Callback callback) {
+    public CrossDeviceSyncController(Context context,
+            CompanionTransportManager companionTransportManager) {
         mContext = context;
-        mCdmCallback = callback;
+        mCompanionTransportManager = companionTransportManager;
+        mCompanionTransportManager.addListener(new IOnTransportsChangedListener.Stub() {
+            @Override
+            public void onTransportsChanged(List<AssociationInfo> newAssociations) {
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    if (!CompanionDeviceConfig.isEnabled(
+                            CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+                        return;
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+                final List<AssociationInfo> existingAssociations = new ArrayList<>(
+                        mConnectedAssociations);
+                mConnectedAssociations.clear();
+                mConnectedAssociations.addAll(newAssociations);
+
+                if (mCrossDeviceSyncControllerCallback == null) {
+                    Slog.w(TAG, "No callback to report transports changed");
+                    return;
+                }
+                for (AssociationInfo associationInfo : newAssociations) {
+                    if (!existingAssociations.contains(associationInfo)
+                            && !isAssociationBlocked(associationInfo.getId())) {
+                        mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations(
+                                associationInfo.getUserId(), /* added= */ true);
+                        mCrossDeviceSyncControllerCallback.requestCrossDeviceSync(associationInfo);
+                    }
+                }
+                for (AssociationInfo associationInfo : existingAssociations) {
+                    if (!newAssociations.contains(associationInfo)) {
+                        if (isAssociationBlocked(associationInfo.getId())) {
+                            mBlocklist.remove(associationInfo.getId());
+                        } else {
+                            mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations(
+                                    associationInfo.getUserId(), /* added= */ false);
+                        }
+                    }
+                }
+            }
+        });
+        mCompanionTransportManager.addListener(MESSAGE_REQUEST_CONTEXT_SYNC,
+                new IOnMessageReceivedListener.Stub() {
+                    @Override
+                    public void onMessageReceived(int associationId, byte[] data) {
+                        if (mCrossDeviceSyncControllerCallback == null) {
+                            Slog.w(TAG, "No callback to process context sync message");
+                            return;
+                        }
+                        mCrossDeviceSyncControllerCallback.processContextSyncMessage(associationId,
+                                processTelecomDataFromSync(data));
+                    }
+                });
+    }
+
+    private boolean isAssociationBlocked(int associationId) {
+        return mBlocklist.contains(associationId);
     }
 
     /** Registers the call metadata callback. */
-    public void registerCallMetadataSyncCallback(CallMetadataSyncCallback callback) {
-        mInCallServiceCallMetadataSyncCallback = callback;
+    public void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback) {
+        mCrossDeviceSyncControllerCallback = callback;
+        for (AssociationInfo associationInfo : mConnectedAssociations) {
+            if (!isAssociationBlocked(associationInfo.getId())) {
+                mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations(
+                        associationInfo.getUserId(), /* added= */ true);
+                mCrossDeviceSyncControllerCallback.requestCrossDeviceSync(associationInfo);
+            }
+        }
     }
 
     /** Allow specific associated devices to enable / disable syncing. */
     public void setSyncEnabled(AssociationInfo associationInfo, boolean enabled) {
         if (enabled) {
-            if (mBlocklist.contains(associationInfo.getId())) {
+            if (isAssociationBlocked(associationInfo.getId())) {
                 mBlocklist.remove(associationInfo.getId());
-                openChannel(associationInfo);
+                mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations(
+                        associationInfo.getUserId(), /* added= */ true);
+                mCrossDeviceSyncControllerCallback.requestCrossDeviceSync(associationInfo);
             }
         } else {
-            if (!mBlocklist.contains(associationInfo.getId())) {
+            if (!isAssociationBlocked(associationInfo.getId())) {
                 mBlocklist.add(associationInfo.getId());
-                closeChannel(associationInfo);
+                mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations(
+                        associationInfo.getUserId(), /* added= */ false);
+                // Send empty message to device to clear its data (otherwise it will get stale)
+                syncMessageToDevice(associationInfo.getId(), createEmptyMessage());
             }
         }
     }
 
-    /**
-     * Opens channels to newly associated devices, and closes channels to newly disassociated
-     * devices.
-     *
-     * TODO(b/265466098): this needs to be limited to just connected devices
-     */
-    public void onAssociationsChanged(int userId, List<AssociationInfo> newAssociationInfoList) {
-        final List<AssociationInfo> existingAssociationInfoList = mUserIdToAssociationInfo.get(
-                userId);
-        // Close channels to newly-disconnected devices.
-        for (AssociationInfo existingAssociationInfo : existingAssociationInfoList) {
-            if (!newAssociationInfoList.contains(existingAssociationInfo) && !mBlocklist.contains(
-                    existingAssociationInfo.getId())) {
-                closeChannel(existingAssociationInfo);
-            }
-        }
-        // Open channels to newly-connected devices.
-        for (AssociationInfo newAssociationInfo : newAssociationInfoList) {
-            if (!existingAssociationInfoList.contains(newAssociationInfo) && !mBlocklist.contains(
-                    newAssociationInfo.getId())) {
-                openChannel(newAssociationInfo);
-            }
-        }
-        mUserIdToAssociationInfo.put(userId, newAssociationInfoList);
-    }
-
     private boolean isAdminBlocked(int userId) {
         return mContext.getSystemService(DevicePolicyManager.class)
                 .getBluetoothContactSharingDisabled(UserHandle.of(userId));
     }
 
-    /** Stop reading, close streams, and close secure channel. */
-    private void closeChannel(AssociationInfo associationInfo) {
-        // TODO(b/265466098): stop reading from secure channel
-        final Pair<InputStream, OutputStream> streams = mAssociationIdToStreams.get(
-                associationInfo.getId());
-        if (streams != null) {
-            try {
-                if (streams.first != null) {
-                    streams.first.close();
-                }
-                if (streams.second != null) {
-                    streams.second.close();
-                }
-            } catch (IOException e) {
-                Slog.e(TAG, "Could not close streams for association " + associationInfo.getId(),
-                        e);
-            }
-        }
-        mCdmCallback.closeSecureChannel(associationInfo.getId());
-    }
-
-    /** Sync initial snapshot and start reading. */
-    private void openChannel(AssociationInfo associationInfo) {
-        final InputStream is = new ByteArrayInputStream(new byte[BYTE_ARRAY_SIZE]);
-        final OutputStream os = new ByteArrayOutputStream(BYTE_ARRAY_SIZE);
-        mAssociationIdToStreams.put(associationInfo.getId(), new Pair<>(is, os));
-        mCdmCallback.createSecureChannel(associationInfo.getId(), is, os);
-        // TODO(b/265466098): only requestSync for this specific association / connection?
-        mInCallServiceCallMetadataSyncCallback.requestCrossDeviceSync(associationInfo.getUserId());
-        // TODO(b/265466098): start reading from secure channel
-    }
-
     /**
      * Sync data to associated devices.
      *
      * @param userId The user whose data should be synced.
      * @param calls The full list of current calls for all users.
      */
-    public void crossDeviceSync(int userId, Collection<CrossDeviceCall> calls) {
-        final boolean isAdminBlocked = isAdminBlocked(userId);
-        for (AssociationInfo associationInfo : mUserIdToAssociationInfo.get(userId)) {
-            final Pair<InputStream, OutputStream> streams = mAssociationIdToStreams.get(
-                    associationInfo.getId());
-            final ProtoOutputStream pos = new ProtoOutputStream(streams.second);
-            final long telecomToken = pos.start(ContextSyncMessage.TELECOM);
-            for (CrossDeviceCall call : calls) {
-                final long callsToken = pos.start(Telecom.CALLS);
-                pos.write(Call.ID, call.getId());
-                final long originToken = pos.start(Call.ORIGIN);
-                pos.write(Call.Origin.CALLER_ID, call.getReadableCallerId(isAdminBlocked));
-                pos.write(Call.Origin.APP_ICON, call.getCallingAppIcon());
-                pos.write(Call.Origin.APP_NAME, call.getCallingAppName());
-                pos.end(originToken);
-                pos.write(Call.STATUS, call.getStatus());
-                for (int control : call.getControls()) {
-                    pos.write(Call.CONTROLS_AVAILABLE, control);
-                }
-                pos.end(callsToken);
+    public void syncToAllDevicesForUserId(int userId, Collection<CrossDeviceCall> calls) {
+        final Set<Integer> associationIds = new HashSet<>();
+        for (AssociationInfo associationInfo : mConnectedAssociations) {
+            if (associationInfo.getUserId() == userId && !isAssociationBlocked(
+                    associationInfo.getId())) {
+                associationIds.add(associationInfo.getId());
             }
-            pos.end(telecomToken);
-            pos.flush();
         }
+        if (associationIds.isEmpty()) {
+            Slog.w(TAG, "No eligible devices to sync to");
+            return;
+        }
+
+        mCompanionTransportManager.sendMessage(MESSAGE_REQUEST_CONTEXT_SYNC,
+                createCallUpdateMessage(calls, userId),
+                associationIds.stream().mapToInt(Integer::intValue).toArray());
     }
 
     /**
-     * Callback to be implemented by CompanionDeviceManagerService.
+     * Sync data to associated devices.
+     *
+     * @param associationInfo The association whose data should be synced.
+     * @param calls           The full list of current calls for all users.
      */
-    public interface Callback {
-        /**
-         * Create a secure channel to send messages.
-         */
-        void createSecureChannel(int associationId, InputStream input, OutputStream output);
+    public void syncToSingleDevice(AssociationInfo associationInfo,
+            Collection<CrossDeviceCall> calls) {
+        if (isAssociationBlocked(associationInfo.getId())) {
+            Slog.e(TAG, "Cannot sync to requested device; connection is blocked");
+            return;
+        }
 
-        /**
-         * Close the secure channel created previously.
-         */
-        void closeSecureChannel(int associationId);
+        mCompanionTransportManager.sendMessage(MESSAGE_REQUEST_CONTEXT_SYNC,
+                createCallUpdateMessage(calls, associationInfo.getUserId()),
+                new int[]{associationInfo.getId()});
+    }
+
+    /**
+     * Sync data to associated devices.
+     *
+     * @param associationId   The association whose data should be synced.
+     * @param message         The message to sync.
+     */
+    public void syncMessageToDevice(int associationId, byte[] message) {
+        if (isAssociationBlocked(associationId)) {
+            Slog.e(TAG, "Cannot sync to requested device; connection is blocked");
+            return;
+        }
+
+        mCompanionTransportManager.sendMessage(MESSAGE_REQUEST_CONTEXT_SYNC, message,
+                new int[]{associationId});
+    }
+
+    @VisibleForTesting
+    CallMetadataSyncData processTelecomDataFromSync(byte[] data) {
+        final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+        final ProtoInputStream pis = new ProtoInputStream(data);
+        try {
+            int version = -1;
+            while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                switch (pis.getFieldNumber()) {
+                    case (int) ContextSyncMessage.VERSION:
+                        version = pis.readInt(ContextSyncMessage.VERSION);
+                        Slog.e(TAG, "Processing context sync message version " + version);
+                        break;
+                    case (int) ContextSyncMessage.TELECOM:
+                        if (version == VERSION_1) {
+                            final long telecomToken = pis.start(ContextSyncMessage.TELECOM);
+                            while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                                if (pis.getFieldNumber() == (int) Telecom.CALLS) {
+                                    final long callsToken = pis.start(Telecom.CALLS);
+                                    callMetadataSyncData.addCall(processCallDataFromSync(pis));
+                                    pis.end(callsToken);
+                                } else if (pis.getFieldNumber() == (int) Telecom.REQUESTS) {
+                                    final long requestsToken = pis.start(Telecom.REQUESTS);
+                                    callMetadataSyncData.addRequest(processCallDataFromSync(pis));
+                                    pis.end(requestsToken);
+                                } else {
+                                    Slog.e(TAG, "Unhandled field in Telecom:"
+                                            + ProtoUtils.currentFieldToString(pis));
+                                }
+                            }
+                            pis.end(telecomToken);
+                        } else {
+                            Slog.e(TAG, "Cannot process unsupported version " + version);
+                        }
+                        break;
+                    default:
+                        Slog.e(TAG, "Unhandled field in ContextSyncMessage:"
+                                + ProtoUtils.currentFieldToString(pis));
+                }
+            }
+        } catch (IOException | ProtoParseException e) {
+            throw new RuntimeException(e);
+        }
+        return callMetadataSyncData;
+    }
+
+    @VisibleForTesting
+    CallMetadataSyncData.Call processCallDataFromSync(ProtoInputStream pis) throws IOException {
+        final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+        while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pis.getFieldNumber()) {
+                case (int) Telecom.Call.ID:
+                    call.setId(pis.readString(Telecom.Call.ID));
+                    break;
+                case (int) Telecom.Call.ORIGIN:
+                    final long originToken = pis.start(Telecom.Call.ORIGIN);
+                    while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                        switch (pis.getFieldNumber()) {
+                            case (int) Telecom.Call.Origin.APP_ICON:
+                                call.setAppIcon(pis.readBytes(Telecom.Call.Origin.APP_ICON));
+                                break;
+                            case (int) Telecom.Call.Origin.APP_NAME:
+                                call.setAppName(pis.readString(Telecom.Call.Origin.APP_NAME));
+                                break;
+                            case (int) Telecom.Call.Origin.CALLER_ID:
+                                call.setCallerId(pis.readString(Telecom.Call.Origin.CALLER_ID));
+                                break;
+                            case (int) Telecom.Call.Origin.APP_IDENTIFIER:
+                                call.setAppIdentifier(
+                                        pis.readString(Telecom.Call.Origin.APP_IDENTIFIER));
+                                break;
+                            default:
+                                Slog.e(TAG, "Unhandled field in Origin:"
+                                        + ProtoUtils.currentFieldToString(pis));
+                        }
+                    }
+                    pis.end(originToken);
+                    break;
+                case (int) Telecom.Call.STATUS:
+                    call.setStatus(pis.readInt(Telecom.Call.STATUS));
+                    break;
+                case (int) Telecom.Call.CONTROLS:
+                    call.addControl(pis.readInt(Telecom.Call.CONTROLS));
+                    break;
+                default:
+                    Slog.e(TAG,
+                            "Unhandled field in Telecom:" + ProtoUtils.currentFieldToString(pis));
+            }
+        }
+        return call;
+    }
+
+    @VisibleForTesting
+    byte[] createCallUpdateMessage(Collection<CrossDeviceCall> calls, int userId) {
+        final ProtoOutputStream pos = new ProtoOutputStream();
+        pos.write(ContextSyncMessage.VERSION, CURRENT_VERSION);
+        final long telecomToken = pos.start(ContextSyncMessage.TELECOM);
+        for (CrossDeviceCall call : calls) {
+            final long callsToken = pos.start(Telecom.CALLS);
+            pos.write(Telecom.Call.ID, call.getId());
+            final long originToken = pos.start(Telecom.Call.ORIGIN);
+            pos.write(Telecom.Call.Origin.CALLER_ID,
+                    call.getReadableCallerId(isAdminBlocked(userId)));
+            pos.write(Telecom.Call.Origin.APP_ICON, call.getCallingAppIcon());
+            pos.write(Telecom.Call.Origin.APP_NAME, call.getCallingAppName());
+            pos.write(Telecom.Call.Origin.APP_IDENTIFIER, call.getCallingAppPackageName());
+            pos.end(originToken);
+            pos.write(Telecom.Call.STATUS, call.getStatus());
+            for (int control : call.getControls()) {
+                pos.write(Telecom.Call.CONTROLS, control);
+            }
+            pos.end(callsToken);
+        }
+        pos.end(telecomToken);
+        return pos.getBytes();
+    }
+
+    /** Create a call control message. */
+    public static byte[] createCallControlMessage(String callId, int control) {
+        final ProtoOutputStream pos = new ProtoOutputStream();
+        pos.write(ContextSyncMessage.VERSION, CURRENT_VERSION);
+        final long telecomToken = pos.start(ContextSyncMessage.TELECOM);
+        final long requestsToken = pos.start(Telecom.REQUESTS);
+        pos.write(Telecom.Call.ID, callId);
+        pos.write(Telecom.Call.CONTROLS, control);
+        pos.end(requestsToken);
+        pos.end(telecomToken);
+        return pos.getBytes();
+    }
+
+    /** Create an empty context sync message, used to clear state. */
+    public static byte[] createEmptyMessage() {
+        final ProtoOutputStream pos = new ProtoOutputStream();
+        pos.write(ContextSyncMessage.VERSION, CURRENT_VERSION);
+        return pos.getBytes();
     }
 }
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java
similarity index 67%
rename from services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java
rename to services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java
index 7c339d2..31e10a8 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java
@@ -16,12 +16,14 @@
 
 package com.android.server.companion.datatransfer.contextsync;
 
+import android.companion.AssociationInfo;
+
 /** Callback for call metadata syncing. */
-public abstract class CallMetadataSyncCallback {
+public abstract class CrossDeviceSyncControllerCallback {
 
-    abstract void processCallControlAction(int crossDeviceCallId, int callControlAction);
+    void processContextSyncMessage(int associationId, CallMetadataSyncData callMetadataSyncData) {}
 
-    abstract void requestCrossDeviceSync(int userId);
+    void requestCrossDeviceSync(AssociationInfo associationInfo) {}
 
-    abstract void updateStatus(int userId, boolean shouldSyncCallMetadata);
+    void updateNumberOfActiveSyncAssociations(int userId, boolean added) {}
 }
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index 82d0325..a5410e4 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -30,6 +30,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -60,11 +61,13 @@
     /** A set of ALL connected BT device (not only companion.) */
     private final @NonNull Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>();
 
-
-    @GuardedBy("mPendingReportConnectedDevices")
+    /**
+     * A structure hold the connected BT devices that are pending to be reported to the companion
+     * app when the user unlocks the local device per userId.
+     */
+    @GuardedBy("mPendingConnectedDevices")
     @NonNull
-    final SparseArray<Set<BluetoothDevice>> mPendingReportConnectedDevices =
-            new SparseArray<>();
+    final SparseArray<Set<BluetoothDevice>> mPendingConnectedDevices = new SparseArray<>();
 
     BluetoothCompanionDeviceConnectionListener(UserManager userManager,
             @NonNull AssociationStore associationStore, @NonNull Callback callback) {
@@ -98,19 +101,13 @@
         }
         // Try to bind and notify the app after the phone is unlocked.
         if (!mUserManager.isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
-            if (DEBUG) {
-                Log.i(TAG, "Current user is not in unlocking or unlocked stage yet. Notify "
+            Slog.i(TAG, "Current user is not in unlocking or unlocked stage yet. Notify "
                         + "the application when the phone is unlocked");
-            }
-            synchronized (mPendingReportConnectedDevices) {
-                Set<BluetoothDevice> bluetoothDevices = mPendingReportConnectedDevices.get(userId);
-
-                if (bluetoothDevices == null) {
-                    bluetoothDevices = new HashSet<>();
-                    mPendingReportConnectedDevices.put(userId, bluetoothDevices);
-                }
-
+            synchronized (mPendingConnectedDevices) {
+                Set<BluetoothDevice> bluetoothDevices = mPendingConnectedDevices.get(
+                        userId, new HashSet<>());
                 bluetoothDevices.add(device);
+                mPendingConnectedDevices.put(userId, bluetoothDevices);
             }
 
         } else {
@@ -144,8 +141,8 @@
         // Do not need to report the connectivity since the user is not unlock the phone so
         // that cdm is not bind with the app yet.
         if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
-            synchronized (mPendingReportConnectedDevices) {
-                Set<BluetoothDevice> bluetoothDevices = mPendingReportConnectedDevices.get(userId);
+            synchronized (mPendingConnectedDevices) {
+                Set<BluetoothDevice> bluetoothDevices = mPendingConnectedDevices.get(userId);
                 if (bluetoothDevices != null) {
                     bluetoothDevices.remove(device);
                 }
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index 6c56500..f6e9415 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -303,9 +303,9 @@
     /**
      * Return a set of devices that pending to report connectivity
      */
-    public SparseArray<Set<BluetoothDevice>> getPendingReportConnectedDevices() {
-        synchronized (mBtConnectionListener.mPendingReportConnectedDevices) {
-            return mBtConnectionListener.mPendingReportConnectedDevices;
+    public SparseArray<Set<BluetoothDevice>> getPendingConnectedDevices() {
+        synchronized (mBtConnectionListener.mPendingConnectedDevices) {
+            return mBtConnectionListener.mPendingConnectedDevices;
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 678d582..ca50af8 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -52,6 +52,7 @@
 import static android.os.PowerExemptionManager.REASON_ACTIVE_DEVICE_ADMIN;
 import static android.os.PowerExemptionManager.REASON_ACTIVITY_STARTER;
 import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
+import static android.os.PowerExemptionManager.REASON_ALARM_MANAGER_ALARM_CLOCK;
 import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
 import static android.os.PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION;
 import static android.os.PowerExemptionManager.REASON_BACKGROUND_FGS_PERMISSION;
@@ -458,11 +459,12 @@
         public void updateBackgroundRestrictedForUidPackage(int uid, String packageName,
                 boolean restricted) {
             synchronized (mAm) {
-                if (!isForegroundServiceAllowedInBackgroundRestricted(uid, packageName)) {
-                    stopAllForegroundServicesLocked(uid, packageName);
-                }
                 mAm.mProcessList.updateBackgroundRestrictedForUidPackageLocked(
                         uid, packageName, restricted);
+                if (!isForegroundServiceAllowedInBackgroundRestricted(uid, packageName)
+                        && !isTempAllowedByAlarmClock(uid)) {
+                    stopAllForegroundServicesLocked(uid, packageName);
+                }
             }
         }
     }
@@ -475,7 +477,11 @@
             final ServiceRecord r = smap.mServicesByInstanceName.valueAt(i);
             if (uid == r.serviceInfo.applicationInfo.uid
                     || packageName.equals(r.serviceInfo.packageName)) {
-                if (r.isForeground) {
+                // If the FGS is started by temp allowlist of alarm-clock
+                // (REASON_ALARM_MANAGER_ALARM_CLOCK), allow it to continue and do not stop it,
+                // even the app is background-restricted.
+                if (r.isForeground
+                        && r.mAllowStartForegroundAtEntering != REASON_ALARM_MANAGER_ALARM_CLOCK) {
                     toStop.add(r);
                 }
             }
@@ -762,6 +768,15 @@
         }
     }
 
+    private static void traceInstant(@NonNull String message, @NonNull ServiceRecord service) {
+        if (!Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            return;
+        }
+        final String serviceName = (service.getComponentName() != null)
+                ? service.getComponentName().toShortString() : "(?)";
+        Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, message + serviceName);
+    }
+
     ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
             int callingPid, int callingUid, boolean fgRequired, String callingPackage,
             @Nullable String callingFeatureId, final int userId, boolean isSdkSandboxService,
@@ -818,6 +833,9 @@
         }
 
         ServiceRecord r = res.record;
+
+        traceInstant("startService(): ", r);
+
         // Note, when startService() or startForegroundService() is called on an already
         // running SHORT_SERVICE FGS, the call will succeed (i.e. we won't throw
         // ForegroundServiceStartNotAllowedException), even when the service is already timed
@@ -860,7 +878,9 @@
         // start analogously to the legacy-app forced-restrictions case, regardless
         // of its target SDK version.
         boolean forcedStandby = false;
-        if (bgLaunch && appRestrictedAnyInBackground(appUid, appPackageName)) {
+        if (bgLaunch
+                && appRestrictedAnyInBackground(appUid, appPackageName)
+                && !isTempAllowedByAlarmClock(appUid)) {
             if (DEBUG_FOREGROUND_SERVICE) {
                 Slog.d(TAG, "Forcing bg-only service start only for " + r.shortInstanceName
                         + " : bgLaunch=" + bgLaunch + " callerFg=" + callerFg);
@@ -1407,6 +1427,7 @@
     }
 
     private void stopServiceLocked(ServiceRecord service, boolean enqueueOomAdj) {
+        traceInstant("stopService(): ", service);
         try {
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "stopServiceLocked()");
             if (service.delayed) {
@@ -1918,6 +1939,20 @@
                 && isForegroundServiceAllowedInBackgroundRestricted(app);
     }
 
+    /*
+     * If the FGS start is temp allowlisted by alarm-clock(REASON_ALARM_MANAGER_ALARM_CLOCK), it is
+     * allowed even the app is background-restricted.
+     */
+    private boolean isTempAllowedByAlarmClock(int uid) {
+        final ActivityManagerService.FgsTempAllowListItem item =
+                mAm.isAllowlistedForFgsStartLOSP(uid);
+        if (item != null) {
+            return item.mReasonCode == REASON_ALARM_MANAGER_ALARM_CLOCK;
+        } else {
+            return false;
+        }
+    }
+
     void logFgsApiBeginLocked(int uid, int pid, int apiType) {
         synchronized (mFGSLogger) {
             mFGSLogger.logForegroundServiceApiEventBegin(uid, pid, apiType, "");
@@ -1946,6 +1981,7 @@
             if (notification == null) {
                 throw new IllegalArgumentException("null notification");
             }
+            traceInstant("startForeground(): ", r);
             final int foregroundServiceStartType = foregroundServiceType;
             // Instant apps need permission to create foreground services.
             if (r.appInfo.isInstantApp()) {
@@ -2050,7 +2086,8 @@
                 // Apps that are TOP or effectively similar may call startForeground() on
                 // their services even if they are restricted from doing that while in bg.
                 if (!ignoreForeground
-                        && !isForegroundServiceAllowedInBackgroundRestricted(r.app)) {
+                        && !isForegroundServiceAllowedInBackgroundRestricted(r.app)
+                        && !isTempAllowedByAlarmClock(r.app.uid)) {
                     Slog.w(TAG,
                             "Service.startForeground() not allowed due to bg restriction: service "
                                     + r.shortInstanceName);
@@ -2484,6 +2521,7 @@
             }
         } else {
             if (r.isForeground) {
+                traceInstant("stopForeground(): ", r);
                 final ServiceMap smap = getServiceMapLocked(r.userId);
                 if (smap != null) {
                     decActiveForegroundAppLocked(smap, r);
@@ -3311,6 +3349,7 @@
                     Slog.i(TAG_SERVICE, "Short FGS started: " + sr);
                 }
             }
+            traceInstant("short FGS start/extend: ", sr);
             sr.setShortFgsInfo(SystemClock.uptimeMillis());
 
             // We'll restart the timeout.
@@ -3356,10 +3395,11 @@
                 return;
             }
             Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr);
-            final long now = SystemClock.uptimeMillis();
+            traceInstant("short FGS timeout: ", sr);
+
             logFGSStateChangeLocked(sr,
                     FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT,
-                    now > sr.mFgsEnterTime ? (int) (now - sr.mFgsEnterTime) : 0,
+                    nowUptime > sr.mFgsEnterTime ? (int) (nowUptime - sr.mFgsEnterTime) : 0,
                     FGS_STOP_REASON_UNKNOWN,
                     FGS_TYPE_POLICY_CHECK_UNKNOWN);
             try {
@@ -3410,6 +3450,7 @@
             }
 
             Slog.e(TAG_SERVICE, "Short FGS procstate demoted: " + sr);
+            traceInstant("short FGS demote: ", sr);
 
             mAm.updateOomAdjLocked(sr.app, OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
         }
@@ -3440,6 +3481,9 @@
             } else {
                 Slog.e(TAG_SERVICE, message);
             }
+
+            traceInstant("short FGS ANR: ", sr);
+
             mAm.appNotResponding(sr.app, tr);
 
             // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR
@@ -8196,7 +8240,11 @@
                         : ForegroundServiceDelegationOptions.DELEGATION_SERVICE_DEFAULT,
                 0 /* api_sate */,
                 null /* api_type */,
-                null /* api_timestamp */);
+                null /* api_timestamp */,
+                mAm.getUidStateLocked(r.appInfo.uid),
+                mAm.getUidProcessCapabilityLocked(r.appInfo.uid),
+                mAm.getUidStateLocked(r.mRecentCallingUid),
+                mAm.getUidProcessCapabilityLocked(r.mRecentCallingUid));
 
         int event = 0;
         if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a54e8e9..48898a6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5190,7 +5190,10 @@
                         throw new IllegalArgumentException(
                                 "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
                     }
-                    if (PendingIntent.isNewMutableDisallowedImplicitPendingIntent(flags, intent)) {
+                    boolean isActivityResultType =
+                            type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT;
+                    if (PendingIntent.isNewMutableDisallowedImplicitPendingIntent(flags, intent,
+                            isActivityResultType)) {
                         boolean isChangeEnabled = CompatChanges.isChangeEnabled(
                                         PendingIntent.BLOCK_MUTABLE_IMPLICIT_PENDING_INTENT,
                                         owningUid);
@@ -6915,7 +6918,7 @@
         mActivityTaskManager.unhandledBack();
     }
 
-    // TODO: Move to ContentProviderHelper?
+    // TODO: Replace this method with one that returns a bound IContentProvider.
     public ParcelFileDescriptor openContentUri(String uriString) throws RemoteException {
         enforceNotIsolatedCaller("openContentUri");
         final int userId = UserHandle.getCallingUserId();
@@ -6944,6 +6947,16 @@
                     Log.e(TAG, "Cannot find package for uid: " + uid);
                     return null;
                 }
+
+                final ApplicationInfo appInfo = mPackageManagerInt.getApplicationInfo(
+                        androidPackage.getPackageName(), /*flags*/0, Process.SYSTEM_UID,
+                        UserHandle.USER_SYSTEM);
+                if (!appInfo.isVendor() && !appInfo.isSystemApp() && !appInfo.isSystemExt()
+                        && !appInfo.isProduct()) {
+                    Log.e(TAG, "openContentUri may only be used by vendor/system/product.");
+                    return null;
+                }
+
                 final AttributionSource attributionSource = new AttributionSource(
                         Binder.getCallingUid(), androidPackage.getPackageName(), null);
                 pfd = cph.provider.openFile(attributionSource, uri, "r", null);
@@ -14234,7 +14247,8 @@
                 throw new SecurityException(
                         "Intent " + intent.getAction() + " may not be broadcast from an SDK sandbox"
                         + " uid. Given caller package " + callerPackage + " (pid=" + callingPid
-                        + ", uid=" + callingUid + ")");
+                        + ", realCallingUid=" + realCallingUid + ", callingUid= " + callingUid
+                        + ")");
             }
         }
 
@@ -16047,6 +16061,8 @@
 
         final int procState = uidRec != null
                 ? uidRec.getSetProcState() : PROCESS_STATE_NONEXISTENT;
+        final int procAdj = uidRec != null
+                ? uidRec.getMinProcAdj() : ProcessList.INVALID_ADJ;
         final long procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
         final int capability = uidRec != null ? uidRec.getSetCapability() : 0;
         final boolean ephemeral = uidRec != null ? uidRec.isEphemeral() : isEphemeralLocked(uid);
@@ -16062,7 +16078,7 @@
         }
         final int enqueuedChange = mUidObserverController.enqueueUidChange(
                 uidRec == null ? null : uidRec.pendingChange,
-                uid, change, procState, procStateSeq, capability, ephemeral);
+                uid, change, procState, procAdj, procStateSeq, capability, ephemeral);
         if (uidRec != null) {
             uidRec.setLastReportedChange(enqueuedChange);
         }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 5a4d315..4a69f90 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -67,7 +67,6 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.IndentingPrintWriter;
@@ -78,6 +77,7 @@
 import com.android.internal.os.TimeoutRecord;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
+import com.android.server.pm.UserJourneyLogger;
 import com.android.server.pm.UserManagerInternal;
 
 import dalvik.annotation.optimization.NeverCompile;
@@ -1518,7 +1518,7 @@
             final UserInfo userInfo =
                     (umInternal != null) ? umInternal.getUserInfo(r.userId) : null;
             if (userInfo != null) {
-                userType = UserManager.getUserTypeForStatsd(userInfo.userType);
+                userType = UserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
             }
             Slog.i(TAG_BROADCAST,
                     "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 1426cfd..3e82d55 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1015,6 +1015,12 @@
     private static native String getFreezerCheckPath();
 
     /**
+     * Check if task_profiles.json includes valid freezer profiles and actions
+     * @return false if there are invalid profiles or actions
+     */
+    private static native boolean isFreezerProfileValid();
+
+    /**
      * Determines whether the freezer is supported by this system
      */
     public static boolean isFreezerSupported() {
@@ -1031,16 +1037,19 @@
                 // Also check freezer binder ioctl
                 Slog.d(TAG_AM, "Checking binder freezer ioctl");
                 getBinderFreezeInfo(Process.myPid());
-                supported = true;
+
+                // Check if task_profiles.json contains invalid profiles
+                Slog.d(TAG_AM, "Checking freezer profiles");
+                supported = isFreezerProfileValid();
             } else {
-                Slog.e(TAG_AM, "unexpected value in cgroup.freeze");
+                Slog.e(TAG_AM, "Unexpected value in cgroup.freeze");
             }
         } catch (java.io.FileNotFoundException e) {
-            Slog.w(TAG_AM, "cgroup.freeze not present");
+            Slog.w(TAG_AM, "File cgroup.freeze not present");
         } catch (RuntimeException e) {
-            Slog.w(TAG_AM, "unable to read freezer info");
+            Slog.w(TAG_AM, "Unable to read freezer info");
         } catch (Exception e) {
-            Slog.w(TAG_AM, "unable to read cgroup.freeze: " + e.toString());
+            Slog.w(TAG_AM, "Unable to read cgroup.freeze: " + e.toString());
         }
 
         if (fr != null) {
@@ -2100,9 +2109,12 @@
             final boolean frozen;
             final ProcessCachedOptimizerRecord opt = proc.mOptRecord;
 
-            opt.setPendingFreeze(false);
-
             synchronized (mProcLock) {
+                // someone has canceled this freeze
+                if (!opt.isPendingFreeze()) {
+                    return;
+                }
+                opt.setPendingFreeze(false);
                 pid = proc.getPid();
 
                 if (mFreezerOverride) {
@@ -2148,7 +2160,6 @@
                 try {
                     traceAppFreeze(proc.processName, pid, -1);
                     Process.setProcessFrozen(pid, proc.uid, true);
-
                     opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
                     opt.setFrozen(true);
                     opt.setHasCollectedFrozenPSS(false);
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 8f84b08..7908907 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -28,12 +28,13 @@
 import static android.os.Process.INVALID_UID;
 
 import android.annotation.IntDef;
+import android.app.ActivityManager;
 import android.app.ActivityManager.ForegroundServiceApiType;
 import android.app.ForegroundServiceDelegationOptions;
 import android.content.ComponentName;
 import android.content.pm.ServiceInfo;
 import android.util.ArrayMap;
-import android.util.Log;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -191,13 +192,20 @@
         final ArrayList<Integer> apiTypes = convertFgsTypeToApiTypes(record.foregroundServiceType);
         final UidState uidState = mUids.get(uid);
         if (uidState == null) {
-            Log.e(TAG, "FGS stop call being logged with no start call for UID " + uid);
+            Slog.wtfStack(TAG, "FGS stop call being logged with no start call for UID for UID "
+                    + uid
+                    + " in package " + record.packageName);
             return;
         }
         final ArrayList<Integer> apisFound = new ArrayList<>();
         final ArrayList<Long> timestampsFound = new ArrayList<>();
         for (int i = 0, size = apiTypes.size(); i < size; i++) {
-            int apiType = apiTypes.get(i);
+            final int apiType = apiTypes.get(i);
+            if (!uidState.mOpenWithFgsCount.contains(apiType)) {
+                Slog.wtfStack(TAG, "Logger should be tracking FGS types correctly for UID " + uid
+                        + " in package " + record.packageName);
+                continue;
+            }
             // retrieve the eligible closed call
             // we only want to log if this is the only
             // open in flight call. If there are other calls,
@@ -214,7 +222,8 @@
             final ArrayMap<ComponentName, ServiceRecord> runningFgsOfType =
                     uidState.mRunningFgs.get(apiType);
             if (runningFgsOfType == null) {
-                Log.w(TAG, "Could not find appropriate running FGS for FGS stop");
+                Slog.w(TAG, "Could not find appropriate running FGS for FGS stop for UID " + uid
+                        + " in package " + record.packageName);
                 continue;
             }
 
@@ -321,7 +330,7 @@
         // it's not related to any FGS
         UidState uidState = mUids.get(uid);
         if (uidState == null) {
-            Log.w(TAG, "API event end called before start!");
+            Slog.w(TAG, "API event end called before start!");
             return -1;
         }
         if (uidState.mOpenWithFgsCount.contains(apiType)) {
@@ -466,7 +475,11 @@
                         : ForegroundServiceDelegationOptions.DELEGATION_SERVICE_DEFAULT,
                 apiState,
                 apiType,
-                timestamp);
+                timestamp,
+                ActivityManager.PROCESS_STATE_UNKNOWN,
+                ActivityManager.PROCESS_CAPABILITY_NONE,
+                ActivityManager.PROCESS_STATE_UNKNOWN,
+                ActivityManager.PROCESS_CAPABILITY_NONE);
     }
 
     /**
@@ -500,7 +513,11 @@
                 0,
                 apiState,
                 apiType,
-                timestamp);
+                timestamp,
+                ActivityManager.PROCESS_STATE_UNKNOWN,
+                ActivityManager.PROCESS_CAPABILITY_NONE,
+                ActivityManager.PROCESS_STATE_UNKNOWN,
+                ActivityManager.PROCESS_CAPABILITY_NONE);
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 438a08c..0417b8c 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -644,6 +644,11 @@
         }
     }
 
+    @GuardedBy({"mService", "mProcLock"})
+    int getSetAdj() {
+        return mState.getSetAdj();
+    }
+
     @GuardedBy(anyOf = {"mService", "mProcLock"})
     IApplicationThread getThread() {
         return mThread;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 78aafeb..6551db9 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -643,6 +643,7 @@
 
         pw.print(prefix); pw.print("lastUntrustedSetFgsRestrictionAllowedTime=");
         TimeUtils.formatDuration(mLastUntrustedSetFgsRestrictionAllowedTime, now, pw);
+        pw.println();
 
         if (delayed) {
             pw.print(prefix); pw.print("delayed="); pw.println(delayed);
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index 51cb987..790cc7b 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -96,7 +96,7 @@
     }
 
     int enqueueUidChange(@Nullable ChangeRecord currentRecord, int uid, int change, int procState,
-            long procStateSeq, int capability, boolean ephemeral) {
+            int procAdj, long procStateSeq, int capability, boolean ephemeral) {
         synchronized (mLock) {
             if (mPendingUidChanges.size() == 0) {
                 if (DEBUG_UID_OBSERVERS) {
@@ -117,6 +117,7 @@
             changeRecord.uid = uid;
             changeRecord.change = change;
             changeRecord.procState = procState;
+            changeRecord.procAdj = procAdj;
             changeRecord.procStateSeq = procStateSeq;
             changeRecord.capability = capability;
             changeRecord.ephemeral = ephemeral;
@@ -344,7 +345,7 @@
                     }
                     if ((reg.mWhich & ActivityManager.UID_OBSERVER_PROC_OOM_ADJ) != 0
                             && (change & UidRecord.CHANGE_PROCADJ) != 0) {
-                        observer.onUidProcAdjChanged(item.uid);
+                        observer.onUidProcAdjChanged(item.uid, item.procAdj);
                     }
                 }
                 final int duration = (int) (SystemClock.uptimeMillis() - start);
@@ -426,6 +427,7 @@
         public int uid;
         public int change;
         public int procState;
+        public int procAdj;
         public int capability;
         public boolean ephemeral;
         public long procStateSeq;
@@ -435,6 +437,7 @@
             changeRecord.uid = uid;
             changeRecord.change = change;
             changeRecord.procState = procState;
+            changeRecord.procAdj = procAdj;
             changeRecord.capability = capability;
             changeRecord.ephemeral = ephemeral;
             changeRecord.procStateSeq = procStateSeq;
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index e39ac2b..993088e 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -51,6 +51,12 @@
     private boolean mProcAdjChanged;
 
     @CompositeRWLock({"mService", "mProcLock"})
+    private int mCurAdj;
+
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mSetAdj;
+
+    @CompositeRWLock({"mService", "mProcLock"})
     private int mCurCapability;
 
     @CompositeRWLock({"mService", "mProcLock"})
@@ -201,12 +207,24 @@
         mProcAdjChanged = false;
     }
 
-    @GuardedBy({"mService", "mProcLock"})
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     boolean getProcAdjChanged() {
         return mProcAdjChanged;
     }
 
     @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getMinProcAdj() {
+        int minAdj = ProcessList.UNKNOWN_ADJ;
+        for (int i = mProcRecords.size() - 1; i >= 0; i--) {
+            int adj = mProcRecords.valueAt(i).getSetAdj();
+            if (adj < minAdj) {
+                minAdj = adj;
+            }
+        }
+        return minAdj;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     int getCurCapability() {
         return mCurCapability;
     }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index a181402..334c145 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -46,13 +46,24 @@
 import static com.android.server.am.UserState.STATE_RUNNING_LOCKED;
 import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED;
 import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKING;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_INVALID_SESSION_ID;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_BEGIN;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_FINISH;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_NONE;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_START;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_STOP;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_SWITCH_FG;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_UNLOCKED_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_UNLOCKING_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED;
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
 import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND_VISIBLE;
 import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND;
 import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
 import static com.android.server.pm.UserManagerInternal.userStartModeToString;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -123,6 +134,8 @@
 import com.android.server.SystemService.UserCompletedEventType;
 import com.android.server.SystemServiceManager;
 import com.android.server.am.UserState.KeyEvictedCallback;
+import com.android.server.pm.UserJourneyLogger;
+import com.android.server.pm.UserJourneyLogger.UserJourneySession;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
 import com.android.server.pm.UserManagerInternal.UserStartMode;
@@ -138,7 +151,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
-import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
@@ -221,75 +233,6 @@
     // TODO(b/197344658): Increase to 10s or 15s once we have a switch-UX-is-done invocation too.
     private static final int USER_COMPLETED_EVENT_DELAY_MS = 5 * 1000;
 
-    // Used for statsd logging with UserLifecycleJourneyReported + UserLifecycleEventOccurred atoms
-    private static final long INVALID_SESSION_ID = 0;
-
-    // The various user journeys, defined in the UserLifecycleJourneyReported atom for statsd
-    private static final int USER_JOURNEY_UNKNOWN =
-            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__UNKNOWN;
-    private static final int USER_JOURNEY_USER_SWITCH_FG =
-            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_FG;
-    private static final int USER_JOURNEY_USER_SWITCH_UI =
-            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_UI;
-    private static final int USER_JOURNEY_USER_START =
-            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_START;
-    private static final int USER_JOURNEY_USER_CREATE =
-            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE;
-    private static final int USER_JOURNEY_USER_STOP =
-            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_STOP;
-    @IntDef(prefix = { "USER_JOURNEY" }, value = {
-            USER_JOURNEY_UNKNOWN,
-            USER_JOURNEY_USER_SWITCH_FG,
-            USER_JOURNEY_USER_SWITCH_UI,
-            USER_JOURNEY_USER_START,
-            USER_JOURNEY_USER_CREATE,
-            USER_JOURNEY_USER_STOP
-    })
-    @interface UserJourney {}
-
-    // The various user lifecycle events, defined in the UserLifecycleEventOccurred atom for statsd
-    private static final int USER_LIFECYCLE_EVENT_UNKNOWN =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN;
-    private static final int USER_LIFECYCLE_EVENT_SWITCH_USER =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__SWITCH_USER;
-    private static final int USER_LIFECYCLE_EVENT_START_USER =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__START_USER;
-    private static final int USER_LIFECYCLE_EVENT_CREATE_USER =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
-    private static final int USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__USER_RUNNING_LOCKED;
-    private static final int USER_LIFECYCLE_EVENT_UNLOCKING_USER =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKING_USER;
-    private static final int USER_LIFECYCLE_EVENT_UNLOCKED_USER =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKED_USER;
-    private static final int USER_LIFECYCLE_EVENT_STOP_USER =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__STOP_USER;
-    @IntDef(prefix = { "USER_LIFECYCLE_EVENT" }, value = {
-            USER_LIFECYCLE_EVENT_UNKNOWN,
-            USER_LIFECYCLE_EVENT_SWITCH_USER,
-            USER_LIFECYCLE_EVENT_START_USER,
-            USER_LIFECYCLE_EVENT_CREATE_USER,
-            USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
-            USER_LIFECYCLE_EVENT_UNLOCKING_USER,
-            USER_LIFECYCLE_EVENT_UNLOCKED_USER,
-            USER_LIFECYCLE_EVENT_STOP_USER
-    })
-    @interface UserLifecycleEvent {}
-
-    // User lifecyle event state, defined in the UserLifecycleEventOccurred atom for statsd
-    private static final int USER_LIFECYCLE_EVENT_STATE_BEGIN =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__BEGIN;
-    private static final int USER_LIFECYCLE_EVENT_STATE_FINISH =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__FINISH;
-    private static final int USER_LIFECYCLE_EVENT_STATE_NONE =
-            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__NONE;
-    @IntDef(prefix = { "USER_LIFECYCLE_EVENT_STATE" }, value = {
-            USER_LIFECYCLE_EVENT_STATE_BEGIN,
-            USER_LIFECYCLE_EVENT_STATE_FINISH,
-            USER_LIFECYCLE_EVENT_STATE_NONE,
-    })
-    @interface UserLifecycleEventState {}
-
     /**
      * Maximum number of users we allow to be running at a time, including system user.
      *
@@ -420,13 +363,6 @@
     private final ArrayList<Integer> mLastActiveUsers = new ArrayList<>();
 
     /**
-     * {@link UserIdInt} to {@link UserJourneySession} mapping used for statsd logging for the
-     * UserLifecycleJourneyReported and UserLifecycleEventOccurred atoms.
-     */
-    @GuardedBy("mUserIdToUserJourneyMap")
-    private final SparseArray<UserJourneySession> mUserIdToUserJourneyMap = new SparseArray<>();
-
-    /**
      * Map of userId to {@link UserCompletedEventType} event flags, indicating which as-yet-
      * unreported user-starting events have transpired for the given user.
      */
@@ -621,8 +557,9 @@
         // but we might immediately step into RUNNING below if the user
         // storage is already unlocked.
         if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
-            logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
-                    USER_LIFECYCLE_EVENT_STATE_NONE);
+            mInjector.getUserJourneyLogger()
+                    .logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
+                    EVENT_STATE_NONE);
             mInjector.getUserManagerInternal().setUserState(userId, uss.state);
             // Do not report secondary users, runtime restarts or first boot/upgrade
             if (userId == UserHandle.USER_SYSTEM
@@ -646,7 +583,10 @@
                 // user transitions to RUNNING_LOCKED.  However, in "headless system user mode", the
                 // system user is explicitly started before the device has finished booting.  In
                 // that case, we need to wait until onBootComplete() to send the broadcast.
-                if (!(mInjector.isHeadlessSystemUserMode() && uss.mHandle.isSystem())) {
+                // Similarly, this occurs after a user switch, but in HSUM we switch to the main
+                // user before boot is complete, so again this should be delayed until
+                // onBootComplete if boot has not yet completed.
+                if (mAllowUserUnlocking) {
                     // ACTION_LOCKED_BOOT_COMPLETED
                     sendLockedBootCompletedBroadcast(resultTo, userId);
                 }
@@ -694,8 +634,9 @@
     private boolean finishUserUnlocking(final UserState uss) {
         final int userId = uss.mHandle.getIdentifier();
         EventLog.writeEvent(EventLogTags.UC_FINISH_USER_UNLOCKING, userId);
-        logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
-                USER_LIFECYCLE_EVENT_STATE_BEGIN);
+        mInjector.getUserJourneyLogger()
+                .logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
+                EVENT_STATE_BEGIN);
         // If the user key hasn't been unlocked yet, we cannot proceed.
         if (!StorageManager.isUserKeyUnlocked(userId)) return false;
         synchronized (mLock) {
@@ -1073,9 +1014,7 @@
             return;
         }
 
-        logUserJourneyInfo(null, getUserInfo(userId), USER_JOURNEY_USER_STOP);
-        logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
-                USER_LIFECYCLE_EVENT_STATE_BEGIN);
+        logUserJourneyBegin(userId, USER_JOURNEY_USER_STOP);
 
         if (stopUserCallback != null) {
             uss.mStopCallbacks.add(stopUserCallback);
@@ -1138,9 +1077,16 @@
         synchronized (mLock) {
             if (uss.state != UserState.STATE_STOPPING) {
                 // Whoops, we are being started back up.  Abort, abort!
-                logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
-                        USER_LIFECYCLE_EVENT_STATE_NONE);
-                clearSessionId(userId);
+                UserJourneySession session = mInjector.getUserJourneyLogger()
+                        .logUserJourneyFinishWithError(-1, getUserInfo(userId),
+                                USER_JOURNEY_USER_STOP, ERROR_CODE_ABORTED);
+                if (session != null) {
+                    mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
+                } else {
+                    mInjector.getUserJourneyLogger()
+                            .logUserJourneyFinishWithError(-1, getUserInfo(userId),
+                                    USER_JOURNEY_USER_STOP, ERROR_CODE_INVALID_SESSION_ID);
+                }
                 return;
             }
             uss.setState(UserState.STATE_SHUTDOWN);
@@ -1247,9 +1193,11 @@
                 mInjector.getUserManager().removeUserEvenWhenDisallowed(userId);
             }
 
-            logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
-                    USER_LIFECYCLE_EVENT_STATE_FINISH);
-            clearSessionId(userId);
+            UserJourneySession session = mInjector.getUserJourneyLogger()
+                    .logUserJourneyFinish(-1, userInfo, USER_JOURNEY_USER_STOP);
+            if (session != null) {
+                mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
+            }
 
             if (lockUser) {
                 dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
@@ -1259,9 +1207,11 @@
             // which was paused while the SHUTDOWN flow of the user was in progress.
             resumePendingUserStarts(userId);
         } else {
-            logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
-                    USER_LIFECYCLE_EVENT_STATE_NONE);
-            clearSessionId(userId);
+            UserJourneySession session = mInjector.getUserJourneyLogger()
+                    .finishAndClearIncompleteUserJourney(userId, USER_JOURNEY_USER_STOP);
+            if (session != null) {
+                mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
+            }
         }
     }
 
@@ -2617,9 +2567,9 @@
         // we should *not* transition users out of the BOOTING state using finishUserBoot(), as that
         // doesn't handle issuing the needed onUserStarting() call, and it would just race with an
         // explicit start anyway.  We do, however, need to send the "locked boot complete" broadcast
-        // for the system user, as that got skipped earlier due to the *device* boot not being
-        // complete yet.  We also need to try to unlock all started users, since until now explicit
-        // user starts didn't proceed to unlocking, due to it being too early in the device boot.
+        // as that got skipped earlier due to the *device* boot not being complete yet.
+        // We also need to try to unlock all started users, since until now explicit user starts
+        // didn't proceed to unlocking, due to it being too early in the device boot.
         //
         // USER_SYSTEM must be processed first.  It will be first in the array, as its ID is lowest.
         Preconditions.checkArgument(startedUsers.keyAt(0) == UserHandle.USER_SYSTEM);
@@ -2629,9 +2579,7 @@
             if (!mInjector.isHeadlessSystemUserMode()) {
                 finishUserBoot(uss, resultTo);
             } else {
-                if (userId == UserHandle.USER_SYSTEM) {
-                    sendLockedBootCompletedBroadcast(resultTo, userId);
-                }
+                sendLockedBootCompletedBroadcast(resultTo, userId);
                 maybeUnlockUser(userId);
             }
         }
@@ -3135,10 +3083,7 @@
     public boolean handleMessage(Message msg) {
         switch (msg.what) {
             case START_USER_SWITCH_FG_MSG:
-                logUserJourneyInfo(getUserInfo(getCurrentUserId()), getUserInfo(msg.arg1),
-                        USER_JOURNEY_USER_SWITCH_FG);
-                logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_SWITCH_USER,
-                        USER_LIFECYCLE_EVENT_STATE_BEGIN);
+                logUserJourneyBegin(msg.arg1, USER_JOURNEY_USER_SWITCH_FG);
                 startUserInForeground(msg.arg1);
                 break;
             case REPORT_USER_SWITCH_MSG:
@@ -3160,18 +3105,15 @@
                 mInjector.batteryStatsServiceNoteEvent(
                         BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
                         Integer.toString(msg.arg1), msg.arg1);
-                logUserJourneyInfo(null, getUserInfo(msg.arg1), USER_JOURNEY_USER_START);
-                logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER,
-                        USER_LIFECYCLE_EVENT_STATE_BEGIN);
+                logUserJourneyBegin(msg.arg1, USER_JOURNEY_USER_START);
 
                 mInjector.onUserStarting(/* userId= */ msg.arg1);
                 scheduleOnUserCompletedEvent(msg.arg1,
                         UserCompletedEventType.EVENT_TYPE_USER_STARTING,
                         USER_COMPLETED_EVENT_DELAY_MS);
 
-                logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER,
-                        USER_LIFECYCLE_EVENT_STATE_FINISH);
-                clearSessionId(msg.arg1, USER_JOURNEY_USER_START);
+                mInjector.getUserJourneyLogger().logUserJourneyFinish(-1 , getUserInfo(msg.arg1),
+                        USER_JOURNEY_USER_START);
                 break;
             case USER_UNLOCK_MSG:
                 final int userId = msg.arg1;
@@ -3180,10 +3122,11 @@
                 FgThread.getHandler().post(() -> {
                     mInjector.loadUserRecents(userId);
                 });
-                logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
-                        USER_LIFECYCLE_EVENT_STATE_FINISH);
-                logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKED_USER,
-                        USER_LIFECYCLE_EVENT_STATE_BEGIN);
+
+                mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
+                        USER_LIFECYCLE_EVENT_UNLOCKING_USER, EVENT_STATE_FINISH);
+                mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
+                        USER_LIFECYCLE_EVENT_UNLOCKED_USER, EVENT_STATE_BEGIN);
 
                 final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
                 t.traceBegin("finishUserUnlocked-" + userId);
@@ -3199,9 +3142,9 @@
                         // (No need to acquire lock to read mCurrentUserId since it is volatile.)
                         // TODO: Find something to wait for in the case of a profile.
                         mCurrentUserId == msg.arg1 ? USER_COMPLETED_EVENT_DELAY_MS : 1000);
-                logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKED_USER,
-                        USER_LIFECYCLE_EVENT_STATE_FINISH);
-                clearSessionId(msg.arg1);
+                mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
+                        USER_LIFECYCLE_EVENT_UNLOCKED_USER, EVENT_STATE_FINISH);
+                // Unlocking user is not a journey no need to clear sessionId
                 break;
             case USER_CURRENT_MSG:
                 mInjector.batteryStatsServiceNoteEvent(
@@ -3224,22 +3167,24 @@
                 break;
             case REPORT_USER_SWITCH_COMPLETE_MSG:
                 dispatchUserSwitchComplete(msg.arg1, msg.arg2);
-                logUserLifecycleEvent(msg.arg2, USER_LIFECYCLE_EVENT_SWITCH_USER,
-                        USER_LIFECYCLE_EVENT_STATE_FINISH);
+                UserJourneySession session = mInjector.getUserJourneyLogger()
+                        .logUserSwitchJourneyFinish(msg.arg1, getUserInfo(msg.arg2));
+                if (session != null) {
+                    mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
+                }
                 break;
             case REPORT_LOCKED_BOOT_COMPLETE_MSG:
                 dispatchLockedBootComplete(msg.arg1);
                 break;
             case START_USER_SWITCH_UI_MSG:
                 final Pair<UserInfo, UserInfo> fromToUserPair = (Pair<UserInfo, UserInfo>) msg.obj;
-                logUserJourneyInfo(fromToUserPair.first, fromToUserPair.second,
-                        USER_JOURNEY_USER_SWITCH_UI);
-                logUserLifecycleEvent(fromToUserPair.second.id, USER_LIFECYCLE_EVENT_SWITCH_USER,
-                        USER_LIFECYCLE_EVENT_STATE_BEGIN);
+                logUserJourneyBegin(fromToUserPair.second.id, USER_JOURNEY_USER_SWITCH_UI);
                 showUserSwitchDialog(fromToUserPair);
                 break;
             case CLEAR_USER_JOURNEY_SESSION_MSG:
-                logAndClearSessionId(msg.arg1);
+                mInjector.getUserJourneyLogger()
+                        .finishAndClearIncompleteUserJourney(msg.arg1, msg.arg2);
+                mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, msg.obj);
                 break;
             case COMPLETE_USER_SWITCH_MSG:
                 completeUserSwitch(msg.arg1, msg.arg2);
@@ -3317,123 +3262,29 @@
      * statsd helper method for logging the start of a user journey via a UserLifecycleEventOccurred
      * atom given the originating and targeting users for the journey.
      */
-    private void logUserJourneyInfo(UserInfo origin, UserInfo target, @UserJourney int journey) {
-        final long newSessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
-        synchronized (mUserIdToUserJourneyMap) {
-            UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(target.id);
-            if (userJourneySession != null) {
-                // TODO(b/157007231): Move this logic to a separate class/file.
-                if ((userJourneySession.mJourney == USER_JOURNEY_USER_SWITCH_UI
-                        || userJourneySession.mJourney == USER_JOURNEY_USER_SWITCH_FG)
-                        && (journey == USER_JOURNEY_USER_START
-                                || journey == USER_JOURNEY_USER_STOP)) {
-                    /*
-                     * There is already a user switch journey, and a user start or stop journey for
-                     * the same target user received. New journey is most likely a part of user
-                     * switch journey so no need to create a new journey.
-                     */
-                    if (DEBUG_MU) {
-                        Slogf.d(TAG, journey + " not logged as it is expected to be part of "
-                                + userJourneySession.mJourney);
-                    }
-                    return;
-                }
-                /*
-                 * Possible reasons for this condition to be true:
-                 * - A user switch journey is received while another user switch journey is in
-                 *   process for the same user.
-                 * - A user switch journey is received while user start journey is in process for
-                 *   the same user.
-                 * - A user start journey is received while another user start journey is in process
-                 *   for the same user.
-                 * In all cases potentially an incomplete, timed-out session or multiple
-                 * simultaneous requests. It is not possible to keep track of multiple sessions for
-                 * the same user, so previous session is abandoned.
-                 */
-                FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED,
-                        userJourneySession.mSessionId, target.id, USER_LIFECYCLE_EVENT_UNKNOWN,
-                        USER_LIFECYCLE_EVENT_STATE_NONE);
-            }
-
+    private void logUserJourneyBegin(int targetId,
+            @UserJourneyLogger.UserJourney int journey) {
+        UserJourneySession oldSession = mInjector.getUserJourneyLogger()
+                    .finishAndClearIncompleteUserJourney(targetId, journey);
+        if (oldSession != null) {
             if (DEBUG_MU) {
                 Slogf.d(TAG,
-                        "Starting a new journey: " + journey + " with session id: " + newSessionId);
+                        "Starting a new journey: " + journey + " with session id: "
+                                + oldSession);
             }
-
-            userJourneySession = new UserJourneySession(newSessionId, journey);
-            mUserIdToUserJourneyMap.put(target.id, userJourneySession);
             /*
-             * User lifecyle journey would be complete when {@code #clearSessionId} is called after
-             * the last expected lifecycle event for the journey. It may be possible that the last
-             * event is not called, e.g., user not unlocked after user switching. In such cases user
-             * journey is cleared after {@link USER_JOURNEY_TIMEOUT}.
+             * User lifecycle journey would be complete when {@code #clearSessionId} is called
+             * after the last expected lifecycle event for the journey. It may be possible that
+             * the last event is not called, e.g., user not unlocked after user switching. In such
+             * cases user journey is cleared after {@link USER_JOURNEY_TIMEOUT}.
              */
-            mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG);
-            mHandler.sendMessageDelayed(mHandler.obtainMessage(CLEAR_USER_JOURNEY_SESSION_MSG,
-                    target.id, /* arg2= */ 0), USER_JOURNEY_TIMEOUT_MS);
+            mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, oldSession);
         }
+        UserJourneySession newSession = mInjector.getUserJourneyLogger()
+                .logUserJourneyBegin(targetId, journey);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(CLEAR_USER_JOURNEY_SESSION_MSG,
+                targetId, /* arg2= */ journey, newSession), USER_JOURNEY_TIMEOUT_MS);
 
-        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, newSessionId,
-                journey, origin != null ? origin.id : -1,
-                target.id, UserManager.getUserTypeForStatsd(target.userType), target.flags);
-    }
-
-    /**
-     * statsd helper method for logging the given event for the UserLifecycleEventOccurred statsd
-     * atom.
-     */
-    private void logUserLifecycleEvent(@UserIdInt int userId, @UserLifecycleEvent int event,
-            @UserLifecycleEventState int eventState) {
-        final long sessionId;
-        synchronized (mUserIdToUserJourneyMap) {
-            final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(userId);
-            if (userJourneySession == null || userJourneySession.mSessionId == INVALID_SESSION_ID) {
-                Slogf.w(TAG, "UserLifecycleEvent " + event
-                        + " received without an active userJourneySession.");
-                return;
-            }
-            sessionId = userJourneySession.mSessionId;
-        }
-
-        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
-                event, eventState);
-    }
-
-    /**
-     * Clears the {@link UserJourneySession} for a given {@link UserIdInt} and {@link UserJourney}.
-     */
-    private void clearSessionId(@UserIdInt int userId, @UserJourney int journey) {
-        synchronized (mUserIdToUserJourneyMap) {
-            final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(userId);
-            if (userJourneySession != null && userJourneySession.mJourney == journey) {
-                clearSessionId(userId);
-            }
-        }
-    }
-
-    /**
-     * Clears the {@link UserJourneySession} for a given {@link UserIdInt}.
-     */
-    private void clearSessionId(@UserIdInt int userId) {
-        synchronized (mUserIdToUserJourneyMap) {
-            mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG);
-            mUserIdToUserJourneyMap.delete(userId);
-        }
-    }
-
-    /**
-     * Log a final event of the {@link UserJourneySession} and clear it.
-     */
-    private void logAndClearSessionId(@UserIdInt int userId) {
-        synchronized (mUserIdToUserJourneyMap) {
-            final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(userId);
-            if (userJourneySession != null) {
-                FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED,
-                        userJourneySession.mSessionId, userId, USER_LIFECYCLE_EVENT_UNKNOWN,
-                        USER_LIFECYCLE_EVENT_STATE_NONE);
-            }
-            clearSessionId(userId);
-        }
     }
 
     private BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
@@ -3471,23 +3322,6 @@
         return mLastUserUnlockingUptime;
     }
 
-    /**
-     * Helper class to store user journey and session id.
-     *
-     * <p> User journey tracks a chain of user lifecycle events occurring during different user
-     * activities such as user start, user switch, and user creation.
-     */
-    // TODO(b/157007231): Move this class and user journey tracking logic to a separate file.
-    private static class UserJourneySession {
-        final long mSessionId;
-        @UserJourney final int mJourney;
-
-        UserJourneySession(long sessionId, @UserJourney int journey) {
-            mJourney = journey;
-            mSessionId = sessionId;
-        }
-    }
-
     private static class UserProgressListener extends IProgressListener.Stub {
         private volatile long mUnlockStarted;
         @Override
@@ -3562,6 +3396,10 @@
             return new Handler(mService.mUiHandler.getLooper(), callback);
         }
 
+        protected UserJourneyLogger getUserJourneyLogger() {
+            return getUserManager().getUserJourneyLogger();
+        }
+
         protected Context getContext() {
             return mService.mContext;
         }
@@ -3747,7 +3585,8 @@
             synchronized (mUserSwitchingDialogLock) {
                 dismissUserSwitchingDialog(null);
                 mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser,
-                        switchingFromSystemUserMessage, switchingToSystemUserMessage);
+                        switchingFromSystemUserMessage, switchingToSystemUserMessage,
+                        getWindowManager());
                 mUserSwitchingDialog.show(onShown);
             }
         }
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 649305f..4e7865c 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.app.ActivityManager;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.pm.UserInfo;
@@ -34,10 +33,13 @@
 import android.graphics.drawable.Animatable2;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.util.Slog;
 import android.util.TypedValue;
 import android.view.View;
@@ -51,6 +53,9 @@
 import com.android.internal.R;
 import com.android.internal.util.ObjectUtils;
 import com.android.internal.util.UserIcons;
+import com.android.server.wm.WindowManagerService;
+
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Dialog to show during the user switch. This dialog shows target user's name and their profile
@@ -63,18 +68,26 @@
 
     // User switching doesn't happen that frequently, so it doesn't hurt to have it always on
     protected static final boolean DEBUG = true;
+
     private static final long DIALOG_SHOW_HIDE_ANIMATION_DURATION_MS = 300;
     private final boolean mDisableAnimations;
 
+    // Time to wait for the onAnimationEnd() callbacks before moving on
+    private static final int ANIMATION_TIMEOUT_MS = 1000;
+    private final Handler mHandler = new Handler(Looper.myLooper());
+
     protected final UserInfo mOldUser;
     protected final UserInfo mNewUser;
     private final String mSwitchingFromSystemUserMessage;
     private final String mSwitchingToSystemUserMessage;
+    private final WindowManagerService mWindowManager;
     protected final Context mContext;
     private final int mTraceCookie;
+    private final boolean mNeedToFreezeScreen;
 
     UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser,
-            String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
+            String switchingFromSystemUserMessage, String switchingToSystemUserMessage,
+            WindowManagerService windowManager) {
         // TODO(b/278857848): Make full screen user switcher cover top part of the screen as well.
         //                    This problem is seen only on phones, it works fine on tablets.
         super(context, R.style.Theme_Material_NoActionBar_Fullscreen);
@@ -84,8 +97,10 @@
         mNewUser = newUser;
         mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage;
         mSwitchingToSystemUserMessage = switchingToSystemUserMessage;
-        mDisableAnimations = ActivityManager.isLowRamDeviceStatic() || SystemProperties.getBoolean(
+        mDisableAnimations = SystemProperties.getBoolean(
                 "debug.usercontroller.disable_user_switching_dialog_animations", false);
+        mWindowManager = windowManager;
+        mNeedToFreezeScreen = !mDisableAnimations && !isUserSetupComplete(newUser);
         mTraceCookie = UserHandle.MAX_SECONDARY_USER_ID * oldUser.id + newUser.id;
 
         inflateContent();
@@ -167,38 +182,38 @@
                 : res.getString(R.string.user_switching_message, mNewUser.name);
     }
 
+    private boolean isUserSetupComplete(UserInfo user) {
+        return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.USER_SETUP_COMPLETE, /* default= */ 0, user.id) == 1;
+    }
+
     @Override
     public void show() {
-        asyncTraceBegin("", 0);
+        asyncTraceBegin("dialog", 0);
         super.show();
     }
 
     @Override
     public void dismiss() {
         super.dismiss();
-        asyncTraceEnd("", 0);
+        stopFreezingScreen();
+        asyncTraceEnd("dialog", 0);
     }
 
     public void show(@NonNull Runnable onShown) {
         if (DEBUG) Slog.d(TAG, "show called");
         show();
-
-        if (mDisableAnimations) {
+        startShowAnimation(() -> {
+            startFreezingScreen();
             onShown.run();
-        } else {
-            startShowAnimation(onShown);
-        }
+        });
     }
 
     public void dismiss(@Nullable Runnable onDismissed) {
         if (DEBUG) Slog.d(TAG, "dismiss called");
-
         if (onDismissed == null) {
             // no animation needed
             dismiss();
-        } else if (mDisableAnimations) {
-            dismiss();
-            onDismissed.run();
         } else {
             startDismissAnimation(() -> {
                 dismiss();
@@ -207,14 +222,36 @@
         }
     }
 
-    private void startShowAnimation(Runnable onAnimationEnd) {
-        asyncTraceBegin("-showAnimation", 1);
-        startDialogAnimation(new AlphaAnimation(0, 1), () -> {
-            asyncTraceEnd("-showAnimation", 1);
+    private void startFreezingScreen() {
+        if (!mNeedToFreezeScreen) {
+            return;
+        }
+        traceBegin("startFreezingScreen");
+        mWindowManager.startFreezingScreen(0, 0);
+        traceEnd("startFreezingScreen");
+    }
 
-            asyncTraceBegin("-spinnerAnimation", 2);
+    private void stopFreezingScreen() {
+        if (!mNeedToFreezeScreen) {
+            return;
+        }
+        traceBegin("stopFreezingScreen");
+        mWindowManager.stopFreezingScreen();
+        traceEnd("stopFreezingScreen");
+    }
+
+    private void startShowAnimation(Runnable onAnimationEnd) {
+        if (mDisableAnimations) {
+            onAnimationEnd.run();
+            return;
+        }
+        asyncTraceBegin("showAnimation", 1);
+        startDialogAnimation("show", new AlphaAnimation(0, 1), () -> {
+            asyncTraceEnd("showAnimation", 1);
+
+            asyncTraceBegin("spinnerAnimation", 2);
             startProgressAnimation(() -> {
-                asyncTraceEnd("-spinnerAnimation", 2);
+                asyncTraceEnd("spinnerAnimation", 2);
 
                 onAnimationEnd.run();
             });
@@ -222,27 +259,53 @@
     }
 
     private void startDismissAnimation(Runnable onAnimationEnd) {
-        asyncTraceBegin("-dismissAnimation", 3);
-        startDialogAnimation(new AlphaAnimation(1, 0), () -> {
-            asyncTraceEnd("-dismissAnimation", 3);
+        if (mDisableAnimations || mNeedToFreezeScreen) {
+            // animations are disabled or screen is frozen, no need to play an animation
+            onAnimationEnd.run();
+            return;
+        }
+        asyncTraceBegin("dismissAnimation", 3);
+        startDialogAnimation("dismiss", new AlphaAnimation(1, 0), () -> {
+            asyncTraceEnd("dismissAnimation", 3);
 
             onAnimationEnd.run();
         });
     }
 
     private void startProgressAnimation(Runnable onAnimationEnd) {
-        final ImageView progressCircular = findViewById(R.id.progress_circular);
-        final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) progressCircular.getDrawable();
+        final AnimatedVectorDrawable avd = getSpinnerAVD();
+        if (mDisableAnimations || avd == null) {
+            onAnimationEnd.run();
+            return;
+        }
+        final Runnable onAnimationEndWithTimeout = animationWithTimeout("spinner", onAnimationEnd);
         avd.registerAnimationCallback(new Animatable2.AnimationCallback() {
             @Override
             public void onAnimationEnd(Drawable drawable) {
-                onAnimationEnd.run();
+                onAnimationEndWithTimeout.run();
             }
         });
         avd.start();
     }
 
-    private void startDialogAnimation(Animation animation, Runnable onAnimationEnd) {
+    private AnimatedVectorDrawable getSpinnerAVD() {
+        final ImageView view = findViewById(R.id.progress_circular);
+        if (view != null) {
+            final Drawable drawable = view.getDrawable();
+            if (drawable instanceof AnimatedVectorDrawable) {
+                return (AnimatedVectorDrawable) drawable;
+            }
+        }
+        return null;
+    }
+
+    private void startDialogAnimation(String name, Animation animation, Runnable onAnimationEnd) {
+        final View view = findViewById(R.id.content);
+        if (mDisableAnimations || view == null) {
+            onAnimationEnd.run();
+            return;
+        }
+        final Runnable onAnimationEndWithTimeout = animationWithTimeout(name, onAnimationEnd);
         animation.setDuration(DIALOG_SHOW_HIDE_ANIMATION_DURATION_MS);
         animation.setAnimationListener(new Animation.AnimationListener() {
             @Override
@@ -252,7 +315,7 @@
 
             @Override
             public void onAnimationEnd(Animation animation) {
-                onAnimationEnd.run();
+                onAnimationEndWithTimeout.run();
             }
 
             @Override
@@ -260,14 +323,42 @@
 
             }
         });
-        findViewById(R.id.content).startAnimation(animation);
+        view.startAnimation(animation);
+    }
+
+    private Runnable animationWithTimeout(String name, Runnable onAnimationEnd) {
+        final AtomicBoolean isFirst = new AtomicBoolean(true);
+        final Runnable onAnimationEndOrTimeout = () -> {
+            if (isFirst.getAndSet(false)) {
+                mHandler.removeCallbacksAndMessages(null);
+                onAnimationEnd.run();
+            }
+        };
+        mHandler.postDelayed(() -> {
+            Slog.w(TAG, name + " animation not completed in " + ANIMATION_TIMEOUT_MS + " ms");
+            onAnimationEndOrTimeout.run();
+        }, ANIMATION_TIMEOUT_MS);
+
+        return onAnimationEndOrTimeout;
     }
 
     private void asyncTraceBegin(String subTag, int subCookie) {
+        if (DEBUG) Slog.d(TAG, "asyncTraceBegin-" + subTag);
         Trace.asyncTraceBegin(TRACE_TAG, TAG + subTag, mTraceCookie + subCookie);
     }
 
     private void asyncTraceEnd(String subTag, int subCookie) {
         Trace.asyncTraceEnd(TRACE_TAG, TAG + subTag, mTraceCookie + subCookie);
+        if (DEBUG) Slog.d(TAG, "asyncTraceEnd-" + subTag);
+    }
+
+    private void traceBegin(String msg) {
+        if (DEBUG) Slog.d(TAG, "traceBegin-" + msg);
+        Trace.traceBegin(TRACE_TAG, msg);
+    }
+
+    private void traceEnd(String msg) {
+        Trace.traceEnd(TRACE_TAG);
+        if (DEBUG) Slog.d(TAG, "traceEnd-" + msg);
     }
 }
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index d369af6..e4a5a3e 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -402,11 +402,15 @@
     public enum FrameRate {
         FPS_DEFAULT(0),
         FPS_30(30),
+        FPS_36(36),
         FPS_40(40),
         FPS_45(45),
+        FPS_48(48),
         FPS_60(60),
+        FPS_72(72),
         FPS_90(90),
         FPS_120(120),
+        FPS_144(144),
         FPS_INVALID(-1);
 
         public final int fps;
@@ -423,16 +427,24 @@
         switch (raw) {
             case "30":
                 return FrameRate.FPS_30.fps;
+            case "36":
+                return FrameRate.FPS_36.fps;
             case "40":
                 return FrameRate.FPS_40.fps;
             case "45":
                 return FrameRate.FPS_45.fps;
+            case "48":
+                return FrameRate.FPS_48.fps;
             case "60":
                 return FrameRate.FPS_60.fps;
+            case "72":
+                return FrameRate.FPS_72.fps;
             case "90":
                 return FrameRate.FPS_90.fps;
             case "120":
                 return FrameRate.FPS_120.fps;
+            case "144":
+                return FrameRate.FPS_144.fps;
             case "disable":
             case "":
                 return FrameRate.FPS_DEFAULT.fps;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index ada92f5..bc4e8df 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -88,14 +88,14 @@
     private final @NonNull AudioSystemAdapter mAudioSystem;
 
     /** ID for Communication strategy retrieved form audio policy manager */
-    private int mCommunicationStrategyId = -1;
+    /*package*/  int mCommunicationStrategyId = -1;
 
     /** ID for Accessibility strategy retrieved form audio policy manager */
     private int mAccessibilityStrategyId = -1;
 
 
     /** Active communication device reported by audio policy manager */
-    private AudioDeviceInfo mActiveCommunicationDevice;
+    /*package*/ AudioDeviceInfo mActiveCommunicationDevice;
     /** Last preferred device set for communication strategy */
     private AudioDeviceAttributes mPreferredCommunicationDevice;
 
@@ -755,6 +755,19 @@
             mIsLeOutput = false;
         }
 
+        BtDeviceInfo(@NonNull BtDeviceInfo src, int state) {
+            mDevice = src.mDevice;
+            mState = state;
+            mProfile = src.mProfile;
+            mSupprNoisy = src.mSupprNoisy;
+            mVolume = src.mVolume;
+            mIsLeOutput = src.mIsLeOutput;
+            mEventSource = src.mEventSource;
+            mAudioSystemDevice = src.mAudioSystemDevice;
+            mMusicDevice = src.mMusicDevice;
+            mCodec = src.mCodec;
+        }
+
         // redefine equality op so we can match messages intended for this device
         @Override
         public boolean equals(Object o) {
@@ -821,7 +834,7 @@
      * @param info struct with the (dis)connection information
      */
     /*package*/ void queueOnBluetoothActiveDeviceChanged(@NonNull BtDeviceChangedData data) {
-        if (data.mInfo.getProfile() == BluetoothProfile.A2DP && data.mPreviousDevice != null
+        if (data.mPreviousDevice != null
                 && data.mPreviousDevice.equals(data.mNewDevice)) {
             final String name = TextUtils.emptyIfNull(data.mNewDevice.getName());
             new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE + MediaMetrics.SEPARATOR
@@ -830,7 +843,8 @@
                     .set(MediaMetrics.Property.STATUS, data.mInfo.getProfile())
                     .record();
             synchronized (mDeviceStateLock) {
-                postBluetoothA2dpDeviceConfigChange(data.mNewDevice);
+                postBluetoothDeviceConfigChange(createBtDeviceInfo(data, data.mNewDevice,
+                        BluetoothProfile.STATE_CONNECTED));
             }
         } else {
             synchronized (mDeviceStateLock) {
@@ -1064,8 +1078,8 @@
                 new AudioModeInfo(mode, pid, uid));
     }
 
-    /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
-        sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device);
+    /*package*/ void postBluetoothDeviceConfigChange(@NonNull BtDeviceInfo info) {
+        sendLMsgNoDelay(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, info);
     }
 
     /*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode,
@@ -1092,21 +1106,21 @@
 
     /*package*/ int setPreferredDevicesForStrategySync(int strategy,
             @NonNull List<AudioDeviceAttributes> devices) {
-        return mDeviceInventory.setPreferredDevicesForStrategySync(strategy, devices);
+        return mDeviceInventory.setPreferredDevicesForStrategyAndSave(strategy, devices);
     }
 
     /*package*/ int removePreferredDevicesForStrategySync(int strategy) {
-        return mDeviceInventory.removePreferredDevicesForStrategySync(strategy);
+        return mDeviceInventory.removePreferredDevicesForStrategyAndSave(strategy);
     }
 
     /*package*/ int setDeviceAsNonDefaultForStrategySync(int strategy,
             @NonNull AudioDeviceAttributes device) {
-        return mDeviceInventory.setDeviceAsNonDefaultForStrategySync(strategy, device);
+        return mDeviceInventory.setDeviceAsNonDefaultForStrategyAndSave(strategy, device);
     }
 
     /*package*/ int removeDeviceAsNonDefaultForStrategySync(int strategy,
             @NonNull AudioDeviceAttributes device) {
-        return mDeviceInventory.removeDeviceAsNonDefaultForStrategySync(strategy, device);
+        return mDeviceInventory.removeDeviceAsNonDefaultForStrategyAndSave(strategy, device);
     }
 
     /*package*/ void registerStrategyPreferredDevicesDispatcher(
@@ -1131,11 +1145,11 @@
 
     /*package*/ int setPreferredDevicesForCapturePresetSync(int capturePreset,
             @NonNull List<AudioDeviceAttributes> devices) {
-        return mDeviceInventory.setPreferredDevicesForCapturePresetSync(capturePreset, devices);
+        return mDeviceInventory.setPreferredDevicesForCapturePresetAndSave(capturePreset, devices);
     }
 
     /*package*/ int clearPreferredDevicesForCapturePresetSync(int capturePreset) {
-        return mDeviceInventory.clearPreferredDevicesForCapturePresetSync(capturePreset);
+        return mDeviceInventory.clearPreferredDevicesForCapturePresetAndSave(capturePreset);
     }
 
     /*package*/ void registerCapturePresetDevicesRoleDispatcher(
@@ -1322,6 +1336,10 @@
         sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state);
     }
 
+    /*package*/ void postNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) {
+        sendLMsgNoDelay(MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED, SENDMSG_QUEUE, btDevice);
+    }
+
     /*package*/ static final class CommunicationDeviceInfo {
         final @NonNull IBinder mCb; // Identifies the requesting client for death handler
         final int mPid; // Requester process ID
@@ -1397,9 +1415,11 @@
         }
     }
 
-    /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect) {
+    /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes,
+                                boolean connect, @Nullable BluetoothDevice btDevice) {
         synchronized (mDeviceStateLock) {
-            return mDeviceInventory.handleDeviceConnection(attributes, connect, false /*for test*/);
+            return mDeviceInventory.handleDeviceConnection(
+                    attributes, connect, false /*for test*/, btDevice);
         }
     }
 
@@ -1640,13 +1660,10 @@
                                 (String) msg.obj, msg.arg1);
                     }
                     break;
-                case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
-                    final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
+                case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
                     synchronized (mDeviceStateLock) {
-                        final int a2dpCodec = mBtHelper.getA2dpCodec(btDevice);
-                        mDeviceInventory.onBluetoothA2dpDeviceConfigChange(
-                                new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec),
-                                        BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
+                        mDeviceInventory.onBluetoothDeviceConfigChange(
+                                (BtDeviceInfo) msg.obj, BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
                     }
                     break;
                 case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
@@ -1810,6 +1827,10 @@
                 case MSG_IL_SET_LEAUDIO_SUSPENDED: {
                     setLeAudioSuspended((msg.arg1 == 1), false /*internal*/, (String) msg.obj);
                 } break;
+                case MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED: {
+                    final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
+                    BtHelper.onNotifyPreferredAudioProfileApplied(btDevice);
+                } break;
                 default:
                     Log.wtf(TAG, "Invalid message " + msg.what);
             }
@@ -1845,7 +1866,7 @@
     private static final int MSG_IL_BTA2DP_TIMEOUT = 10;
 
     // process change of A2DP device configuration, obj is BluetoothDevice
-    private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11;
+    private static final int MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE = 11;
 
     private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12;
     private static final int MSG_REPORT_NEW_ROUTES = 13;
@@ -1887,13 +1908,15 @@
     private static final int MSG_IL_SET_A2DP_SUSPENDED = 50;
     private static final int MSG_IL_SET_LEAUDIO_SUSPENDED = 51;
 
+    private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52;
+
     private static boolean isMessageHandledUnderWakelock(int msgId) {
         switch(msgId) {
             case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
             case MSG_L_SET_BT_ACTIVE_DEVICE:
             case MSG_IL_BTA2DP_TIMEOUT:
             case MSG_IL_BTLEAUDIO_TIMEOUT:
-            case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+            case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
             case MSG_TOGGLE_HDMI:
             case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT:
             case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT:
@@ -1985,7 +2008,7 @@
                 case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
                 case MSG_IL_BTA2DP_TIMEOUT:
                 case MSG_IL_BTLEAUDIO_TIMEOUT:
-                case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+                case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
                     if (sLastDeviceConnectMsgTime >= time) {
                         // add a little delay to make sure messages are ordered as expected
                         time = sLastDeviceConnectMsgTime + 30;
@@ -2005,7 +2028,7 @@
     static {
         MESSAGES_MUTE_MUSIC = new HashSet<>();
         MESSAGES_MUTE_MUSIC.add(MSG_L_SET_BT_ACTIVE_DEVICE);
-        MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONFIG_CHANGE);
+        MESSAGES_MUTE_MUSIC.add(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE);
         MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT);
         MESSAGES_MUTE_MUSIC.add(MSG_IIL_SET_FORCE_BT_A2DP_USE);
     }
@@ -2026,7 +2049,7 @@
         // Do not mute on bluetooth event if music is playing on a wired headset.
         if ((message == MSG_L_SET_BT_ACTIVE_DEVICE
                 || message == MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT
-                || message == MSG_L_A2DP_DEVICE_CONFIG_CHANGE)
+                || message == MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE)
                 && AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
                 && hasIntersection(mDeviceInventory.DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET,
                         mAudioService.getDeviceSetForStream(AudioSystem.STREAM_MUSIC))) {
@@ -2165,18 +2188,19 @@
         if (preferredCommunicationDevice == null) {
             AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice();
             if (defaultDevice != null) {
-                setPreferredDevicesForStrategySync(
+                mDeviceInventory.setPreferredDevicesForStrategy(
                         mCommunicationStrategyId, Arrays.asList(defaultDevice));
-                setPreferredDevicesForStrategySync(
+                mDeviceInventory.setPreferredDevicesForStrategy(
                         mAccessibilityStrategyId, Arrays.asList(defaultDevice));
             } else {
-                removePreferredDevicesForStrategySync(mCommunicationStrategyId);
-                removePreferredDevicesForStrategySync(mAccessibilityStrategyId);
+                mDeviceInventory.removePreferredDevicesForStrategy(mCommunicationStrategyId);
+                mDeviceInventory.removePreferredDevicesForStrategy(mAccessibilityStrategyId);
             }
+            mDeviceInventory.applyConnectedDevicesRoles();
         } else {
-            setPreferredDevicesForStrategySync(
+            mDeviceInventory.setPreferredDevicesForStrategy(
                     mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice));
-            setPreferredDevicesForStrategySync(
+            mDeviceInventory.setPreferredDevicesForStrategy(
                     mAccessibilityStrategyId, Arrays.asList(preferredCommunicationDevice));
         }
         onUpdatePhoneStrategyDevice(preferredCommunicationDevice);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 228bc87..773df37 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -34,26 +34,36 @@
 import android.media.IStrategyNonDefaultDevicesDispatcher;
 import android.media.IStrategyPreferredDevicesDispatcher;
 import android.media.MediaMetrics;
+import android.media.MediaRecorder.AudioSource;
+import android.media.audiopolicy.AudioProductStrategy;
 import android.media.permission.ClearCallingIdentityContext;
 import android.media.permission.SafeCloseable;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.utils.EventLogger;
 
+import com.google.android.collect.Sets;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
@@ -175,18 +185,26 @@
     final RemoteCallbackList<ICapturePresetDevicesRoleDispatcher> mDevRoleCapturePresetDispatchers =
             new RemoteCallbackList<ICapturePresetDevicesRoleDispatcher>();
 
+    final List<AudioProductStrategy> mStrategies;
+
     /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
-        mDeviceBroker = broker;
-        mAudioSystem = AudioSystemAdapter.getDefaultAdapter();
+        this(broker, AudioSystemAdapter.getDefaultAdapter());
     }
 
     //-----------------------------------------------------------
     /** for mocking only, allows to inject AudioSystem adapter */
     /*package*/ AudioDeviceInventory(@NonNull AudioSystemAdapter audioSystem) {
-        mDeviceBroker = null;
-        mAudioSystem = audioSystem;
+        this(null, audioSystem);
     }
 
+    private AudioDeviceInventory(@Nullable AudioDeviceBroker broker,
+                       @Nullable AudioSystemAdapter audioSystem) {
+        mDeviceBroker = broker;
+        mAudioSystem = audioSystem;
+        mStrategies = AudioProductStrategy.getAudioProductStrategies();
+        mBluetoothDualModeEnabled = SystemProperties.getBoolean(
+                "persist.bluetooth.enable_dual_mode_audio", false);
+    }
     /*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) {
         mDeviceBroker = broker;
     }
@@ -203,8 +221,13 @@
         int mDeviceCodecFormat;
         final UUID mSensorUuid;
 
+        /** Disabled operating modes for this device. Use a negative logic so that by default
+         * an empty list means all modes are allowed.
+         * See BluetoothAdapter.AUDIO_MODE_DUPLEX and BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY */
+        @NonNull ArraySet<String> mDisabledModes = new ArraySet(0);
+
         DeviceInfo(int deviceType, String deviceName, String deviceAddress,
-                   int deviceCodecFormat, UUID sensorUuid) {
+                   int deviceCodecFormat, @Nullable UUID sensorUuid) {
             mDeviceType = deviceType;
             mDeviceName = deviceName == null ? "" : deviceName;
             mDeviceAddress = deviceAddress == null ? "" : deviceAddress;
@@ -212,11 +235,31 @@
             mSensorUuid = sensorUuid;
         }
 
+        void setModeDisabled(String mode) {
+            mDisabledModes.add(mode);
+        }
+        void setModeEnabled(String mode) {
+            mDisabledModes.remove(mode);
+        }
+        boolean isModeEnabled(String mode) {
+            return !mDisabledModes.contains(mode);
+        }
+        boolean isOutputOnlyModeEnabled() {
+            return isModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
+        }
+        boolean isDuplexModeEnabled() {
+            return isModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
+        }
+
         DeviceInfo(int deviceType, String deviceName, String deviceAddress,
                    int deviceCodecFormat) {
             this(deviceType, deviceName, deviceAddress, deviceCodecFormat, null);
         }
 
+        DeviceInfo(int deviceType, String deviceName, String deviceAddress) {
+            this(deviceType, deviceName, deviceAddress, AudioSystem.AUDIO_FORMAT_DEFAULT);
+        }
+
         @Override
         public String toString() {
             return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
@@ -224,7 +267,8 @@
                     + ") name:" + mDeviceName
                     + " addr:" + mDeviceAddress
                     + " codec: " + Integer.toHexString(mDeviceCodecFormat)
-                    + " sensorUuid: " + Objects.toString(mSensorUuid) + "]";
+                    + " sensorUuid: " + Objects.toString(mSensorUuid)
+                    + " disabled modes: " + mDisabledModes + "]";
         }
 
         @NonNull String getKey() {
@@ -276,9 +320,18 @@
             pw.println("  " + prefix + " type:0x" + Integer.toHexString(keyType)
                     + " (" + AudioSystem.getDeviceName(keyType)
                     + ") addr:" + valueAddress); });
+        pw.println("\n" + prefix + "Preferred devices for capture preset:");
         mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> {
             pw.println("  " + prefix + "capturePreset:" + capturePreset
                     + " devices:" + devices); });
+        pw.println("\n" + prefix + "Applied devices roles for strategies:");
+        mAppliedStrategyRoles.forEach((key, devices) -> {
+            pw.println("  " + prefix + "strategy: " + key.first
+                    +  " role:" + key.second + " devices:" + devices); });
+        pw.println("\n" + prefix + "Applied devices roles for presets:");
+        mAppliedPresetRoles.forEach((key, devices) -> {
+            pw.println("  " + prefix + "preset: " + key.first
+                    +  " role:" + key.second + " devices:" + devices); });
     }
 
     //------------------------------------------------------------
@@ -299,15 +352,16 @@
                         AudioSystem.DEVICE_STATE_AVAILABLE,
                         di.mDeviceCodecFormat);
             }
+            mAppliedStrategyRoles.clear();
+            applyConnectedDevicesRoles_l();
         }
         synchronized (mPreferredDevices) {
             mPreferredDevices.forEach((strategy, devices) -> {
-                mAudioSystem.setDevicesRoleForStrategy(
-                        strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices); });
+                setPreferredDevicesForStrategy(strategy, devices); });
         }
         synchronized (mNonDefaultDevices) {
             mNonDefaultDevices.forEach((strategy, devices) -> {
-                mAudioSystem.setDevicesRoleForStrategy(
+                addDevicesRoleForStrategy(
                         strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices); });
         }
         synchronized (mPreferredDevicesForCapturePreset) {
@@ -380,8 +434,7 @@
                                     btInfo.mVolume * 10, btInfo.mAudioSystemDevice,
                                     "onSetBtActiveDevice");
                         }
-                        makeA2dpDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
-                                "onSetBtActiveDevice", btInfo.mCodec);
+                        makeA2dpDeviceAvailable(btInfo, "onSetBtActiveDevice");
                     }
                     break;
                 case BluetoothProfile.HEARING_AID:
@@ -397,10 +450,7 @@
                     if (switchToUnavailable) {
                         makeLeAudioDeviceUnavailableNow(address, btInfo.mAudioSystemDevice);
                     } else if (switchToAvailable) {
-                        makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
-                                streamType, btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10,
-                                btInfo.mAudioSystemDevice,
-                                "onSetBtActiveDevice");
+                        makeLeAudioDeviceAvailable(btInfo, streamType, "onSetBtActiveDevice");
                     }
                     break;
                 default: throw new IllegalArgumentException("Invalid profile "
@@ -411,30 +461,30 @@
 
 
     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
-        /*package*/ void onBluetoothA2dpDeviceConfigChange(
-            @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) {
+    /*package*/ void onBluetoothDeviceConfigChange(
+            @NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int event) {
         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
-                + "onBluetoothA2dpDeviceConfigChange")
-                .set(MediaMetrics.Property.EVENT, BtHelper.a2dpDeviceEventToString(event));
+                + "onBluetoothDeviceConfigChange")
+                .set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event));
 
-        final BluetoothDevice btDevice = btInfo.getBtDevice();
+        final BluetoothDevice btDevice = btInfo.mDevice;
         if (btDevice == null) {
             mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record();
             return;
         }
         if (AudioService.DEBUG_DEVICES) {
-            Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice);
+            Log.d(TAG, "onBluetoothDeviceConfigChange btDevice=" + btDevice);
         }
-        int a2dpVolume = btInfo.getVolume();
-        @AudioSystem.AudioFormatNativeEnumForBtCodec final int a2dpCodec = btInfo.getCodec();
+        int volume = btInfo.mVolume;
+        @AudioSystem.AudioFormatNativeEnumForBtCodec final int audioCodec = btInfo.mCodec;
 
         String address = btDevice.getAddress();
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
             address = "";
         }
         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                "onBluetoothA2dpDeviceConfigChange addr=" + address
-                    + " event=" + BtHelper.a2dpDeviceEventToString(event)));
+                "onBluetoothDeviceConfigChange addr=" + address
+                    + " event=" + BtHelper.deviceEventToString(event)));
 
         synchronized (mDevicesLock) {
             if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) {
@@ -449,53 +499,53 @@
                     AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
             final DeviceInfo di = mConnectedDevices.get(key);
             if (di == null) {
-                Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange");
+                Log.e(TAG, "invalid null DeviceInfo in onBluetoothDeviceConfigChange");
                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record();
                 return;
             }
 
             mmi.set(MediaMetrics.Property.ADDRESS, address)
                     .set(MediaMetrics.Property.ENCODING,
-                            AudioSystem.audioFormatToString(a2dpCodec))
-                    .set(MediaMetrics.Property.INDEX, a2dpVolume)
+                            AudioSystem.audioFormatToString(audioCodec))
+                    .set(MediaMetrics.Property.INDEX, volume)
                     .set(MediaMetrics.Property.NAME, di.mDeviceName);
 
-            if (event == BtHelper.EVENT_ACTIVE_DEVICE_CHANGE) {
-                // Device is connected
-                if (a2dpVolume != -1) {
-                    mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
-                            // convert index to internal representation in VolumeStreamState
-                            a2dpVolume * 10,
-                            AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                            "onBluetoothA2dpDeviceConfigChange");
-                }
-            } else if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
-                if (di.mDeviceCodecFormat != a2dpCodec) {
-                    di.mDeviceCodecFormat = a2dpCodec;
-                    mConnectedDevices.replace(key, di);
-                }
-            }
-            final int res = mAudioSystem.handleDeviceConfigChange(
-                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
-                    BtHelper.getName(btDevice), a2dpCodec);
 
-            if (res != AudioSystem.AUDIO_STATUS_OK) {
-                AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                        "APM handleDeviceConfigChange failed for A2DP device addr=" + address
-                                + " codec=" + AudioSystem.audioFormatToString(a2dpCodec))
-                        .printLog(TAG));
+            if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
+                boolean a2dpCodecChange = false;
+                if (btInfo.mProfile == BluetoothProfile.A2DP) {
+                    if (di.mDeviceCodecFormat != audioCodec) {
+                        di.mDeviceCodecFormat = audioCodec;
+                        mConnectedDevices.replace(key, di);
+                        a2dpCodecChange = true;
+                    }
+                    final int res = mAudioSystem.handleDeviceConfigChange(
+                            btInfo.mAudioSystemDevice, address,
+                            BtHelper.getName(btDevice), audioCodec);
 
-                int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
-                // force A2DP device disconnection in case of error so that AudioService state is
-                // consistent with audio policy manager state
-                setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btDevice,
-                                BluetoothProfile.A2DP, BluetoothProfile.STATE_DISCONNECTED,
-                                musicDevice, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
-            } else {
-                AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                        "APM handleDeviceConfigChange success for A2DP device addr=" + address
-                                + " codec=" + AudioSystem.audioFormatToString(a2dpCodec))
-                        .printLog(TAG));
+                    if (res != AudioSystem.AUDIO_STATUS_OK) {
+                        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                                "APM handleDeviceConfigChange failed for A2DP device addr="
+                                        + address + " codec="
+                                        + AudioSystem.audioFormatToString(audioCodec))
+                                .printLog(TAG));
+
+                        // force A2DP device disconnection in case of error so that AudioService
+                        // state is consistent with audio policy manager state
+                        setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btInfo,
+                                BluetoothProfile.STATE_DISCONNECTED));
+                    } else {
+                        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                                "APM handleDeviceConfigChange success for A2DP device addr="
+                                        + address
+                                        + " codec=" + AudioSystem.audioFormatToString(audioCodec))
+                                .printLog(TAG));
+
+                    }
+                }
+                if (!a2dpCodecChange) {
+                    updateBluetoothPreferredModes_l(btDevice /*connectedDevice*/);
+                }
             }
         }
         mmi.record();
@@ -578,7 +628,7 @@
             }
 
             if (!handleDeviceConnection(wdcs.mAttributes,
-                    wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest)) {
+                    wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest, null)) {
                 // change of connection state failed, bailout
                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed")
                         .record();
@@ -710,43 +760,51 @@
     //------------------------------------------------------------
     // preferred/non-default device(s)
 
-    /*package*/ int setPreferredDevicesForStrategySync(int strategy,
+    /*package*/ int setPreferredDevicesForStrategyAndSave(int strategy,
             @NonNull List<AudioDeviceAttributes> devices) {
-        int status = AudioSystem.ERROR;
-
-        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
-            AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
-                            "setPreferredDevicesForStrategySync, strategy: " + strategy
-                            + " devices: " + devices)).printLog(TAG));
-            status = mAudioSystem.setDevicesRoleForStrategy(
-                    strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
-        }
-
+        final int status = setPreferredDevicesForStrategy(strategy, devices);
         if (status == AudioSystem.SUCCESS) {
             mDeviceBroker.postSaveSetPreferredDevicesForStrategy(strategy, devices);
         }
         return status;
     }
 
-    /*package*/ int removePreferredDevicesForStrategySync(int strategy) {
+    /*package*/ int setPreferredDevicesForStrategy(int strategy,
+            @NonNull List<AudioDeviceAttributes> devices) {
         int status = AudioSystem.ERROR;
-
         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
             AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
-                            "removePreferredDevicesForStrategySync, strategy: "
-                            + strategy)).printLog(TAG));
-
-            status = mAudioSystem.clearDevicesRoleForStrategy(
-                    strategy, AudioSystem.DEVICE_ROLE_PREFERRED);
+                    "setPreferredDevicesForStrategy, strategy: " + strategy
+                            + " devices: " + devices)).printLog(TAG));
+            status = setDevicesRoleForStrategy(
+                    strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
         }
+        return status;
+    }
 
+    /*package*/ int removePreferredDevicesForStrategyAndSave(int strategy) {
+        final int status = removePreferredDevicesForStrategy(strategy);
         if (status == AudioSystem.SUCCESS) {
             mDeviceBroker.postSaveRemovePreferredDevicesForStrategy(strategy);
         }
         return status;
     }
 
-    /*package*/ int setDeviceAsNonDefaultForStrategySync(int strategy,
+    /*package*/ int removePreferredDevicesForStrategy(int strategy) {
+        int status = AudioSystem.ERROR;
+
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
+                            "removePreferredDevicesForStrategy, strategy: "
+                            + strategy)).printLog(TAG));
+
+            status = clearDevicesRoleForStrategy(
+                    strategy, AudioSystem.DEVICE_ROLE_PREFERRED);
+        }
+        return status;
+    }
+
+    /*package*/ int setDeviceAsNonDefaultForStrategyAndSave(int strategy,
             @NonNull AudioDeviceAttributes device) {
         int status = AudioSystem.ERROR;
 
@@ -755,9 +813,9 @@
             devices.add(device);
 
             AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
-                            "setDeviceAsNonDefaultForStrategySync, strategy: " + strategy
+                            "setDeviceAsNonDefaultForStrategyAndSave, strategy: " + strategy
                             + " device: " + device)).printLog(TAG));
-            status = mAudioSystem.setDevicesRoleForStrategy(
+            status = addDevicesRoleForStrategy(
                     strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices);
         }
 
@@ -767,7 +825,7 @@
         return status;
     }
 
-    /*package*/ int removeDeviceAsNonDefaultForStrategySync(int strategy,
+    /*package*/ int removeDeviceAsNonDefaultForStrategyAndSave(int strategy,
             @NonNull AudioDeviceAttributes device) {
         int status = AudioSystem.ERROR;
 
@@ -776,10 +834,10 @@
             devices.add(device);
 
             AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
-                            "removeDeviceAsNonDefaultForStrategySync, strategy: "
+                            "removeDeviceAsNonDefaultForStrategyAndSave, strategy: "
                             + strategy + " devices: " + device)).printLog(TAG));
 
-            status = mAudioSystem.removeDevicesRoleForStrategy(
+            status = removeDevicesRoleForStrategy(
                     strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices);
         }
 
@@ -810,35 +868,72 @@
         mNonDefDevDispatchers.unregister(dispatcher);
     }
 
-    /*package*/ int setPreferredDevicesForCapturePresetSync(
+    /*package*/ int setPreferredDevicesForCapturePresetAndSave(
             int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
-        int status = AudioSystem.ERROR;
-
-        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
-            status = mAudioSystem.setDevicesRoleForCapturePreset(
-                    capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
-        }
-
+        final int status = setPreferredDevicesForCapturePreset(capturePreset, devices);
         if (status == AudioSystem.SUCCESS) {
             mDeviceBroker.postSaveSetPreferredDevicesForCapturePreset(capturePreset, devices);
         }
         return status;
     }
 
-    /*package*/ int clearPreferredDevicesForCapturePresetSync(int capturePreset) {
-        int status  = AudioSystem.ERROR;
-
+    private int setPreferredDevicesForCapturePreset(
+            int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
+        int status = AudioSystem.ERROR;
         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
-            status = mAudioSystem.clearDevicesRoleForCapturePreset(
-                    capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED);
+            status = setDevicesRoleForCapturePreset(
+                    capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
         }
+        return status;
+    }
 
+    /*package*/ int clearPreferredDevicesForCapturePresetAndSave(int capturePreset) {
+        final int status  = clearPreferredDevicesForCapturePreset(capturePreset);
         if (status == AudioSystem.SUCCESS) {
             mDeviceBroker.postSaveClearPreferredDevicesForCapturePreset(capturePreset);
         }
         return status;
     }
 
+    private int clearPreferredDevicesForCapturePreset(int capturePreset) {
+        int status  = AudioSystem.ERROR;
+
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            status = clearDevicesRoleForCapturePreset(
+                    capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED);
+        }
+        return status;
+    }
+
+    private int addDevicesRoleForCapturePreset(int capturePreset, int role,
+                                               @NonNull List<AudioDeviceAttributes> devices) {
+        return addDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
+            return mAudioSystem.addDevicesRoleForCapturePreset(p, r, d);
+        }, capturePreset, role, devices);
+    }
+
+    private int removeDevicesRoleForCapturePreset(int capturePreset, int role,
+                                                  @NonNull List<AudioDeviceAttributes> devices) {
+        return removeDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
+            return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d);
+        }, capturePreset, role, devices);
+    }
+
+    private int setDevicesRoleForCapturePreset(int capturePreset, int role,
+                                               @NonNull List<AudioDeviceAttributes> devices) {
+        return setDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
+            return mAudioSystem.addDevicesRoleForCapturePreset(p, r, d);
+        }, (p, r, d) -> {
+                return mAudioSystem.clearDevicesRoleForCapturePreset(p, r);
+            }, capturePreset, role, devices);
+    }
+
+    private int clearDevicesRoleForCapturePreset(int capturePreset, int role) {
+        return clearDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
+            return mAudioSystem.clearDevicesRoleForCapturePreset(p, r);
+        }, capturePreset, role);
+    }
+
     /*package*/ void registerCapturePresetDevicesRoleDispatcher(
             @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
         mDevRoleCapturePresetDispatchers.register(dispatcher);
@@ -849,7 +944,208 @@
         mDevRoleCapturePresetDispatchers.unregister(dispatcher);
     }
 
-    //-----------------------------------------------------------------------
+    private int addDevicesRoleForStrategy(int strategy, int role,
+                                          @NonNull List<AudioDeviceAttributes> devices) {
+        return addDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
+            return mAudioSystem.setDevicesRoleForStrategy(s, r, d);
+        }, strategy, role, devices);
+    }
+
+    private int removeDevicesRoleForStrategy(int strategy, int role,
+                                      @NonNull List<AudioDeviceAttributes> devices) {
+        return removeDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
+            return mAudioSystem.removeDevicesRoleForStrategy(s, r, d);
+        }, strategy, role, devices);
+    }
+
+    private int setDevicesRoleForStrategy(int strategy, int role,
+                                          @NonNull List<AudioDeviceAttributes> devices) {
+        return setDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
+            return mAudioSystem.setDevicesRoleForStrategy(s, r, d);
+        }, (s, r, d) -> {
+                return mAudioSystem.clearDevicesRoleForStrategy(s, r);
+            }, strategy, role, devices);
+    }
+
+    private int clearDevicesRoleForStrategy(int strategy, int role) {
+        return clearDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
+            return mAudioSystem.clearDevicesRoleForStrategy(s, r);
+        }, strategy, role);
+    }
+
+    //------------------------------------------------------------
+    // Cache for applied roles for strategies and devices. The cache avoids reapplying the
+    // same list of devices for a given role and strategy and the corresponding systematic
+    // redundant work in audio policy manager and audio flinger.
+    // The key is the pair <Strategy , Role> and the value is the current list of devices.
+
+    private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
+            mAppliedStrategyRoles = new ArrayMap<>();
+
+    // Cache for applied roles for capture presets and devices. The cache avoids reapplying the
+    // same list of devices for a given role and capture preset and the corresponding systematic
+    // redundant work in audio policy manager and audio flinger.
+    // The key is the pair <Preset , Role> and the value is the current list of devices.
+    private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
+            mAppliedPresetRoles = new ArrayMap<>();
+
+    interface AudioSystemInterface {
+        int deviceRoleAction(int usecase, int role, @Nullable List<AudioDeviceAttributes> devices);
+    }
+
+    private int addDevicesRole(
+            ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
+            AudioSystemInterface asi,
+            int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) {
+        synchronized (rolesMap) {
+            Pair<Integer, Integer> key = new Pair<>(useCase, role);
+            List<AudioDeviceAttributes> roleDevices = new ArrayList<>();
+            List<AudioDeviceAttributes> appliedDevices = new ArrayList<>();
+
+            if (rolesMap.containsKey(key)) {
+                roleDevices = rolesMap.get(key);
+                for (AudioDeviceAttributes device : devices) {
+                    if (!roleDevices.contains(device)) {
+                        appliedDevices.add(device);
+                    }
+                }
+            } else {
+                appliedDevices.addAll(devices);
+            }
+            if (appliedDevices.isEmpty()) {
+                return AudioSystem.SUCCESS;
+            }
+            final int status = asi.deviceRoleAction(useCase, role, appliedDevices);
+            if (status == AudioSystem.SUCCESS) {
+                roleDevices.addAll(appliedDevices);
+                rolesMap.put(key, roleDevices);
+            }
+            return status;
+        }
+    }
+
+    private int removeDevicesRole(
+            ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
+            AudioSystemInterface asi,
+            int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) {
+        synchronized (rolesMap) {
+            Pair<Integer, Integer> key = new Pair<>(useCase, role);
+            if (!rolesMap.containsKey(key)) {
+                return AudioSystem.SUCCESS;
+            }
+            List<AudioDeviceAttributes> roleDevices = rolesMap.get(key);
+            List<AudioDeviceAttributes> appliedDevices = new ArrayList<>();
+            for (AudioDeviceAttributes device : devices) {
+                if (roleDevices.contains(device)) {
+                    appliedDevices.add(device);
+                }
+            }
+            if (appliedDevices.isEmpty()) {
+                return AudioSystem.SUCCESS;
+            }
+            final int status = asi.deviceRoleAction(useCase, role, appliedDevices);
+            if (status == AudioSystem.SUCCESS) {
+                roleDevices.removeAll(appliedDevices);
+                if (roleDevices.isEmpty()) {
+                    rolesMap.remove(key);
+                } else {
+                    rolesMap.put(key, roleDevices);
+                }
+            }
+            return status;
+        }
+    }
+
+    private int setDevicesRole(
+            ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
+            AudioSystemInterface addOp,
+            AudioSystemInterface clearOp,
+            int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) {
+        synchronized (rolesMap) {
+            Pair<Integer, Integer> key = new Pair<>(useCase, role);
+            List<AudioDeviceAttributes> roleDevices = new ArrayList<>();
+            List<AudioDeviceAttributes> appliedDevices = new ArrayList<>();
+
+            if (rolesMap.containsKey(key)) {
+                roleDevices = rolesMap.get(key);
+                boolean equal = false;
+                if (roleDevices.size() == devices.size()) {
+                    roleDevices.retainAll(devices);
+                    equal = roleDevices.size() == devices.size();
+                }
+                if (!equal) {
+                    clearOp.deviceRoleAction(useCase, role, null);
+                    roleDevices.clear();
+                    appliedDevices.addAll(devices);
+                }
+            } else {
+                appliedDevices.addAll(devices);
+            }
+            if (appliedDevices.isEmpty()) {
+                return AudioSystem.SUCCESS;
+            }
+            final int status = addOp.deviceRoleAction(useCase, role, appliedDevices);
+            if (status == AudioSystem.SUCCESS) {
+                roleDevices.addAll(appliedDevices);
+                rolesMap.put(key, roleDevices);
+            }
+            return status;
+        }
+    }
+
+    private int clearDevicesRole(
+            ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
+            AudioSystemInterface asi, int useCase, int role) {
+        synchronized (rolesMap) {
+            Pair<Integer, Integer> key = new Pair<>(useCase, role);
+            if (!rolesMap.containsKey(key)) {
+                return AudioSystem.SUCCESS;
+            }
+            final int status = asi.deviceRoleAction(useCase, role, null);
+            if (status == AudioSystem.SUCCESS) {
+                rolesMap.remove(key);
+            }
+            return status;
+        }
+    }
+
+    @GuardedBy("mDevicesLock")
+    private void purgeDevicesRoles_l() {
+        purgeRoles(mAppliedStrategyRoles, (s, r, d) -> {
+            return mAudioSystem.removeDevicesRoleForStrategy(s, r, d); });
+        purgeRoles(mAppliedPresetRoles, (p, r, d) -> {
+            return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d); });
+    }
+
+    @GuardedBy("mDevicesLock")
+    private void purgeRoles(
+            ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
+            AudioSystemInterface asi) {
+        synchronized (rolesMap) {
+            Iterator<Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>>> itRole =
+                    rolesMap.entrySet().iterator();
+            while (itRole.hasNext()) {
+                Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>> entry =
+                        itRole.next();
+                Pair<Integer, Integer> keyRole = entry.getKey();
+                Iterator<AudioDeviceAttributes> itDev = rolesMap.get(keyRole).iterator();
+                while (itDev.hasNext()) {
+                    AudioDeviceAttributes ada = itDev.next();
+                    final String devKey = DeviceInfo.makeDeviceListKey(ada.getInternalType(),
+                            ada.getAddress());
+                    if (mConnectedDevices.get(devKey) == null) {
+                        asi.deviceRoleAction(keyRole.first, keyRole.second, Arrays.asList(ada));
+                        itDev.remove();
+                    }
+                }
+                if (rolesMap.get(keyRole).isEmpty()) {
+                    itRole.remove();
+                }
+            }
+        }
+    }
+
+//-----------------------------------------------------------------------
 
     /**
      * Check if a device is in the list of connected devices
@@ -871,10 +1167,11 @@
      * @param connect true if connection
      * @param isForTesting if true, not calling AudioSystem for the connection as this is
      *                    just for testing
+     * @param btDevice the corresponding Bluetooth device when relevant.
      * @return false if an error was reported by AudioSystem
      */
     /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect,
-            boolean isForTesting) {
+            boolean isForTesting, @Nullable BluetoothDevice btDevice) {
         int device = attributes.getInternalType();
         String address = attributes.getAddress();
         String deviceName = attributes.getName();
@@ -889,6 +1186,7 @@
                 .set(MediaMetrics.Property.MODE, connect
                         ? MediaMetrics.Value.CONNECT : MediaMetrics.Value.DISCONNECT)
                 .set(MediaMetrics.Property.NAME, deviceName);
+        boolean status = false;
         synchronized (mDevicesLock) {
             final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
             if (AudioService.DEBUG_DEVICES) {
@@ -916,24 +1214,31 @@
                             .record();
                     return false;
                 }
-                mConnectedDevices.put(deviceKey, new DeviceInfo(
-                        device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+                mConnectedDevices.put(deviceKey, new DeviceInfo(device, deviceName, address));
                 mDeviceBroker.postAccessoryPlugMediaUnmute(device);
-                mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
-                return true;
+                status = true;
             } else if (!connect && isConnected) {
                 mAudioSystem.setDeviceConnectionState(attributes,
                         AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
                 // always remove even if disconnection failed
                 mConnectedDevices.remove(deviceKey);
-                mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
-                return true;
+                status = true;
             }
-            Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
-                    + ", deviceSpec=" + di + ", connect=" + connect);
+            if (status) {
+                if (AudioSystem.isBluetoothScoDevice(device)) {
+                    updateBluetoothPreferredModes_l(connect ? btDevice : null /*connectedDevice*/);
+                    if (!connect) {
+                        purgeDevicesRoles_l();
+                    }
+                }
+                mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
+            } else {
+                Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
+                        + ", deviceSpec=" + di + ", connect=" + connect);
+                mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record();
+            }
         }
-        mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record();
-        return false;
+        return status;
     }
 
 
@@ -1142,15 +1447,20 @@
     // Internal utilities
 
     @GuardedBy("mDevicesLock")
-    private void makeA2dpDeviceAvailable(String address, String name, String eventSource,
-            int a2dpCodec) {
+    private void makeA2dpDeviceAvailable(AudioDeviceBroker.BtDeviceInfo btInfo,
+                                         String eventSource) {
+        final String address = btInfo.mDevice.getAddress();
+        final String name = BtHelper.getName(btInfo.mDevice);
+        final int a2dpCodec = btInfo.mCodec;
+
         // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
         // audio policy manager
         mDeviceBroker.setBluetoothA2dpOnInt(true, true /*fromA2dp*/, eventSource);
         // at this point there could be another A2DP device already connected in APM, but it
         // doesn't matter as this new one will overwrite the previous one
-        final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name),
+        AudioDeviceAttributes ada = new AudioDeviceAttributes(
+                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name);
+        final int res = mAudioSystem.setDeviceConnectionState(ada,
                 AudioSystem.DEVICE_STATE_AVAILABLE, a2dpCodec);
 
         // TODO: log in MediaMetrics once distinction between connection failure and
@@ -1172,8 +1482,7 @@
         // The convention for head tracking sensors associated with A2DP devices is to
         // use a UUID derived from the MAC address as follows:
         //   time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address
-        UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(
-                new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
+        UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
         final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
                 address, a2dpCodec, sensorUuid);
         final String diKey = di.getKey();
@@ -1184,6 +1493,208 @@
 
         mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
         setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/);
+
+        updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
+    }
+
+    static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
+            AudioSource.VOICE_RECOGNITION, AudioSource.VOICE_COMMUNICATION,
+            AudioSource.UNPROCESSED, AudioSource.VOICE_PERFORMANCE, AudioSource.HOTWORD};
+
+    // reflects system property persist.bluetooth.enable_dual_mode_audio
+    final boolean mBluetoothDualModeEnabled;
+    /**
+     * Goes over all connected Bluetooth devices and set the audio policy device role to DISABLED
+     * or not according to their own and other devices modes.
+     * The top priority is given to LE devices, then SCO ,then A2DP.
+     */
+    @GuardedBy("mDevicesLock")
+    private void applyConnectedDevicesRoles_l() {
+        if (!mBluetoothDualModeEnabled) {
+            return;
+        }
+        DeviceInfo leOutDevice =
+                getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_BLE_SET);
+        DeviceInfo leInDevice =
+                getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_BLE_SET);
+        DeviceInfo a2dpDevice =
+                getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
+        DeviceInfo scoOutDevice =
+                getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_SCO_SET);
+        DeviceInfo scoInDevice =
+                getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_SCO_SET);
+        boolean disableA2dp = (leOutDevice != null && leOutDevice.isOutputOnlyModeEnabled());
+        boolean disableSco = (leOutDevice != null && leOutDevice.isDuplexModeEnabled())
+                || (leInDevice != null && leInDevice.isDuplexModeEnabled());
+        AudioDeviceAttributes communicationDevice =
+                mDeviceBroker.mActiveCommunicationDevice == null
+                        ? null : ((mDeviceBroker.isInCommunication()
+                                    && mDeviceBroker.mActiveCommunicationDevice != null)
+                            ? new AudioDeviceAttributes(mDeviceBroker.mActiveCommunicationDevice)
+                            : null);
+
+        if (AudioService.DEBUG_DEVICES) {
+            Log.i(TAG, "applyConnectedDevicesRoles_l\n - leOutDevice: " + leOutDevice
+                    + "\n - leInDevice: " + leInDevice
+                    + "\n - a2dpDevice: " + a2dpDevice
+                    + "\n - scoOutDevice: " + scoOutDevice
+                    + "\n - scoInDevice: " + scoInDevice
+                    + "\n - disableA2dp: " + disableA2dp
+                    + ", disableSco: " + disableSco);
+        }
+
+        for (DeviceInfo di : mConnectedDevices.values()) {
+            if (!AudioSystem.isBluetoothDevice(di.mDeviceType)) {
+                continue;
+            }
+            AudioDeviceAttributes ada =
+                    new AudioDeviceAttributes(di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
+            if (AudioService.DEBUG_DEVICES) {
+                Log.i(TAG, "  + checking Device: " + ada);
+            }
+            if (ada.equalTypeAddress(communicationDevice)) {
+                continue;
+            }
+
+            if (AudioSystem.isBluetoothOutDevice(di.mDeviceType)) {
+                for (AudioProductStrategy strategy : mStrategies) {
+                    boolean disable = false;
+                    if (strategy.getId() == mDeviceBroker.mCommunicationStrategyId) {
+                        if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
+                            disable = disableSco || !di.isDuplexModeEnabled();
+                        } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
+                            disable = !di.isDuplexModeEnabled();
+                        }
+                    } else {
+                        if (AudioSystem.isBluetoothA2dpOutDevice(di.mDeviceType)) {
+                            disable = disableA2dp || !di.isOutputOnlyModeEnabled();
+                        } else if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
+                            disable = disableSco || !di.isOutputOnlyModeEnabled();
+                        } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
+                            disable = !di.isOutputOnlyModeEnabled();
+                        }
+                    }
+                    if (AudioService.DEBUG_DEVICES) {
+                        Log.i(TAG, "     - strategy: " + strategy.getId()
+                                + ", disable: " + disable);
+                    }
+                    if (disable) {
+                        addDevicesRoleForStrategy(strategy.getId(),
+                                AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+                    } else {
+                        removeDevicesRoleForStrategy(strategy.getId(),
+                                AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+                    }
+                }
+            }
+            if (AudioSystem.isBluetoothInDevice(di.mDeviceType)) {
+                for (int capturePreset : CAPTURE_PRESETS) {
+                    boolean disable = false;
+                    if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
+                        disable = disableSco || !di.isDuplexModeEnabled();
+                    } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
+                        disable = !di.isDuplexModeEnabled();
+                    }
+                    if (AudioService.DEBUG_DEVICES) {
+                        Log.i(TAG, "      - capturePreset: " + capturePreset
+                                + ", disable: " + disable);
+                    }
+                    if (disable) {
+                        addDevicesRoleForCapturePreset(capturePreset,
+                                AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+                    } else {
+                        removeDevicesRoleForCapturePreset(capturePreset,
+                                AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+                    }
+                }
+            }
+        }
+    }
+
+    /* package */ void applyConnectedDevicesRoles() {
+        synchronized (mDevicesLock) {
+            applyConnectedDevicesRoles_l();
+        }
+    }
+
+    @GuardedBy("mDevicesLock")
+    int checkProfileIsConnected(int profile) {
+        switch (profile) {
+            case BluetoothProfile.HEADSET:
+                if (getFirstConnectedDeviceOfTypes(
+                        AudioSystem.DEVICE_OUT_ALL_SCO_SET) != null
+                        || getFirstConnectedDeviceOfTypes(
+                                AudioSystem.DEVICE_IN_ALL_SCO_SET) != null) {
+                    return profile;
+                }
+                break;
+            case BluetoothProfile.A2DP:
+                if (getFirstConnectedDeviceOfTypes(
+                        AudioSystem.DEVICE_OUT_ALL_A2DP_SET) != null) {
+                    return profile;
+                }
+                break;
+            case BluetoothProfile.LE_AUDIO:
+            case BluetoothProfile.LE_AUDIO_BROADCAST:
+                if (getFirstConnectedDeviceOfTypes(
+                        AudioSystem.DEVICE_OUT_ALL_BLE_SET) != null
+                        || getFirstConnectedDeviceOfTypes(
+                                AudioSystem.DEVICE_IN_ALL_BLE_SET) != null) {
+                    return profile;
+                }
+                break;
+            default:
+                break;
+        }
+        return 0;
+    }
+
+    @GuardedBy("mDevicesLock")
+    private void updateBluetoothPreferredModes_l(BluetoothDevice connectedDevice) {
+        if (!mBluetoothDualModeEnabled) {
+            return;
+        }
+        HashSet<String> processedAddresses = new HashSet<>(0);
+        for (DeviceInfo di : mConnectedDevices.values()) {
+            if (!AudioSystem.isBluetoothDevice(di.mDeviceType)
+                    || processedAddresses.contains(di.mDeviceAddress)) {
+                continue;
+            }
+            Bundle preferredProfiles = BtHelper.getPreferredAudioProfiles(di.mDeviceAddress);
+            if (AudioService.DEBUG_DEVICES) {
+                Log.i(TAG, "updateBluetoothPreferredModes_l processing device address: "
+                        + di.mDeviceAddress + ", preferredProfiles: " + preferredProfiles);
+            }
+            for (DeviceInfo di2 : mConnectedDevices.values()) {
+                if (!AudioSystem.isBluetoothDevice(di2.mDeviceType)
+                        || !di.mDeviceAddress.equals(di2.mDeviceAddress)) {
+                    continue;
+                }
+                int profile = BtHelper.getProfileFromType(di2.mDeviceType);
+                if (profile == 0) {
+                    continue;
+                }
+                int preferredProfile = checkProfileIsConnected(
+                        preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX));
+                if (preferredProfile == profile || preferredProfile == 0) {
+                    di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
+                } else {
+                    di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
+                }
+                preferredProfile = checkProfileIsConnected(
+                        preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY));
+                if (preferredProfile == profile || preferredProfile == 0) {
+                    di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
+                } else {
+                    di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
+                }
+            }
+            processedAddresses.add(di.mDeviceAddress);
+        }
+        applyConnectedDevicesRoles_l();
+        if (connectedDevice != null) {
+            mDeviceBroker.postNotifyPreferredAudioProfileApplied(connectedDevice);
+        }
     }
 
     @GuardedBy("mDevicesLock")
@@ -1231,6 +1742,8 @@
         // Remove A2DP routes as well
         setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/);
         mmi.record();
+        updateBluetoothPreferredModes_l(null /*connectedDevice*/);
+        purgeDevicesRoles_l();
     }
 
     @GuardedBy("mDevicesLock")
@@ -1260,8 +1773,7 @@
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.put(
                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
-                new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
-                        address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+                new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", address));
     }
 
     @GuardedBy("mDevicesLock")
@@ -1287,8 +1799,7 @@
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.put(
                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
-                new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name,
-                        address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+                new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, address));
         mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID);
         mDeviceBroker.postApplyVolumeOnDevice(streamType,
                 AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
@@ -1326,29 +1837,56 @@
      * @return true if a DEVICE_OUT_HEARING_AID is connected, false otherwise.
      */
     boolean isHearingAidConnected() {
+        return getFirstConnectedDeviceOfTypes(
+                Sets.newHashSet(AudioSystem.DEVICE_OUT_HEARING_AID)) != null;
+    }
+
+    /**
+     * Returns a DeviceInfo for the first connected device matching one of the supplied types
+     */
+    private DeviceInfo getFirstConnectedDeviceOfTypes(Set<Integer> internalTypes) {
+        List<DeviceInfo> devices = getConnectedDevicesOfTypes(internalTypes);
+        return devices.isEmpty() ? null : devices.get(0);
+    }
+
+    /**
+     * Returns a list of connected devices matching one of the supplied types
+     */
+    private List<DeviceInfo> getConnectedDevicesOfTypes(Set<Integer> internalTypes) {
+        ArrayList<DeviceInfo> devices = new ArrayList<>();
         synchronized (mDevicesLock) {
             for (DeviceInfo di : mConnectedDevices.values()) {
-                if (di.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
-                    return true;
+                if (internalTypes.contains(di.mDeviceType)) {
+                    devices.add(di);
                 }
             }
-            return false;
         }
+        return devices;
+    }
+
+    /* package */ AudioDeviceAttributes getDeviceOfType(int type) {
+        DeviceInfo di = getFirstConnectedDeviceOfTypes(Sets.newHashSet(type));
+        return di == null ? null : new AudioDeviceAttributes(
+                    di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
     }
 
     @GuardedBy("mDevicesLock")
-    private void makeLeAudioDeviceAvailable(String address, String name, int streamType,
-            int volumeIndex, int device, String eventSource) {
+    private void makeLeAudioDeviceAvailable(
+            AudioDeviceBroker.BtDeviceInfo btInfo, int streamType, String eventSource) {
+        final String address = btInfo.mDevice.getAddress();
+        final String name = BtHelper.getName(btInfo.mDevice);
+        final int volumeIndex = btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10;
+        final int device = btInfo.mAudioSystemDevice;
+
         if (device != AudioSystem.DEVICE_NONE) {
             /* Audio Policy sees Le Audio similar to A2DP. Let's make sure
              * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
              */
             mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource);
 
-            final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                    device, address, name),
-                    AudioSystem.DEVICE_STATE_AVAILABLE,
-                    AudioSystem.AUDIO_FORMAT_DEFAULT);
+            AudioDeviceAttributes ada = new AudioDeviceAttributes(device, address, name);
+            final int res = AudioSystem.setDeviceConnectionState(ada,
+                    AudioSystem.DEVICE_STATE_AVAILABLE,  AudioSystem.AUDIO_FORMAT_DEFAULT);
             if (res != AudioSystem.AUDIO_STATUS_OK) {
                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                         "APM failed to make available LE Audio device addr=" + address
@@ -1359,12 +1897,13 @@
                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                         "LE Audio device addr=" + address + " now available").printLog(TAG));
             }
-
             // Reset LEA suspend state each time a new sink is connected
             mDeviceBroker.clearLeAudioSuspended();
 
+            UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
             mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
-                    new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+                    new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT,
+                            sensorUuid));
             mDeviceBroker.postAccessoryPlugMediaUnmute(device);
             setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
         }
@@ -1380,6 +1919,8 @@
         final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType);
         mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType);
         mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable");
+
+        updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
     }
 
     @GuardedBy("mDevicesLock")
@@ -1404,6 +1945,8 @@
         }
 
         setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/);
+        updateBluetoothPreferredModes_l(null /*connectedDevice*/);
+        purgeDevicesRoles_l();
     }
 
     @GuardedBy("mDevicesLock")
@@ -1739,18 +2282,6 @@
         }
     }
 
-    /* package */ AudioDeviceAttributes getDeviceOfType(int type) {
-        synchronized (mDevicesLock) {
-            for (DeviceInfo di : mConnectedDevices.values()) {
-                if (di.mDeviceType == type) {
-                    return new AudioDeviceAttributes(
-                            di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
-                }
-            }
-        }
-        return null;
-    }
-
     //----------------------------------------------------------
     // For tests only
 
@@ -1761,10 +2292,12 @@
      */
     @VisibleForTesting
     public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) {
-        final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                device.getAddress());
-        synchronized (mDevicesLock) {
-            return (mConnectedDevices.get(key) != null);
+        for (DeviceInfo di : getConnectedDevicesOfTypes(
+                Sets.newHashSet(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) {
+            if (di.mDeviceAddress.equals(device.getAddress())) {
+                return true;
+            }
         }
+        return false;
     }
 }
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index d066340..4343894 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -445,7 +445,7 @@
     }
 
     /**
-     * Same as {@link AudioSystem#removeDevicesRoleForCapturePreset(int, int, int[], String[])}
+     * Same as {@link AudioSystem#removeDevicesRoleForCapturePreset(int, int, List)}
      * @param capturePreset
      * @param role
      * @param devicesToRemove
@@ -458,6 +458,19 @@
     }
 
     /**
+     * Same as {@link AudioSystem#addDevicesRoleForCapturePreset(int, int, List)}
+     * @param capturePreset the capture preset to configure
+     * @param role the role of the devices
+     * @param devices the list of devices to be added as role for the given capture preset
+     * @return {@link #SUCCESS} if successfully added
+     */
+    public int addDevicesRoleForCapturePreset(
+            int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) {
+        invalidateRoutingCache();
+        return AudioSystem.addDevicesRoleForCapturePreset(capturePreset, role, devices);
+    }
+
+    /**
      * Same as {@link AudioSystem#}
      * @param capturePreset
      * @param role
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 8c27c3e..e46c3cc 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -33,6 +33,7 @@
 import android.media.AudioSystem;
 import android.media.BluetoothProfileConnectionInfo;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -150,60 +151,12 @@
         }
     }
 
-    //----------------------------------------------------------------------
-    /*package*/ static class BluetoothA2dpDeviceInfo {
-        private final @NonNull BluetoothDevice mBtDevice;
-        private final int mVolume;
-        private final @AudioSystem.AudioFormatNativeEnumForBtCodec int mCodec;
-
-        BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice) {
-            this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT);
-        }
-
-        BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice, int volume, int codec) {
-            mBtDevice = btDevice;
-            mVolume = volume;
-            mCodec = codec;
-        }
-
-        public @NonNull BluetoothDevice getBtDevice() {
-            return mBtDevice;
-        }
-
-        public int getVolume() {
-            return mVolume;
-        }
-
-        public @AudioSystem.AudioFormatNativeEnumForBtCodec int getCodec() {
-            return mCodec;
-        }
-
-        // redefine equality op so we can match messages intended for this device
-        @Override
-        public boolean equals(Object o) {
-            if (o == null) {
-                return false;
-            }
-            if (this == o) {
-                return true;
-            }
-            if (o instanceof BluetoothA2dpDeviceInfo) {
-                return mBtDevice.equals(((BluetoothA2dpDeviceInfo) o).getBtDevice());
-            }
-            return false;
-        }
-
-
-    }
-
     // A2DP device events
     /*package*/ static final int EVENT_DEVICE_CONFIG_CHANGE = 0;
-    /*package*/ static final int EVENT_ACTIVE_DEVICE_CHANGE = 1;
 
-    /*package*/ static String a2dpDeviceEventToString(int event) {
+    /*package*/ static String deviceEventToString(int event) {
         switch (event) {
             case EVENT_DEVICE_CONFIG_CHANGE: return "DEVICE_CONFIG_CHANGE";
-            case EVENT_ACTIVE_DEVICE_CHANGE: return "ACTIVE_DEVICE_CHANGE";
             default:
                 return new String("invalid event:" + event);
         }
@@ -620,11 +573,12 @@
         return btHeadsetDeviceToAudioDevice(mBluetoothHeadsetDevice);
     }
 
-    private AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) {
+    private static AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) {
         if (btDevice == null) {
             return new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, "");
         }
         String address = btDevice.getAddress();
+        String name = getName(btDevice);
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
             address = "";
         }
@@ -646,7 +600,7 @@
                     + " btClass: " + (btClass == null ? "Unknown" : btClass)
                     + " nativeType: " + nativeType + " address: " + address);
         }
-        return new AudioDeviceAttributes(nativeType, address);
+        return new AudioDeviceAttributes(nativeType, address, name);
     }
 
     private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
@@ -655,12 +609,9 @@
         }
         int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
         AudioDeviceAttributes audioDevice =  btHeadsetDeviceToAudioDevice(btDevice);
-        String btDeviceName =  getName(btDevice);
         boolean result = false;
         if (isActive) {
-            result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
-                    audioDevice.getInternalType(), audioDevice.getAddress(), btDeviceName),
-                    isActive);
+            result |= mDeviceBroker.handleDeviceConnection(audioDevice, isActive, btDevice);
         } else {
             int[] outDeviceTypes = {
                     AudioSystem.DEVICE_OUT_BLUETOOTH_SCO,
@@ -669,14 +620,14 @@
             };
             for (int outDeviceType : outDeviceTypes) {
                 result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
-                        outDeviceType, audioDevice.getAddress(), btDeviceName),
-                        isActive);
+                        outDeviceType, audioDevice.getAddress(), audioDevice.getName()),
+                        isActive, btDevice);
             }
         }
         // handleDeviceConnection() && result to make sure the method get executed
         result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
-                        inDevice, audioDevice.getAddress(), btDeviceName),
-                isActive) && result;
+                        inDevice, audioDevice.getAddress(), audioDevice.getName()),
+                isActive, btDevice) && result;
         return result;
     }
 
@@ -973,6 +924,30 @@
         }
     }
 
+    /*package */ static int getProfileFromType(int deviceType) {
+        if (AudioSystem.isBluetoothA2dpOutDevice(deviceType)) {
+            return BluetoothProfile.A2DP;
+        } else if (AudioSystem.isBluetoothScoDevice(deviceType)) {
+            return BluetoothProfile.HEADSET;
+        } else if (AudioSystem.isBluetoothLeDevice(deviceType)) {
+            return BluetoothProfile.LE_AUDIO;
+        }
+        return 0; // 0 is not a valid profile
+    }
+
+    /*package */ static Bundle getPreferredAudioProfiles(String address) {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        return adapter.getPreferredAudioProfiles(adapter.getRemoteDevice(address));
+    }
+
+    /**
+     * Notifies Bluetooth framework that new preferred audio profiles for Bluetooth devices
+     * have been applied.
+     */
+    public static void onNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) {
+        BluetoothAdapter.getDefaultAdapter().notifyActiveDeviceChangeApplied(btDevice);
+    }
+
     /**
      * Returns the string equivalent for the btDeviceClass class.
      */
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 7cdea8d..aece17e7 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -34,11 +34,13 @@
 import android.media.ISoundDoseCallback;
 import android.media.SoundDoseRecord;
 import android.os.Binder;
+import android.os.HandlerExecutor;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
@@ -57,6 +59,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
 
@@ -119,6 +122,8 @@
 
     private static final int SAFE_MEDIA_VOLUME_UNINITIALIZED = -1;
 
+    private static final String FEATURE_FLAG_ENABLE_CSD = "enable_csd";
+
     private final EventLogger mLogger = new EventLogger(AudioService.LOG_NB_EVENTS_SOUND_DOSE,
             "CSD updates");
 
@@ -168,7 +173,7 @@
     @NonNull private final AudioHandler mAudioHandler;
     @NonNull private final ISafeHearingVolumeController mVolumeController;
 
-    private final boolean mEnableCsd;
+    private final AtomicBoolean mEnableCsd = new AtomicBoolean(false);
 
     private final Object mCsdStateLock = new Object();
 
@@ -195,7 +200,7 @@
 
     private final ISoundDoseCallback.Stub mSoundDoseCallback = new ISoundDoseCallback.Stub() {
         public void onMomentaryExposure(float currentMel, int deviceId) {
-            if (!mEnableCsd) {
+            if (!mEnableCsd.get()) {
                 Log.w(TAG, "onMomentaryExposure: csd not supported, ignoring callback");
                 return;
             }
@@ -222,7 +227,7 @@
         }
 
         public void onNewCsdValue(float currentCsd, SoundDoseRecord[] records) {
-            if (!mEnableCsd) {
+            if (!mEnableCsd.get()) {
                 Log.w(TAG, "onNewCsdValue: csd not supported, ignoring value");
                 return;
             }
@@ -272,8 +277,6 @@
 
         mContext = context;
 
-        mEnableCsd = mContext.getResources().getBoolean(R.bool.config_audio_csd_enabled_default);
-        initCsd();
         initSafeVolumes();
 
         mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
@@ -285,8 +288,16 @@
         mSafeMediaVolumeIndex = mContext.getResources().getInteger(
                 R.integer.config_safe_media_volume_index) * 10;
 
+        mSoundDose.set(AudioSystem.getSoundDoseInterface(mSoundDoseCallback));
+        // Csd will be initially disabled until the mcc is read in onConfigureSafeMedia()
+        initCsd();
+
         mAlarmManager = (AlarmManager) mContext.getSystemService(
                 Context.ALARM_SERVICE);
+
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_MEDIA,
+                new HandlerExecutor(mAudioHandler),
+                p -> updateCsdEnabled("onPropertiesChanged"));
     }
 
     void initSafeVolumes() {
@@ -300,8 +311,6 @@
                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
         mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLE_BROADCAST,
                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
-        mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_HEARING_AID,
-                SAFE_MEDIA_VOLUME_UNINITIALIZED);
         mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
         // TODO(b/278265907): enable A2DP when we can distinguish A2DP headsets
@@ -310,7 +319,7 @@
     }
 
     float getOutputRs2UpperBound() {
-        if (!mEnableCsd) {
+        if (!mEnableCsd.get()) {
             return 0.f;
         }
 
@@ -329,7 +338,7 @@
     }
 
     void setOutputRs2UpperBound(float rs2Value) {
-        if (!mEnableCsd) {
+        if (!mEnableCsd.get()) {
             return;
         }
 
@@ -347,7 +356,7 @@
     }
 
     float getCsd() {
-        if (!mEnableCsd) {
+        if (!mEnableCsd.get()) {
             return -1.f;
         }
 
@@ -366,7 +375,7 @@
     }
 
     void setCsd(float csd) {
-        if (!mEnableCsd) {
+        if (!mEnableCsd.get()) {
             return;
         }
 
@@ -400,7 +409,7 @@
     }
 
     void resetCsdTimeouts() {
-        if (!mEnableCsd) {
+        if (!mEnableCsd.get()) {
             return;
         }
 
@@ -416,7 +425,7 @@
     }
 
     void forceUseFrameworkMel(boolean useFrameworkMel) {
-        if (!mEnableCsd) {
+        if (!mEnableCsd.get()) {
             return;
         }
 
@@ -434,7 +443,7 @@
     }
 
     void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) {
-        if (!mEnableCsd) {
+        if (!mEnableCsd.get()) {
             return;
         }
 
@@ -454,7 +463,7 @@
     }
 
     boolean isCsdEnabled() {
-        if (!mEnableCsd) {
+        if (!mEnableCsd.get()) {
             return false;
         }
 
@@ -697,8 +706,8 @@
     }
 
     /*package*/ void dump(PrintWriter pw) {
-        pw.print("  mEnableCsd="); pw.println(mEnableCsd);
-        if (mEnableCsd) {
+        pw.print("  mEnableCsd="); pw.println(mEnableCsd.get());
+        if (mEnableCsd.get()) {
             synchronized (mCsdStateLock) {
                 pw.print("  mCurrentCsd="); pw.println(mCurrentCsd);
             }
@@ -719,9 +728,11 @@
         pw.println();
     }
 
-    /*package*/void reset() {
+    /*package*/void  reset() {
         Log.d(TAG, "Reset the sound dose helper");
-        mSoundDose.set(AudioSystem.getSoundDoseInterface(mSoundDoseCallback));
+
+        mSoundDose.compareAndExchange(/*expectedValue=*/null,
+                AudioSystem.getSoundDoseInterface(mSoundDoseCallback));
 
         synchronized (mCsdStateLock) {
             try {
@@ -743,7 +754,7 @@
 
     private void updateDoseAttenuation(int newIndex, int device, int streamType,
             boolean isAbsoluteVolume) {
-        if (!mEnableCsd) {
+        if (!mEnableCsd.get()) {
             return;
         }
 
@@ -775,17 +786,19 @@
     }
 
     private void initCsd() {
-        if (!mEnableCsd) {
-            final ISoundDose soundDose = AudioSystem.getSoundDoseInterface(mSoundDoseCallback);
-            if (soundDose == null) {
-                Log.w(TAG,  "ISoundDose instance is null.");
-                return;
-            }
-            try {
-                soundDose.disableCsd();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Cannot disable CSD", e);
-            }
+        ISoundDose soundDose = mSoundDose.get();
+        if (soundDose == null) {
+            Log.w(TAG, "ISoundDose instance is null.");
+            return;
+        }
+
+        try {
+            soundDose.setCsdEnabled(mEnableCsd.get());
+        } catch (RemoteException e) {
+            Log.e(TAG, "Cannot disable CSD", e);
+        }
+
+        if (!mEnableCsd.get()) {
             return;
         }
 
@@ -829,7 +842,6 @@
                         SystemProperties.getBoolean("audio.safemedia.force", false)
                                 || mContext.getResources().getBoolean(
                                 com.android.internal.R.bool.config_safe_media_volume_enabled);
-
                 boolean safeMediaVolumeBypass =
                         SystemProperties.getBoolean("audio.safemedia.bypass", false);
 
@@ -861,6 +873,28 @@
                                 persistedState, /*arg2=*/0,
                                 /*obj=*/null), /*delay=*/0);
             }
+
+            updateCsdEnabled(caller);
+        }
+    }
+
+    private void updateCsdEnabled(String caller) {
+        boolean newEnableCsd = SystemProperties.getBoolean("audio.safemedia.force", false);
+        if (!newEnableCsd) {
+            final String featureFlagEnableCsdValue = DeviceConfig.getProperty(
+                    DeviceConfig.NAMESPACE_MEDIA,
+                    FEATURE_FLAG_ENABLE_CSD);
+            if (featureFlagEnableCsdValue != null) {
+                newEnableCsd = Boolean.parseBoolean(featureFlagEnableCsdValue);
+            } else {
+                newEnableCsd = mContext.getResources().getBoolean(
+                        R.bool.config_safe_sound_dosage_enabled);
+            }
+        }
+
+        if (mEnableCsd.compareAndSet(!newEnableCsd, newEnableCsd)) {
+            Log.i(TAG, caller + ": enable CSD " + newEnableCsd);
+            initCsd();
         }
     }
 
@@ -913,7 +947,7 @@
         // legacy implementation uses mSafeMediaVolumeIndex for wired HS/HP
         // instead of computing it from the volume curves
         if ((deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
-                || deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADSET) && !mEnableCsd) {
+                || deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADSET) && !mEnableCsd.get()) {
             return mSafeMediaVolumeIndex;
         }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorList.java b/services/core/java/com/android/server/biometrics/sensors/SensorList.java
new file mode 100644
index 0000000..1cff92f
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/SensorList.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import android.app.IActivityManager;
+import android.app.SynchronousUserSwitchObserver;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.util.SparseArray;
+
+/**
+ * Keep track of the sensors that is supported by the HAL.
+ * @param <T> T is either face sensor or fingerprint sensor.
+ */
+public class SensorList<T> {
+    private static final String TAG = "SensorList";
+    private final SparseArray<T> mSensors;
+    private final IActivityManager mActivityManager;
+
+    public SensorList(IActivityManager activityManager) {
+        mSensors = new SparseArray<T>();
+        mActivityManager = activityManager;
+    }
+
+    /**
+     * Adding sensor to the map with the sensor id as key. Also, starts a session if the user Id is
+     * NULL.
+     */
+    public void addSensor(int sensorId, T sensor, int sessionUserId,
+            SynchronousUserSwitchObserver userSwitchObserver) {
+        mSensors.put(sensorId, sensor);
+        registerUserSwitchObserver(sessionUserId, userSwitchObserver);
+    }
+
+    private void registerUserSwitchObserver(int sessionUserId,
+            SynchronousUserSwitchObserver userSwitchObserver) {
+        try {
+            mActivityManager.registerUserSwitchObserver(userSwitchObserver,
+                    TAG);
+            if (sessionUserId == UserHandle.USER_NULL) {
+                userSwitchObserver.onUserSwitching(UserHandle.USER_SYSTEM);
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to register user switch observer");
+        }
+    }
+
+    /**
+     * Returns the sensor corresponding to the key at a specific position.
+     */
+    public T valueAt(int position) {
+        return mSensors.valueAt(position);
+    }
+
+    /**
+     * Returns the sensor associated with sensorId as key.
+     */
+    public T get(int sensorId) {
+        return mSensors.get(sensorId);
+    }
+
+    /**
+     * Returns the sensorId at the specified position.
+     */
+    public int keyAt(int position) {
+        return mSensors.keyAt(position);
+    }
+
+    /**
+     * Returns the number of sensors added.
+     */
+    public int size() {
+        return mSensors.size();
+    }
+
+    /**
+     * Returns true if a sensor exists for the specified sensorId.
+     */
+    public boolean contains(int sensorId) {
+        return mSensors.contains(sensorId);
+    }
+}
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 c5037b7..a501647 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
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
+import android.app.SynchronousUserSwitchObserver;
 import android.app.TaskStackListener;
 import android.content.Context;
 import android.content.pm.UserInfo;
@@ -41,9 +42,9 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.Surface;
 
@@ -62,6 +63,7 @@
 import com.android.server.biometrics.sensors.InvalidationRequesterClient;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.PerformanceTracker;
+import com.android.server.biometrics.sensors.SensorList;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 import com.android.server.biometrics.sensors.face.ServiceProvider;
 import com.android.server.biometrics.sensors.face.UsageStats;
@@ -86,7 +88,7 @@
 
     @NonNull
     @VisibleForTesting
-    final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
+    final SensorList<Sensor> mFaceSensors;
     @NonNull
     private final Context mContext;
     @NonNull
@@ -117,8 +119,8 @@
         @Override
         public void onTaskStackChanged() {
             mHandler.post(() -> {
-                for (int i = 0; i < mSensors.size(); i++) {
-                    final BaseClientMonitor client = mSensors.valueAt(i).getScheduler()
+                for (int i = 0; i < mFaceSensors.size(); i++) {
+                    final BaseClientMonitor client = mFaceSensors.valueAt(i).getScheduler()
                             .getCurrentClient();
                     if (!(client instanceof AuthenticationClient)) {
                         Slog.e(getTag(), "Task stack changed for client: " + client);
@@ -133,7 +135,7 @@
                             && !client.isAlreadyDone()) {
                         Slog.e(getTag(), "Stopping background authentication,"
                                 + " currentClient: " + client);
-                        mSensors.valueAt(i).getScheduler().cancelAuthenticationOrDetection(
+                        mFaceSensors.valueAt(i).getScheduler().cancelAuthenticationOrDetection(
                                 client.getToken(), client.getRequestId());
                     }
                 }
@@ -150,7 +152,7 @@
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
         mHalInstanceName = halInstanceName;
-        mSensors = new SparseArray<>();
+        mFaceSensors = new SensorList<>(ActivityManager.getService());
         mHandler = new Handler(Looper.getMainLooper());
         mUsageStats = new UsageStats(context);
         mLockoutResetDispatcher = lockoutResetDispatcher;
@@ -178,8 +180,15 @@
                     false /* resetLockoutRequiresChallenge */);
             final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
                     internalProp, lockoutResetDispatcher, mBiometricContext);
-
-            mSensors.put(sensorId, sensor);
+            final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+                    sensor.getLazySession().get().getUserId();
+            mFaceSensors.addSensor(sensorId, sensor, userId,
+                    new SynchronousUserSwitchObserver() {
+                        @Override
+                        public void onUserSwitching(int newUserId) {
+                            scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+                        }
+                    });
             Slog.d(getTag(), "Added: " + internalProp);
         }
     }
@@ -223,8 +232,8 @@
             Slog.e(getTag(), "Unable to linkToDeath", e);
         }
 
-        for (int i = 0; i < mSensors.size(); i++) {
-            final int sensorId = mSensors.keyAt(i);
+        for (int i = 0; i < mFaceSensors.size(); i++) {
+            final int sensorId = mFaceSensors.keyAt(i);
             scheduleLoadAuthenticatorIds(sensorId);
             scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
                     null /* callback */);
@@ -234,20 +243,20 @@
     }
 
     private void scheduleForSensor(int sensorId, @NonNull BaseClientMonitor client) {
-        if (!mSensors.contains(sensorId)) {
+        if (!mFaceSensors.contains(sensorId)) {
             throw new IllegalStateException("Unable to schedule client: " + client
                     + " for sensor: " + sensorId);
         }
-        mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+        mFaceSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
     }
 
     private void scheduleForSensor(int sensorId, @NonNull BaseClientMonitor client,
             ClientMonitorCallback callback) {
-        if (!mSensors.contains(sensorId)) {
+        if (!mFaceSensors.contains(sensorId)) {
             throw new IllegalStateException("Unable to schedule client: " + client
                     + " for sensor: " + sensorId);
         }
-        mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
+        mFaceSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
     }
 
     private void scheduleLoadAuthenticatorIds(int sensorId) {
@@ -259,12 +268,12 @@
     private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
         mHandler.post(() -> {
             final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
-                    mContext, mSensors.get(sensorId).getLazySession(), userId,
+                    mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
                     mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
                     mBiometricContext,
-                    mSensors.get(sensorId).getAuthenticatorIds());
+                    mFaceSensors.get(sensorId).getAuthenticatorIds());
 
             scheduleForSensor(sensorId, client);
         });
@@ -283,15 +292,15 @@
 
     @Override
     public boolean containsSensor(int sensorId) {
-        return mSensors.contains(sensorId);
+        return mFaceSensors.contains(sensorId);
     }
 
     @NonNull
     @Override
     public List<FaceSensorPropertiesInternal> getSensorProperties() {
         final List<FaceSensorPropertiesInternal> props = new ArrayList<>();
-        for (int i = 0; i < mSensors.size(); ++i) {
-            props.add(mSensors.valueAt(i).getSensorProperties());
+        for (int i = 0; i < mFaceSensors.size(); ++i) {
+            props.add(mFaceSensors.valueAt(i).getSensorProperties());
         }
         return props;
     }
@@ -299,7 +308,7 @@
     @NonNull
     @Override
     public FaceSensorPropertiesInternal getSensorProperties(int sensorId) {
-        return mSensors.get(sensorId).getSensorProperties();
+        return mFaceSensors.get(sensorId).getSensorProperties();
     }
 
     @NonNull
@@ -318,11 +327,11 @@
             @NonNull IInvalidationCallback callback) {
         mHandler.post(() -> {
             final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
-                    mSensors.get(sensorId).getLazySession(), userId, sensorId,
+                    mFaceSensors.get(sensorId).getLazySession(), userId, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
                     mBiometricContext,
-                    mSensors.get(sensorId).getAuthenticatorIds(), callback);
+                    mFaceSensors.get(sensorId).getAuthenticatorIds(), callback);
             scheduleForSensor(sensorId, client);
         });
     }
@@ -335,7 +344,7 @@
 
     @Override
     public long getAuthenticatorId(int sensorId, int userId) {
-        return mSensors.get(sensorId).getAuthenticatorIds().getOrDefault(userId, 0L);
+        return mFaceSensors.get(sensorId).getAuthenticatorIds().getOrDefault(userId, 0L);
     }
 
     @Override
@@ -348,7 +357,7 @@
             @NonNull IFaceServiceReceiver receiver, String opPackageName) {
         mHandler.post(() -> {
             final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
-                    mSensors.get(sensorId).getLazySession(), token,
+                    mFaceSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
@@ -362,7 +371,8 @@
             @NonNull String opPackageName, long challenge) {
         mHandler.post(() -> {
             final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
-                    mSensors.get(sensorId).getLazySession(), token, userId, opPackageName, sensorId,
+                    mFaceSensors.get(sensorId).getLazySession(), token, userId,
+                    opPackageName, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
                     mBiometricContext, challenge);
@@ -377,10 +387,10 @@
             @Nullable Surface previewSurface, boolean debugConsent) {
         final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
-            final int maxTemplatesPerUser = mSensors.get(
+            final int maxTemplatesPerUser = mFaceSensors.get(
                     sensorId).getSensorProperties().maxEnrollmentsPerUser;
             final FaceEnrollClient client = new FaceEnrollClient(mContext,
-                    mSensors.get(sensorId).getLazySession(), token,
+                    mFaceSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                     opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
                     ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
@@ -406,7 +416,7 @@
     @Override
     public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
         mHandler.post(() ->
-                mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId));
+                mFaceSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId));
     }
 
     @Override
@@ -419,7 +429,7 @@
         mHandler.post(() -> {
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FaceDetectClient client = new FaceDetectClient(mContext,
-                    mSensors.get(sensorId).getLazySession(),
+                    mFaceSensors.get(sensorId).getLazySession(),
                     token, id, callback, options,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric);
@@ -431,7 +441,7 @@
 
     @Override
     public void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId) {
-        mHandler.post(() -> mSensors.get(sensorId).getScheduler()
+        mHandler.post(() -> mFaceSensors.get(sensorId).getScheduler()
                 .cancelAuthenticationOrDetection(token, requestId));
     }
 
@@ -446,12 +456,12 @@
             final int sensorId = options.getSensorId();
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FaceAuthenticationClient client = new FaceAuthenticationClient(
-                    mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
-                    operationId, restricted, options, cookie,
+                    mContext, mFaceSensors.get(sensorId).getLazySession(), token, requestId,
+                    callback, operationId, restricted, options, cookie,
                     false /* requireConfirmation */,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric,
-                    mUsageStats, mSensors.get(sensorId).getLockoutCache(),
+                    mUsageStats, mFaceSensors.get(sensorId).getLockoutCache(),
                     allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
                 @Override
@@ -486,7 +496,7 @@
 
     @Override
     public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
-        mHandler.post(() -> mSensors.get(sensorId).getScheduler()
+        mHandler.post(() -> mFaceSensors.get(sensorId).getScheduler()
                 .cancelAuthenticationOrDetection(token, requestId));
     }
 
@@ -514,13 +524,13 @@
             int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
         mHandler.post(() -> {
             final FaceRemovalClient client = new FaceRemovalClient(mContext,
-                    mSensors.get(sensorId).getLazySession(), token,
+                    mFaceSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), faceIds, userId,
                     opPackageName, FaceUtils.getInstance(sensorId), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
                     mBiometricContext,
-                    mSensors.get(sensorId).getAuthenticatorIds());
+                    mFaceSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
         });
     }
@@ -529,12 +539,12 @@
     public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
         mHandler.post(() -> {
             final FaceResetLockoutClient client = new FaceResetLockoutClient(
-                    mContext, mSensors.get(sensorId).getLazySession(), userId,
+                    mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
                     mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
                     mBiometricContext, hardwareAuthToken,
-                    mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
+                    mFaceSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
                     Utils.getCurrentStrength(sensorId));
 
             scheduleForSensor(sensorId, client);
@@ -553,7 +563,7 @@
                 return;
             }
             final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
-                    mSensors.get(sensorId).getLazySession(), token,
+                    mFaceSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), userId,
                     mContext.getOpPackageName(), sensorId,
                     BiometricLogger.ofUnknown(mContext), mBiometricContext,
@@ -573,7 +583,7 @@
                 return;
             }
             final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext,
-                    mSensors.get(sensorId).getLazySession(), token, callback, userId,
+                    mFaceSensors.get(sensorId).getLazySession(), token, callback, userId,
                     mContext.getOpPackageName(), sensorId, BiometricLogger.ofUnknown(mContext),
                     mBiometricContext);
             scheduleForSensor(sensorId, client);
@@ -583,7 +593,7 @@
     @Override
     public void startPreparedClient(int sensorId, int cookie) {
         mHandler.post(() -> {
-            mSensors.get(sensorId).getScheduler().startPreparedClient(cookie);
+            mFaceSensors.get(sensorId).getScheduler().startPreparedClient(cookie);
         });
     }
 
@@ -599,13 +609,13 @@
         mHandler.post(() -> {
             final FaceInternalCleanupClient client =
                     new FaceInternalCleanupClient(mContext,
-                            mSensors.get(sensorId).getLazySession(), userId,
+                            mFaceSensors.get(sensorId).getLazySession(), userId,
                             mContext.getOpPackageName(), sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
                                     BiometricsProtoEnums.CLIENT_UNKNOWN),
                             mBiometricContext,
                             FaceUtils.getInstance(sensorId),
-                            mSensors.get(sensorId).getAuthenticatorIds());
+                            mFaceSensors.get(sensorId).getAuthenticatorIds());
             if (favorHalEnrollments) {
                 client.setFavorHalEnrollments();
             }
@@ -622,8 +632,8 @@
     @Override
     public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
             boolean clearSchedulerBuffer) {
-        if (mSensors.contains(sensorId)) {
-            mSensors.get(sensorId).dumpProtoState(sensorId, proto, clearSchedulerBuffer);
+        if (mFaceSensors.contains(sensorId)) {
+            mFaceSensors.get(sensorId).dumpProtoState(sensorId, proto, clearSchedulerBuffer);
         }
     }
 
@@ -672,7 +682,7 @@
         pw.println(mBiometricContext.getAuthSessionCoordinator());
         pw.println("---AuthSessionCoordinator logs end  ---");
 
-        mSensors.get(sensorId).getScheduler().dump(pw);
+        mFaceSensors.get(sensorId).getScheduler().dump(pw);
         mUsageStats.print(pw);
     }
 
@@ -680,7 +690,7 @@
     @Override
     public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
             @NonNull String opPackageName) {
-        return mSensors.get(sensorId).createTestSession(callback);
+        return mFaceSensors.get(sensorId).createTestSession(callback);
     }
 
     @Override
@@ -692,9 +702,9 @@
         Slog.e(getTag(), "HAL died");
         mHandler.post(() -> {
             mDaemon = null;
-            for (int i = 0; i < mSensors.size(); i++) {
-                final Sensor sensor = mSensors.valueAt(i);
-                final int sensorId = mSensors.keyAt(i);
+            for (int i = 0; i < mFaceSensors.size(); i++) {
+                final Sensor sensor = mFaceSensors.valueAt(i);
+                final int sensorId = mFaceSensors.keyAt(i);
                 PerformanceTracker.getInstanceForSensorId(sensorId).incrementHALDeathCount();
                 sensor.onBinderDied();
             }
@@ -708,7 +718,7 @@
     @Override
     public void scheduleWatchdog(int sensorId) {
         Slog.d(getTag(), "Starting watchdog for face");
-        final BiometricScheduler biometricScheduler = mSensors.get(sensorId).getScheduler();
+        final BiometricScheduler biometricScheduler = mFaceSensors.get(sensorId).getScheduler();
         if (biometricScheduler == null) {
             return;
         }
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 468bf55..ffbf4e1 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
@@ -18,9 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.SynchronousUserSwitchObserver;
-import android.app.UserSwitchObserver;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricsProtoEnums;
@@ -94,14 +91,6 @@
     @NonNull private final Supplier<AidlSession> mLazySession;
     @Nullable private AidlSession mCurrentSession;
 
-    private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
-        @Override
-        public void onUserSwitching(int newUserId) {
-            mProvider.scheduleInternalCleanup(
-                    mSensorProperties.sensorId, newUserId, null /* callback */);
-        }
-    };
-
     @VisibleForTesting
     public static class HalSessionCallback extends ISessionCallback.Stub {
         /**
@@ -558,12 +547,6 @@
         mLockoutCache = new LockoutCache();
         mAuthenticatorIds = new HashMap<>();
         mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
-
-        try {
-            ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, mTag);
-        } catch (RemoteException e) {
-            Slog.e(mTag, "Unable to register user switch observer");
-        }
     }
 
     @NonNull Supplier<AidlSession> getLazySession() {
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 23b6f84..58ece89 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
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
+import android.app.SynchronousUserSwitchObserver;
 import android.app.TaskStackListener;
 import android.content.Context;
 import android.content.pm.UserInfo;
@@ -51,9 +52,9 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -71,6 +72,7 @@
 import com.android.server.biometrics.sensors.InvalidationRequesterClient;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.PerformanceTracker;
+import com.android.server.biometrics.sensors.SensorList;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
@@ -99,7 +101,7 @@
 
     @NonNull
     @VisibleForTesting
-    final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
+    final SensorList<Sensor> mFingerprintSensors;
     @NonNull
     private final Context mContext;
     @NonNull
@@ -127,8 +129,8 @@
         @Override
         public void onTaskStackChanged() {
             mHandler.post(() -> {
-                for (int i = 0; i < mSensors.size(); i++) {
-                    final BaseClientMonitor client = mSensors.valueAt(i).getScheduler()
+                for (int i = 0; i < mFingerprintSensors.size(); i++) {
+                    final BaseClientMonitor client = mFingerprintSensors.valueAt(i).getScheduler()
                             .getCurrentClient();
                     if (!(client instanceof AuthenticationClient)) {
                         Slog.e(getTag(), "Task stack changed for client: " + client);
@@ -143,8 +145,9 @@
                             && !client.isAlreadyDone()) {
                         Slog.e(getTag(), "Stopping background authentication,"
                                 + " currentClient: " + client);
-                        mSensors.valueAt(i).getScheduler().cancelAuthenticationOrDetection(
-                                client.getToken(), client.getRequestId());
+                        mFingerprintSensors.valueAt(i).getScheduler()
+                                .cancelAuthenticationOrDetection(
+                                        client.getToken(), client.getRequestId());
                     }
                 }
             });
@@ -160,7 +163,7 @@
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
         mHalInstanceName = halInstanceName;
-        mSensors = new SparseArray<>();
+        mFingerprintSensors = new SensorList<>(ActivityManager.getService());
         mHandler = new Handler(Looper.getMainLooper());
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mActivityTaskManager = ActivityTaskManager.getInstance();
@@ -201,8 +204,15 @@
             final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
                     internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher,
                     mBiometricContext);
-
-            mSensors.put(sensorId, sensor);
+            final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+                    sensor.getLazySession().get().getUserId();
+            mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
+                    new SynchronousUserSwitchObserver() {
+                        @Override
+                        public void onUserSwitching(int newUserId) {
+                            scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+                        }
+                    });
             Slog.d(getTag(), "Added: " + internalProp);
         }
     }
@@ -250,8 +260,8 @@
             Slog.e(getTag(), "Unable to linkToDeath", e);
         }
 
-        for (int i = 0; i < mSensors.size(); i++) {
-            final int sensorId = mSensors.keyAt(i);
+        for (int i = 0; i < mFingerprintSensors.size(); i++) {
+            final int sensorId = mFingerprintSensors.keyAt(i);
             scheduleLoadAuthenticatorIds(sensorId);
             scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
                     null /* callback */);
@@ -261,33 +271,33 @@
     }
 
     private void scheduleForSensor(int sensorId, @NonNull BaseClientMonitor client) {
-        if (!mSensors.contains(sensorId)) {
+        if (!mFingerprintSensors.contains(sensorId)) {
             throw new IllegalStateException("Unable to schedule client: " + client
                     + " for sensor: " + sensorId);
         }
-        mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+        mFingerprintSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
     }
 
     private void scheduleForSensor(int sensorId, @NonNull BaseClientMonitor client,
             ClientMonitorCallback callback) {
-        if (!mSensors.contains(sensorId)) {
+        if (!mFingerprintSensors.contains(sensorId)) {
             throw new IllegalStateException("Unable to schedule client: " + client
                     + " for sensor: " + sensorId);
         }
-        mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
+        mFingerprintSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
     }
 
     @Override
     public boolean containsSensor(int sensorId) {
-        return mSensors.contains(sensorId);
+        return mFingerprintSensors.contains(sensorId);
     }
 
     @NonNull
     @Override
     public List<FingerprintSensorPropertiesInternal> getSensorProperties() {
         final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
-        for (int i = 0; i < mSensors.size(); i++) {
-            props.add(mSensors.valueAt(i).getSensorProperties());
+        for (int i = 0; i < mFingerprintSensors.size(); i++) {
+            props.add(mFingerprintSensors.valueAt(i).getSensorProperties());
         }
         return props;
     }
@@ -295,12 +305,12 @@
     @Nullable
     @Override
     public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId) {
-        if (mSensors.size() == 0) {
+        if (mFingerprintSensors.size() == 0) {
             return null;
         } else if (sensorId == SENSOR_ID_ANY) {
-            return mSensors.valueAt(0).getSensorProperties();
+            return mFingerprintSensors.valueAt(0).getSensorProperties();
         } else {
-            final Sensor sensor = mSensors.get(sensorId);
+            final Sensor sensor = mFingerprintSensors.get(sensorId);
             return sensor != null ? sensor.getSensorProperties() : null;
         }
     }
@@ -315,12 +325,12 @@
         mHandler.post(() -> {
             final FingerprintGetAuthenticatorIdClient client =
                     new FingerprintGetAuthenticatorIdClient(mContext,
-                            mSensors.get(sensorId).getLazySession(), userId,
+                            mFingerprintSensors.get(sensorId).getLazySession(), userId,
                             mContext.getOpPackageName(), sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                                     BiometricsProtoEnums.CLIENT_UNKNOWN),
                             mBiometricContext,
-                            mSensors.get(sensorId).getAuthenticatorIds());
+                            mFingerprintSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client);
         });
     }
@@ -340,12 +350,12 @@
     public void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
         mHandler.post(() -> {
             final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(
-                    mContext, mSensors.get(sensorId).getLazySession(), userId,
+                    mContext, mFingerprintSensors.get(sensorId).getLazySession(), userId,
                     mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
                     mBiometricContext, hardwareAuthToken,
-                    mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
+                    mFingerprintSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
                     Utils.getCurrentStrength(sensorId));
             scheduleForSensor(sensorId, client);
         });
@@ -357,7 +367,7 @@
         mHandler.post(() -> {
             final FingerprintGenerateChallengeClient client =
                     new FingerprintGenerateChallengeClient(mContext,
-                            mSensors.get(sensorId).getLazySession(), token,
+                            mFingerprintSensors.get(sensorId).getLazySession(), token,
                             new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
                             sensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
@@ -372,7 +382,7 @@
         mHandler.post(() -> {
             final FingerprintRevokeChallengeClient client =
                     new FingerprintRevokeChallengeClient(mContext,
-                            mSensors.get(sensorId).getLazySession(), token,
+                            mFingerprintSensors.get(sensorId).getLazySession(), token,
                             userId, opPackageName, sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                                     BiometricsProtoEnums.CLIENT_UNKNOWN),
@@ -388,16 +398,16 @@
             @FingerprintManager.EnrollReason int enrollReason) {
         final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
-            final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
+            final int maxTemplatesPerUser = mFingerprintSensors.get(sensorId).getSensorProperties()
                     .maxEnrollmentsPerUser;
             final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
-                    mSensors.get(sensorId).getLazySession(), token, id,
+                    mFingerprintSensors.get(sensorId).getLazySession(), token, id,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
                     mBiometricContext,
-                    mSensors.get(sensorId).getSensorProperties(),
+                    mFingerprintSensors.get(sensorId).getSensorProperties(),
                     mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
                     maxTemplatesPerUser, enrollReason);
             scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
@@ -419,7 +429,8 @@
     @Override
     public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
         mHandler.post(() ->
-                mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId));
+                mFingerprintSensors.get(sensorId).getScheduler()
+                        .cancelEnrollment(token, requestId));
     }
 
     @Override
@@ -432,7 +443,7 @@
             final int sensorId = options.getSensorId();
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
-                    mSensors.get(sensorId).getLazySession(), token, id, callback,
+                    mFingerprintSensors.get(sensorId).getLazySession(), token, id, callback,
                     options,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext,
@@ -454,15 +465,15 @@
             final int sensorId = options.getSensorId();
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
-                    mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
-                    operationId, restricted, options, cookie,
+                    mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, requestId,
+                    callback, operationId, restricted, options, cookie,
                     false /* requireConfirmation */,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric,
-                    mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
+                    mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(),
                     mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
                     allowBackgroundAuthentication,
-                    mSensors.get(sensorId).getSensorProperties(), mHandler,
+                    mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
                     Utils.getCurrentStrength(sensorId),
                     SystemClock.elapsedRealtimeClock());
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@@ -505,12 +516,13 @@
 
     @Override
     public void startPreparedClient(int sensorId, int cookie) {
-        mHandler.post(() -> mSensors.get(sensorId).getScheduler().startPreparedClient(cookie));
+        mHandler.post(() -> mFingerprintSensors.get(sensorId).getScheduler()
+                .startPreparedClient(cookie));
     }
 
     @Override
     public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
-        mHandler.post(() -> mSensors.get(sensorId).getScheduler()
+        mHandler.post(() -> mFingerprintSensors.get(sensorId).getScheduler()
                 .cancelAuthenticationOrDetection(token, requestId));
     }
 
@@ -541,13 +553,13 @@
             @NonNull String opPackageName) {
         mHandler.post(() -> {
             final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
-                    mSensors.get(sensorId).getLazySession(), token,
+                    mFingerprintSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
                     mBiometricContext,
-                    mSensors.get(sensorId).getAuthenticatorIds());
+                    mFingerprintSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
         });
     }
@@ -564,13 +576,13 @@
         mHandler.post(() -> {
             final FingerprintInternalCleanupClient client =
                     new FingerprintInternalCleanupClient(mContext,
-                            mSensors.get(sensorId).getLazySession(), userId,
+                            mFingerprintSensors.get(sensorId).getLazySession(), userId,
                             mContext.getOpPackageName(), sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
                                     BiometricsProtoEnums.CLIENT_UNKNOWN),
                             mBiometricContext,
                             FingerprintUtils.getInstance(sensorId),
-                            mSensors.get(sensorId).getAuthenticatorIds());
+                            mFingerprintSensors.get(sensorId).getAuthenticatorIds());
             if (favorHalEnrollments) {
                 client.setFavorHalEnrollments();
             }
@@ -612,11 +624,11 @@
         mHandler.post(() -> {
             final FingerprintInvalidationClient client =
                     new FingerprintInvalidationClient(mContext,
-                            mSensors.get(sensorId).getLazySession(), userId, sensorId,
+                            mFingerprintSensors.get(sensorId).getLazySession(), userId, sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                                     BiometricsProtoEnums.CLIENT_UNKNOWN),
                             mBiometricContext,
-                            mSensors.get(sensorId).getAuthenticatorIds(), callback);
+                            mFingerprintSensors.get(sensorId).getAuthenticatorIds(), callback);
             scheduleForSensor(sensorId, client);
         });
     }
@@ -629,40 +641,43 @@
 
     @Override
     public long getAuthenticatorId(int sensorId, int userId) {
-        return mSensors.get(sensorId).getAuthenticatorIds().getOrDefault(userId, 0L);
+        return mFingerprintSensors.get(sensorId).getAuthenticatorIds().getOrDefault(userId, 0L);
     }
 
     @Override
     public void onPointerDown(long requestId, int sensorId, PointerContext pc) {
-        mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId, (client) -> {
-            if (!(client instanceof Udfps)) {
-                Slog.e(getTag(), "onPointerDown received during client: " + client);
-                return;
-            }
-            ((Udfps) client).onPointerDown(pc);
-        });
+        mFingerprintSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(
+                requestId, (client) -> {
+                    if (!(client instanceof Udfps)) {
+                        Slog.e(getTag(), "onPointerDown received during client: " + client);
+                        return;
+                    }
+                    ((Udfps) client).onPointerDown(pc);
+                });
     }
 
     @Override
     public void onPointerUp(long requestId, int sensorId, PointerContext pc) {
-        mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId, (client) -> {
-            if (!(client instanceof Udfps)) {
-                Slog.e(getTag(), "onPointerUp received during client: " + client);
-                return;
-            }
-            ((Udfps) client).onPointerUp(pc);
-        });
+        mFingerprintSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(
+                requestId, (client) -> {
+                    if (!(client instanceof Udfps)) {
+                        Slog.e(getTag(), "onPointerUp received during client: " + client);
+                        return;
+                    }
+                    ((Udfps) client).onPointerUp(pc);
+                });
     }
 
     @Override
     public void onUiReady(long requestId, int sensorId) {
-        mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId, (client) -> {
-            if (!(client instanceof Udfps)) {
-                Slog.e(getTag(), "onUiReady received during client: " + client);
-                return;
-            }
-            ((Udfps) client).onUiReady();
-        });
+        mFingerprintSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(
+                requestId, (client) -> {
+                    if (!(client instanceof Udfps)) {
+                        Slog.e(getTag(), "onUiReady received during client: " + client);
+                        return;
+                    }
+                    ((Udfps) client).onUiReady();
+                });
     }
 
     @Override
@@ -672,8 +687,8 @@
 
     @Override
     public void onPowerPressed() {
-        for (int i = 0; i < mSensors.size(); i++) {
-            final Sensor sensor = mSensors.valueAt(i);
+        for (int i = 0; i < mFingerprintSensors.size(); i++) {
+            final Sensor sensor = mFingerprintSensors.valueAt(i);
             BaseClientMonitor client = sensor.getScheduler().getCurrentClient();
             if (client == null) {
                 return;
@@ -698,8 +713,8 @@
     @Override
     public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
             boolean clearSchedulerBuffer) {
-        if (mSensors.contains(sensorId)) {
-            mSensors.get(sensorId).dumpProtoState(sensorId, proto, clearSchedulerBuffer);
+        if (mFingerprintSensors.contains(sensorId)) {
+            mFingerprintSensors.get(sensorId).dumpProtoState(sensorId, proto, clearSchedulerBuffer);
         }
     }
 
@@ -748,14 +763,15 @@
         pw.println(mBiometricContext.getAuthSessionCoordinator());
         pw.println("---AuthSessionCoordinator logs end  ---");
 
-        mSensors.get(sensorId).getScheduler().dump(pw);
+        mFingerprintSensors.get(sensorId).getScheduler().dump(pw);
     }
 
     @NonNull
     @Override
     public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
             @NonNull String opPackageName) {
-        return mSensors.get(sensorId).createTestSession(callback, mBiometricStateCallback);
+        return mFingerprintSensors.get(sensorId).createTestSession(callback,
+                mBiometricStateCallback);
     }
 
     @Override
@@ -764,9 +780,9 @@
         mHandler.post(() -> {
             mDaemon = null;
 
-            for (int i = 0; i < mSensors.size(); i++) {
-                final Sensor sensor = mSensors.valueAt(i);
-                final int sensorId = mSensors.keyAt(i);
+            for (int i = 0; i < mFingerprintSensors.size(); i++) {
+                final Sensor sensor = mFingerprintSensors.valueAt(i);
+                final int sensorId = mFingerprintSensors.keyAt(i);
                 PerformanceTracker.getInstanceForSensorId(sensorId).incrementHALDeathCount();
                 sensor.onBinderDied();
             }
@@ -821,7 +837,8 @@
     @Override
     public void scheduleWatchdog(int sensorId) {
         Slog.d(getTag(), "Starting watchdog for fingerprint");
-        final BiometricScheduler biometricScheduler = mSensors.get(sensorId).getScheduler();
+        final BiometricScheduler biometricScheduler = mFingerprintSensors.get(sensorId)
+                .getScheduler();
         if (biometricScheduler == null) {
             return;
         }
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 22ca816..c0dde72 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
@@ -18,9 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.SynchronousUserSwitchObserver;
-import android.app.UserSwitchObserver;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricsProtoEnums;
@@ -96,14 +93,6 @@
     @Nullable private AidlSession mCurrentSession;
     @NonNull private final Supplier<AidlSession> mLazySession;
 
-    private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
-        @Override
-        public void onUserSwitching(int newUserId) {
-            mProvider.scheduleInternalCleanup(
-                    mSensorProperties.sensorId, newUserId, null /* callback */);
-        }
-    };
-
     @VisibleForTesting
     public static class HalSessionCallback extends ISessionCallback.Stub {
 
@@ -512,12 +501,6 @@
                 });
         mAuthenticatorIds = new HashMap<>();
         mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
-
-        try {
-            ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, mTag);
-        } catch (RemoteException e) {
-            Slog.e(mTag, "Unable to register user switch observer");
-        }
     }
 
     @NonNull Supplier<AidlSession> getLazySession() {
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 1f82961..6d43061 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -41,6 +41,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.BitUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.RingBuffer;
@@ -278,6 +279,11 @@
         }
     }
 
+    private boolean hasWifiTransport(Network network) {
+        final NetworkCapabilities nc = mCm.getNetworkCapabilities(network);
+        return nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
+    }
+
     @Override
     public synchronized void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader,
             byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs) {
@@ -286,12 +292,21 @@
             throw new IllegalArgumentException("Prefix " + prefix
                     + " required in format <nethandle>:<interface>");
         }
+        final long netHandle = Long.parseLong(prefixParts[0]);
+        final Network network = Network.fromNetworkHandle(netHandle);
 
         final WakeupEvent event = new WakeupEvent();
         event.iface = prefixParts[1];
         event.uid = uid;
         event.ethertype = ethertype;
-        event.dstHwAddr = MacAddress.fromBytes(dstHw);
+        if (ArrayUtils.isEmpty(dstHw)) {
+            if (hasWifiTransport(network)) {
+                Log.e(TAG, "Empty mac address on WiFi transport, network: " + network);
+            }
+            event.dstHwAddr = null;
+        } else {
+            event.dstHwAddr = MacAddress.fromBytes(dstHw);
+        }
         event.srcIp = srcIp;
         event.dstIp = dstIp;
         event.ipNextHeader = ipNextHeader;
@@ -306,14 +321,12 @@
 
         final BatteryStatsInternal bsi = LocalServices.getService(BatteryStatsInternal.class);
         if (bsi != null) {
-            final long netHandle = Long.parseLong(prefixParts[0]);
             final long elapsedMs = SystemClock.elapsedRealtime() + event.timestampMs
                     - System.currentTimeMillis();
-            bsi.noteCpuWakingNetworkPacket(Network.fromNetworkHandle(netHandle), elapsedMs,
-                    event.uid);
+            bsi.noteCpuWakingNetworkPacket(network, elapsedMs, event.uid);
         }
 
-        final String dstMac = event.dstHwAddr.toString();
+        final String dstMac = String.valueOf(event.dstHwAddr);
         FrameworkStatsLog.write(FrameworkStatsLog.PACKET_WAKEUP_OCCURRED,
                 uid, event.iface, ethertype, dstMac, srcIp, dstIp, ipNextHeader, srcPort, dstPort);
     }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 1dc2725..c678a92 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -85,6 +85,7 @@
 import android.net.NetworkSpecifier;
 import android.net.RouteInfo;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.TransportInfo;
 import android.net.UidRangeParcel;
 import android.net.UnderlyingNetworkInfo;
 import android.net.Uri;
@@ -107,6 +108,8 @@
 import android.net.ipsec.ike.exceptions.IkeNonProtocolException;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.net.ipsec.ike.exceptions.IkeTimeoutException;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnTransportInfo;
 import android.os.Binder;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
@@ -453,22 +456,22 @@
 
     private static class CarrierConfigInfo {
         public final String mccMnc;
-        public final int keepaliveDelayMs;
+        public final int keepaliveDelaySec;
         public final int encapType;
         public final int ipVersion;
 
-        CarrierConfigInfo(String mccMnc, int keepaliveDelayMs,
+        CarrierConfigInfo(String mccMnc, int keepaliveDelaySec,
                 int encapType,
                 int ipVersion) {
             this.mccMnc = mccMnc;
-            this.keepaliveDelayMs = keepaliveDelayMs;
+            this.keepaliveDelaySec = keepaliveDelaySec;
             this.encapType = encapType;
             this.ipVersion = ipVersion;
         }
 
         @Override
         public String toString() {
-            return "CarrierConfigInfo(" + mccMnc + ") [keepaliveDelayMs=" + keepaliveDelayMs
+            return "CarrierConfigInfo(" + mccMnc + ") [keepaliveDelaySec=" + keepaliveDelaySec
                     + ", encapType=" + encapType + ", ipVersion=" + ipVersion + "]";
         }
     }
@@ -3556,39 +3559,63 @@
         }
 
         private int guessEspIpVersionForNetwork() {
-            final CarrierConfigInfo carrierconfig = getCarrierConfig();
+            if (mUnderlyingNetworkCapabilities.getTransportInfo() instanceof VcnTransportInfo) {
+                Log.d(TAG, "Running over VCN, esp IP version is auto");
+                return ESP_IP_VERSION_AUTO;
+            }
+            final CarrierConfigInfo carrierconfig = getCarrierConfigForUnderlyingNetwork();
             final int ipVersion = (carrierconfig != null)
                     ? carrierconfig.ipVersion : ESP_IP_VERSION_AUTO;
             if (carrierconfig != null) {
-                Log.d(TAG, "Get customized IP version(" + ipVersion + ") on SIM("
+                Log.d(TAG, "Get customized IP version (" + ipVersion + ") on SIM (mccmnc="
                         + carrierconfig.mccMnc + ")");
             }
             return ipVersion;
         }
 
         private int guessEspEncapTypeForNetwork() {
-            final CarrierConfigInfo carrierconfig = getCarrierConfig();
+            if (mUnderlyingNetworkCapabilities.getTransportInfo() instanceof VcnTransportInfo) {
+                Log.d(TAG, "Running over VCN, encap type is auto");
+                return ESP_ENCAP_TYPE_AUTO;
+            }
+            final CarrierConfigInfo carrierconfig = getCarrierConfigForUnderlyingNetwork();
             final int encapType = (carrierconfig != null)
                     ? carrierconfig.encapType : ESP_ENCAP_TYPE_AUTO;
             if (carrierconfig != null) {
-                Log.d(TAG, "Get customized encap type(" + encapType + ") on SIM("
+                Log.d(TAG, "Get customized encap type (" + encapType + ") on SIM (mccmnc="
                         + carrierconfig.mccMnc + ")");
             }
             return encapType;
         }
 
+
         private int guessNattKeepaliveTimerForNetwork() {
-            final CarrierConfigInfo carrierconfig = getCarrierConfig();
-            final int natKeepalive = (carrierconfig != null)
-                    ? carrierconfig.keepaliveDelayMs : AUTOMATIC_KEEPALIVE_DELAY_SECONDS;
+            final TransportInfo transportInfo = mUnderlyingNetworkCapabilities.getTransportInfo();
+            if (transportInfo instanceof VcnTransportInfo) {
+                final int nattKeepaliveSec =
+                        ((VcnTransportInfo) transportInfo).getMinUdpPort4500NatTimeoutSeconds();
+                Log.d(TAG, "Running over VCN, keepalive timer : " + nattKeepaliveSec + "s");
+                if (VcnGatewayConnectionConfig.MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET
+                        != nattKeepaliveSec) {
+                    return nattKeepaliveSec;
+                }
+                // else fall back to carrier config, if any
+            }
+            final CarrierConfigInfo carrierconfig = getCarrierConfigForUnderlyingNetwork();
+            final int nattKeepaliveSec = (carrierconfig != null)
+                    ? carrierconfig.keepaliveDelaySec : AUTOMATIC_KEEPALIVE_DELAY_SECONDS;
             if (carrierconfig != null) {
-                Log.d(TAG, "Get customized keepalive(" + natKeepalive + ") on SIM("
+                Log.d(TAG, "Get customized keepalive (" + nattKeepaliveSec + "s) on SIM (mccmnc="
                         + carrierconfig.mccMnc + ")");
             }
-            return natKeepalive;
+            return nattKeepaliveSec;
         }
 
-        private CarrierConfigInfo getCarrierConfig() {
+        /**
+         * Returns the carrier config for the underlying network, or null if not a cell network.
+         */
+        @Nullable
+        private CarrierConfigInfo getCarrierConfigForUnderlyingNetwork() {
             final int subId = getCellSubIdForNetworkCapabilities(mUnderlyingNetworkCapabilities);
             if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
                 Log.d(TAG, "Underlying network is not a cellular network");
diff --git a/services/core/java/com/android/server/display/color/CctEvaluator.java b/services/core/java/com/android/server/display/color/CctEvaluator.java
new file mode 100644
index 0000000..878f7e5
--- /dev/null
+++ b/services/core/java/com/android/server/display/color/CctEvaluator.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.color;
+
+import android.animation.TypeEvaluator;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * Interpolates between CCT values by a given step.
+ */
+class CctEvaluator implements TypeEvaluator<Integer> {
+
+    private static final String TAG = "CctEvaluator";
+
+    /**
+     * The minimum input value, which will represent index 0 in the mValues array. Each
+     * subsequent input value is offset by this amount.
+     */
+    private final int mIndexOffset;
+    /**
+     * Cached step values at each CCT value (offset by the {@link #mIndexOffset} above). For
+     * example, if the minimum CCT is 2000K (which is set to mIndexOffset), then the 0th index of
+     * this array is equivalent to the step value at 2000K, 1st index corresponds to 2001K, and so
+     * on.
+     */
+    @VisibleForTesting
+    final int[] mStepsAtOffsetCcts;
+    /**
+     * Pre-computed stepped CCTs. These will be accessed frequently; the memory cost of caching them
+     * is well-spent.
+     */
+    @VisibleForTesting
+    final int[] mSteppedCctsAtOffsetCcts;
+
+    CctEvaluator(int min, int max, int[] cctRangeMinimums, int[] steps) {
+        final int delta = max - min + 1;
+        mStepsAtOffsetCcts = new int[delta];
+        mSteppedCctsAtOffsetCcts = new int[delta];
+        mIndexOffset = min;
+
+        final int parallelArraysLength = cctRangeMinimums.length;
+        if (cctRangeMinimums.length != steps.length) {
+            Slog.e(TAG,
+                    "Parallel arrays cctRangeMinimums and steps are different lengths; setting "
+                            + "step of 1");
+            setStepOfOne();
+        } else if (parallelArraysLength == 0) {
+            Slog.e(TAG, "No cctRangeMinimums or steps are set; setting step of 1");
+            setStepOfOne();
+        } else {
+            int parallelArraysIndex = 0;
+            int index = 0;
+            int lastSteppedCct = Integer.MIN_VALUE;
+            while (index < delta) {
+                final int cct = index + mIndexOffset;
+                int nextParallelArraysIndex = parallelArraysIndex + 1;
+                while (nextParallelArraysIndex < parallelArraysLength
+                        && cct >= cctRangeMinimums[nextParallelArraysIndex]) {
+                    parallelArraysIndex = nextParallelArraysIndex;
+                    nextParallelArraysIndex++;
+                }
+                mStepsAtOffsetCcts[index] = steps[parallelArraysIndex];
+                if (lastSteppedCct == Integer.MIN_VALUE
+                        || Math.abs(lastSteppedCct - cct) >= steps[parallelArraysIndex]) {
+                    lastSteppedCct = cct;
+                }
+                mSteppedCctsAtOffsetCcts[index] = lastSteppedCct;
+                index++;
+            }
+        }
+    }
+
+    @Override
+    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
+        final int cct = (int) (startValue + fraction * (endValue - startValue));
+        final int index = cct - mIndexOffset;
+        if (index < 0 || index >= mSteppedCctsAtOffsetCcts.length) {
+            Slog.e(TAG, "steppedCctValueAt: returning same since invalid requested index=" + index);
+            return cct;
+        }
+        return mSteppedCctsAtOffsetCcts[index];
+    }
+
+    private void setStepOfOne() {
+        Arrays.fill(mStepsAtOffsetCcts, 1);
+        for (int i = 0; i < mSteppedCctsAtOffsetCcts.length; i++) {
+            mSteppedCctsAtOffsetCcts[i] = mIndexOffset + i;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 0284d9c..c0ea5fea 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -180,6 +180,8 @@
      */
     private SparseIntArray mColorModeCompositionColorSpaces = null;
 
+    private final Object mCctTintApplierLock = new Object();
+
     public ColorDisplayService(Context context) {
         super(context);
         mHandler = new TintHandler(DisplayThread.get().getLooper());
@@ -698,6 +700,79 @@
         }
     }
 
+    private void applyTintByCct(ColorTemperatureTintController tintController, boolean immediate) {
+        synchronized (mCctTintApplierLock) {
+            tintController.cancelAnimator();
+
+            final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
+            final int from = tintController.getAppliedCct();
+            final int to = tintController.isActivated() ? tintController.getTargetCct()
+                    : tintController.getDisabledCct();
+
+            if (immediate) {
+                Slog.d(TAG, tintController.getClass().getSimpleName()
+                        + " applied immediately: toCct=" + to + " fromCct=" + from);
+                dtm.setColorMatrix(tintController.getLevel(),
+                        tintController.computeMatrixForCct(to));
+                tintController.setAppliedCct(to);
+            } else {
+                Slog.d(TAG, tintController.getClass().getSimpleName() + " animation started: toCct="
+                        + to + " fromCct=" + from);
+                ValueAnimator valueAnimator = ValueAnimator.ofInt(from, to);
+                tintController.setAnimator(valueAnimator);
+                final CctEvaluator evaluator = tintController.getEvaluator();
+                if (evaluator != null) {
+                    valueAnimator.setEvaluator(evaluator);
+                }
+                valueAnimator.setDuration(tintController.getTransitionDurationMilliseconds());
+                valueAnimator.setInterpolator(AnimationUtils.loadInterpolator(
+                        getContext(), android.R.interpolator.linear));
+                valueAnimator.addUpdateListener((ValueAnimator animator) -> {
+                    synchronized (mCctTintApplierLock) {
+                        final int value = (int) animator.getAnimatedValue();
+                        if (value != tintController.getAppliedCct()) {
+                            dtm.setColorMatrix(tintController.getLevel(),
+                                    tintController.computeMatrixForCct(value));
+                            tintController.setAppliedCct(value);
+                        }
+                    }
+                });
+                valueAnimator.addListener(new AnimatorListenerAdapter() {
+
+                    private boolean mIsCancelled;
+
+                    @Override
+                    public void onAnimationCancel(Animator animator) {
+                        Slog.d(TAG, tintController.getClass().getSimpleName()
+                                + " animation cancelled");
+                        mIsCancelled = true;
+                    }
+
+                    @Override
+                    public void onAnimationEnd(Animator animator) {
+                        synchronized (mCctTintApplierLock) {
+                            Slog.d(TAG, tintController.getClass().getSimpleName()
+                                    + " animation ended: wasCancelled=" + mIsCancelled
+                                    + " toCct=" + to
+                                    + " fromCct=" + from);
+                            if (!mIsCancelled) {
+                                // Ensure final color matrix is set at the end of the animation.
+                                // If the animation is cancelled then don't set the final color
+                                // matrix so the new animator can pick up from where this one left
+                                // off.
+                                dtm.setColorMatrix(tintController.getLevel(),
+                                        tintController.computeMatrixForCct(to));
+                                tintController.setAppliedCct(to);
+                            }
+                            tintController.setAnimator(null);
+                        }
+                    }
+                });
+                valueAnimator.start();
+            }
+        }
+    }
+
     /**
      * Returns the first date time corresponding to the local time that occurs before the provided
      * date time.
@@ -747,7 +822,7 @@
 
         // If disabled, clear the tint. If enabled, do nothing more here and let the next
         // temperature update set the correct tint.
-        if (!activated) {
+        if (oldActivated && !activated) {
             mHandler.sendEmptyMessage(MSG_APPLY_DISPLAY_WHITE_BALANCE);
         }
     }
@@ -1452,7 +1527,7 @@
     public class ColorDisplayServiceInternal {
 
         /** Sets whether DWB should be allowed in the current state. */
-        public void setDisplayWhiteBalanceAllowed(boolean allowed)  {
+        public void setDisplayWhiteBalanceAllowed(boolean allowed) {
             mDisplayWhiteBalanceTintController.setAllowed(allowed);
             updateDisplayWhiteBalanceStatus();
         }
@@ -1464,8 +1539,8 @@
          * @param cct the color temperature in Kelvin.
          */
         public boolean setDisplayWhiteBalanceColorTemperature(int cct) {
-            // Update the transform matrix even if it can't be applied.
-            mDisplayWhiteBalanceTintController.setMatrix(cct);
+            // Update the transform target CCT even if it can't be applied.
+            mDisplayWhiteBalanceTintController.setTargetCct(cct);
 
             if (mDisplayWhiteBalanceTintController.isActivated()) {
                 mHandler.sendEmptyMessage(MSG_APPLY_DISPLAY_WHITE_BALANCE);
@@ -1601,7 +1676,7 @@
                     applyTint(mNightDisplayTintController, false);
                     break;
                 case MSG_APPLY_DISPLAY_WHITE_BALANCE:
-                    applyTint(mDisplayWhiteBalanceTintController, false);
+                    applyTintByCct(mDisplayWhiteBalanceTintController, false);
                     break;
             }
         }
diff --git a/services/core/java/com/android/server/display/color/ColorTemperatureTintController.java b/services/core/java/com/android/server/display/color/ColorTemperatureTintController.java
new file mode 100644
index 0000000..0fbd9d4
--- /dev/null
+++ b/services/core/java/com/android/server/display/color/ColorTemperatureTintController.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.color;
+
+abstract class ColorTemperatureTintController extends TintController {
+
+    abstract int getAppliedCct();
+
+    abstract void setAppliedCct(int cct);
+
+    abstract int getTargetCct();
+
+    abstract void setTargetCct(int cct);
+
+    /**
+     * Returns the CCT value most closely associated with the "disabled" (identity) matrix for
+     * this device, to use as the target when deactivating this transform.
+     */
+    abstract int getDisabledCct();
+
+    abstract float[] computeMatrixForCct(int cct);
+
+    abstract CctEvaluator getEvaluator();
+}
diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
index 106ac0c..bf0139f 100644
--- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
+++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
@@ -21,6 +21,7 @@
 import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.Size;
 import android.content.Context;
 import android.content.res.Resources;
@@ -36,7 +37,7 @@
 
 import java.io.PrintWriter;
 
-final class DisplayWhiteBalanceTintController extends TintController {
+final class DisplayWhiteBalanceTintController extends ColorTemperatureTintController {
 
     // Three chromaticity coordinates per color: X, Y, and Z
     private static final int NUM_VALUES_PER_PRIMARY = 3;
@@ -52,9 +53,11 @@
     private int mTemperatureDefault;
     @VisibleForTesting
     float[] mDisplayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
+    private int mDisplayNominalWhiteCct;
     @VisibleForTesting
     ColorSpace.Rgb mDisplayColorSpaceRGB;
     private float[] mChromaticAdaptationMatrix;
+    // The temperature currently represented in the matrix.
     @VisibleForTesting
     int mCurrentColorTemperature;
     private float[] mCurrentColorTemperatureXYZ;
@@ -65,6 +68,9 @@
     private Boolean mIsAvailable;
     // This feature becomes disallowed if the device is in an unsupported strong/light state.
     private boolean mIsAllowed = true;
+    private int mTargetCct;
+    private int mAppliedCct;
+    private CctEvaluator mCctEvaluator;
 
     private final DisplayManagerInternal mDisplayManagerInternal;
 
@@ -108,6 +114,9 @@
             displayNominalWhiteXYZ[i] = Float.parseFloat(nominalWhiteValues[i]);
         }
 
+        final int displayNominalWhiteCct = res.getInteger(
+                R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
+
         final int colorTemperatureMin = res.getInteger(
                 R.integer.config_displayWhiteBalanceColorTemperatureMin);
         if (colorTemperatureMin <= 0) {
@@ -124,19 +133,28 @@
             return;
         }
 
-        final int colorTemperature = res.getInteger(
+        final int defaultTemperature = res.getInteger(
                 R.integer.config_displayWhiteBalanceColorTemperatureDefault);
 
         mTransitionDuration = res.getInteger(
                 R.integer.config_displayWhiteBalanceTransitionTime);
 
+        int[] cctRangeMinimums = res.getIntArray(
+                R.array.config_displayWhiteBalanceDisplayRangeMinimums);
+        int[] steps = res.getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
+
         synchronized (mLock) {
             mDisplayColorSpaceRGB = displayColorSpaceRGB;
             mDisplayNominalWhiteXYZ = displayNominalWhiteXYZ;
+            mDisplayNominalWhiteCct = displayNominalWhiteCct;
+            mTargetCct = mDisplayNominalWhiteCct;
+            mAppliedCct = mDisplayNominalWhiteCct;
             mTemperatureMin = colorTemperatureMin;
             mTemperatureMax = colorTemperatureMax;
-            mTemperatureDefault = colorTemperature;
+            mTemperatureDefault = defaultTemperature;
             mSetUp = true;
+            mCctEvaluator = new CctEvaluator(mTemperatureMin, mTemperatureMax,
+                    cctRangeMinimums, steps);
         }
 
         setMatrix(mTemperatureDefault);
@@ -144,8 +162,16 @@
 
     @Override
     public float[] getMatrix() {
-        return mSetUp && isActivated() ? mMatrixDisplayWhiteBalance
-                : ColorDisplayService.MATRIX_IDENTITY;
+        if (!mSetUp || !isActivated()) {
+            return ColorDisplayService.MATRIX_IDENTITY;
+        }
+        computeMatrixForCct(mAppliedCct);
+        return mMatrixDisplayWhiteBalance;
+    }
+
+    @Override
+    public int getTargetCct() {
+        return mTargetCct;
     }
 
     /**
@@ -174,6 +200,12 @@
 
     @Override
     public void setMatrix(int cct) {
+        setTargetCct(cct);
+        computeMatrixForCct(mTargetCct);
+    }
+
+    @Override
+    public void setTargetCct(int cct) {
         if (!mSetUp) {
             Slog.w(ColorDisplayService.TAG,
                     "Can't set display white balance temperature: uninitialized");
@@ -183,50 +215,93 @@
         if (cct < mTemperatureMin) {
             Slog.w(ColorDisplayService.TAG,
                     "Requested display color temperature is below allowed minimum");
-            cct = mTemperatureMin;
+            mTargetCct = mTemperatureMin;
         } else if (cct > mTemperatureMax) {
             Slog.w(ColorDisplayService.TAG,
                     "Requested display color temperature is above allowed maximum");
-            cct = mTemperatureMax;
+            mTargetCct = mTemperatureMax;
+        } else {
+            mTargetCct = cct;
+        }
+    }
+
+    @Override
+    public int getDisabledCct() {
+        return mDisplayNominalWhiteCct;
+    }
+
+    @Override
+    public float[] computeMatrixForCct(int cct) {
+        if (!mSetUp || cct == 0) {
+            Slog.w(ColorDisplayService.TAG, "Couldn't compute matrix for cct=" + cct);
+            return ColorDisplayService.MATRIX_IDENTITY;
         }
 
         synchronized (mLock) {
             mCurrentColorTemperature = cct;
 
-            // Adapt the display's nominal white point to match the requested CCT value
-            mCurrentColorTemperatureXYZ = ColorSpace.cctToXyz(cct);
-
-            mChromaticAdaptationMatrix =
-                    ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
-                            mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
-
-            // Convert the adaptation matrix to RGB space
-            float[] result = mul3x3(mChromaticAdaptationMatrix,
-                    mDisplayColorSpaceRGB.getTransform());
-            result = mul3x3(mDisplayColorSpaceRGB.getInverseTransform(), result);
-
-            // Normalize the transform matrix to peak white value in RGB space
-            final float adaptedMaxR = result[0] + result[3] + result[6];
-            final float adaptedMaxG = result[1] + result[4] + result[7];
-            final float adaptedMaxB = result[2] + result[5] + result[8];
-            final float denum = Math.max(Math.max(adaptedMaxR, adaptedMaxG), adaptedMaxB);
-
-            Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
-            for (int i = 0; i < result.length; i++) {
-                result[i] /= denum;
-                if (!isColorMatrixCoeffValid(result[i])) {
-                    Slog.e(ColorDisplayService.TAG, "Invalid DWB color matrix");
-                    return;
-                }
+            if (cct == mDisplayNominalWhiteCct && !isActivated()) {
+                // DWB is finished turning off. Clear the matrix.
+                Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
+            } else {
+                computeMatrixForCctLocked(cct);
             }
 
-            java.lang.System.arraycopy(result, 0, mMatrixDisplayWhiteBalance, 0, 3);
-            java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3);
-            java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3);
+            Slog.d(ColorDisplayService.TAG, "computeDisplayWhiteBalanceMatrix: cct =" + cct
+                    + " matrix =" + matrixToString(mMatrixDisplayWhiteBalance, 16));
+
+            return mMatrixDisplayWhiteBalance;
+        }
+    }
+
+    private void computeMatrixForCctLocked(int cct) {
+        // Adapt the display's nominal white point to match the requested CCT value
+        mCurrentColorTemperatureXYZ = ColorSpace.cctToXyz(cct);
+
+        mChromaticAdaptationMatrix =
+                ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
+                        mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
+
+        // Convert the adaptation matrix to RGB space
+        float[] result = mul3x3(mChromaticAdaptationMatrix,
+                mDisplayColorSpaceRGB.getTransform());
+        result = mul3x3(mDisplayColorSpaceRGB.getInverseTransform(), result);
+
+        // Normalize the transform matrix to peak white value in RGB space
+        final float adaptedMaxR = result[0] + result[3] + result[6];
+        final float adaptedMaxG = result[1] + result[4] + result[7];
+        final float adaptedMaxB = result[2] + result[5] + result[8];
+        final float denum = Math.max(Math.max(adaptedMaxR, adaptedMaxG), adaptedMaxB);
+
+        Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
+
+        for (int i = 0; i < result.length; i++) {
+            result[i] /= denum;
+            if (!isColorMatrixCoeffValid(result[i])) {
+                Slog.e(ColorDisplayService.TAG, "Invalid DWB color matrix");
+                return;
+            }
         }
 
-        Slog.d(ColorDisplayService.TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct
-                + " matrix = " + matrixToString(mMatrixDisplayWhiteBalance, 16));
+        java.lang.System.arraycopy(result, 0, mMatrixDisplayWhiteBalance, 0, 3);
+        java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3);
+        java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3);
+    }
+
+    @Override
+    int getAppliedCct() {
+        return mAppliedCct;
+    }
+
+    @Override
+    void setAppliedCct(int cct) {
+        mAppliedCct = cct;
+    }
+
+    @Override
+    @Nullable
+    CctEvaluator getEvaluator() {
+        return mCctEvaluator;
     }
 
     @Override
@@ -258,7 +333,10 @@
             pw.println("    mTemperatureMin = " + mTemperatureMin);
             pw.println("    mTemperatureMax = " + mTemperatureMax);
             pw.println("    mTemperatureDefault = " + mTemperatureDefault);
+            pw.println("    mDisplayNominalWhiteCct = " + mDisplayNominalWhiteCct);
             pw.println("    mCurrentColorTemperature = " + mCurrentColorTemperature);
+            pw.println("    mTargetCct = " + mTargetCct);
+            pw.println("    mAppliedCct = " + mAppliedCct);
             pw.println("    mCurrentColorTemperatureXYZ = "
                     + matrixToString(mCurrentColorTemperatureXYZ, 3));
             pw.println("    mDisplayColorSpaceRGB RGB-to-XYZ = "
@@ -340,11 +418,7 @@
     }
 
     private boolean isColorMatrixCoeffValid(float coeff) {
-        if (Float.isNaN(coeff) || Float.isInfinite(coeff)) {
-            return false;
-        }
-
-        return true;
+        return !Float.isNaN(coeff) && !Float.isInfinite(coeff);
     }
 
     private boolean isColorMatrixValid(float[] matrix) {
@@ -352,8 +426,8 @@
             return false;
         }
 
-        for (int i = 0; i < matrix.length; i++) {
-            if (!isColorMatrixCoeffValid(matrix[i])) {
+        for (float value : matrix) {
+            if (!isColorMatrixCoeffValid(value)) {
                 return false;
             }
         }
diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java
index c53ac06..384333a 100644
--- a/services/core/java/com/android/server/display/color/TintController.java
+++ b/services/core/java/com/android/server/display/color/TintController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display.color;
 
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.util.Slog;
 
@@ -28,14 +29,14 @@
      */
     private static final long TRANSITION_DURATION = 3000L;
 
-    private ColorDisplayService.TintValueAnimator mAnimator;
+    private ValueAnimator mAnimator;
     private Boolean mIsActivated;
 
-    public ColorDisplayService.TintValueAnimator getAnimator() {
+    public ValueAnimator getAnimator() {
         return mAnimator;
     }
 
-    public void setAnimator(ColorDisplayService.TintValueAnimator animator) {
+    public void setAnimator(ValueAnimator animator) {
         mAnimator = animator;
     }
 
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index fd94be9..17f928a 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -65,7 +65,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.display.BrightnessSynchronizer;
-import com.android.internal.display.RefreshRateSettingsUtils;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
 import com.android.server.display.DisplayDeviceConfig;
@@ -1067,10 +1066,10 @@
 
     @VisibleForTesting
     final class SettingsObserver extends ContentObserver {
-        private final Uri mSmoothDisplaySetting =
-                Settings.System.getUriFor(Settings.System.SMOOTH_DISPLAY);
-        private final Uri mForcePeakRefreshRateSetting =
-                Settings.System.getUriFor(Settings.System.FORCE_PEAK_REFRESH_RATE);
+        private final Uri mPeakRefreshRateSetting =
+                Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
+        private final Uri mMinRefreshRateSetting =
+                Settings.System.getUriFor(Settings.System.MIN_REFRESH_RATE);
         private final Uri mLowPowerModeSetting =
                 Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE);
         private final Uri mMatchContentFrameRateSetting =
@@ -1106,8 +1105,9 @@
 
         public void observe() {
             final ContentResolver cr = mContext.getContentResolver();
-            mInjector.registerSmoothDisplayObserver(cr, this);
-            mInjector.registerForcePeakRefreshRateObserver(cr, this);
+            mInjector.registerPeakRefreshRateObserver(cr, this);
+            cr.registerContentObserver(mMinRefreshRateSetting, false /*notifyDescendants*/, this,
+                    UserHandle.USER_SYSTEM);
             cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this,
                     UserHandle.USER_SYSTEM);
             cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/,
@@ -1149,8 +1149,8 @@
         @Override
         public void onChange(boolean selfChange, Uri uri, int userId) {
             synchronized (mLock) {
-                if (mSmoothDisplaySetting.equals(uri)
-                        || mForcePeakRefreshRateSetting.equals(uri)) {
+                if (mPeakRefreshRateSetting.equals(uri)
+                        || mMinRefreshRateSetting.equals(uri)) {
                     updateRefreshRateSettingLocked();
                 } else if (mLowPowerModeSetting.equals(uri)) {
                     updateLowPowerModeSettingLocked();
@@ -1205,9 +1205,12 @@
         }
 
         private void updateRefreshRateSettingLocked() {
-            updateRefreshRateSettingLocked(RefreshRateSettingsUtils.getMinRefreshRate(mContext),
-                    RefreshRateSettingsUtils.getPeakRefreshRate(mContext, mDefaultPeakRefreshRate),
-                    mDefaultRefreshRate);
+            final ContentResolver cr = mContext.getContentResolver();
+            float minRefreshRate = Settings.System.getFloatForUser(cr,
+                    Settings.System.MIN_REFRESH_RATE, 0f, cr.getUserId());
+            float peakRefreshRate = Settings.System.getFloatForUser(cr,
+                    Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate, cr.getUserId());
+            updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate);
         }
 
         private void updateRefreshRateSettingLocked(
@@ -2840,17 +2843,12 @@
     }
 
     interface Injector {
-        Uri SMOOTH_DISPLAY_URI = Settings.System.getUriFor(Settings.System.SMOOTH_DISPLAY);
-        Uri FORCE_PEAK_REFRESH_RATE_URI =
-                Settings.System.getUriFor(Settings.System.FORCE_PEAK_REFRESH_RATE);
+        Uri PEAK_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
 
         @NonNull
         DeviceConfigInterface getDeviceConfig();
 
-        void registerSmoothDisplayObserver(@NonNull ContentResolver cr,
-                @NonNull ContentObserver observer);
-
-        void registerForcePeakRefreshRateObserver(@NonNull ContentResolver cr,
+        void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
                 @NonNull ContentObserver observer);
 
         void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
@@ -2890,16 +2888,9 @@
         }
 
         @Override
-        public void registerSmoothDisplayObserver(@NonNull ContentResolver cr,
+        public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
                 @NonNull ContentObserver observer) {
-            cr.registerContentObserver(SMOOTH_DISPLAY_URI, false /*notifyDescendants*/,
-                    observer, UserHandle.USER_SYSTEM);
-        }
-
-        @Override
-        public void registerForcePeakRefreshRateObserver(@NonNull ContentResolver cr,
-                @NonNull ContentObserver observer) {
-            cr.registerContentObserver(FORCE_PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/,
+            cr.registerContentObserver(PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/,
                     observer, UserHandle.USER_SYSTEM);
         }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index 220a438..b82129b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -35,6 +35,7 @@
             ERROR_DESTINATION,
             ERROR_PARAMETER,
             ERROR_PARAMETER_SHORT,
+            ERROR_PARAMETER_LONG,
     })
     public @interface ValidationResult {};
 
@@ -43,6 +44,7 @@
     static final int ERROR_DESTINATION = 2;
     static final int ERROR_PARAMETER = 3;
     static final int ERROR_PARAMETER_SHORT = 4;
+    static final int ERROR_PARAMETER_LONG = 5;
 
     interface ParameterValidator {
         /**
@@ -159,11 +161,13 @@
         addValidationInfo(Constants.MESSAGE_SET_MENU_LANGUAGE,
                 new AsciiValidator(3), DEST_BROADCAST);
 
-        ParameterValidator statusRequestValidator = new OneByteRangeValidator(0x01, 0x03);
+        ParameterValidator statusRequestValidator = new MinimumOneByteRangeValidator(0x01, 0x03);
         addValidationInfo(
-                Constants.MESSAGE_DECK_CONTROL, new OneByteRangeValidator(0x01, 0x04), DEST_DIRECT);
+                Constants.MESSAGE_DECK_CONTROL,
+                        new MinimumOneByteRangeValidator(0x01, 0x04), DEST_DIRECT);
         addValidationInfo(
-                Constants.MESSAGE_DECK_STATUS, new OneByteRangeValidator(0x11, 0x1F), DEST_DIRECT);
+                Constants.MESSAGE_DECK_STATUS,
+                        new MinimumOneByteRangeValidator(0x11, 0x1F), DEST_DIRECT);
         addValidationInfo(Constants.MESSAGE_GIVE_DECK_STATUS, statusRequestValidator, DEST_DIRECT);
         addValidationInfo(Constants.MESSAGE_PLAY, new PlayModeValidator(), DEST_DIRECT);
 
@@ -201,9 +205,11 @@
 
         // Messages for the Device Menu Control.
         addValidationInfo(
-                Constants.MESSAGE_MENU_REQUEST, new OneByteRangeValidator(0x00, 0x02), DEST_DIRECT);
+                Constants.MESSAGE_MENU_REQUEST,
+                        new MinimumOneByteRangeValidator(0x00, 0x02), DEST_DIRECT);
         addValidationInfo(
-                Constants.MESSAGE_MENU_STATUS, new OneByteRangeValidator(0x00, 0x01), DEST_DIRECT);
+                Constants.MESSAGE_MENU_STATUS,
+                        new MinimumOneByteRangeValidator(0x00, 0x01), DEST_DIRECT);
 
         // Messages for the Remote Control Passthrough.
         addValidationInfo(
@@ -214,7 +220,7 @@
         // Messages for the Power Status.
         addValidationInfo(
                 Constants.MESSAGE_REPORT_POWER_STATUS,
-                new OneByteRangeValidator(0x00, 0x03),
+                new MinimumOneByteRangeValidator(0x00, 0x03),
                 DEST_DIRECT | DEST_BROADCAST);
 
         // Messages for the General Protocol.
@@ -229,17 +235,17 @@
                 oneByteValidator, DEST_DIRECT);
         addValidationInfo(
                 Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE,
-                new OneByteRangeValidator(0x00, 0x01),
+                new MinimumOneByteRangeValidator(0x00, 0x01),
                 DEST_ALL);
         addValidationInfo(
                 Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS,
-                new OneByteRangeValidator(0x00, 0x01),
+                new SingleByteRangeValidator(0x00, 0x01),
                 DEST_DIRECT);
 
         // Messages for the Audio Rate Control.
         addValidationInfo(
                 Constants.MESSAGE_SET_AUDIO_RATE,
-                new OneByteRangeValidator(0x00, 0x06),
+                new MinimumOneByteRangeValidator(0x00, 0x06),
                 DEST_DIRECT);
 
         // Messages for Feature Discovery.
@@ -900,11 +906,14 @@
         }
     }
 
-    /** Check if the given parameters are one byte parameters and within range. */
-    private static class OneByteRangeValidator implements ParameterValidator {
+    /**
+     * Check if the given parameters are at least one byte parameters
+     * and the first byte is within range.
+     */
+    private static class MinimumOneByteRangeValidator implements ParameterValidator {
         private final int mMinValue, mMaxValue;
 
-        OneByteRangeValidator(int minValue, int maxValue) {
+        MinimumOneByteRangeValidator(int minValue, int maxValue) {
             mMinValue = minValue;
             mMaxValue = maxValue;
         }
@@ -918,6 +927,26 @@
         }
     }
 
+    /** Check if the given parameters are exactly one byte parameters and within range. */
+    private static class SingleByteRangeValidator implements ParameterValidator {
+        private final int mMinValue, mMaxValue;
+
+        SingleByteRangeValidator(int minValue, int maxValue) {
+            mMinValue = minValue;
+            mMaxValue = maxValue;
+        }
+
+        @Override
+        public int isValid(byte[] params) {
+            if (params.length < 1) {
+                return ERROR_PARAMETER_SHORT;
+            } else if (params.length > 1) {
+                return ERROR_PARAMETER_LONG;
+            }
+            return toErrorCode(isWithinRange(params[0], mMinValue, mMaxValue));
+        }
+    }
+
     /**
      * Check if the given Analogue Timer message parameters are valid. Valid parameters should
      * adhere to message description of Analogue Timer defined in CEC 1.4 Specification : Message
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 75fe63a..9cd5272 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1611,6 +1611,7 @@
         @HdmiCecMessageValidator.ValidationResult
         int validationResult = message.getValidationResult();
         if (validationResult == HdmiCecMessageValidator.ERROR_PARAMETER
+                || validationResult == HdmiCecMessageValidator.ERROR_PARAMETER_LONG
                 || !verifyPhysicalAddresses(message)) {
             return Constants.ABORT_INVALID_OPERAND;
         } else if (validationResult != HdmiCecMessageValidator.OK
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index d8716b3..6ec4022 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -562,7 +562,7 @@
                 key.append(",languageTag:").append(inputDevice.getKeyboardLanguageTag());
             }
             if (!TextUtils.isEmpty(inputDevice.getKeyboardLayoutType())) {
-                key.append(",layoutType:").append(inputDevice.getKeyboardLanguageTag());
+                key.append(",layoutType:").append(inputDevice.getKeyboardLayoutType());
             }
         }
         return key.toString();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index c70d555..57f8d14 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -856,13 +856,15 @@
     @GuardedBy("ImfLock.class")
     private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>();
 
-    private static final class SoftInputShowHideHistory {
+    @VisibleForTesting
+    static final class SoftInputShowHideHistory {
         private final Entry[] mEntries = new Entry[16];
         private int mNextIndex = 0;
         private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
 
-        private static final class Entry {
+        static final class Entry {
             final int mSequenceNumber = sSequenceNumber.getAndIncrement();
+            @Nullable
             final ClientState mClientState;
             @SoftInputModeFlags
             final int mFocusedWindowSoftInputMode;
@@ -874,7 +876,7 @@
             final boolean mInFullscreenMode;
             @NonNull
             final String mFocusedWindowName;
-            @NonNull
+            @Nullable
             final EditorInfo mEditorInfo;
             @NonNull
             final String mRequestWindowName;
@@ -953,9 +955,13 @@
 
                 pw.print(prefix);
                 pw.print(" editorInfo: ");
-                pw.print(" inputType=" + entry.mEditorInfo.inputType);
-                pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
-                pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
+                if (entry.mEditorInfo != null) {
+                    pw.print(" inputType=" + entry.mEditorInfo.inputType);
+                    pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
+                    pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
+                } else {
+                    pw.println("null");
+                }
 
                 pw.print(prefix);
                 pw.println(" focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 464a256..02b7053 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -16,6 +16,12 @@
 
 package com.android.server.media;
 
+import static android.media.VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
+import static android.media.VolumeProvider.VOLUME_CONTROL_FIXED;
+import static android.media.VolumeProvider.VOLUME_CONTROL_RELATIVE;
+import static android.media.session.MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+import static android.media.session.MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -173,8 +179,8 @@
     // Volume handling fields
     private AudioAttributes mAudioAttrs;
     private AudioManager mAudioManager;
-    private int mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL;
-    private int mVolumeControlType = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
+    private int mVolumeType = PLAYBACK_TYPE_LOCAL;
+    private int mVolumeControlType = VOLUME_CONTROL_ABSOLUTE;
     private int mMaxVolume = 0;
     private int mCurrentVolume = 0;
     private int mOptimisticVolume = -1;
@@ -309,13 +315,13 @@
         if (checkPlaybackActiveState(true) || isSystemPriority()) {
             flags &= ~AudioManager.FLAG_PLAY_SOUND;
         }
-        if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
+        if (mVolumeType == PLAYBACK_TYPE_LOCAL) {
             // Adjust the volume with a handler not to be blocked by other system service.
             int stream = getVolumeStream(mAudioAttrs);
             postAdjustLocalVolume(stream, direction, flags, opPackageName, pid, uid,
                     asSystemService, useSuggested, previousFlagPlaySound);
         } else {
-            if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) {
+            if (mVolumeControlType == VOLUME_CONTROL_FIXED) {
                 if (DEBUG) {
                     Log.d(TAG, "Session does not support volume adjustment");
                 }
@@ -354,7 +360,7 @@
 
     private void setVolumeTo(String packageName, String opPackageName, int pid, int uid, int value,
             int flags) {
-        if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
+        if (mVolumeType == PLAYBACK_TYPE_LOCAL) {
             int stream = getVolumeStream(mAudioAttrs);
             final int volumeValue = value;
             mHandler.post(new Runnable() {
@@ -371,7 +377,7 @@
                 }
             });
         } else {
-            if (mVolumeControlType != VolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
+            if (mVolumeControlType != VOLUME_CONTROL_ABSOLUTE) {
                 if (DEBUG) {
                     Log.d(TAG, "Session does not support setting volume");
                 }
@@ -433,7 +439,7 @@
      */
     @Override
     public boolean isPlaybackTypeLocal() {
-        return mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+        return mVolumeType == PLAYBACK_TYPE_LOCAL;
     }
 
     @Override
@@ -495,7 +501,7 @@
 
     @Override
     public boolean canHandleVolumeKey() {
-        return mVolumeControlType != VolumeProvider.VOLUME_CONTROL_FIXED;
+        return mVolumeControlType != VOLUME_CONTROL_FIXED;
     }
 
     @Override
@@ -528,13 +534,48 @@
         pw.println(indent + "controllers: " + mControllerCallbackHolders.size());
         pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString()));
         pw.println(indent + "audioAttrs=" + mAudioAttrs);
-        pw.println(indent + "volumeType=" + mVolumeType + ", controlType=" + mVolumeControlType
-                + ", max=" + mMaxVolume + ", current=" + mCurrentVolume);
+        pw.append(indent)
+                .append("volumeType=")
+                .append(toVolumeTypeString(mVolumeType))
+                .append(", controlType=")
+                .append(toVolumeControlTypeString(mVolumeControlType))
+                .append(", max=")
+                .append(Integer.toString(mMaxVolume))
+                .append(", current=")
+                .append(Integer.toString(mCurrentVolume))
+                .append(", volumeControlId=")
+                .append(mVolumeControlId)
+                .println();
         pw.println(indent + "metadata: " + mMetadataDescription);
         pw.println(indent + "queueTitle=" + mQueueTitle + ", size="
                 + (mQueue == null ? 0 : mQueue.size()));
     }
 
+    private static String toVolumeControlTypeString(
+            @VolumeProvider.ControlType int volumeControlType) {
+        switch (volumeControlType) {
+            case VOLUME_CONTROL_FIXED:
+                return "FIXED";
+            case VOLUME_CONTROL_RELATIVE:
+                return "RELATIVE";
+            case VOLUME_CONTROL_ABSOLUTE:
+                return "ABSOLUTE";
+            default:
+                return TextUtils.formatSimple("unknown(%d)", volumeControlType);
+        }
+    }
+
+    private static String toVolumeTypeString(@PlaybackInfo.PlaybackType int volumeType) {
+        switch (volumeType) {
+            case PLAYBACK_TYPE_LOCAL:
+                return "LOCAL";
+            case PLAYBACK_TYPE_REMOTE:
+                return "REMOTE";
+            default:
+                return TextUtils.formatSimple("unknown(%d)", volumeType);
+        }
+    }
+
     @Override
     public String toString() {
         return mPackageName + "/" + mTag + " (userId=" + mUserId + ")";
@@ -877,8 +918,8 @@
         int stream = getVolumeStream(attributes);
         int max = mAudioManager.getStreamMaxVolume(stream);
         int current = mAudioManager.getStreamVolume(stream);
-        return new PlaybackInfo(volumeType, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max,
-                current, attributes, null);
+        return new PlaybackInfo(
+                volumeType, VOLUME_CONTROL_ABSOLUTE, max, current, attributes, null);
     }
 
     private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
@@ -1124,7 +1165,7 @@
             boolean typeChanged;
             synchronized (mLock) {
                 typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
-                mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+                mVolumeType = PLAYBACK_TYPE_LOCAL;
                 mVolumeControlId = null;
                 if (attributes != null) {
                     mAudioAttrs = attributes;
@@ -1148,7 +1189,7 @@
                 throws RemoteException {
             boolean typeChanged;
             synchronized (mLock) {
-                typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+                typeChanged = mVolumeType == PLAYBACK_TYPE_LOCAL;
                 mVolumeType = PlaybackInfo.PLAYBACK_TYPE_REMOTE;
                 mVolumeControlType = control;
                 mMaxVolume = max;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 33e6a8f1..f0ab815 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -316,7 +316,6 @@
 import com.android.server.notification.toast.ToastRecord;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.powerstats.StatsPullAtomCallbackImpl;
 import com.android.server.statusbar.StatusBarManagerInternal;
@@ -2559,8 +2558,8 @@
                         Context.STATS_MANAGER),
                 getContext().getSystemService(TelephonyManager.class),
                 LocalServices.getService(ActivityManagerInternal.class),
-                createToastRateLimiter(), new PermissionHelper(LocalServices.getService(
-                        PermissionManagerServiceInternal.class), AppGlobals.getPackageManager(),
+                createToastRateLimiter(), new PermissionHelper(getContext(),
+                        AppGlobals.getPackageManager(),
                         AppGlobals.getPermissionManager()),
                 LocalServices.getService(UsageStatsManagerInternal.class),
                 getContext().getSystemService(TelecomManager.class),
@@ -11599,6 +11598,8 @@
             StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
             try {
                 listener.onNotificationPosted(sbnHolder, rankingUpdate);
+            } catch (android.os.DeadObjectException ex) {
+                Slog.wtf(TAG, "unable to notify listener (posted): " + info, ex);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "unable to notify listener (posted): " + info, ex);
             }
@@ -11620,6 +11621,8 @@
                     reason = REASON_LISTENER_CANCEL;
                 }
                 listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
+            } catch (android.os.DeadObjectException ex) {
+                Slog.wtf(TAG, "unable to notify listener (removed): " + info, ex);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "unable to notify listener (removed): " + info, ex);
             }
@@ -11630,6 +11633,8 @@
             final INotificationListener listener = (INotificationListener) info.service;
             try {
                 listener.onNotificationRankingUpdate(rankingUpdate);
+            } catch (android.os.DeadObjectException ex) {
+                Slog.wtf(TAG, "unable to notify listener (ranking update): " + info, ex);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "unable to notify listener (ranking update): " + info, ex);
             }
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index b6fd822..93c83e1 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -25,6 +25,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.content.Context;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -37,7 +38,6 @@
 import android.util.Slog;
 
 import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
 import java.util.Collections;
 import java.util.HashSet;
@@ -53,13 +53,13 @@
 
     private static final String NOTIFICATION_PERMISSION = Manifest.permission.POST_NOTIFICATIONS;
 
-    private final PermissionManagerServiceInternal mPmi;
+    private final Context mContext;
     private final IPackageManager mPackageManager;
     private final IPermissionManager mPermManager;
 
-    public PermissionHelper(PermissionManagerServiceInternal pmi, IPackageManager packageManager,
+    public PermissionHelper(Context context, IPackageManager packageManager,
             IPermissionManager permManager) {
-        mPmi = pmi;
+        mContext = context;
         mPackageManager = packageManager;
         mPermManager = permManager;
     }
@@ -71,7 +71,7 @@
     public boolean hasPermission(int uid) {
         final long callingId = Binder.clearCallingIdentity();
         try {
-            return mPmi.checkUidPermission(uid, NOTIFICATION_PERMISSION) == PERMISSION_GRANTED;
+            return mContext.checkPermission(NOTIFICATION_PERMISSION, -1, uid) == PERMISSION_GRANTED;
         } finally {
             Binder.restoreCallingIdentity(callingId);
         }
@@ -193,8 +193,8 @@
                 return;
             }
 
-            boolean currentlyGranted = mPmi.checkPermission(packageName, NOTIFICATION_PERMISSION,
-                    userId) != PackageManager.PERMISSION_DENIED;
+            int uid = mPackageManager.getPackageUid(packageName, 0, userId);
+            boolean currentlyGranted = hasPermission(uid);
             if (grant && !currentlyGranted) {
                 mPermManager.grantRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId);
             } else if (!grant && currentlyGranted) {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index a267e8a..5932929 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -4360,12 +4360,22 @@
 
         // A new application appeared on /system, and we are seeing it for the first time.
         // Its also not updated as we don't have a copy of it on /data. So, scan it in a
-        // STOPPED state. Ignore if it's an APEX package since stopped state does not affect them.
+        // STOPPED state.
+        // We'll skip this step under the following conditions:
+        //   - It's "android"
+        //   - It's an APEX or overlay package since stopped state does not affect them.
+        //   - It is enumerated with a <initial-package-state> tag having the stopped attribute
+        //     set to false
         final boolean isApexPkg = (scanFlags & SCAN_AS_APEX) != 0;
-        if (mPm.mShouldStopSystemPackagesByDefault && scanSystemPartition
-                && !pkgAlreadyExists && !isApexPkg) {
+        if (mPm.mShouldStopSystemPackagesByDefault
+                && scanSystemPartition
+                && !pkgAlreadyExists
+                && !isApexPkg
+                && !parsedPackage.isOverlayIsStatic()
+        ) {
             String packageName = parsedPackage.getPackageName();
-            if (!mPm.mInitialNonStoppedSystemPackages.contains(packageName)) {
+            if (!mPm.mInitialNonStoppedSystemPackages.contains(packageName)
+                    && !"android".contentEquals(packageName)) {
                 scanFlags |= SCAN_AS_STOPPED_SYSTEM_APP;
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 29c5ada..97e7f6f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1148,12 +1148,21 @@
             info.userId = userId;
             info.installerPackageName = mInstallSource.mInstallerPackageName;
             info.installerAttributionTag = mInstallSource.mInstallerAttributionTag;
+            info.resolvedBaseCodePath = null;
             if (mContext.checkCallingOrSelfPermission(
                     Manifest.permission.READ_INSTALLED_SESSION_PATHS)
-                            == PackageManager.PERMISSION_GRANTED && mResolvedBaseFile != null) {
-                info.resolvedBaseCodePath = mResolvedBaseFile.getAbsolutePath();
-            } else {
-                info.resolvedBaseCodePath = null;
+                    == PackageManager.PERMISSION_GRANTED) {
+                File file = mResolvedBaseFile;
+                if (file == null) {
+                    // Try to guess mResolvedBaseFile file.
+                    final List<File> addedFiles = getAddedApksLocked();
+                    if (addedFiles.size() > 0) {
+                        file = addedFiles.get(0);
+                    }
+                }
+                if (file != null) {
+                    info.resolvedBaseCodePath = file.getAbsolutePath();
+                }
             }
             info.progress = progress;
             info.sealed = mSealed;
@@ -1355,9 +1364,12 @@
 
     @GuardedBy("mLock")
     private String[] getStageDirContentsLocked() {
+        if (stageDir == null) {
+            return EmptyArray.STRING;
+        }
         String[] result = stageDir.list();
         if (result == null) {
-            result = EmptyArray.STRING;
+            return EmptyArray.STRING;
         }
         return result;
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b5108af..f482046 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5258,6 +5258,12 @@
         }
 
         @Override
+        public @NonNull List<String> getInitialNonStoppedSystemPackages() {
+            return mInitialNonStoppedSystemPackages != null
+                    ? new ArrayList<>(mInitialNonStoppedSystemPackages) : new ArrayList<>();
+        }
+
+        @Override
         public String[] getUnsuspendablePackagesForUser(String[] packageNames, int userId) {
             Objects.requireNonNull(packageNames, "packageNames cannot be null");
             mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index f78b611..58183f0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -782,6 +782,8 @@
                         getInFileDescriptor(), getOutFileDescriptor(), getErrFileDescriptor(),
                         new String[] { "list" }, getShellCallback(), adoptResultReceiver());
                 return 0;
+            case "initial-non-stopped-system-packages":
+                return runListInitialNonStoppedSystemPackages();
         }
         pw.println("Error: unknown list type '" + type + "'");
         return -1;
@@ -794,6 +796,21 @@
         return 0;
     }
 
+    private int runListInitialNonStoppedSystemPackages() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final List<String> list = mInterface.getInitialNonStoppedSystemPackages();
+
+        Collections.sort(list);
+
+        for (String pkgName : list) {
+            pw.print("package:");
+            pw.print(pkgName);
+            pw.println();
+        }
+
+        return 0;
+    }
+
     private int runListFeatures() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
         final List<FeatureInfo> list = mInterface.getSystemAvailableFeatures().getList();
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 84a9888..2f0cea3 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -93,7 +93,6 @@
 import android.text.TextUtils;
 import android.text.format.TimeMigrationUtils;
 import android.util.ArraySet;
-import android.util.AtomicFile;
 import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.Slog;
@@ -149,6 +148,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.regex.Pattern;
@@ -321,8 +321,7 @@
     private final ArrayList<LauncherApps.ShortcutChangeCallback> mShortcutChangeCallbacks =
             new ArrayList<>(1);
 
-    @GuardedBy("mLock")
-    private long mRawLastResetTime;
+    private final AtomicLong mRawLastResetTime = new AtomicLong(0);
 
     /**
      * User ID -> UserShortcuts
@@ -756,10 +755,15 @@
     }
 
     /** Return the base state file name */
-    private AtomicFile getBaseStateFile() {
-        final File path = new File(injectSystemDataPath(), FILENAME_BASE_STATE);
-        path.mkdirs();
-        return new AtomicFile(path);
+    final ResilientAtomicFile getBaseStateFile() {
+        File mainFile = new File(injectSystemDataPath(), FILENAME_BASE_STATE);
+        File temporaryBackup = new File(injectSystemDataPath(),
+                FILENAME_BASE_STATE + ".backup");
+        File reserveCopy = new File(injectSystemDataPath(),
+                FILENAME_BASE_STATE + ".reservecopy");
+        int fileMode = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH;
+        return new ResilientAtomicFile(mainFile, temporaryBackup, reserveCopy, fileMode,
+                "base shortcut", null);
     }
 
     /**
@@ -976,80 +980,91 @@
         writeAttr(out, name, intent.toUri(/* flags =*/ 0));
     }
 
-    @GuardedBy("mLock")
     @VisibleForTesting
-    void saveBaseStateLocked() {
-        final AtomicFile file = getBaseStateFile();
-        if (DEBUG || DEBUG_REBOOT) {
-            Slog.d(TAG, "Saving to " + file.getBaseFile());
-        }
+    void saveBaseState() {
+        try (ResilientAtomicFile file = getBaseStateFile()) {
+            if (DEBUG || DEBUG_REBOOT) {
+                Slog.d(TAG, "Saving to " + file.getBaseFile());
+            }
 
-        FileOutputStream outs = null;
-        try {
-            outs = file.startWrite();
+            FileOutputStream outs = null;
+            try {
+                synchronized (mLock) {
+                    outs = file.startWrite();
+                }
 
-            // Write to XML
-            TypedXmlSerializer out = Xml.resolveSerializer(outs);
-            out.startDocument(null, true);
-            out.startTag(null, TAG_ROOT);
+                // Write to XML
+                TypedXmlSerializer out = Xml.resolveSerializer(outs);
+                out.startDocument(null, true);
+                out.startTag(null, TAG_ROOT);
 
-            // Body.
-            writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime);
+                // Body.
+                // No locking required. Ok to add lock later if we save more data.
+                writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime.get());
 
-            // Epilogue.
-            out.endTag(null, TAG_ROOT);
-            out.endDocument();
+                // Epilogue.
+                out.endTag(null, TAG_ROOT);
+                out.endDocument();
 
-            // Close.
-            file.finishWrite(outs);
-        } catch (IOException e) {
-            Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
-            file.failWrite(outs);
+                // Close.
+                file.finishWrite(outs);
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
+                file.failWrite(outs);
+            }
         }
     }
 
     @GuardedBy("mLock")
     private void loadBaseStateLocked() {
-        mRawLastResetTime = 0;
+        mRawLastResetTime.set(0);
 
-        final AtomicFile file = getBaseStateFile();
-        if (DEBUG || DEBUG_REBOOT) {
-            Slog.d(TAG, "Loading from " + file.getBaseFile());
-        }
-        try (FileInputStream in = file.openRead()) {
-            TypedXmlPullParser parser = Xml.resolvePullParser(in);
-
-            int type;
-            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
-                if (type != XmlPullParser.START_TAG) {
-                    continue;
-                }
-                final int depth = parser.getDepth();
-                // Check the root tag
-                final String tag = parser.getName();
-                if (depth == 1) {
-                    if (!TAG_ROOT.equals(tag)) {
-                        Slog.e(TAG, "Invalid root tag: " + tag);
-                        return;
-                    }
-                    continue;
-                }
-                // Assume depth == 2
-                switch (tag) {
-                    case TAG_LAST_RESET_TIME:
-                        mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE);
-                        break;
-                    default:
-                        Slog.e(TAG, "Invalid tag: " + tag);
-                        break;
-                }
+        try (ResilientAtomicFile file = getBaseStateFile()) {
+            if (DEBUG || DEBUG_REBOOT) {
+                Slog.d(TAG, "Loading from " + file.getBaseFile());
             }
-        } catch (FileNotFoundException e) {
-            // Use the default
-        } catch (IOException | XmlPullParserException e) {
-            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
+            FileInputStream in = null;
+            try {
+                in = file.openRead();
+                if (in == null) {
+                    throw new FileNotFoundException(file.getBaseFile().getAbsolutePath());
+                }
 
-            mRawLastResetTime = 0;
+                TypedXmlPullParser parser = Xml.resolvePullParser(in);
+
+                int type;
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                    if (type != XmlPullParser.START_TAG) {
+                        continue;
+                    }
+                    final int depth = parser.getDepth();
+                    // Check the root tag
+                    final String tag = parser.getName();
+                    if (depth == 1) {
+                        if (!TAG_ROOT.equals(tag)) {
+                            Slog.e(TAG, "Invalid root tag: " + tag);
+                            return;
+                        }
+                        continue;
+                    }
+                    // Assume depth == 2
+                    switch (tag) {
+                        case TAG_LAST_RESET_TIME:
+                            mRawLastResetTime.set(parseLongAttribute(parser, ATTR_VALUE));
+                            break;
+                        default:
+                            Slog.e(TAG, "Invalid tag: " + tag);
+                            break;
+                    }
+                }
+            } catch (FileNotFoundException e) {
+                // Use the default
+            } catch (IOException | XmlPullParserException e) {
+                // Remove corrupted file and retry.
+                file.failRead(in, e);
+                loadBaseStateLocked();
+                return;
+            }
         }
         // Adjust the last reset time.
         getLastResetTimeLocked();
@@ -1067,8 +1082,7 @@
                 "user shortcut", null);
     }
 
-    @GuardedBy("mLock")
-    private void saveUserLocked(@UserIdInt int userId) {
+    private void saveUser(@UserIdInt int userId) {
         try (ResilientAtomicFile file = getUserFile(userId)) {
             FileOutputStream os = null;
             try {
@@ -1076,9 +1090,10 @@
                     Slog.d(TAG, "Saving to " + file);
                 }
 
-                os = file.startWrite();
-
-                saveUserInternalLocked(userId, os, /* forBackup= */ false);
+                synchronized (mLock) {
+                    os = file.startWrite();
+                    saveUserInternalLocked(userId, os, /* forBackup= */ false);
+                }
 
                 file.finishWrite(os);
 
@@ -1215,16 +1230,19 @@
         }
         try {
             Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutSaveDirtyInfo");
+            List<Integer> dirtyUserIds = new ArrayList<>();
             synchronized (mLock) {
-                for (int i = mDirtyUserIds.size() - 1; i >= 0; i--) {
-                    final int userId = mDirtyUserIds.get(i);
-                    if (userId == UserHandle.USER_NULL) { // USER_NULL for base state.
-                        saveBaseStateLocked();
-                    } else {
-                        saveUserLocked(userId);
-                    }
+                List<Integer> tmp = mDirtyUserIds;
+                mDirtyUserIds = dirtyUserIds;
+                dirtyUserIds = tmp;
+            }
+            for (int i = dirtyUserIds.size() - 1; i >= 0; i--) {
+                final int userId = dirtyUserIds.get(i);
+                if (userId == UserHandle.USER_NULL) { // USER_NULL for base state.
+                    saveBaseState();
+                } else {
+                    saveUser(userId);
                 }
-                mDirtyUserIds.clear();
             }
         } catch (Exception e) {
             wtf("Exception in saveDirtyInfo", e);
@@ -1237,14 +1255,14 @@
     @GuardedBy("mLock")
     long getLastResetTimeLocked() {
         updateTimesLocked();
-        return mRawLastResetTime;
+        return mRawLastResetTime.get();
     }
 
     /** Return the next reset time. */
     @GuardedBy("mLock")
     long getNextResetTimeLocked() {
         updateTimesLocked();
-        return mRawLastResetTime + mResetInterval;
+        return mRawLastResetTime.get() + mResetInterval;
     }
 
     static boolean isClockValid(long time) {
@@ -1259,25 +1277,26 @@
 
         final long now = injectCurrentTimeMillis();
 
-        final long prevLastResetTime = mRawLastResetTime;
+        final long prevLastResetTime = mRawLastResetTime.get();
+        long newLastResetTime = prevLastResetTime;
 
-        if (mRawLastResetTime == 0) { // first launch.
+        if (newLastResetTime == 0) { // first launch.
             // TODO Randomize??
-            mRawLastResetTime = now;
-        } else if (now < mRawLastResetTime) {
+            newLastResetTime = now;
+        } else if (now < newLastResetTime) {
             // Clock rewound.
             if (isClockValid(now)) {
                 Slog.w(TAG, "Clock rewound");
                 // TODO Randomize??
-                mRawLastResetTime = now;
+                newLastResetTime = now;
             }
-        } else {
-            if ((mRawLastResetTime + mResetInterval) <= now) {
-                final long offset = mRawLastResetTime % mResetInterval;
-                mRawLastResetTime = ((now / mResetInterval) * mResetInterval) + offset;
-            }
+        } else if ((newLastResetTime + mResetInterval) <= now) {
+            final long offset = newLastResetTime % mResetInterval;
+            newLastResetTime = ((now / mResetInterval) * mResetInterval) + offset;
         }
-        if (prevLastResetTime != mRawLastResetTime) {
+
+        mRawLastResetTime.set(newLastResetTime);
+        if (prevLastResetTime != newLastResetTime) {
             scheduleSaveBaseState();
         }
     }
@@ -2705,9 +2724,7 @@
     }
 
     void resetAllThrottlingInner() {
-        synchronized (mLock) {
-            mRawLastResetTime = injectCurrentTimeMillis();
-        }
+        mRawLastResetTime.set(injectCurrentTimeMillis());
         scheduleSaveBaseState();
         Slog.i(TAG, "ShortcutManager: throttling counter reset for all users");
     }
@@ -2725,8 +2742,8 @@
             }
             getPackageShortcutsLocked(packageName, userId)
                     .resetRateLimitingForCommandLineNoSaving();
-            saveUserLocked(userId);
         }
+        saveUser(userId);
     }
 
     // We override this method in unit tests to do a simpler check.
@@ -4505,8 +4522,8 @@
                 dumpCurrentTime(pw);
                 pw.println();
             });
-            saveUserLocked(userId);
         }
+        saveUser(userId);
     }
 
     // === Dump ===
@@ -4717,9 +4734,9 @@
                 pw.print(formatTime(now));
 
                 pw.print("  Raw last reset: [");
-                pw.print(mRawLastResetTime);
+                pw.print(mRawLastResetTime.get());
                 pw.print("] ");
-                pw.print(formatTime(mRawLastResetTime));
+                pw.print(formatTime(mRawLastResetTime.get()));
 
                 final long last = getLastResetTimeLocked();
                 pw.print("  Last reset: [");
diff --git a/services/core/java/com/android/server/pm/UserJourneyLogger.java b/services/core/java/com/android/server/pm/UserJourneyLogger.java
new file mode 100644
index 0000000..f48a166
--- /dev/null
+++ b/services/core/java/com/android/server/pm/UserJourneyLogger.java
@@ -0,0 +1,552 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.os.UserManager.USER_TYPE_FULL_DEMO;
+import static android.os.UserManager.USER_TYPE_FULL_GUEST;
+import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED;
+import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
+import static android.os.UserManager.USER_TYPE_FULL_SYSTEM;
+import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
+import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
+import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
+
+import static com.android.internal.util.FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.UserInfo;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * This class is logging User Lifecycle statsd events and synchronise User Lifecycle Journeys
+ * by making sure all events are called in correct order and errors are reported in case of
+ * unexpected journeys. This class also makes sure that all user sub-journeys are logged so
+ * for example User Switch also log User Start Journey.
+ */
+public class UserJourneyLogger {
+
+    public static final int ERROR_CODE_INVALID_SESSION_ID = 0;
+    public static final int ERROR_CODE_UNSPECIFIED = -1;
+    /*
+     * Possible reasons for ERROR_CODE_INCOMPLETE_OR_TIMEOUT to occur:
+     * - A user switch journey is received while another user switch journey is in
+     *   process for the same user.
+     * - A user switch journey is received while user start journey is in process for
+     *   the same user.
+     * - A user start journey is received while another user start journey is in process
+     *   for the same user.
+     * In all cases potentially an incomplete, timed-out session or multiple
+     * simultaneous requests. It is not possible to keep track of multiple sessions for
+     * the same user, so previous session is abandoned.
+     */
+    public static final int ERROR_CODE_INCOMPLETE_OR_TIMEOUT = 2;
+    public static final int ERROR_CODE_ABORTED = 3;
+    public static final int ERROR_CODE_NULL_USER_INFO = 4;
+    public static final int ERROR_CODE_USER_ALREADY_AN_ADMIN = 5;
+    public static final int ERROR_CODE_USER_IS_NOT_AN_ADMIN = 6;
+
+    @IntDef(prefix = {"ERROR_CODE"}, value = {
+            ERROR_CODE_UNSPECIFIED,
+            ERROR_CODE_INCOMPLETE_OR_TIMEOUT,
+            ERROR_CODE_ABORTED,
+            ERROR_CODE_NULL_USER_INFO,
+            ERROR_CODE_USER_ALREADY_AN_ADMIN,
+            ERROR_CODE_USER_IS_NOT_AN_ADMIN,
+            ERROR_CODE_INVALID_SESSION_ID
+    })
+    public @interface UserJourneyErrorCode {
+    }
+
+    // The various user journeys, defined in the UserLifecycleJourneyReported atom for statsd
+    public static final int USER_JOURNEY_UNKNOWN =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__UNKNOWN;
+    public static final int USER_JOURNEY_USER_SWITCH_FG =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_FG;
+    public static final int USER_JOURNEY_USER_SWITCH_UI =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_UI;
+    public static final int USER_JOURNEY_USER_START =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_START;
+    public static final int USER_JOURNEY_USER_CREATE =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE;
+    public static final int USER_JOURNEY_USER_STOP =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_STOP;
+    public static final int USER_JOURNEY_USER_REMOVE =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE;
+    public static final int USER_JOURNEY_GRANT_ADMIN =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN;
+    public static final int USER_JOURNEY_REVOKE_ADMIN =
+            FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN;
+
+    @IntDef(prefix = {"USER_JOURNEY"}, value = {
+            USER_JOURNEY_UNKNOWN,
+            USER_JOURNEY_USER_SWITCH_FG,
+            USER_JOURNEY_USER_SWITCH_UI,
+            USER_JOURNEY_USER_START,
+            USER_JOURNEY_USER_STOP,
+            USER_JOURNEY_USER_CREATE,
+            USER_JOURNEY_USER_REMOVE,
+            USER_JOURNEY_GRANT_ADMIN,
+            USER_JOURNEY_REVOKE_ADMIN
+    })
+    public @interface UserJourney {
+    }
+
+
+    // The various user lifecycle events, defined in the UserLifecycleEventOccurred atom for statsd
+    public static final int USER_LIFECYCLE_EVENT_UNKNOWN =
+            USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN;
+    public static final int USER_LIFECYCLE_EVENT_SWITCH_USER =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__SWITCH_USER;
+    public static final int USER_LIFECYCLE_EVENT_START_USER =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__START_USER;
+    public static final int USER_LIFECYCLE_EVENT_CREATE_USER =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
+    public static final int USER_LIFECYCLE_EVENT_REMOVE_USER =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REMOVE_USER;
+    public static final int USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__USER_RUNNING_LOCKED;
+    public static final int USER_LIFECYCLE_EVENT_UNLOCKING_USER =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKING_USER;
+    public static final int USER_LIFECYCLE_EVENT_UNLOCKED_USER =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKED_USER;
+    public static final int USER_LIFECYCLE_EVENT_STOP_USER =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__STOP_USER;
+    public static final int USER_LIFECYCLE_EVENT_GRANT_ADMIN =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
+    public static final int USER_LIFECYCLE_EVENT_REVOKE_ADMIN =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
+
+    @IntDef(prefix = {"USER_LIFECYCLE_EVENT"}, value = {
+            USER_LIFECYCLE_EVENT_UNKNOWN,
+            USER_LIFECYCLE_EVENT_SWITCH_USER,
+            USER_LIFECYCLE_EVENT_START_USER,
+            USER_LIFECYCLE_EVENT_CREATE_USER,
+            USER_LIFECYCLE_EVENT_REMOVE_USER,
+            USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
+            USER_LIFECYCLE_EVENT_UNLOCKING_USER,
+            USER_LIFECYCLE_EVENT_UNLOCKED_USER,
+            USER_LIFECYCLE_EVENT_STOP_USER,
+            USER_LIFECYCLE_EVENT_GRANT_ADMIN,
+            USER_LIFECYCLE_EVENT_REVOKE_ADMIN
+    })
+    public @interface UserLifecycleEvent {
+    }
+
+    // User lifecycle event state, defined in the UserLifecycleEventOccurred atom for statsd
+    public static final int EVENT_STATE_BEGIN =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__BEGIN;
+    public static final int EVENT_STATE_FINISH =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__FINISH;
+    public static final int EVENT_STATE_NONE =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__NONE;
+    public static final int EVENT_STATE_CANCEL =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__CANCEL;
+    public static final int EVENT_STATE_ERROR =
+            FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__ERROR;
+
+    @IntDef(prefix = {"EVENT_STATE"}, value = {
+            EVENT_STATE_BEGIN,
+            EVENT_STATE_FINISH,
+            EVENT_STATE_NONE,
+            EVENT_STATE_CANCEL,
+            EVENT_STATE_ERROR,
+    })
+    public @interface UserLifecycleEventState {
+    }
+
+    private static final int USER_ID_KEY_MULTIPLICATION = 100;
+
+    private final Object mLock = new Object();
+
+    /**
+     * {@link UserIdInt} and {@link UserJourney} to {@link UserJourneySession} mapping used for
+     * statsd logging for the UserLifecycleJourneyReported and UserLifecycleEventOccurred atoms.
+     */
+    @GuardedBy("mLock")
+    private final SparseArray<UserJourneySession> mUserIdToUserJourneyMap = new SparseArray<>();
+
+    /**
+     * Returns event equivalent of given journey
+     */
+    @UserLifecycleEvent
+    private static int journeyToEvent(@UserJourney int journey) {
+        switch (journey) {
+            case USER_JOURNEY_USER_SWITCH_UI:
+            case USER_JOURNEY_USER_SWITCH_FG:
+                return USER_LIFECYCLE_EVENT_SWITCH_USER;
+            case USER_JOURNEY_USER_START:
+                return USER_LIFECYCLE_EVENT_START_USER;
+            case USER_JOURNEY_USER_CREATE:
+                return USER_LIFECYCLE_EVENT_CREATE_USER;
+            case USER_JOURNEY_USER_STOP:
+                return USER_LIFECYCLE_EVENT_STOP_USER;
+            case USER_JOURNEY_USER_REMOVE:
+                return USER_LIFECYCLE_EVENT_REMOVE_USER;
+            case USER_JOURNEY_GRANT_ADMIN:
+                return USER_LIFECYCLE_EVENT_GRANT_ADMIN;
+            case USER_JOURNEY_REVOKE_ADMIN:
+                return USER_LIFECYCLE_EVENT_REVOKE_ADMIN;
+            default:
+                return USER_LIFECYCLE_EVENT_UNKNOWN;
+        }
+    }
+
+    /**
+     * Returns the enum defined in the statsd UserLifecycleJourneyReported atom corresponding to
+     * the user type.
+     * Changes to this method require changes in CTS file
+     * com.android.cts.packagemanager.stats.device.UserInfoUtil
+     * which is duplicate for CTS tests purposes.
+     */
+    public static int getUserTypeForStatsd(@NonNull String userType) {
+        switch (userType) {
+            case USER_TYPE_FULL_SYSTEM:
+                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SYSTEM;
+            case USER_TYPE_FULL_SECONDARY:
+                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY;
+            case USER_TYPE_FULL_GUEST:
+                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_GUEST;
+            case USER_TYPE_FULL_DEMO:
+                return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_DEMO;
+            case USER_TYPE_FULL_RESTRICTED:
+                return FrameworkStatsLog
+                        .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_RESTRICTED;
+            case USER_TYPE_PROFILE_MANAGED:
+                return FrameworkStatsLog
+                        .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_MANAGED;
+            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;
+        }
+    }
+
+    /**
+     * Map error code to the event finish state.
+     */
+    @UserLifecycleEventState
+    private static int errorToFinishState(@UserJourneyErrorCode int errorCode) {
+        switch (errorCode) {
+            case ERROR_CODE_ABORTED:
+                return EVENT_STATE_CANCEL;
+            case ERROR_CODE_UNSPECIFIED:
+                return EVENT_STATE_FINISH;
+            default:
+                return EVENT_STATE_ERROR;
+        }
+    }
+
+    /**
+     * Simply logging USER_LIFECYCLE_JOURNEY_REPORTED if session exists.
+     * If session does not exist then it logs ERROR_CODE_INVALID_SESSION_ID
+     */
+    @VisibleForTesting
+    public void logUserLifecycleJourneyReported(@Nullable UserJourneySession session,
+            @UserJourney int journey, @UserIdInt int originalUserId, @UserIdInt int targetUserId,
+            int userType, int userFlags, @UserJourneyErrorCode int errorCode) {
+        if (session == null) {
+            writeUserLifecycleJourneyReported(-1, journey, originalUserId, targetUserId,
+                    userType, userFlags, ERROR_CODE_INVALID_SESSION_ID);
+        } else {
+            writeUserLifecycleJourneyReported(
+                    session.mSessionId, journey, originalUserId, targetUserId, userType, userFlags,
+                    errorCode);
+        }
+    }
+
+    /**
+     * Helper method for spy testing
+     */
+    @VisibleForTesting
+    public void writeUserLifecycleJourneyReported(long sessionId, int journey, int originalUserId,
+            int targetUserId, int userType, int userFlags, int errorCode) {
+        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED,
+                sessionId, journey, originalUserId, targetUserId, userType, userFlags,
+                errorCode);
+    }
+
+    /**
+     * Simply logging USER_LIFECYCLE_EVENT_OCCURRED if session exists.
+     * If session does not exist then it logs ERROR_CODE_INVALID_SESSION_ID
+     * and EVENT_STATE_ERROR
+     */
+    @VisibleForTesting
+    public void logUserLifecycleEventOccurred(UserJourneySession session,
+            @UserIdInt int targetUserId, @UserLifecycleEvent int event,
+            @UserLifecycleEventState int state, @UserJourneyErrorCode int errorCode) {
+        if (session == null) {
+            writeUserLifecycleEventOccurred(-1, targetUserId, event,
+                    EVENT_STATE_ERROR, ERROR_CODE_INVALID_SESSION_ID);
+        } else {
+            writeUserLifecycleEventOccurred(session.mSessionId, targetUserId, event, state,
+                    errorCode);
+        }
+    }
+
+    /**
+     * Helper method for spy testing
+     */
+    @VisibleForTesting
+    public void writeUserLifecycleEventOccurred(long sessionId, int userId, int event, int state,
+            int errorCode) {
+        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED,
+                sessionId, userId, event, state, errorCode);
+    }
+
+    /**
+     * statsd helper method for logging the given event for the UserLifecycleEventOccurred statsd
+     * atom. It finds the user journey session for target user id and logs it as that journey.
+     */
+    public void logUserLifecycleEvent(@UserIdInt int userId, @UserLifecycleEvent int event,
+            @UserLifecycleEventState int eventState) {
+        final UserJourneySession userJourneySession = findUserJourneySession(userId);
+        logUserLifecycleEventOccurred(userJourneySession, userId,
+                event, eventState, UserJourneyLogger.ERROR_CODE_UNSPECIFIED);
+    }
+
+    /**
+     * Returns first user session from mUserIdToUserJourneyMap for given user id,
+     * or null if user id was not found in mUserIdToUserJourneyMap.
+     */
+    private @Nullable UserJourneySession findUserJourneySession(@UserIdInt int userId) {
+        synchronized (mLock) {
+            final int keyMapSize = mUserIdToUserJourneyMap.size();
+            for (int i = 0; i < keyMapSize; i++) {
+                int key = mUserIdToUserJourneyMap.keyAt(i);
+                if (key / USER_ID_KEY_MULTIPLICATION == userId) {
+                    return mUserIdToUserJourneyMap.get(key);
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns unique id for user and journey. For example if user id = 11 and journey = 7
+     * then unique key = 11 * 100 + 7 = 1107
+     */
+    private int getUserJourneyKey(@UserIdInt int targetUserId, @UserJourney int journey) {
+        // We leave 99 for user journeys ids.
+        return (targetUserId * USER_ID_KEY_MULTIPLICATION) + journey;
+    }
+
+    /**
+     * Special use case when user journey incomplete or timeout and current user is unclear
+     */
+    @VisibleForTesting
+    public UserJourneySession finishAndClearIncompleteUserJourney(@UserIdInt int targetUserId,
+            @UserJourney int journey) {
+        synchronized (mLock) {
+            final int key = getUserJourneyKey(targetUserId, journey);
+            final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(key);
+            if (userJourneySession != null) {
+                logUserLifecycleEventOccurred(
+                        userJourneySession,
+                        targetUserId,
+                        journeyToEvent(userJourneySession.mJourney),
+                        EVENT_STATE_ERROR,
+                        UserJourneyLogger.ERROR_CODE_INCOMPLETE_OR_TIMEOUT);
+
+                logUserLifecycleJourneyReported(
+                        userJourneySession,
+                        journey,
+                        /* originalUserId= */ -1,
+                        targetUserId,
+                        getUserTypeForStatsd(""), -1,
+                        ERROR_CODE_INCOMPLETE_OR_TIMEOUT);
+                mUserIdToUserJourneyMap.remove(key);
+
+                return userJourneySession;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Log user journey event and report finishing without error
+     */
+    public UserJourneySession logUserJourneyFinish(@UserIdInt int originalUserId,
+            UserInfo targetUser, @UserJourney int journey) {
+        return logUserJourneyFinishWithError(originalUserId, targetUser, journey,
+                ERROR_CODE_UNSPECIFIED);
+    }
+
+    /**
+     * Special case when it is unknown which user switch  journey was used and checking both
+     */
+    @VisibleForTesting
+    public UserJourneySession logUserSwitchJourneyFinish(@UserIdInt int originalUserId,
+            UserInfo targetUser) {
+        synchronized (mLock) {
+            final int key_fg = getUserJourneyKey(targetUser.id, USER_JOURNEY_USER_SWITCH_FG);
+            final int key_ui = getUserJourneyKey(targetUser.id, USER_JOURNEY_USER_SWITCH_UI);
+
+            if (mUserIdToUserJourneyMap.contains(key_fg)) {
+                return logUserJourneyFinish(originalUserId, targetUser,
+                        USER_JOURNEY_USER_SWITCH_FG);
+            }
+
+            if (mUserIdToUserJourneyMap.contains(key_ui)) {
+                return logUserJourneyFinish(originalUserId, targetUser,
+                        USER_JOURNEY_USER_SWITCH_UI);
+            }
+
+            return null;
+        }
+    }
+
+    /**
+     * Log user journey event and report finishing with error
+     */
+    public UserJourneySession logUserJourneyFinishWithError(@UserIdInt int originalUserId,
+            UserInfo targetUser, @UserJourney int journey, @UserJourneyErrorCode int errorCode) {
+        synchronized (mLock) {
+            final int state = errorToFinishState(errorCode);
+            final int key = getUserJourneyKey(targetUser.id, journey);
+            final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(key);
+            if (userJourneySession != null) {
+                logUserLifecycleEventOccurred(
+                        userJourneySession, targetUser.id,
+                        journeyToEvent(userJourneySession.mJourney),
+                        state,
+                        errorCode);
+
+                logUserLifecycleJourneyReported(
+                        userJourneySession,
+                        journey, originalUserId, targetUser.id,
+                        getUserTypeForStatsd(targetUser.userType),
+                        targetUser.flags,
+                        errorCode);
+                mUserIdToUserJourneyMap.remove(key);
+
+                return userJourneySession;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Log event and report finish when user is null. This is edge case when UserInfo
+     * can not be passed because it is null, therefore all information are passed as arguments.
+     */
+    public UserJourneySession logNullUserJourneyError(@UserJourney int journey,
+            @UserIdInt int currentUserId, @UserIdInt int targetUserId, String targetUserType,
+            int targetUserFlags) {
+        synchronized (mLock) {
+            final int key = getUserJourneyKey(targetUserId, journey);
+            final UserJourneySession session = mUserIdToUserJourneyMap.get(key);
+
+            logUserLifecycleEventOccurred(
+                    session, targetUserId, journeyToEvent(journey),
+                    EVENT_STATE_ERROR,
+                    ERROR_CODE_NULL_USER_INFO);
+
+            logUserLifecycleJourneyReported(
+                    session, journey, currentUserId, targetUserId,
+                    getUserTypeForStatsd(targetUserType), targetUserFlags,
+                    ERROR_CODE_NULL_USER_INFO);
+
+            mUserIdToUserJourneyMap.remove(key);
+            return session;
+        }
+    }
+
+    /**
+     * Log for user creation finish event and report. This is edge case when target user id is
+     * different in begin event and finish event as it is unknown what is user id
+     * until it has been created.
+     */
+    public UserJourneySession logUserCreateJourneyFinish(@UserIdInt int originalUserId,
+            UserInfo targetUser) {
+        synchronized (mLock) {
+            // we do not know user id until we create new user which is why we use -1
+            // as user id to create and find session, but we log correct id.
+            final int key = getUserJourneyKey(-1, USER_JOURNEY_USER_CREATE);
+            final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(key);
+            if (userJourneySession != null) {
+                logUserLifecycleEventOccurred(
+                        userJourneySession, targetUser.id,
+                        USER_LIFECYCLE_EVENT_CREATE_USER,
+                        EVENT_STATE_FINISH,
+                        ERROR_CODE_UNSPECIFIED);
+
+                logUserLifecycleJourneyReported(
+                        userJourneySession,
+                        USER_JOURNEY_USER_CREATE, originalUserId, targetUser.id,
+                        getUserTypeForStatsd(targetUser.userType),
+                        targetUser.flags,
+                        ERROR_CODE_UNSPECIFIED);
+                mUserIdToUserJourneyMap.remove(key);
+
+                return userJourneySession;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Adds new UserJourneySession to mUserIdToUserJourneyMap and log UserJourneyEvent Begin state
+     */
+    public UserJourneySession logUserJourneyBegin(@UserIdInt int targetId,
+            @UserJourney int journey) {
+        final long newSessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
+        synchronized (mLock) {
+            final int key = getUserJourneyKey(targetId, journey);
+            final UserJourneySession userJourneySession =
+                    new UserJourneySession(newSessionId, journey);
+            mUserIdToUserJourneyMap.append(key, userJourneySession);
+
+            logUserLifecycleEventOccurred(
+                    userJourneySession, targetId,
+                    journeyToEvent(userJourneySession.mJourney),
+                    EVENT_STATE_BEGIN,
+                    ERROR_CODE_UNSPECIFIED);
+
+            return userJourneySession;
+        }
+    }
+
+    /**
+     * Helper class to store user journey and session id.
+     *
+     * <p> User journey tracks a chain of user lifecycle events occurring during different user
+     * activities such as user start, user switch, and user creation.
+     */
+    public static class UserJourneySession {
+        public final long mSessionId;
+        @UserJourney
+        public final int mJourney;
+
+        @VisibleForTesting
+        public UserJourneySession(long sessionId, @UserJourney int journey) {
+            mJourney = journey;
+            mSessionId = sessionId;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5f8efe2..b92cdde 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -23,6 +23,15 @@
 import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
 import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
 
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_UNSPECIFIED;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_ALREADY_AN_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_IS_NOT_AN_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_GRANT_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_REVOKE_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_CREATE;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_REMOVE;
+
 import android.Manifest;
 import android.accounts.Account;
 import android.accounts.AccountManager;
@@ -162,7 +171,6 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
@@ -519,6 +527,8 @@
     @GuardedBy("mUserLifecycleListeners")
     private final ArrayList<UserLifecycleListener> mUserLifecycleListeners = new ArrayList<>();
 
+    private final UserJourneyLogger mUserJourneyLogger = new UserJourneyLogger();
+
     private final LockPatternUtils mLockPatternUtils;
 
     private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK =
@@ -1580,45 +1590,56 @@
     @Override
     public void setUserAdmin(@UserIdInt int userId) {
         checkManageUserAndAcrossUsersFullPermission("set user admin");
-        final long sessionId = logGrantAdminJourneyBegin(userId);
+        mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_GRANT_ADMIN);
         UserInfo info;
         synchronized (mPackagesLock) {
             synchronized (mUsersLock) {
                 info = getUserInfoLU(userId);
             }
-            if (info == null || info.isAdmin()) {
-                // Exit if no user found with that id, or the user is already an Admin.
-                logUserJourneyError(sessionId,
-                        FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN,
-                        userId);
+            if (info == null) {
+                // Exit if no user found with that id,
+                mUserJourneyLogger.logNullUserJourneyError(USER_JOURNEY_GRANT_ADMIN,
+                        getCurrentUserId(), userId, /* userType */ "", /* userFlags */ -1);
+                return;
+            } else if (info.isAdmin()) {
+                // Exit if the user is already an Admin.
+                mUserJourneyLogger.logUserJourneyFinishWithError(getCurrentUserId(), info,
+                        USER_JOURNEY_GRANT_ADMIN, ERROR_CODE_USER_ALREADY_AN_ADMIN);
                 return;
             }
             info.flags ^= UserInfo.FLAG_ADMIN;
             writeUserLP(getUserDataLU(info.id));
         }
-        logGrantAdminJourneyFinish(sessionId, userId, info.userType, info.flags);
+        mUserJourneyLogger.logUserJourneyFinishWithError(getCurrentUserId(), info,
+                USER_JOURNEY_GRANT_ADMIN, ERROR_CODE_UNSPECIFIED);
     }
 
     @Override
     public void revokeUserAdmin(@UserIdInt int userId) {
         checkManageUserAndAcrossUsersFullPermission("revoke admin privileges");
-        final long sessionId = logRevokeAdminJourneyBegin(userId);
+        mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_REVOKE_ADMIN);
         UserData user;
         synchronized (mPackagesLock) {
             synchronized (mUsersLock) {
                 user = getUserDataLU(userId);
-                if (user == null || !user.info.isAdmin()) {
-                    // Exit if no user found with that id, or the user is not an Admin.
-                    logUserJourneyError(sessionId, FrameworkStatsLog
-                                    .USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN,
-                            userId);
+                if (user == null) {
+                    // Exit if no user found with that id
+                    mUserJourneyLogger.logNullUserJourneyError(
+                            USER_JOURNEY_REVOKE_ADMIN,
+                            getCurrentUserId(), userId, "", -1);
+                    return;
+                } else if (!user.info.isAdmin()) {
+                    // Exit if no user is not an Admin.
+                    mUserJourneyLogger.logUserJourneyFinishWithError(getCurrentUserId(), user.info,
+                            USER_JOURNEY_REVOKE_ADMIN, ERROR_CODE_USER_IS_NOT_AN_ADMIN);
                     return;
                 }
                 user.info.flags ^= UserInfo.FLAG_ADMIN;
                 writeUserLP(user);
             }
         }
-        logRevokeAdminJourneyFinish(sessionId, userId, user.info.userType, user.info.flags);
+        mUserJourneyLogger.logUserJourneyFinishWithError(getCurrentUserId(), user.info,
+                USER_JOURNEY_REVOKE_ADMIN, ERROR_CODE_UNSPECIFIED);
     }
 
     /**
@@ -4700,16 +4721,20 @@
         final int noneUserId = -1;
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
         t.traceBegin("createUser-" + flags);
-        final long sessionId = logUserCreateJourneyBegin(noneUserId);
+        mUserJourneyLogger.logUserJourneyBegin(noneUserId, USER_JOURNEY_USER_CREATE);
         UserInfo newUser = null;
         try {
             newUser = createUserInternalUncheckedNoTracing(name, userType, flags, parentId,
                         preCreate, disallowedPackages, t, token);
             return newUser;
         } finally {
-            logUserCreateJourneyFinish(sessionId,
-                    newUser != null ? newUser.id : noneUserId, userType, flags,
-                    newUser != null);
+            if (newUser != null) {
+                mUserJourneyLogger.logUserCreateJourneyFinish(getCurrentUserId(), newUser);
+            } else {
+                mUserJourneyLogger.logNullUserJourneyError(
+                        USER_JOURNEY_USER_CREATE,
+                        getCurrentUserId(), noneUserId, userType, flags);
+            }
             t.traceEnd();
         }
     }
@@ -5198,137 +5223,6 @@
                 && !userTypeDetails.getName().equals(UserManager.USER_TYPE_FULL_RESTRICTED);
     }
 
-    private long logUserCreateJourneyBegin(@UserIdInt int userId) {
-        return logUserJourneyBegin(
-                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE,
-                userId);
-    }
-
-    private void logUserCreateJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
-            @UserInfoFlag int flags, boolean finish) {
-        logUserJourneyFinish(sessionId,
-                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE,
-                userId, userType, flags, finish);
-    }
-
-    private long logUserRemoveJourneyBegin(@UserIdInt int userId) {
-        return logUserJourneyBegin(
-                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE,
-                userId);
-    }
-
-    private void logUserRemoveJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
-            @UserInfoFlag int flags, boolean finish) {
-        logUserJourneyFinish(sessionId,
-                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE,
-                userId, userType, flags, finish);
-    }
-
-    private long logGrantAdminJourneyBegin(@UserIdInt int userId) {
-        return logUserJourneyBegin(
-                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN,
-                userId);
-    }
-
-    private void logGrantAdminJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
-            @UserInfoFlag int flags) {
-        logUserJourneyFinish(sessionId,
-                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN,
-                userId, userType, flags, true);
-    }
-
-    private long logRevokeAdminJourneyBegin(@UserIdInt int userId) {
-        return logUserJourneyBegin(
-                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN,
-                userId);
-    }
-
-    private void logRevokeAdminJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
-            @UserInfoFlag int flags) {
-        logUserJourneyFinish(sessionId,
-                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN,
-                userId, userType, flags, true);
-    }
-
-    private void logUserJourneyFinish(long sessionId, int journey, @UserIdInt int userId,
-            String userType, @UserInfoFlag int flags, boolean finish) {
-
-        // log the journey atom with the user metadata
-        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, sessionId,
-                journey, /* origin_user= */ getCurrentUserId(), userId,
-                UserManager.getUserTypeForStatsd(userType), flags);
-
-        int event;
-        switch (journey) {
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
-                break;
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REMOVE_USER;
-                break;
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
-                break;
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
-                break;
-            default:
-                throw new IllegalArgumentException("Journey " + journey + " not expected.");
-        }
-        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
-                event,
-                finish ? FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__FINISH
-                        : FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__NONE);
-    }
-
-    private long logUserJourneyBegin(int journey, @UserIdInt int userId) {
-        final long sessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
-
-        // log the event atom to indicate the event start
-        int event;
-        switch (journey) {
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
-                break;
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REMOVE_USER;
-                break;
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
-                break;
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
-                break;
-            default:
-                throw new IllegalArgumentException("Journey " + journey + " not expected.");
-        }
-
-        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
-                event, FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__BEGIN);
-        return sessionId;
-    }
-
-    private void logUserJourneyError(long sessionId, int journey, @UserIdInt int userId) {
-
-        // log the journey atom with the user metadata
-        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, sessionId,
-                journey, /* origin_user= */ getCurrentUserId(), userId);
-
-        int event;
-        switch (journey) {
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
-                break;
-            case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN:
-                event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
-                break;
-            default:
-                throw new IllegalArgumentException("Journey " + journey + " not expected.");
-        }
-        FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
-                event, FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__ERROR);
-    }
-
     /** Register callbacks for statsd pulled atoms. */
     private void registerStatsCallbacks() {
         final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
@@ -5352,7 +5246,8 @@
             if (size > 1) {
                 for (int idx = 0; idx < size; idx++) {
                     final UserInfo user = users.get(idx);
-                    final int userTypeStandard = UserManager.getUserTypeForStatsd(user.userType);
+                    final int userTypeStandard = mUserJourneyLogger
+                            .getUserTypeForStatsd(user.userType);
                     final String userTypeCustom = (userTypeStandard == FrameworkStatsLog
                             .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN)
                             ?
@@ -5635,7 +5530,7 @@
                 writeUserLP(userData);
             }
 
-            final long sessionId = logUserRemoveJourneyBegin(userId);
+            mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_USER_REMOVE);
 
             try {
                 mAppOpsService.removeUser(userId);
@@ -5657,13 +5552,17 @@
                             @Override
                             public void userStopped(int userIdParam) {
                                 finishRemoveUser(userIdParam);
-                                logUserRemoveJourneyFinish(sessionId, userIdParam,
-                                        userData.info.userType, userData.info.flags, true);
+                                int originUserId = UserManagerService.this.getCurrentUserId();
+                                mUserJourneyLogger.logUserJourneyFinishWithError(originUserId,
+                                        userData.info, USER_JOURNEY_USER_REMOVE,
+                                        ERROR_CODE_UNSPECIFIED);
                             }
                             @Override
                             public void userStopAborted(int userIdParam) {
-                                logUserRemoveJourneyFinish(sessionId, userIdParam,
-                                        userData.info.userType, userData.info.flags, false);
+                                int originUserId = UserManagerService.this.getCurrentUserId();
+                                mUserJourneyLogger.logUserJourneyFinishWithError(originUserId,
+                                        userData.info, USER_JOURNEY_USER_REMOVE,
+                                        ERROR_CODE_ABORTED);
                             }
                         });
             } catch (RemoteException e) {
@@ -7297,9 +7196,9 @@
                 final UserInfo userInfo = getUserInfo(userIds[i]);
                 if (userInfo == null) {
                     // Not possible because the input user ids should all be valid
-                    userTypes[i] = UserManager.getUserTypeForStatsd("");
+                    userTypes[i] = mUserJourneyLogger.getUserTypeForStatsd("");
                 } else {
-                    userTypes[i] = UserManager.getUserTypeForStatsd(userInfo.userType);
+                    userTypes[i] = mUserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
                 }
             }
             return userTypes;
@@ -7536,4 +7435,11 @@
                 .getBoolean(R.bool.config_canSwitchToHeadlessSystemUser);
     }
 
+    /**
+     * Returns instance of {@link com.android.server.pm.UserJourneyLogger}.
+     */
+    public UserJourneyLogger getUserJourneyLogger() {
+        return mUserJourneyLogger;
+    }
+
 }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 9ff98be..f8954b7 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -5690,8 +5690,14 @@
             }
 
             if (eventTime > now) {
-                Slog.e(TAG, "Event time " + eventTime + " cannot be newer than " + now);
-                throw new IllegalArgumentException("event time must not be in the future");
+                Slog.wtf(TAG, "Event cannot be newer than the current time ("
+                        + "now=" + now
+                        + ", eventTime=" + eventTime
+                        + ", displayId=" + displayId
+                        + ", event=" + PowerManager.userActivityEventToString(event)
+                        + ", flags=" + flags
+                        + ")");
+                return;
             }
 
             final int uid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index f971db9..e796275 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -161,7 +161,13 @@
                     mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
                     if ((flags & FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0) {
                         mWaitingForTrustableDowngrade = true;
-                        setSecurityWindowTimer();
+                        resultCallback.thenAccept(result -> {
+                            if (result.getStatus() == GrantTrustResult.STATUS_UNLOCKED_BY_GRANT) {
+                                // if we are not unlocked by grantTrust, then we don't need to
+                                // have the timer for the security window
+                                setSecurityWindowTimer();
+                            }
+                        });
                     } else {
                         mWaitingForTrustableDowngrade = false;
                     }
@@ -562,6 +568,7 @@
      * @see android.service.trust.TrustAgentService#onDeviceLocked()
      */
     public void onDeviceLocked() {
+        mWithinSecurityLockdownWindow = false;
         try {
             if (mTrustAgentService != null) mTrustAgentService.onDeviceLocked();
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 8786005..1ab9823 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -393,6 +393,23 @@
                 true /* overrideHardTimeout */);
     }
 
+    private void cancelBothTrustableAlarms(int userId) {
+        TrustableTimeoutAlarmListener idleTimeout =
+                mIdleTrustableTimeoutAlarmListenerForUser.get(
+                        userId);
+        TrustableTimeoutAlarmListener trustableTimeout =
+                mTrustableTimeoutAlarmListenerForUser.get(
+                        userId);
+        if (idleTimeout != null && idleTimeout.isQueued()) {
+            idleTimeout.setQueued(false);
+            mAlarmManager.cancel(idleTimeout);
+        }
+        if (trustableTimeout != null && trustableTimeout.isQueued()) {
+            trustableTimeout.setQueued(false);
+            mAlarmManager.cancel(trustableTimeout);
+        }
+    }
+
     private void handleScheduleTrustedTimeout(int userId, boolean shouldOverride) {
         long when = SystemClock.elapsedRealtime() + TRUST_TIMEOUT_IN_MILLIS;
         TrustedTimeoutAlarmListener alarm = mTrustTimeoutAlarmListenerForUser.get(userId);
@@ -657,6 +674,11 @@
                 resultCallback.complete(new GrantTrustResult(STATUS_UNLOCKED_BY_GRANT));
             }
         }
+
+        if ((wasTrusted || wasTrustable) && pendingTrustState == TrustState.UNTRUSTED) {
+            if (DEBUG) Slog.d(TAG, "Trust was revoked, destroy trustable alarms");
+            cancelBothTrustableAlarms(userId);
+        }
     }
 
     private void updateTrustUsuallyManaged(int userId, boolean managed) {
@@ -1908,7 +1930,11 @@
                     handleScheduleTrustTimeout(shouldOverride, timeoutType);
                     break;
                 case MSG_REFRESH_TRUSTABLE_TIMERS_AFTER_AUTH:
-                    refreshTrustableTimers(msg.arg1);
+                    TrustableTimeoutAlarmListener trustableAlarm =
+                            mTrustableTimeoutAlarmListenerForUser.get(msg.arg1);
+                    if (trustableAlarm != null && trustableAlarm.isQueued()) {
+                        refreshTrustableTimers(msg.arg1);
+                    }
                     break;
             }
         }
@@ -2160,7 +2186,7 @@
             TrustedTimeoutAlarmListener otherAlarm;
             boolean otherAlarmPresent;
             if (ENABLE_ACTIVE_UNLOCK_FLAG) {
-                cancelBothTrustableAlarms();
+                cancelBothTrustableAlarms(mUserId);
                 otherAlarm = mTrustTimeoutAlarmListenerForUser.get(mUserId);
                 otherAlarmPresent = (otherAlarm != null) && otherAlarm.isQueued();
                 if (otherAlarmPresent) {
@@ -2172,23 +2198,6 @@
             }
         }
 
-        private void cancelBothTrustableAlarms() {
-            TrustableTimeoutAlarmListener idleTimeout =
-                    mIdleTrustableTimeoutAlarmListenerForUser.get(
-                            mUserId);
-            TrustableTimeoutAlarmListener trustableTimeout =
-                    mTrustableTimeoutAlarmListenerForUser.get(
-                            mUserId);
-            if (idleTimeout != null && idleTimeout.isQueued()) {
-                idleTimeout.setQueued(false);
-                mAlarmManager.cancel(idleTimeout);
-            }
-            if (trustableTimeout != null && trustableTimeout.isQueued()) {
-                trustableTimeout.setQueued(false);
-                mAlarmManager.cancel(trustableTimeout);
-            }
-        }
-
         private void disableRenewableTrustWhileNonrenewableTrustIsPresent() {
             // if non-renewable trust is running, we need to temporarily prevent
             // renewable trust from being used
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 98d2d3d..45c7c9a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1353,9 +1353,6 @@
 
         void complete() {
             // Only changes from home+lock to just home or lock need attention
-            // If setting the wallpaper fails, this callback will be called
-            // when the wallpaper is detached, in which case wallpapers may have
-            // already changed. Make sure we're not overwriting a more recent wallpaper.
             if (mNewWallpaper.mSystemWasBoth) {
                 if (DEBUG) {
                     Slog.v(TAG, "Handling change from system+lock wallpaper");
@@ -1378,7 +1375,8 @@
                                     mOriginalSystem.wallpaperComponent;
                             lockWp.connection = mOriginalSystem.connection;
                             lockWp.connection.mWallpaper = lockWp;
-                            updateEngineFlags(mOriginalSystem, FLAG_LOCK);
+                            mOriginalSystem.mWhich = FLAG_LOCK;
+                            updateEngineFlags(mOriginalSystem);
                             notifyWallpaperColorsChanged(lockWp, FLAG_LOCK);
                         } else {
                             // Failed rename, use current system wp for both
@@ -1387,7 +1385,7 @@
                             }
                             WallpaperData currentSystem = mWallpaperMap.get(mNewWallpaper.userId);
                             currentSystem.mWhich = FLAG_SYSTEM | FLAG_LOCK;
-                            updateEngineFlags(currentSystem, FLAG_SYSTEM | FLAG_LOCK);
+                            updateEngineFlags(currentSystem);
                             mLockWallpaperMap.remove(mNewWallpaper.userId);
                         }
                     } else {
@@ -1396,7 +1394,7 @@
                             Slog.v(TAG, "live system+lock to system success");
                         }
                         mOriginalSystem.mWhich = FLAG_LOCK;
-                        updateEngineFlags(mOriginalSystem, FLAG_LOCK);
+                        updateEngineFlags(mOriginalSystem);
                         mLockWallpaperMap.put(mNewWallpaper.userId, mOriginalSystem);
                         mLastLockWallpaper = mOriginalSystem;
                         notifyWallpaperColorsChanged(mOriginalSystem, FLAG_LOCK);
@@ -1409,7 +1407,7 @@
                     WallpaperData currentSystem = mWallpaperMap.get(mNewWallpaper.userId);
                     if (currentSystem.wallpaperId == mOriginalSystem.wallpaperId) {
                         currentSystem.mWhich = FLAG_SYSTEM;
-                        updateEngineFlags(currentSystem, FLAG_SYSTEM);
+                        updateEngineFlags(currentSystem);
                     }
                 }
             }
@@ -1422,24 +1420,6 @@
                 Slog.v(TAG, "new lastLockWp: " + mLastLockWallpaper);
             }
         }
-
-        private void updateEngineFlags(WallpaperData wallpaper, @SetWallpaperFlags int which) {
-            if (wallpaper.connection == null) {
-                return;
-            }
-            wallpaper.connection.forEachDisplayConnector(
-                    connector -> {
-                        try {
-                            if (connector.mEngine != null) {
-                                connector.mEngine.setWallpaperFlags(which);
-                                mWindowManagerInternal.setWallpaperShowWhenLocked(
-                                        connector.mToken, (which & FLAG_LOCK) != 0);
-                            }
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "Failed to update wallpaper engine flags", e);
-                        }
-                    });
-        }
     }
 
     class MyPackageMonitor extends PackageMonitor {
@@ -2465,12 +2445,36 @@
 
     /**
      * TODO(multi-display) Extends this method with specific display.
-     * Propagate ambient state to wallpaper engine.
+     * Propagate ambient state to wallpaper engine(s).
      *
      * @param inAmbientMode {@code true} when in ambient mode, {@code false} otherwise.
      * @param animationDuration Duration of the animation, or 0 when immediate.
      */
     public void setInAmbientMode(boolean inAmbientMode, long animationDuration) {
+        if (mIsLockscreenLiveWallpaperEnabled) {
+            List<IWallpaperEngine> engines = new ArrayList<>();
+            synchronized (mLock) {
+                mInAmbientMode = inAmbientMode;
+                for (WallpaperData data : getActiveWallpapers()) {
+                    if (data.connection.mInfo == null
+                            || data.connection.mInfo.supportsAmbientMode()) {
+                        // TODO(multi-display) Extends this method with specific display.
+                        IWallpaperEngine engine = data.connection
+                                .getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine;
+                        if (engine != null) engines.add(engine);
+                    }
+                }
+            }
+            for (IWallpaperEngine engine : engines) {
+                try {
+                    engine.setInAmbientMode(inAmbientMode, animationDuration);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to set ambient mode", e);
+                }
+            }
+            return;
+        }
+
         final IWallpaperEngine engine;
         synchronized (mLock) {
             mInAmbientMode = inAmbientMode;
@@ -2495,10 +2499,25 @@
     }
 
     /**
-     * Propagate a wake event to the wallpaper engine.
+     * Propagate a wake event to the wallpaper engine(s).
      */
     public void notifyWakingUp(int x, int y, @NonNull Bundle extras) {
         synchronized (mLock) {
+            if (mIsLockscreenLiveWallpaperEnabled) {
+                for (WallpaperData data : getActiveWallpapers()) {
+                    data.connection.forEachDisplayConnector(displayConnector -> {
+                        if (displayConnector.mEngine != null) {
+                            try {
+                                displayConnector.mEngine.dispatchWallpaperCommand(
+                                        WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras);
+                            } catch (RemoteException e) {
+                                Slog.w(TAG, "Failed to dispatch COMMAND_WAKING_UP", e);
+                            }
+                        }
+                    });
+                }
+                return;
+            }
             final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
             if (data != null && data.connection != null) {
                 data.connection.forEachDisplayConnector(
@@ -2517,10 +2536,26 @@
     }
 
     /**
-     * Propagate a sleep event to the wallpaper engine.
+     * Propagate a sleep event to the wallpaper engine(s).
      */
     public void notifyGoingToSleep(int x, int y, @NonNull Bundle extras) {
         synchronized (mLock) {
+            if (mIsLockscreenLiveWallpaperEnabled) {
+                for (WallpaperData data : getActiveWallpapers()) {
+                    data.connection.forEachDisplayConnector(displayConnector -> {
+                        if (displayConnector.mEngine != null) {
+                            try {
+                                displayConnector.mEngine.dispatchWallpaperCommand(
+                                        WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1,
+                                        extras);
+                            } catch (RemoteException e) {
+                                Slog.w(TAG, "Failed to dispatch COMMAND_GOING_TO_SLEEP", e);
+                            }
+                        }
+                    });
+                }
+                return;
+            }
             final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
             if (data != null && data.connection != null) {
                 data.connection.forEachDisplayConnector(
@@ -2540,11 +2575,27 @@
     }
 
     /**
-     * Propagates screen turned on event to wallpaper engine.
+     * Propagates screen turned on event to wallpaper engine(s).
      */
     @Override
     public void notifyScreenTurnedOn(int displayId) {
         synchronized (mLock) {
+            if (mIsLockscreenLiveWallpaperEnabled) {
+                for (WallpaperData data : getActiveWallpapers()) {
+                    if (data.connection.containsDisplay(displayId)) {
+                        final IWallpaperEngine engine = data.connection
+                                .getDisplayConnectorOrCreate(displayId).mEngine;
+                        if (engine != null) {
+                            try {
+                                engine.onScreenTurnedOn();
+                            } catch (RemoteException e) {
+                                Slog.w(TAG, "Failed to notify that the screen turned on", e);
+                            }
+                        }
+                    }
+                }
+                return;
+            }
             final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
             if (data != null
                     && data.connection != null
@@ -2565,11 +2616,27 @@
 
 
     /**
-     * Propagate screen turning on event to wallpaper engine.
+     * Propagate screen turning on event to wallpaper engine(s).
      */
     @Override
     public void notifyScreenTurningOn(int displayId) {
         synchronized (mLock) {
+            if (mIsLockscreenLiveWallpaperEnabled) {
+                for (WallpaperData data : getActiveWallpapers()) {
+                    if (data.connection.containsDisplay(displayId)) {
+                        final IWallpaperEngine engine = data.connection
+                                .getDisplayConnectorOrCreate(displayId).mEngine;
+                        if (engine != null) {
+                            try {
+                                engine.onScreenTurningOn();
+                            } catch (RemoteException e) {
+                                Slog.w(TAG, "Failed to notify that the screen is turning on", e);
+                            }
+                        }
+                    }
+                }
+                return;
+            }
             final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
             if (data != null
                     && data.connection != null
@@ -2596,6 +2663,17 @@
         return true;
     }
 
+    private WallpaperData[] getActiveWallpapers() {
+        WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId);
+        WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId);
+        boolean systemValid = systemWallpaper != null && systemWallpaper.connection != null;
+        boolean lockValid = lockWallpaper != null && lockWallpaper.connection != null;
+        return systemValid && lockValid ? new WallpaperData[]{systemWallpaper, lockWallpaper}
+                : systemValid ? new WallpaperData[]{systemWallpaper}
+                : lockValid ? new WallpaperData[]{lockWallpaper}
+                : new WallpaperData[0];
+    }
+
     private IWallpaperEngine getEngine(int which, int userId, int displayId) {
         WallpaperData wallpaperData = findWallpaperAtDisplay(userId, displayId);
         if (wallpaperData == null) return null;
@@ -3095,6 +3173,9 @@
                                 newWallpaper.userId);
                         if (lockedWallpaper != null) {
                             detachWallpaperLocked(lockedWallpaper);
+                            if (same) {
+                                updateEngineFlags(newWallpaper);
+                            }
                         }
                         mLockWallpaperMap.remove(newWallpaper.userId);
                     }
@@ -3430,6 +3511,27 @@
         }
     }
 
+    // Updates the given wallpaper's Engine so that its destination flags are the same as those of
+    // the wallpaper, e.g., after a wallpaper has been changed from displaying on home+lock to home
+    // or lock only.
+    private void updateEngineFlags(WallpaperData wallpaper) {
+        if (wallpaper.connection == null) {
+            return;
+        }
+        wallpaper.connection.forEachDisplayConnector(
+                connector -> {
+                    try {
+                        if (connector.mEngine != null) {
+                            connector.mEngine.setWallpaperFlags(wallpaper.mWhich);
+                            mWindowManagerInternal.setWallpaperShowWhenLocked(
+                                    connector.mToken, (wallpaper.mWhich & FLAG_LOCK) != 0);
+                        }
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Failed to update wallpaper engine flags", e);
+                    }
+                });
+    }
+
     private void clearWallpaperComponentLocked(WallpaperData wallpaper) {
         wallpaper.wallpaperComponent = null;
         detachWallpaperLocked(wallpaper);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 78c066b..26b40b4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -582,6 +582,9 @@
 
     boolean mPauseSchedulePendingForPip = false;
 
+    // Gets set to indicate that the activity is currently being auto-pipped.
+    boolean mAutoEnteringPip = false;
+
     private void updateEnterpriseThumbnailDrawable(Context context) {
         DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
         mEnterpriseThumbnailDrawable = dpm.getResources().getDrawable(
@@ -4908,9 +4911,14 @@
             mTransitionController.setStatusBarTransitionDelay(
                     mPendingRemoteAnimation.getStatusBarTransitionDelay());
         } else {
-            if (mPendingOptions == null
-                    || mPendingOptions.getAnimationType() == ANIM_SCENE_TRANSITION) {
-                // Scene transition will run on the client side.
+            if (mPendingOptions == null) {
+                return;
+            } else if (mPendingOptions.getAnimationType() == ANIM_SCENE_TRANSITION) {
+                // Scene transition will run on the client side, so just notify transition
+                // controller but don't clear the animation information from the options since they
+                // need to be sent to the animating activity.
+                mTransitionController.setOverrideAnimation(
+                        AnimationOptions.makeSceneTransitionAnimOptions(), null, null);
                 return;
             }
             applyOptionsAnimation(mPendingOptions, intent);
@@ -5220,6 +5228,11 @@
         }
         logAppCompatState();
         if (!visible) {
+            final InputTarget imeInputTarget = mDisplayContent.getImeInputTarget();
+            mLastImeShown = imeInputTarget != null && imeInputTarget.getWindowState() != null
+                    && imeInputTarget.getWindowState().mActivityRecord == this
+                    && mDisplayContent.mInputMethodWindow != null
+                    && mDisplayContent.mInputMethodWindow.isVisible();
             finishOrAbortReplacingWindow();
         }
         return true;
@@ -5400,7 +5413,9 @@
             return;
         }
         if (inFinishingTransition) {
-            // Let the finishing transition commit the visibility.
+            // Let the finishing transition commit the visibility, but let the controller know
+            // about it so that we can recover from degenerate cases.
+            mTransitionController.mValidateCommitVis.add(this);
             return;
         }
         // If we are preparing an app transition, then delay changing
@@ -5607,11 +5622,6 @@
         }
 
         if (!visible) {
-            final InputTarget imeInputTarget = mDisplayContent.getImeInputTarget();
-            mLastImeShown = imeInputTarget != null && imeInputTarget.getWindowState() != null
-                    && imeInputTarget.getWindowState().mActivityRecord == this
-                    && mDisplayContent.mInputMethodWindow != null
-                    && mDisplayContent.mInputMethodWindow.isVisible();
             mImeInsetsFrozenUntilStartInput = true;
         }
 
@@ -6092,8 +6102,7 @@
             try {
                 mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
                         PauseActivityItem.obtain(finishing, false /* userLeaving */,
-                                configChangeFlags, false /* dontReport */,
-                                false /* autoEnteringPip */));
+                                configChangeFlags, false /* dontReport */, mAutoEnteringPip));
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
             }
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index bfe2986..a6e5040 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -45,6 +45,7 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
@@ -554,7 +555,25 @@
                 .execute();
     }
 
+    /**
+     * A quick path (skip general intent/task resolving) to start recents animation if the recents
+     * (or home) activity is available in background.
+     * @return {@code true} if the recents activity is moved to front.
+     */
     boolean startExistingRecentsIfPossible(Intent intent, ActivityOptions options) {
+        try {
+            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startExistingRecents");
+            if (startExistingRecents(intent, options)) {
+                return true;
+            }
+            // Else follow the standard launch procedure.
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+        return false;
+    }
+
+    private boolean startExistingRecents(Intent intent, ActivityOptions options) {
         final int activityType = mService.getRecentTasks().getRecentsComponent()
                 .equals(intent.getComponent()) ? ACTIVITY_TYPE_RECENTS : ACTIVITY_TYPE_HOME;
         final Task rootTask = mService.mRootWindowContainer.getDefaultTaskDisplayArea()
@@ -563,6 +582,7 @@
         final ActivityRecord r = rootTask.topRunningActivity();
         if (r == null || r.isVisibleRequested() || !r.attachedToProcess()
                 || !r.mActivityComponent.equals(intent.getComponent())
+                || !mService.isCallerRecents(r.getUid())
                 // Recents keeps invisible while device is locked.
                 || r.mDisplayContent.isKeyguardLocked()) {
             return false;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index a27f3e4..19a12f2 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1468,9 +1468,8 @@
         // transition based on a sub-action.
         // Only do the create here (and defer requestStart) since startActivityInner might abort.
         final TransitionController transitionController = r.mTransitionController;
-        Transition newTransition = (!transitionController.isCollecting()
-                && transitionController.getTransitionPlayer() != null)
-                ? transitionController.createTransition(TRANSIT_OPEN) : null;
+        Transition newTransition = transitionController.isShellTransitionsEnabled()
+                ? transitionController.createAndStartCollecting(TRANSIT_OPEN) : null;
         RemoteTransition remoteTransition = r.takeRemoteTransition();
         try {
             mService.deferWindowLayout();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 1f4606b..b816dad 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3592,15 +3592,21 @@
         }
     }
 
+    boolean enterPictureInPictureMode(@NonNull ActivityRecord r,
+            @NonNull PictureInPictureParams params, boolean fromClient) {
+        return enterPictureInPictureMode(r, params, fromClient, false /* isAutoEnter */);
+    }
+
     /**
      * Puts the given activity in picture in picture mode if possible.
      *
      * @param fromClient true if this comes from a client call (eg. Activity.enterPip).
+     * @param isAutoEnter true if this comes from an automatic pip-enter.
      * @return true if the activity is now in picture-in-picture mode, or false if it could not
      * enter picture-in-picture mode.
      */
     boolean enterPictureInPictureMode(@NonNull ActivityRecord r,
-            @NonNull PictureInPictureParams params, boolean fromClient) {
+            @NonNull PictureInPictureParams params, boolean fromClient, boolean isAutoEnter) {
         // If the activity is already in picture in picture mode, then just return early
         if (r.inPinnedWindowingMode()) {
             return true;
@@ -3635,6 +3641,7 @@
                     return;
                 }
                 r.setPictureInPictureParams(params);
+                r.mAutoEnteringPip = isAutoEnter;
                 mRootWindowContainer.moveActivityToPinnedRootTask(r,
                         null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
                         transition);
@@ -3643,6 +3650,7 @@
                     r.getTask().schedulePauseActivity(r, false /* userLeaving */,
                             false /* pauseImmediately */, true /* autoEnteringPip */, "auto-pip");
                 }
+                r.mAutoEnteringPip = false;
             }
         };
 
@@ -5753,23 +5761,6 @@
                 boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
                 BackgroundStartPrivileges backgroundStartPrivileges) {
             assertPackageMatchesCallingUid(callingPackage);
-            // A quick path (skip general intent/task resolving) to start recents animation if the
-            // recents (or home) activity is available in background.
-            if (options != null && options.getOriginalOptions() != null
-                    && options.getOriginalOptions().getTransientLaunch() && isCallerRecents(uid)) {
-                try {
-                    synchronized (mGlobalLock) {
-                        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "startExistingRecents");
-                        if (mActivityStartController.startExistingRecentsIfPossible(
-                                intent, options.getOriginalOptions())) {
-                            return ActivityManager.START_TASK_TO_FRONT;
-                        }
-                        // Else follow the standard launch procedure.
-                    }
-                } finally {
-                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-                }
-            }
             return getActivityStartController().startActivityInPackage(uid, realCallingPid,
                     realCallingUid, callingPackage, callingFeatureId, intent, resolvedType,
                     resultTo, resultWho, requestCode, startFlags, options, userId, inTask,
@@ -6345,6 +6336,8 @@
         public void cleanupDisabledPackageComponents(
                 String packageName, Set<String> disabledClasses, int userId, boolean booted) {
             synchronized (mGlobalLock) {
+                // In case if setWindowManager hasn't been called yet when booting.
+                if (mRootWindowContainer == null) return;
                 // Clean-up disabled activities.
                 if (mRootWindowContainer.finishDisabledPackageActivities(
                         packageName, disabledClasses, true /* doit */, false /* evenPersistent */,
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 7a11120..7e78393 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -267,7 +267,12 @@
             op.mDrawTransaction = null;
             if (DEBUG) Slog.d(TAG, "finishOp merge transaction " + windowToken.getTopChild());
         }
-        if (op.mAction == Operation.ACTION_FADE) {
+        if (op.mAction == Operation.ACTION_TOGGLE_IME) {
+            if (DEBUG) Slog.d(TAG, "finishOp fade-in IME " + windowToken.getTopChild());
+            fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM,
+                    (type, anim) -> mDisplayContent.getInsetsStateController()
+                            .getImeSourceProvider().reportImeDrawnForOrganizer());
+        } else if (op.mAction == Operation.ACTION_FADE) {
             if (DEBUG) Slog.d(TAG, "finishOp fade-in " + windowToken.getTopChild());
             // The previous animation leash will be dropped when preparing fade-in animation, so
             // simply apply new animation without restoring the transformation.
@@ -344,7 +349,7 @@
         for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
             final WindowToken windowToken = mTargetWindowTokens.keyAt(i);
             final Operation op = mTargetWindowTokens.valueAt(i);
-            if (op.mAction == Operation.ACTION_FADE) {
+            if (op.mAction == Operation.ACTION_FADE || op.mAction == Operation.ACTION_TOGGLE_IME) {
                 fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM);
                 op.mLeash = windowToken.getAnimationLeash();
                 if (DEBUG) Slog.d(TAG, "Start fade-out " + windowToken.getTopChild());
@@ -374,17 +379,19 @@
                 WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION);
     }
 
-    /** Hides the window immediately until it is drawn in new rotation. */
-    void hideImmediately(WindowToken windowToken) {
-        if (isTargetToken(windowToken)) return;
+    /** Hides the IME window immediately until it is drawn in new rotation. */
+    void hideImeImmediately() {
+        if (mDisplayContent.mInputMethodWindow == null) return;
+        final WindowToken imeWindowToken = mDisplayContent.mInputMethodWindow.mToken;
+        if (isTargetToken(imeWindowToken)) return;
         final boolean original = mHideImmediately;
         mHideImmediately = true;
-        final Operation op = new Operation(Operation.ACTION_FADE);
-        mTargetWindowTokens.put(windowToken, op);
-        fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM);
-        op.mLeash = windowToken.getAnimationLeash();
+        final Operation op = new Operation(Operation.ACTION_TOGGLE_IME);
+        mTargetWindowTokens.put(imeWindowToken, op);
+        fadeWindowToken(false /* show */, imeWindowToken, ANIMATION_TYPE_TOKEN_TRANSFORM);
+        op.mLeash = imeWindowToken.getAnimationLeash();
         mHideImmediately = original;
-        if (DEBUG) Slog.d(TAG, "hideImmediately " + windowToken.getTopChild());
+        if (DEBUG) Slog.d(TAG, "hideImeImmediately " + imeWindowToken.getTopChild());
     }
 
     /** Returns {@code true} if the window will rotate independently. */
@@ -586,11 +593,13 @@
     /** The operation to control the rotation appearance associated with window token. */
     private static class Operation {
         @Retention(RetentionPolicy.SOURCE)
-        @IntDef(value = { ACTION_SEAMLESS, ACTION_FADE })
+        @IntDef(value = { ACTION_SEAMLESS, ACTION_FADE, ACTION_TOGGLE_IME })
         @interface Action {}
 
         static final int ACTION_SEAMLESS = 1;
         static final int ACTION_FADE = 2;
+        /** The action to toggle the IME window appearance */
+        static final int ACTION_TOGGLE_IME = 3;
         final @Action int mAction;
         /** The leash of window token. It can be animation leash or the token itself. */
         SurfaceControl mLeash;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8bca106..57812c1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1896,7 +1896,12 @@
             case SOFT_INPUT_STATE_HIDDEN:
                 return false;
         }
-        return r.mLastImeShown;
+        final boolean useIme = r.getWindow(
+                w -> WindowManager.LayoutParams.mayUseInputMethod(w.mAttrs.flags)) != null;
+        if (!useIme) {
+            return false;
+        }
+        return r.mLastImeShown || (r.mStartingData != null && r.mStartingData.hasImeSurface());
     }
 
     /** Returns {@code true} if the top activity is transformed with the new rotation of display. */
@@ -4219,7 +4224,7 @@
             // Hide the window until the rotation is done to avoid intermediate artifacts if the
             // parent surface of IME container is changed.
             if (mAsyncRotationController != null) {
-                mAsyncRotationController.hideImmediately(mInputMethodWindow.mToken);
+                mAsyncRotationController.hideImeImmediately();
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index ce43628..be52e5a 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1095,11 +1095,9 @@
                 } else {
                     overrideProviders = null;
                 }
-                final @InsetsType int type = provider.getType();
-                final int id = InsetsSource.createId(
-                        provider.getOwner(), provider.getIndex(), type);
-                mDisplayContent.getInsetsStateController().getOrCreateSourceProvider(id, type)
-                        .setWindowContainer(win, frameProvider, overrideProviders);
+                mDisplayContent.getInsetsStateController().getOrCreateSourceProvider(
+                        provider.getId(), provider.getType()).setWindowContainer(
+                                win, frameProvider, overrideProviders);
                 mInsetsSourceWindowsExceptIme.add(win);
             }
         }
diff --git a/services/core/java/com/android/server/wm/FadeAnimationController.java b/services/core/java/com/android/server/wm/FadeAnimationController.java
index 561a070..7af67e6 100644
--- a/services/core/java/com/android/server/wm/FadeAnimationController.java
+++ b/services/core/java/com/android/server/wm/FadeAnimationController.java
@@ -57,14 +57,21 @@
         return AnimationUtils.loadAnimation(mContext, R.anim.fade_out);
     }
 
+    /** Run the fade in/out animation for a window token. */
+    public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) {
+        fadeWindowToken(show, windowToken, animationType, null);
+    }
+
     /**
      * Run the fade in/out animation for a window token.
      *
      * @param show true for fade-in, otherwise for fade-out.
      * @param windowToken the window token to run the animation.
      * @param animationType the animation type defined in SurfaceAnimator.
+     * @param finishedCallback the callback after the animation finished.
      */
-    public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) {
+    public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType,
+            SurfaceAnimator.OnAnimationFinishedCallback finishedCallback) {
         if (windowToken == null || windowToken.getParent() == null) {
             return;
         }
@@ -75,9 +82,8 @@
         if (animationAdapter == null) {
             return;
         }
-
         windowToken.startAnimation(windowToken.getPendingTransaction(), animationAdapter,
-                show /* hidden */, animationType, null /* finishedCallback */);
+                show /* hidden */, animationType, finishedCallback);
     }
 
     protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec,
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index b4dffdc..ff2985c 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -145,18 +145,44 @@
         }
         boolean changed = super.updateClientVisibility(caller);
         if (changed && caller.isRequestedVisible(mSource.getType())) {
-            reportImeDrawnForOrganizer(caller);
+            reportImeDrawnForOrganizerIfNeeded(caller);
         }
         changed |= mDisplayContent.onImeInsetsClientVisibilityUpdate();
         return changed;
     }
 
-    private void reportImeDrawnForOrganizer(InsetsControlTarget caller) {
-        if (caller.getWindow() != null && caller.getWindow().getTask() != null) {
-            if (caller.getWindow().getTask().isOrganized()) {
-                mWindowContainer.mWmService.mAtmService.mTaskOrganizerController
-                        .reportImeDrawnOnTask(caller.getWindow().getTask());
-            }
+    private void reportImeDrawnForOrganizerIfNeeded(@NonNull InsetsControlTarget caller) {
+        final WindowState callerWindow = caller.getWindow();
+        if (callerWindow == null) {
+            return;
+        }
+        WindowToken imeToken = mWindowContainer.asWindowState() != null
+                ? mWindowContainer.asWindowState().mToken : null;
+        if (mDisplayContent.getAsyncRotationController() != null
+                && mDisplayContent.getAsyncRotationController().isTargetToken(imeToken)) {
+            // Skip reporting IME drawn state when the control target is in fixed
+            // rotation, AsyncRotationController will report after the animation finished.
+            return;
+        }
+        reportImeDrawnForOrganizer(caller);
+    }
+
+    private void reportImeDrawnForOrganizer(@NonNull InsetsControlTarget caller) {
+        final WindowState callerWindow = caller.getWindow();
+        if (callerWindow == null || callerWindow.getTask() == null) {
+            return;
+        }
+        if (callerWindow.getTask().isOrganized()) {
+            mWindowContainer.mWmService.mAtmService.mTaskOrganizerController
+                    .reportImeDrawnOnTask(caller.getWindow().getTask());
+        }
+    }
+
+    /** Report the IME has drawn on the current IME control target for its task organizer */
+    void reportImeDrawnForOrganizer() {
+        final InsetsControlTarget imeControlTarget = getControlTarget();
+        if (imeControlTarget != null) {
+            reportImeDrawnForOrganizer(imeControlTarget);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index fe13b87..ddf96c5 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -311,16 +311,13 @@
             state.removeSource(ID_IME);
         } else if (attrs.providedInsets != null) {
             for (InsetsFrameProvider provider : attrs.providedInsets) {
-                final int id = InsetsSource.createId(
-                        provider.getOwner(), provider.getIndex(), provider.getType());
-                final @InsetsType int type = provider.getType();
-                if ((type & WindowInsets.Type.systemBars()) == 0) {
+                if ((provider.getType() & WindowInsets.Type.systemBars()) == 0) {
                     continue;
                 }
                 if (state == originalState) {
                     state = new InsetsState(state);
                 }
-                state.removeSource(id);
+                state.removeSource(provider.getId());
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 08a6358..5f6d660 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -269,6 +269,8 @@
                     TRANSIT_TO_BACK, transitFlags, null /* trigger */, dc);
             updateKeyguardSleepToken();
 
+            // Make the home wallpaper visible
+            dc.mWallpaperController.showHomeWallpaperInTransition();
             // Some stack visibility might change (e.g. docked stack)
             mRootWindowContainer.resumeFocusedTasksTopActivities();
             mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
index 2edb082..7852249 100644
--- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
+++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
@@ -20,41 +20,73 @@
 
 import static com.android.internal.R.bool.config_unfoldTransitionEnabled;
 import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
+import static com.android.server.wm.DeviceStateController.DeviceState.FOLDED;
+import static com.android.server.wm.DeviceStateController.DeviceState.HALF_FOLDED;
+import static com.android.server.wm.DeviceStateController.DeviceState.OPEN;
 
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.graphics.Rect;
 import android.window.DisplayAreaInfo;
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.DeviceStateController.DeviceState;
+
 public class PhysicalDisplaySwitchTransitionLauncher {
 
     private final DisplayContent mDisplayContent;
-    private final WindowManagerService mService;
+    private final ActivityTaskManagerService mAtmService;
+    private final Context mContext;
     private final TransitionController mTransitionController;
 
     /**
-     * If on a foldable device represents whether the device is folded or not
+     * If on a foldable device represents whether we need to show unfold animation when receiving
+     * a physical display switch event
      */
-    private boolean mIsFolded;
+    private boolean mShouldRequestTransitionOnDisplaySwitch = false;
+    /**
+     * Current device state from {@link android.hardware.devicestate.DeviceStateManager}
+     */
+    private DeviceState mDeviceState = DeviceState.UNKNOWN;
     private Transition mTransition;
 
     public PhysicalDisplaySwitchTransitionLauncher(DisplayContent displayContent,
             TransitionController transitionController) {
+        this(displayContent, displayContent.mWmService.mAtmService,
+                displayContent.mWmService.mContext, transitionController);
+    }
+
+    @VisibleForTesting
+    public PhysicalDisplaySwitchTransitionLauncher(DisplayContent displayContent,
+            ActivityTaskManagerService service, Context context,
+            TransitionController transitionController) {
         mDisplayContent = displayContent;
-        mService = displayContent.mWmService;
+        mAtmService = service;
+        mContext = context;
         mTransitionController = transitionController;
     }
 
     /**
      *   Called by the DeviceStateManager callback when the state changes.
      */
-    void foldStateChanged(DeviceStateController.DeviceState newDeviceState) {
-        // Ignore transitions to/from half-folded.
-        if (newDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) return;
-        mIsFolded = newDeviceState == DeviceStateController.DeviceState.FOLDED;
+    void foldStateChanged(DeviceState newDeviceState) {
+        boolean isUnfolding = mDeviceState == FOLDED
+                && (newDeviceState == HALF_FOLDED || newDeviceState == OPEN);
+
+        if (isUnfolding) {
+            // Request transition only if we are unfolding the device
+            mShouldRequestTransitionOnDisplaySwitch = true;
+        } else if (newDeviceState != HALF_FOLDED && newDeviceState != OPEN) {
+            // Cancel the transition request in case if we are folding or switching to back
+            // to the rear display before the displays got switched
+            mShouldRequestTransitionOnDisplaySwitch = false;
+        }
+
+        mDeviceState = newDeviceState;
     }
 
     /**
@@ -62,12 +94,12 @@
      */
     public void requestDisplaySwitchTransitionIfNeeded(int displayId, int oldDisplayWidth,
             int oldDisplayHeight, int newDisplayWidth, int newDisplayHeight) {
+        if (!mShouldRequestTransitionOnDisplaySwitch) return;
         if (!mTransitionController.isShellTransitionsEnabled()) return;
         if (!mDisplayContent.getLastHasContent()) return;
 
-        boolean shouldRequestUnfoldTransition = !mIsFolded
-                && mService.mContext.getResources().getBoolean(config_unfoldTransitionEnabled)
-                && ValueAnimator.areAnimatorsEnabled();
+        boolean shouldRequestUnfoldTransition = mContext.getResources()
+                .getBoolean(config_unfoldTransitionEnabled) && ValueAnimator.areAnimatorsEnabled();
 
         if (!shouldRequestUnfoldTransition) {
             return;
@@ -91,6 +123,8 @@
             mDisplayContent.mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
             mTransition = t;
         }
+
+        mShouldRequestTransitionOnDisplaySwitch = false;
     }
 
     /**
@@ -118,7 +152,7 @@
         if (mTransition == null) return;
 
         if (transaction != null) {
-            mService.mAtmService.mWindowOrganizerController.applyTransaction(transaction);
+            mAtmService.mWindowOrganizerController.applyTransaction(transaction);
         }
 
         markTransitionAsReady();
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index f5079d3..e47787e 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -33,6 +33,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.os.Process.SYSTEM_UID;
+import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
 
@@ -209,7 +210,8 @@
     private final PointerEventListener mListener = new PointerEventListener() {
         @Override
         public void onPointerEvent(MotionEvent ev) {
-            if (!mFreezeTaskListReordering || ev.getAction() != MotionEvent.ACTION_DOWN) {
+            if (!mFreezeTaskListReordering || ev.getAction() != MotionEvent.ACTION_DOWN
+                    || ev.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE) {
                 // Skip if we aren't freezing or starting a gesture
                 return;
             }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3f4296a..d3edeae 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3239,7 +3239,7 @@
             if (task.getActivity(activity -> !activity.finishing && activity.mUserId == userId)
                     != null) {
                 mService.getTaskChangeNotificationController().notifyTaskProfileLocked(
-                        task.getTaskInfo());
+                        task.getTaskInfo(), userId);
             }
         }, true /* traverseTopToBottom */);
     }
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index b5df3e0..bbb8563 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -193,6 +193,7 @@
                                 .setSourceCrop(new Rect(0, 0, width, height))
                                 .setAllowProtected(true)
                                 .setCaptureSecureLayers(true)
+                                .setHintForSeamlessTransition(true)
                                 .build();
                 screenshotBuffer = ScreenCapture.captureDisplay(captureArgs);
             } else {
@@ -202,6 +203,7 @@
                                 .setCaptureSecureLayers(true)
                                 .setAllowProtected(true)
                                 .setSourceCrop(new Rect(0, 0, width, height))
+                                .setHintForSeamlessTransition(true)
                                 .build();
                 screenshotBuffer = ScreenCapture.captureLayers(captureArgs);
             }
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 49d064f..9324e29 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -144,7 +144,7 @@
     };
 
     private final TaskStackConsumer mNotifyTaskProfileLocked = (l, m) -> {
-        l.onTaskProfileLocked((RunningTaskInfo) m.obj);
+        l.onTaskProfileLocked((RunningTaskInfo) m.obj, m.arg1);
     };
 
     private final TaskStackConsumer mNotifyTaskSnapshotChanged = (l, m) -> {
@@ -467,9 +467,9 @@
      * Notify listeners that the task has been put in a locked state because one or more of the
      * activities inside it belong to a managed profile user that has been locked.
      */
-    void notifyTaskProfileLocked(ActivityManager.RunningTaskInfo taskInfo) {
+    void notifyTaskProfileLocked(RunningTaskInfo taskInfo, int userId) {
         final Message msg = mHandler.obtainMessage(NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG,
-                taskInfo);
+                userId, 0, taskInfo);
         forAllLocalListeners(mNotifyTaskProfileLocked, msg);
         msg.sendToTarget();
     }
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 5626aa7..cdb4ad6 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -18,6 +18,9 @@
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.TaskInfo.cameraCompatControlStateToString;
+import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NONE;
+import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NORMAL;
+import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
 import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
@@ -686,8 +689,19 @@
         final boolean playShiftUpAnimation = !task.inMultiWindowMode();
         final ActivityRecord topActivity = task.topActivityContainsStartingWindow();
         if (topActivity != null) {
-            removalInfo.deferRemoveForIme = topActivity.mDisplayContent
-                    .mayImeShowOnLaunchingActivity(topActivity);
+            // Set defer remove mode for IME
+            final DisplayContent dc = topActivity.getDisplayContent();
+            final WindowState imeWindow = dc.mInputMethodWindow;
+            if (topActivity.isVisibleRequested() && imeWindow != null
+                    && dc.mayImeShowOnLaunchingActivity(topActivity)
+                    && dc.isFixedRotationLaunchingApp(topActivity)) {
+                removalInfo.deferRemoveForImeMode = DEFER_MODE_ROTATION;
+            } else if (dc.mayImeShowOnLaunchingActivity(topActivity)) {
+                removalInfo.deferRemoveForImeMode = DEFER_MODE_NORMAL;
+            } else {
+                removalInfo.deferRemoveForImeMode = DEFER_MODE_NONE;
+            }
+
             final WindowState mainWindow =
                     topActivity.findMainWindow(false/* includeStartingApp */);
             // No app window for this activity, app might be crashed.
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index d531ad1..50bf38b 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -55,6 +55,7 @@
 import static android.window.TransitionInfo.FLAG_TASK_LAUNCHING_BEHIND;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT;
 
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
@@ -65,12 +66,14 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.app.IApplicationThread;
 import android.content.pm.ActivityInfo;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
@@ -85,6 +88,7 @@
 import android.view.WindowManager;
 import android.window.ScreenCapture;
 import android.window.TransitionInfo;
+import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
@@ -185,7 +189,7 @@
     final ArraySet<WindowContainer> mParticipants = new ArraySet<>();
 
     /** The final animation targets derived from participants after promotion. */
-    private ArrayList<ChangeInfo> mTargets;
+    ArrayList<ChangeInfo> mTargets;
 
     /** The displays that this transition is running on. */
     private final ArrayList<DisplayContent> mTargetDisplays = new ArrayList<>();
@@ -271,9 +275,14 @@
     /** Any 2 transitions of this type can run in parallel with each other. Used for testing. */
     static final int PARALLEL_TYPE_MUTUAL = 1;
 
+    /** This is a recents transition. */
+    static final int PARALLEL_TYPE_RECENTS = 2;
+
+
     @IntDef(prefix = { "PARALLEL_TYPE_" }, value = {
             PARALLEL_TYPE_NONE,
-            PARALLEL_TYPE_MUTUAL
+            PARALLEL_TYPE_MUTUAL,
+            PARALLEL_TYPE_RECENTS
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface ParallelType {}
@@ -328,6 +337,21 @@
         mFlags |= flag;
     }
 
+    void calcParallelCollectType(WindowContainerTransaction wct) {
+        for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
+            final WindowContainerTransaction.HierarchyOp hop = wct.getHierarchyOps().get(i);
+            if (hop.getType() != HIERARCHY_OP_TYPE_PENDING_INTENT) continue;
+            final Bundle b = hop.getLaunchOptions();
+            if (b == null || b.isEmpty()) continue;
+            final boolean transientLaunch = b.getBoolean(ActivityOptions.KEY_TRANSIENT_LAUNCH);
+            if (transientLaunch) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "Starting a Recents transition which can be parallel.");
+                mParallelCollectType = PARALLEL_TYPE_RECENTS;
+            }
+        }
+    }
+
     /** Records an activity as transient-launch. This activity must be already collected. */
     void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelow) {
         if (mTransientLaunches == null) {
@@ -380,6 +404,10 @@
         return false;
     }
 
+    boolean hasTransientLaunch() {
+        return mTransientLaunches != null && !mTransientLaunches.isEmpty();
+    }
+
     boolean isTransientLaunch(@NonNull ActivityRecord activity) {
         return mTransientLaunches != null && mTransientLaunches.containsKey(activity);
     }
@@ -579,9 +607,10 @@
         recordDisplay(wc.getDisplayContent());
         if (info.mShowWallpaper) {
             // Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set.
-            final WindowState wallpaper =
-                    wc.getDisplayContent().mWallpaperController.getTopVisibleWallpaper();
-            if (wallpaper != null) {
+            final List<WindowState> wallpapers =
+                    wc.getDisplayContent().mWallpaperController.getAllTopWallpapers();
+            for (int i = wallpapers.size() - 1; i >= 0; i--) {
+                WindowState wallpaper = wallpapers.get(i);
                 collect(wallpaper.mToken);
             }
         }
@@ -926,8 +955,15 @@
                 // to the transient activity.
                 ar.supportsEnterPipOnTaskSwitch = true;
             }
+            // Make sure this activity can enter pip under the current circumstances.
+            // `enterPictureInPicture` internally checks, but with beforeStopping=false which
+            // is specifically for non-auto-enter.
+            if (!ar.checkEnterPictureInPictureState("enterPictureInPictureMode",
+                    true /* beforeStopping */)) {
+                return false;
+            }
             return mController.mAtm.enterPictureInPictureMode(ar, ar.pictureInPictureArgs,
-                    false /* fromClient */);
+                    false /* fromClient */, true /* isAutoEnter */);
         }
 
         // Legacy pip-entry (not via isAutoEnterEnabled).
@@ -984,13 +1020,20 @@
             // Record all the now-hiding activities so that they are committed. Just use
             // mParticipants because we can avoid a new list this way.
             for (int i = 0; i < mTransientHideTasks.size(); ++i) {
-                // Only worry about tasks that were actually hidden. Otherwise, we could end-up
-                // committing visibility for activity-level changes that aren't part of this
-                // transition.
-                if (mTransientHideTasks.get(i).isVisibleRequested()) continue;
-                mTransientHideTasks.get(i).forAllActivities(r -> {
+                final Task rootTask = mTransientHideTasks.get(i);
+                rootTask.forAllActivities(r -> {
                     // Only check leaf-tasks that were collected
                     if (!mParticipants.contains(r.getTask())) return;
+                    if (rootTask.isVisibleRequested()) {
+                        // This transient-hide didn't hide, so don't commit anything (otherwise we
+                        // could prematurely commit invisible on unrelated activities). To be safe,
+                        // though, notify the controller to prevent degenerate cases.
+                        if (!r.isVisibleRequested()) {
+                            mController.mValidateCommitVis.add(r);
+                        }
+                        return;
+                    }
+                    // This did hide: commit immediately so that other transitions know about it.
                     mParticipants.add(r);
                 });
             }
@@ -1282,6 +1325,7 @@
         // ActivityRecord#canShowWindows() may reject to show its window. The visibility also
         // needs to be updated for STATE_ABORT.
         commitVisibleActivities(transaction);
+        commitVisibleWallpapers();
 
         // Fall-back to the default display if there isn't one participating.
         final DisplayContent primaryDisplay = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0)
@@ -1314,6 +1358,7 @@
         }
         // Resolve the animating targets from the participants.
         mTargets = calculateTargets(mParticipants, mChanges);
+
         // Check whether the participants were animated from back navigation.
         mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets);
         final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
@@ -1590,6 +1635,30 @@
         }
     }
 
+    /**
+     * Reset waitingToshow for all wallpapers, and commit the visibility of the visible ones
+     */
+    private void commitVisibleWallpapers() {
+        boolean showWallpaper = shouldWallpaperBeVisible();
+        for (int i = mParticipants.size() - 1; i >= 0; --i) {
+            final WallpaperWindowToken wallpaper = mParticipants.valueAt(i).asWallpaperToken();
+            if (wallpaper != null) {
+                wallpaper.waitingToShow = false;
+                if (!wallpaper.isVisible() && wallpaper.isVisibleRequested()) {
+                    wallpaper.commitVisibility(showWallpaper);
+                }
+            }
+        }
+    }
+
+    private boolean shouldWallpaperBeVisible() {
+        for (int i = mParticipants.size() - 1; i >= 0; --i) {
+            WindowContainer participant = mParticipants.valueAt(i);
+            if (participant.showWallpaper()) return true;
+        }
+        return false;
+    }
+
     // TODO(b/188595497): Remove after migrating to shell.
     /** @see RecentsAnimationController#attachNavigationBarToApp */
     private void handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info) {
@@ -2198,7 +2267,7 @@
                         // Display won't be rotated for multi window Task, so the fixed rotation
                         // won't be applied. This can happen when the windowing mode is changed
                         // before the previous fixed rotation is applied.
-                        && !task.inMultiWindowMode()) {
+                        && (!task.inMultiWindowMode() || !topRunningActivity.inMultiWindowMode())) {
                     // If Activity is in fixed rotation, its will be applied with the next rotation,
                     // when the Task is still in the previous rotation.
                     final int taskRotation = task.getWindowConfiguration().getDisplayRotation();
@@ -2964,11 +3033,14 @@
 
             Rect cropBounds = new Rect(bounds);
             cropBounds.offsetTo(0, 0);
+            final boolean isDisplayRotation = wc.asDisplayContent() != null
+                    && wc.asDisplayContent().isRotationChanging();
             ScreenCapture.LayerCaptureArgs captureArgs =
                     new ScreenCapture.LayerCaptureArgs.Builder(wc.getSurfaceControl())
                             .setSourceCrop(cropBounds)
                             .setCaptureSecureLayers(true)
                             .setAllowProtected(true)
+                            .setHintForSeamlessTransition(isDisplayRotation)
                             .build();
             ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
                     ScreenCapture.captureLayers(captureArgs);
@@ -2979,8 +3051,6 @@
                 Slog.w(TAG, "Failed to capture screenshot for " + wc);
                 return false;
             }
-            final boolean isDisplayRotation = wc.asDisplayContent() != null
-                    && wc.asDisplayContent().isRotationChanging();
             // Some tests may check the name "RotationLayer" to detect display rotation.
             final String name = isDisplayRotation ? "RotationLayer" : "transition snapshot: " + wc;
             SurfaceControl snapshotSurface = wc.makeAnimationLeash()
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 7950eda..c9316bf 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -133,6 +133,13 @@
     final ArrayList<Runnable> mStateValidators = new ArrayList<>();
 
     /**
+     * List of activity-records whose visibility changed outside the main/tracked part of a
+     * transition (eg. in the finish-transaction). These will be checked when idle to recover from
+     * degenerate states.
+     */
+    final ArrayList<ActivityRecord> mValidateCommitVis = new ArrayList<>();
+
+    /**
      * Currently playing transitions (in the order they were started). When finished, records are
      * removed from this list.
      */
@@ -806,6 +813,10 @@
         }
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
         mPlayingTransitions.remove(record);
+        if (!inTransition()) {
+            // reset track-count now since shell-side is idle.
+            mTrackCount = 0;
+        }
         updateRunningRemoteAnimation(record, false /* isPlaying */);
         record.finishTransition();
         for (int i = mAnimatingExitWindows.size() - 1; i >= 0; i--) {
@@ -818,10 +829,9 @@
             }
         }
         mRunningLock.doNotifyLocked();
-        // Run state-validation checks when no transitions are active anymore.
+        // Run state-validation checks when no transitions are active anymore (Note: sometimes
+        // finish can start a transition, so check afterwards -- eg. pip).
         if (!inTransition()) {
-            // Can reset track-count now that everything is idle.
-            mTrackCount = 0;
             validateStates();
             mAtm.mWindowManager.onAnimationFinished();
         }
@@ -848,6 +858,15 @@
             }
         }
         mStateValidators.clear();
+        for (int i = 0; i < mValidateCommitVis.size(); ++i) {
+            final ActivityRecord ar = mValidateCommitVis.get(i);
+            if (!ar.isVisibleRequested() && ar.isVisible()) {
+                Slog.e(TAG, "Uncommitted visibility change: " + ar);
+                ar.commitVisibility(ar.isVisibleRequested(), false /* layout */,
+                        false /* fromTransition */);
+            }
+        }
+        mValidateCommitVis.clear();
     }
 
     /**
@@ -858,7 +877,7 @@
         tryStartCollectFromQueue();
     }
 
-    private boolean canStartCollectingNow(Transition queued) {
+    private boolean canStartCollectingNow(@Nullable Transition queued) {
         if (mCollectingTransition == null) return true;
         // Population (collect until ready) is still serialized, so always wait for that.
         if (!mCollectingTransition.isPopulated()) return false;
@@ -879,6 +898,8 @@
             // If it's a legacy sync, then it needs to wait until there is no collecting transition.
             if (queued.mTransition == null) return;
             if (!canStartCollectingNow(queued.mTransition)) return;
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Moving #%d from collecting"
+                    + " to waiting.", mCollectingTransition.getSyncId());
             mWaitingTransitions.add(mCollectingTransition);
             mCollectingTransition = null;
         } else if (mSyncEngine.hasActiveSync()) {
@@ -929,11 +950,38 @@
      * `collecting` transition. It may still ultimately block in sync-engine or become dependent
      * in {@link #getIsIndependent} later.
      */
-    boolean getCanBeIndependent(Transition collecting, Transition queued) {
-        if (queued.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL
+    boolean getCanBeIndependent(Transition collecting, @Nullable Transition queued) {
+        // For tests
+        if (queued != null && queued.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL
                 && collecting.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL) {
             return true;
         }
+        // For recents
+        if (queued != null && queued.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS) {
+            if (collecting.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS) {
+                // Must serialize with itself.
+                return false;
+            }
+            // allow this if `collecting` only has activities
+            for (int i = 0; i < collecting.mParticipants.size(); ++i) {
+                final WindowContainer wc = collecting.mParticipants.valueAt(i);
+                final ActivityRecord ar = wc.asActivityRecord();
+                if (ar == null && wc.asWindowState() == null && wc.asWindowToken() == null) {
+                    // Is task or above, so can't be independent
+                    return false;
+                }
+                if (ar != null && ar.isActivityTypeHomeOrRecents()) {
+                    // It's a recents or home type, so it conflicts.
+                    return false;
+                }
+            }
+            return true;
+        } else if (collecting.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS) {
+            // We can collect simultaneously with recents if it is populated. This is because
+            // we know that recents will not collect/trampoline any more stuff. If anything in the
+            // queued transition overlaps, it will end up just waiting in sync-queue anyways.
+            return true;
+        }
         return false;
     }
 
@@ -942,11 +990,47 @@
      * `running` is playing based on its current state.
      */
     static boolean getIsIndependent(Transition running, Transition incoming) {
+        // For tests
         if (running.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL
                 && incoming.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL) {
             return true;
         }
-        return false;
+        // For now there's only one mutually-independent pair: an all activity-level transition and
+        // a transient-launch where none of the activities are part of the transient-launch task,
+        // so the following logic is hard-coded specifically for this.
+        // Also, we currently restrict valid transient-launches to just recents.
+        final Transition recents;
+        final Transition other;
+        if (running.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS
+                && running.hasTransientLaunch()) {
+            if (incoming.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS) {
+                // Recents can't be independent from itself.
+                return false;
+            }
+            recents = running;
+            other = incoming;
+        } else if (incoming.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS
+                && incoming.hasTransientLaunch()) {
+            recents = incoming;
+            other = running;
+        } else {
+            return false;
+        }
+        // Check against *targets* because that is the post-promotion set of containers that are
+        // actually animating.
+        for (int i = 0; i < other.mTargets.size(); ++i) {
+            final WindowContainer wc = other.mTargets.get(i).mContainer;
+            final ActivityRecord ar = wc.asActivityRecord();
+            if (ar == null && wc.asWindowState() == null && wc.asWindowToken() == null) {
+                // Is task or above, so for now don't let them be independent.
+                return false;
+            }
+            if (ar != null && recents.isTransientLaunch(ar)) {
+                // Change overlaps with recents, so serialize.
+                return false;
+            }
+        }
+        return true;
     }
 
     void assignTrack(Transition transition, TransitionInfo info) {
@@ -970,9 +1054,15 @@
         if (track < 0) {
             // Didn't overlap with anything, so give it its own track
             track = mTrackCount;
+            if (track > 0) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Playing #%d in parallel on "
+                        + "track #%d", transition.getSyncId(), track);
+            }
         }
         if (sync) {
             info.setFlags(info.getFlags() | TransitionInfo.FLAG_SYNC);
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Marking #%d animation as SYNC.",
+                    transition.getSyncId());
         }
         transition.mAnimationTrack = track;
         info.setTrack(track);
@@ -1145,6 +1235,8 @@
                 // Check if we can run in parallel here.
                 if (canStartCollectingNow(transit)) {
                     // start running in parallel.
+                    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Moving #%d from"
+                            + " collecting to waiting.", mCollectingTransition.getSyncId());
                     mWaitingTransitions.add(mCollectingTransition);
                     mCollectingTransition = null;
                     moveToCollecting(transit);
@@ -1162,6 +1254,44 @@
         return true;
     }
 
+    /**
+     * This will create and start collecting for a transition if possible. If there's no way to
+     * start collecting for `parallelType` now, then this returns null.
+     *
+     * WARNING: ONLY use this if the transition absolutely cannot be deferred!
+     */
+    @NonNull
+    Transition createAndStartCollecting(int type) {
+        if (mTransitionPlayer == null) {
+            return null;
+        }
+        if (!mQueuedTransitions.isEmpty()) {
+            // There is a queue, so it's not possible to start immediately
+            return null;
+        }
+        if (mSyncEngine.hasActiveSync()) {
+            if (isCollecting()) {
+                // Check if we can run in parallel here.
+                if (canStartCollectingNow(null /* transit */)) {
+                    // create and collect in parallel.
+                    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Moving #%d from"
+                            + " collecting to waiting.", mCollectingTransition.getSyncId());
+                    mWaitingTransitions.add(mCollectingTransition);
+                    mCollectingTransition = null;
+                    Transition transit = new Transition(type, 0 /* flags */, this, mSyncEngine);
+                    moveToCollecting(transit);
+                    return transit;
+                }
+            } else {
+                Slog.w(TAG, "Ongoing Sync outside of transition.");
+            }
+            return null;
+        }
+        Transition transit = new Transition(type, 0 /* flags */, this, mSyncEngine);
+        moveToCollecting(transit);
+        return transit;
+    }
+
     /** Returns {@code true} if it started collecting, {@code false} if it was queued. */
     boolean startLegacySyncOrQueue(BLASTSyncEngine.SyncGroup syncGroup, Runnable applySync) {
         if (!mQueuedTransitions.isEmpty() || mSyncEngine.hasActiveSync()) {
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 7ceac4f..edafe06 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -124,24 +124,19 @@
 
     final boolean mIsLockscreenLiveWallpaperEnabled;
 
-    private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> {
-        if ((w.mAttrs.type == TYPE_WALLPAPER)) {
-            if (mFindResults.topWallpaper == null || mFindResults.resetTopWallpaper) {
-                WallpaperWindowToken token = w.mToken.asWallpaperToken();
-                if (token == null) {
-                    Slog.w(TAG, "Window " + w + " has wallpaper type but not wallpaper token");
-                    return false;
-                }
-                if (!token.canShowWhenLocked() && mDisplayContent.isKeyguardLocked()) {
-                    return false;
-                }
-                mFindResults.setTopWallpaper(w);
-                mFindResults.resetTopWallpaper = false;
+    private final Consumer<WindowState> mFindWallpapers = w -> {
+        if (w.mAttrs.type == TYPE_WALLPAPER) {
+            WallpaperWindowToken token = w.mToken.asWallpaperToken();
+            if (token.canShowWhenLocked() && !mFindResults.hasTopShowWhenLockedWallpaper()) {
+                mFindResults.setTopShowWhenLockedWallpaper(w);
+            } else if (!token.canShowWhenLocked()
+                    && !mFindResults.hasTopHideWhenLockedWallpaper()) {
+                mFindResults.setTopHideWhenLockedWallpaper(w);
             }
-            return false;
         }
+    };
 
-        mFindResults.resetTopWallpaper = true;
+    private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> {
         if (!w.mTransitionController.isShellTransitionsEnabled()) {
             if (w.mActivityRecord != null && !w.mActivityRecord.isVisible()
                     && !w.mActivityRecord.isAnimating(TRANSITION | PARENTS)) {
@@ -344,6 +339,31 @@
         }
     }
 
+    /**
+     * Change the visibility if wallpaper is home screen only.
+     * This is called during the keyguard unlocking transition
+     * (see {@link KeyguardController#keyguardGoingAway(int, int)}) and thus assumes that if the
+     * system wallpaper is shared with lock, then it needs no animation.
+     */
+    public void showHomeWallpaperInTransition() {
+        updateWallpaperWindowsTarget(mFindResults);
+
+        if (!mFindResults.hasTopShowWhenLockedWallpaper()) {
+            Slog.w(TAG, "There is no wallpaper for the lock screen");
+            return;
+        }
+        WindowState hideWhenLocked = mFindResults.mTopWallpaper.mTopHideWhenLockedWallpaper;
+        WindowState showWhenLocked = mFindResults.mTopWallpaper.mTopShowWhenLockedWallpaper;
+        if (!mFindResults.hasTopHideWhenLockedWallpaper()) {
+            // Shared wallpaper, ensure its visibility
+            showWhenLocked.mToken.asWallpaperToken().updateWallpaperWindows(true);
+        } else {
+            // Separate lock and home wallpapers: show home wallpaper and hide lock
+            hideWhenLocked.mToken.asWallpaperToken().updateWallpaperWindowsInTransition(true);
+            showWhenLocked.mToken.asWallpaperToken().updateWallpaperWindowsInTransition(false);
+        }
+    }
+
     void hideDeferredWallpapersIfNeededLegacy() {
         for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
             final WallpaperWindowToken token = mWallpaperTokens.get(i);
@@ -668,13 +688,26 @@
             mFindResults.setUseTopWallpaperAsTarget(true);
         }
 
+        mDisplayContent.forAllWindows(mFindWallpapers, true /* traverseTopToBottom */);
         mDisplayContent.forAllWindows(mFindWallpaperTargetFunction, true /* traverseTopToBottom */);
 
         if (mFindResults.wallpaperTarget == null && mFindResults.useTopWallpaperAsTarget) {
-            mFindResults.setWallpaperTarget(mFindResults.topWallpaper);
+            mFindResults.setWallpaperTarget(
+                    mFindResults.getTopWallpaper(mDisplayContent.isKeyguardLocked()));
         }
     }
 
+    List<WindowState> getAllTopWallpapers() {
+        ArrayList<WindowState> wallpapers = new ArrayList<>(2);
+        if (mFindResults.hasTopShowWhenLockedWallpaper()) {
+            wallpapers.add(mFindResults.mTopWallpaper.mTopShowWhenLockedWallpaper);
+        }
+        if (mFindResults.hasTopHideWhenLockedWallpaper()) {
+            wallpapers.add(mFindResults.mTopWallpaper.mTopHideWhenLockedWallpaper);
+        }
+        return wallpapers;
+    }
+
     private boolean isFullscreen(WindowManager.LayoutParams attrs) {
         return attrs.x == 0 && attrs.y == 0
                 && attrs.width == MATCH_PARENT && attrs.height == MATCH_PARENT;
@@ -760,10 +793,16 @@
         result.setWallpaperTarget(wallpaperTarget);
     }
 
+    /**
+     * Change the visibility of the top wallpaper to {@param visibility} and hide all the others.
+     */
     private void updateWallpaperTokens(boolean visibility, boolean locked) {
+        WindowState topWallpaper = mFindResults.getTopWallpaper(locked);
+        WallpaperWindowToken topWallpaperToken =
+                topWallpaper == null ? null : topWallpaper.mToken.asWallpaperToken();
         for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
             final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
-            token.updateWallpaperWindows(visibility && (!locked || token.canShowWhenLocked()));
+            token.updateWallpaperWindows(visibility && (token == topWallpaperToken));
         }
     }
 
@@ -801,8 +840,10 @@
             }
         }
 
-        // Keep both wallpapers visible unless the keyguard is locked (then hide private wp)
-        updateWallpaperTokens(visible, mDisplayContent.isKeyguardLocked());
+        if (!mDisplayContent.isKeyguardGoingAway() || !mIsLockscreenLiveWallpaperEnabled) {
+            // When keyguard goes away, KeyguardController handles the visibility
+            updateWallpaperTokens(visible, mDisplayContent.isKeyguardLocked());
+        }
 
         if (DEBUG_WALLPAPER) {
             Slog.v(TAG, "adjustWallpaperWindows: wallpaper visibility " + visible
@@ -1019,14 +1060,52 @@
 
     /** Helper class for storing the results of a wallpaper target find operation. */
     final private static class FindWallpaperTargetResult {
-        WindowState topWallpaper = null;
+
+        static final class TopWallpaper {
+            // A wp that can be visible on home screen only
+            WindowState mTopHideWhenLockedWallpaper = null;
+            // A wallpaper that has permission to be visible on lock screen (lock or shared wp)
+            WindowState mTopShowWhenLockedWallpaper = null;
+
+            void reset() {
+                mTopHideWhenLockedWallpaper = null;
+                mTopShowWhenLockedWallpaper = null;
+            }
+        }
+
+        TopWallpaper mTopWallpaper = new TopWallpaper();
         boolean useTopWallpaperAsTarget = false;
         WindowState wallpaperTarget = null;
-        boolean resetTopWallpaper = false;
         boolean isWallpaperTargetForLetterbox = false;
 
-        void setTopWallpaper(WindowState win) {
-            topWallpaper = win;
+        void setTopHideWhenLockedWallpaper(WindowState win) {
+            if (DEBUG_WALLPAPER) {
+                Slog.v(TAG, "setTopHideWhenLockedWallpaper " + win);
+            }
+            mTopWallpaper.mTopHideWhenLockedWallpaper = win;
+        }
+
+        void setTopShowWhenLockedWallpaper(WindowState win) {
+            if (DEBUG_WALLPAPER) {
+                Slog.v(TAG, "setTopShowWhenLockedWallpaper " + win);
+            }
+            mTopWallpaper.mTopShowWhenLockedWallpaper = win;
+        }
+
+        boolean hasTopHideWhenLockedWallpaper() {
+            return mTopWallpaper.mTopHideWhenLockedWallpaper != null;
+        }
+
+        boolean hasTopShowWhenLockedWallpaper() {
+            return mTopWallpaper.mTopShowWhenLockedWallpaper != null;
+        }
+
+        WindowState getTopWallpaper(boolean isKeyguardLocked) {
+            if (!isKeyguardLocked && hasTopHideWhenLockedWallpaper()) {
+                return mTopWallpaper.mTopHideWhenLockedWallpaper;
+            } else {
+                return mTopWallpaper.mTopShowWhenLockedWallpaper;
+            }
         }
 
         void setWallpaperTarget(WindowState win) {
@@ -1042,10 +1121,9 @@
         }
 
         void reset() {
-            topWallpaper = null;
+            mTopWallpaper.reset();
             wallpaperTarget = null;
             useTopWallpaperAsTarget = false;
-            resetTopWallpaper = false;
             isWallpaperTargetForLetterbox = false;
         }
     }
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 1ffee05..5ea8f65 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -128,6 +128,20 @@
         }
     }
 
+    /**
+     * Update the visibility of the token to {@param visible}. If a transition will collect the
+     * wallpaper, then the visibility will be committed during the execution of the transition.
+     *
+     * waitingToShow is reset at the beginning of the transition:
+     * {@link Transition#onTransactionReady(int, SurfaceControl.Transaction)}
+     */
+    void updateWallpaperWindowsInTransition(boolean visible) {
+        if (mTransitionController.isCollecting() && mVisibleRequested != visible) {
+            waitingToShow = true;
+        }
+        updateWallpaperWindows(visible);
+    }
+
     void updateWallpaperWindows(boolean visible) {
         if (mVisibleRequested != visible) {
             ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b",
@@ -199,11 +213,11 @@
     }
 
     /**
-     * Commits the visibility of this token. This will directly update the visibility without
-     * regard for other state (like being in a transition).
+     * Commits the visibility of this token. This will directly update the visibility unless the
+     * wallpaper is in a transition.
      */
     void commitVisibility(boolean visible) {
-        if (visible == isVisible()) return;
+        if (visible == isVisible() || waitingToShow) return;
 
         ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                 "commitVisibility: %s: visible=%b mVisibleRequested=%b", this,
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index a5cdd0b..3ccf183 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -435,8 +435,7 @@
         if (mLocalInsetsSources == null) {
             mLocalInsetsSources = new SparseArray<>();
         }
-        final int id = InsetsSource.createId(
-                provider.getOwner(), provider.getIndex(), provider.getType());
+        final int id = provider.getId();
         if (mLocalInsetsSources.get(id) != null) {
             if (DEBUG) {
                 Slog.d(TAG, "The local insets source for this " + provider
@@ -457,8 +456,7 @@
             return;
         }
 
-        final int id = InsetsSource.createId(
-                provider.getOwner(), provider.getIndex(), provider.getType());
+        final int id = provider.getId();
         if (mLocalInsetsSources.get(id) == null) {
             if (DEBUG) {
                 Slog.d(TAG, "Given " + provider + " doesn't have a local insets source.");
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f9b6fc1..40c6c46 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3915,8 +3915,9 @@
         synchronized (mGlobalLock) {
             final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
             if (displayContent == null) {
-                throw new IllegalStateException("No touch mode is defined for displayId {"
-                        + displayId + "}");
+                throw new IllegalStateException("Failed to retrieve the touch mode state for"
+                        + "display {" + displayId + "}: display is not registered in "
+                        + "WindowRootContainer");
             }
             return displayContent.isInTouchMode();
         }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index cd42528..09312ba 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -299,6 +299,7 @@
                     final boolean needsSetReady = t != null;
                     final Transition nextTransition = new Transition(type, 0 /* flags */,
                             mTransitionController, mService.mWindowManager.mSyncEngine);
+                    nextTransition.calcParallelCollectType(wct);
                     mTransitionController.startCollectOrQueue(nextTransition,
                             (deferred) -> {
                                 nextTransition.start();
@@ -971,19 +972,30 @@
 
         switch (type) {
             case HIERARCHY_OP_TYPE_PENDING_INTENT: {
+                final Bundle launchOpts = hop.getLaunchOptions();
+                ActivityOptions activityOptions = launchOpts != null
+                        ? new ActivityOptions(launchOpts) : null;
+                if (activityOptions != null && activityOptions.getTransientLaunch()
+                        && mService.isCallerRecents(hop.getPendingIntent().getCreatorUid())) {
+                    if (mService.getActivityStartController().startExistingRecentsIfPossible(
+                            hop.getActivityIntent(), activityOptions)) {
+                        // Start recents successfully.
+                        break;
+                    }
+                }
+
                 String resolvedType = hop.getActivityIntent() != null
                         ? hop.getActivityIntent().resolveTypeIfNeeded(
                         mService.mContext.getContentResolver())
                         : null;
 
-                ActivityOptions activityOptions = null;
                 if (hop.getPendingIntent().isActivity()) {
                     // Set the context display id as preferred for this activity launches, so that
                     // it can land on caller's display. Or just brought the task to front at the
                     // display where it was on since it has higher preference.
-                    activityOptions = hop.getLaunchOptions() != null
-                            ? new ActivityOptions(hop.getLaunchOptions())
-                            : ActivityOptions.makeBasic();
+                    if (activityOptions == null) {
+                        activityOptions = ActivityOptions.makeBasic();
+                    }
                     activityOptions.setCallerDisplayId(DEFAULT_DISPLAY);
                 }
                 final Bundle options = activityOptions != null ? activityOptions.toBundle() : null;
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 4af685e..0488247 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -561,6 +561,14 @@
     return env->NewStringUTF(path.c_str());
 }
 
+static jboolean com_android_server_am_CachedAppOptimizer_isFreezerProfileValid(JNIEnv* env) {
+    int uid = getuid();
+    int pid = getpid();
+
+    return isProfileValidForProcess("Frozen", uid, pid) &&
+            isProfileValidForProcess("Unfrozen", uid, pid);
+}
+
 static const JNINativeMethod sMethods[] = {
         /* name, signature, funcPtr */
         {"cancelCompaction", "()V",
@@ -578,7 +586,9 @@
         {"getBinderFreezeInfo", "(I)I",
          (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo},
         {"getFreezerCheckPath", "()Ljava/lang/String;",
-         (void*)com_android_server_am_CachedAppOptimizer_getFreezerCheckPath}};
+         (void*)com_android_server_am_CachedAppOptimizer_getFreezerCheckPath},
+        {"isFreezerProfileValid", "()Z",
+         (void*)com_android_server_am_CachedAppOptimizer_isFreezerProfileValid}};
 
 int register_android_server_am_CachedAppOptimizer(JNIEnv* env)
 {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 369c974..f0d718a 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -342,15 +342,15 @@
     void notifySensorAccuracy(int32_t deviceId, InputDeviceSensorType sensorType,
                               InputDeviceSensorAccuracy accuracy) override;
     void notifyVibratorState(int32_t deviceId, bool isOn) override;
-    bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) override;
-    void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override;
-    void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) override;
-    void interceptMotionBeforeQueueing(const int32_t displayId, nsecs_t when,
+    bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override;
+    InputDispatcherConfiguration getDispatcherConfiguration() override;
+    void interceptKeyBeforeQueueing(const KeyEvent& keyEvent, uint32_t& policyFlags) override;
+    void interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
                                        uint32_t& policyFlags) override;
-    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent* keyEvent,
+    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent& keyEvent,
                                           uint32_t policyFlags) override;
-    bool dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent* keyEvent,
-                              uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) override;
+    std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent& keyEvent,
+                                                 uint32_t policyFlags) override;
     void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
     void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
     void setPointerCapture(const PointerCaptureRequest& request) override;
@@ -450,7 +450,7 @@
 
     mServiceObj = env->NewGlobalRef(serviceObj);
 
-    InputManager* im = new InputManager(this, this);
+    InputManager* im = new InputManager(this, *this);
     mInputManager = im;
     defaultServiceManager()->addService(String16("inputflinger"), im);
 }
@@ -1007,21 +1007,22 @@
     checkAndClearExceptionFromCallback(env, "notifyVibratorState");
 }
 
-void NativeInputManager::getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) {
+InputDispatcherConfiguration NativeInputManager::getDispatcherConfiguration() {
     ATRACE_CALL();
+    InputDispatcherConfiguration config;
     JNIEnv* env = jniEnv();
 
-    jint keyRepeatTimeout = env->CallIntMethod(mServiceObj,
-            gServiceClassInfo.getKeyRepeatTimeout);
+    jint keyRepeatTimeout = env->CallIntMethod(mServiceObj, gServiceClassInfo.getKeyRepeatTimeout);
     if (!checkAndClearExceptionFromCallback(env, "getKeyRepeatTimeout")) {
-        outConfig->keyRepeatTimeout = milliseconds_to_nanoseconds(keyRepeatTimeout);
+        config.keyRepeatTimeout = milliseconds_to_nanoseconds(keyRepeatTimeout);
     }
 
-    jint keyRepeatDelay = env->CallIntMethod(mServiceObj,
-            gServiceClassInfo.getKeyRepeatDelay);
+    jint keyRepeatDelay = env->CallIntMethod(mServiceObj, gServiceClassInfo.getKeyRepeatDelay);
     if (!checkAndClearExceptionFromCallback(env, "getKeyRepeatDelay")) {
-        outConfig->keyRepeatDelay = milliseconds_to_nanoseconds(keyRepeatDelay);
+        config.keyRepeatDelay = milliseconds_to_nanoseconds(keyRepeatDelay);
     }
+
+    return config;
 }
 
 void NativeInputManager::displayRemoved(JNIEnv* env, int32_t displayId) {
@@ -1294,110 +1295,112 @@
     checkAndClearExceptionFromCallback(env, "notifyStylusGestureStarted");
 }
 
-bool NativeInputManager::filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) {
+bool NativeInputManager::filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) {
     ATRACE_CALL();
-    jobject inputEventObj;
-
     JNIEnv* env = jniEnv();
-    switch (inputEvent->getType()) {
+
+    ScopedLocalRef<jobject> inputEventObj(env);
+    switch (inputEvent.getType()) {
         case InputEventType::KEY:
-            inputEventObj =
-                    android_view_KeyEvent_fromNative(env, static_cast<const KeyEvent*>(inputEvent));
+            inputEventObj.reset(
+                    android_view_KeyEvent_fromNative(env,
+                                                     static_cast<const KeyEvent&>(inputEvent)));
             break;
         case InputEventType::MOTION:
-            inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
-                                                                  static_cast<const MotionEvent&>(
-                                                                          *inputEvent));
+            inputEventObj.reset(
+                    android_view_MotionEvent_obtainAsCopy(env,
+                                                          static_cast<const MotionEvent&>(
+                                                                  inputEvent)));
             break;
         default:
             return true; // dispatch the event normally
     }
 
-    if (!inputEventObj) {
+    if (!inputEventObj.get()) {
         ALOGE("Failed to obtain input event object for filterInputEvent.");
         return true; // dispatch the event normally
     }
 
     // The callee is responsible for recycling the event.
-    jboolean pass = env->CallBooleanMethod(mServiceObj, gServiceClassInfo.filterInputEvent,
-            inputEventObj, policyFlags);
+    const jboolean continueEventDispatch =
+            env->CallBooleanMethod(mServiceObj, gServiceClassInfo.filterInputEvent,
+                                   inputEventObj.get(), policyFlags);
     if (checkAndClearExceptionFromCallback(env, "filterInputEvent")) {
-        pass = true;
+        return true; // dispatch the event normally
     }
-    env->DeleteLocalRef(inputEventObj);
-    return pass;
+    return continueEventDispatch;
 }
 
-void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent,
-        uint32_t& policyFlags) {
+void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent& keyEvent,
+                                                    uint32_t& policyFlags) {
     ATRACE_CALL();
     // Policy:
     // - Ignore untrusted events and pass them along.
     // - Ask the window manager what to do with normal events and trusted injected events.
     // - For normal events wake and brighten the screen if currently off or dim.
-    bool interactive = mInteractive.load();
+    const bool interactive = mInteractive.load();
     if (interactive) {
         policyFlags |= POLICY_FLAG_INTERACTIVE;
     }
-    if ((policyFlags & POLICY_FLAG_TRUSTED)) {
-        nsecs_t when = keyEvent->getEventTime();
-        JNIEnv* env = jniEnv();
-        jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
-        jint wmActions;
-        if (keyEventObj) {
-            wmActions = env->CallIntMethod(mServiceObj,
-                    gServiceClassInfo.interceptKeyBeforeQueueing,
-                    keyEventObj, policyFlags);
-            if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {
-                wmActions = 0;
-            }
-            android_view_KeyEvent_recycle(env, keyEventObj);
-            env->DeleteLocalRef(keyEventObj);
-        } else {
-            ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");
-            wmActions = 0;
-        }
 
-        handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
-    } else {
+    if ((policyFlags & POLICY_FLAG_TRUSTED) == 0) {
         if (interactive) {
             policyFlags |= POLICY_FLAG_PASS_TO_USER;
         }
+        return;
     }
+
+    const nsecs_t when = keyEvent.getEventTime();
+    JNIEnv* env = jniEnv();
+    ScopedLocalRef<jobject> keyEventObj(env, android_view_KeyEvent_fromNative(env, keyEvent));
+    if (!keyEventObj.get()) {
+        ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");
+        return;
+    }
+
+    jint wmActions = env->CallIntMethod(mServiceObj, gServiceClassInfo.interceptKeyBeforeQueueing,
+                                        keyEventObj.get(), policyFlags);
+    if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {
+        wmActions = 0;
+    }
+    android_view_KeyEvent_recycle(env, keyEventObj.get());
+    handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
 }
 
-void NativeInputManager::interceptMotionBeforeQueueing(const int32_t displayId, nsecs_t when,
-        uint32_t& policyFlags) {
+void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
+                                                       uint32_t& policyFlags) {
     ATRACE_CALL();
     // Policy:
     // - Ignore untrusted events and pass them along.
     // - No special filtering for injected events required at this time.
     // - Filter normal events based on screen state.
     // - For normal events brighten (but do not wake) the screen if currently dim.
-    bool interactive = mInteractive.load();
+    const bool interactive = mInteractive.load();
     if (interactive) {
         policyFlags |= POLICY_FLAG_INTERACTIVE;
     }
-    if ((policyFlags & POLICY_FLAG_TRUSTED) && !(policyFlags & POLICY_FLAG_INJECTED)) {
-        if (policyFlags & POLICY_FLAG_INTERACTIVE) {
-            policyFlags |= POLICY_FLAG_PASS_TO_USER;
-        } else {
-            JNIEnv* env = jniEnv();
-            jint wmActions = env->CallIntMethod(mServiceObj,
-                        gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive,
-                        displayId, when, policyFlags);
-            if (checkAndClearExceptionFromCallback(env,
-                    "interceptMotionBeforeQueueingNonInteractive")) {
-                wmActions = 0;
-            }
 
-            handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
-        }
-    } else {
+    if ((policyFlags & POLICY_FLAG_TRUSTED) == 0 || (policyFlags & POLICY_FLAG_INJECTED)) {
         if (interactive) {
             policyFlags |= POLICY_FLAG_PASS_TO_USER;
         }
+        return;
     }
+
+    if (policyFlags & POLICY_FLAG_INTERACTIVE) {
+        policyFlags |= POLICY_FLAG_PASS_TO_USER;
+        return;
+    }
+
+    JNIEnv* env = jniEnv();
+    const jint wmActions =
+            env->CallIntMethod(mServiceObj,
+                               gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive,
+                               displayId, when, policyFlags);
+    if (checkAndClearExceptionFromCallback(env, "interceptMotionBeforeQueueingNonInteractive")) {
+        return;
+    }
+    handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
 }
 
 void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,
@@ -1411,81 +1414,76 @@
     }
 }
 
-nsecs_t NativeInputManager::interceptKeyBeforeDispatching(
-        const sp<IBinder>& token,
-        const KeyEvent* keyEvent, uint32_t policyFlags) {
+nsecs_t NativeInputManager::interceptKeyBeforeDispatching(const sp<IBinder>& token,
+                                                          const KeyEvent& keyEvent,
+                                                          uint32_t policyFlags) {
     ATRACE_CALL();
     // Policy:
     // - Ignore untrusted events and pass them along.
     // - Filter normal events and trusted injected events through the window manager policy to
     //   handle the HOME key and the like.
-    nsecs_t result = 0;
-    if (policyFlags & POLICY_FLAG_TRUSTED) {
-        JNIEnv* env = jniEnv();
-        ScopedLocalFrame localFrame(env);
-
-        // Token may be null
-        jobject tokenObj = javaObjectForIBinder(env, token);
-
-        jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
-        if (keyEventObj) {
-            jlong delayMillis = env->CallLongMethod(mServiceObj,
-                    gServiceClassInfo.interceptKeyBeforeDispatching,
-                    tokenObj, keyEventObj, policyFlags);
-            bool error = checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching");
-            android_view_KeyEvent_recycle(env, keyEventObj);
-            env->DeleteLocalRef(keyEventObj);
-            if (!error) {
-                if (delayMillis < 0) {
-                    result = -1;
-                } else if (delayMillis > 0) {
-                    result = milliseconds_to_nanoseconds(delayMillis);
-                }
-            }
-        } else {
-            ALOGE("Failed to obtain key event object for interceptKeyBeforeDispatching.");
-        }
+    if ((policyFlags & POLICY_FLAG_TRUSTED) == 0) {
+        return 0;
     }
-    return result;
+
+    JNIEnv* env = jniEnv();
+    ScopedLocalFrame localFrame(env);
+
+    // Token may be null
+    ScopedLocalRef<jobject> tokenObj(env, javaObjectForIBinder(env, token));
+    ScopedLocalRef<jobject> keyEventObj(env, android_view_KeyEvent_fromNative(env, keyEvent));
+    if (!keyEventObj.get()) {
+        ALOGE("Failed to obtain key event object for interceptKeyBeforeDispatching.");
+        return 0;
+    }
+
+    const jlong delayMillis =
+            env->CallLongMethod(mServiceObj, gServiceClassInfo.interceptKeyBeforeDispatching,
+                                tokenObj.get(), keyEventObj.get(), policyFlags);
+    android_view_KeyEvent_recycle(env, keyEventObj.get());
+    if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching")) {
+        return 0;
+    }
+    return delayMillis < 0 ? -1 : milliseconds_to_nanoseconds(delayMillis);
 }
 
-bool NativeInputManager::dispatchUnhandledKey(const sp<IBinder>& token,
-        const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) {
+std::optional<KeyEvent> NativeInputManager::dispatchUnhandledKey(const sp<IBinder>& token,
+                                                                 const KeyEvent& keyEvent,
+                                                                 uint32_t policyFlags) {
     ATRACE_CALL();
     // Policy:
     // - Ignore untrusted events and do not perform default handling.
-    bool result = false;
-    if (policyFlags & POLICY_FLAG_TRUSTED) {
-        JNIEnv* env = jniEnv();
-        ScopedLocalFrame localFrame(env);
-
-        // Note: tokenObj may be null.
-        jobject tokenObj = javaObjectForIBinder(env, token);
-        jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
-        if (keyEventObj) {
-            jobject fallbackKeyEventObj = env->CallObjectMethod(mServiceObj,
-                    gServiceClassInfo.dispatchUnhandledKey,
-                    tokenObj, keyEventObj, policyFlags);
-            if (checkAndClearExceptionFromCallback(env, "dispatchUnhandledKey")) {
-                fallbackKeyEventObj = nullptr;
-            }
-            android_view_KeyEvent_recycle(env, keyEventObj);
-            env->DeleteLocalRef(keyEventObj);
-
-            if (fallbackKeyEventObj) {
-                // Note: outFallbackKeyEvent may be the same object as keyEvent.
-                if (!android_view_KeyEvent_toNative(env, fallbackKeyEventObj,
-                        outFallbackKeyEvent)) {
-                    result = true;
-                }
-                android_view_KeyEvent_recycle(env, fallbackKeyEventObj);
-                env->DeleteLocalRef(fallbackKeyEventObj);
-            }
-        } else {
-            ALOGE("Failed to obtain key event object for dispatchUnhandledKey.");
-        }
+    if ((policyFlags & POLICY_FLAG_TRUSTED) == 0) {
+        return {};
     }
-    return result;
+
+    JNIEnv* env = jniEnv();
+    ScopedLocalFrame localFrame(env);
+
+    // Note: tokenObj may be null.
+    ScopedLocalRef<jobject> tokenObj(env, javaObjectForIBinder(env, token));
+    ScopedLocalRef<jobject> keyEventObj(env, android_view_KeyEvent_fromNative(env, keyEvent));
+    if (!keyEventObj.get()) {
+        ALOGE("Failed to obtain key event object for dispatchUnhandledKey.");
+        return {};
+    }
+
+    ScopedLocalRef<jobject>
+            fallbackKeyEventObj(env,
+                                env->CallObjectMethod(mServiceObj,
+                                                      gServiceClassInfo.dispatchUnhandledKey,
+                                                      tokenObj.get(), keyEventObj.get(),
+                                                      policyFlags));
+
+    android_view_KeyEvent_recycle(env, keyEventObj.get());
+    if (checkAndClearExceptionFromCallback(env, "dispatchUnhandledKey") ||
+        !fallbackKeyEventObj.get()) {
+        return {};
+    }
+
+    const KeyEvent fallbackEvent = android_view_KeyEvent_toNative(env, fallbackKeyEventObj.get());
+    android_view_KeyEvent_recycle(env, fallbackKeyEventObj.get());
+    return fallbackEvent;
 }
 
 void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) {
@@ -1876,13 +1874,7 @@
     InputEventInjectionSync mode = static_cast<InputEventInjectionSync>(syncMode);
 
     if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
-        KeyEvent keyEvent;
-        status_t status = android_view_KeyEvent_toNative(env, inputEventObj, & keyEvent);
-        if (status) {
-            jniThrowRuntimeException(env, "Could not read contents of KeyEvent object.");
-            return static_cast<jint>(InputEventInjectionResult::FAILED);
-        }
-
+        const KeyEvent keyEvent = android_view_KeyEvent_toNative(env, inputEventObj);
         const InputEventInjectionResult result =
                 im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, targetUid, mode,
                                                                         std::chrono::milliseconds(
@@ -1913,13 +1905,7 @@
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
     if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
-        KeyEvent keyEvent;
-        status_t status = android_view_KeyEvent_toNative(env, inputEventObj, &keyEvent);
-        if (status) {
-            jniThrowRuntimeException(env, "Could not read contents of KeyEvent object.");
-            return nullptr;
-        }
-
+        const KeyEvent keyEvent = android_view_KeyEvent_toNative(env, inputEventObj);
         std::unique_ptr<VerifiedInputEvent> verifiedEvent =
                 im->getInputManager()->getDispatcher().verifyInputEvent(keyEvent);
         if (verifiedEvent == nullptr) {
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index 19a0c5e..04ecd6e 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -67,7 +67,7 @@
                 .createNewSession(mContext, mUserId, providerInfo,
                         this, remoteCredentialService);
         if (providerClearSession != null) {
-            Slog.d(TAG, "In startProviderSession - provider session created "
+            Slog.i(TAG, "Provider session created "
                     + "and being added for: " + providerInfo.getComponentName());
             mProviders.put(providerClearSession.getComponentName().flattenToString(),
                     providerClearSession);
@@ -78,12 +78,12 @@
     @Override // from provider session
     public void onProviderStatusChanged(ProviderSession.Status status,
             ComponentName componentName, ProviderSession.CredentialsSource source) {
-        Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source);
+        Slog.i(TAG, "Provider changed with status: " + status + ", and source: " + source);
         if (ProviderSession.isTerminatingStatus(status)) {
-            Slog.d(TAG, "in onProviderStatusChanged terminating status");
+            Slog.i(TAG, "Provider terminating status");
             onProviderTerminated(componentName);
         } else if (ProviderSession.isCompletionStatus(status)) {
-            Slog.d(TAG, "in onProviderStatusChanged isCompletionStatus status");
+            Slog.i(TAG, "Provider has completion status");
             onProviderResponseComplete(componentName);
         }
     }
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index fc7fd1a..4b32062 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -79,7 +79,7 @@
                 .createNewSession(mContext, mUserId, providerInfo,
                         this, remoteCredentialService);
         if (providerCreateSession != null) {
-            Slog.d(TAG, "In initiateProviderSession - provider session created and "
+            Slog.i(TAG, "Provider session created and "
                     + "being added for: " + providerInfo.getComponentName());
             mProviders.put(providerCreateSession.getComponentName().flattenToString(),
                     providerCreateSession);
@@ -98,7 +98,9 @@
                             mRequestId, mClientRequest,
                             mClientAppInfo.getPackageName(),
                             PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
-                                    Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
+                                    Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
+                            // TODO(b/279480457): populate
+                            /*defaultProviderId=*/new ArrayList<>()),
                     providerDataList);
             mClientCallback.onPendingIntent(mPendingIntent);
         } catch (RemoteException e) {
@@ -125,7 +127,7 @@
     @Override
     public void onFinalResponseReceived(ComponentName componentName,
             @Nullable CreateCredentialResponse response) {
-        Slog.d(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
+        Slog.i(TAG, "Final credential received from: " + componentName.flattenToString());
         mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
         mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(mProviders.get(
                 componentName.flattenToString()).mProviderSessionMetric
@@ -168,13 +170,13 @@
     @Override
     public void onProviderStatusChanged(ProviderSession.Status status,
             ComponentName componentName, ProviderSession.CredentialsSource source) {
-        Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source);
+        Slog.i(TAG, "Provider status changed: " + status + ", and source: " + source);
         // If all provider responses have been received, we can either need the UI,
         // or we need to respond with error. The only other case is the entry being
         // selected after the UI has been invoked which has a separate code path.
         if (!isAnyProviderPending()) {
             if (isUiInvocationNeeded()) {
-                Slog.d(TAG, "in onProviderStatusChanged - isUiInvocationNeeded");
+                Slog.i(TAG, "Provider status changed - ui invocation is needed");
                 getProviderDataAndInitiateUi();
             } else {
                 respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 06b96eb..7f95e05 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -58,7 +58,6 @@
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.CredentialProviderInfoFactory;
 import android.text.TextUtils;
-import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -437,7 +436,7 @@
                 IGetCredentialCallback callback,
                 final String callingPackage) {
             final long timestampBegan = System.nanoTime();
-            Slog.d(TAG, "starting executeGetCredential with callingPackage: "
+            Slog.i(TAG, "starting executeGetCredential with callingPackage: "
                     + callingPackage);
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
 
@@ -472,7 +471,7 @@
                             GetCredentialException.TYPE_NO_CREDENTIAL,
                             "No credentials available on this device.");
                 } catch (RemoteException e) {
-                    Log.i(
+                    Slog.e(
                             TAG,
                             "Issue invoking onError on IGetCredentialCallback "
                                     + "callback: "
@@ -528,7 +527,7 @@
                                     false, null,
                                     false, false, null));
                 } catch (RemoteException e) {
-                    Log.i(
+                    Slog.e(
                             TAG,
                             "Issue invoking onError on IGetCredentialCallback "
                                     + "callback: "
@@ -607,7 +606,7 @@
                 ICreateCredentialCallback callback,
                 String callingPackage) {
             final long timestampBegan = System.nanoTime();
-            Slog.d(TAG, "starting executeCreateCredential with callingPackage: "
+            Slog.i(TAG, "starting executeCreateCredential with callingPackage: "
                     + callingPackage);
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
 
@@ -673,7 +672,7 @@
                 MetricUtilities.logApiCalledInitialPhase(initMetric,
                         session.mRequestSessionMetric.returnIncrementSequence());
             } catch (Exception e) {
-                Log.w(TAG, "Unexpected error during metric logging: ", e);
+                Slog.i(TAG, "Unexpected error during metric logging: ", e);
             }
         }
 
@@ -706,7 +705,7 @@
                     Settings.Secure.CREDENTIAL_SERVICE,
                     storedValue,
                     userId)) {
-                Log.e(TAG, "Failed to store setting containing enabled providers");
+                Slog.e(TAG, "Failed to store setting containing enabled providers");
                 try {
                     callback.onError(
                             "failed_setting_store",
@@ -733,7 +732,7 @@
         @Override
         public boolean isEnabledCredentialProviderService(
                 ComponentName componentName, String callingPackage) {
-            Slog.d(TAG, "isEnabledCredentialProviderService with componentName: "
+            Slog.i(TAG, "isEnabledCredentialProviderService with componentName: "
                     + componentName.flattenToString());
 
             // TODO(253157366): Check additional set of services.
@@ -829,7 +828,7 @@
                 IClearCredentialStateCallback callback,
                 String callingPackage) {
             final long timestampBegan = System.nanoTime();
-            Slog.d(TAG, "starting clearCredentialState with callingPackage: "
+            Slog.i(TAG, "starting clearCredentialState with callingPackage: "
                     + callingPackage);
             final int userId = UserHandle.getCallingUserId();
             int callingUid = Binder.getCallingUid();
@@ -882,7 +881,7 @@
         public void registerCredentialDescription(
                 RegisterCredentialDescriptionRequest request, String callingPackage)
                 throws IllegalArgumentException, NonCredentialProviderCallerException {
-            Slog.d(TAG, "registerCredentialDescription with callingPackage: " + callingPackage);
+            Slog.i(TAG, "registerCredentialDescription with callingPackage: " + callingPackage);
 
             if (!isCredentialDescriptionApiEnabled()) {
                 throw new UnsupportedOperationException();
@@ -900,7 +899,7 @@
         public void unregisterCredentialDescription(
                 UnregisterCredentialDescriptionRequest request, String callingPackage)
                 throws IllegalArgumentException {
-            Slog.d(TAG, "unregisterCredentialDescription with callingPackage: "
+            Slog.i(TAG, "unregisterCredentialDescription with callingPackage: "
                     + callingPackage);
 
 
@@ -962,7 +961,6 @@
         @Override
         @GuardedBy("mLock")
         public void onFinishRequestSession(@UserIdInt int userId, IBinder token) {
-            Log.i(TAG, "In onFinishRequestSession");
             if (mRequestSessions.get(userId) != null) {
                 mRequestSessions.get(userId).remove(token);
             }
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index 91be2a7..808fdae 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -48,7 +48,7 @@
             @NonNull Object lock, int userId, String serviceName)
             throws PackageManager.NameNotFoundException {
         super(master, lock, userId);
-        Slog.d(TAG, "CredentialManagerServiceImpl constructed for: " + serviceName);
+        Slog.i(TAG, "CredentialManagerServiceImpl constructed for: " + serviceName);
         synchronized (mLock) {
             newServiceInfoLocked(ComponentName.unflattenFromString(serviceName));
         }
@@ -63,7 +63,7 @@
             @NonNull CredentialManagerService master,
             @NonNull Object lock, int userId, CredentialProviderInfo providerInfo) {
         super(master, lock, userId);
-        Slog.d(TAG, "CredentialManagerServiceImpl constructed for: "
+        Slog.i(TAG, "CredentialManagerServiceImpl constructed for: "
                 + providerInfo.getServiceInfo().getComponentName().flattenToString());
         mInfo = providerInfo;
     }
@@ -74,11 +74,11 @@
             throws PackageManager.NameNotFoundException {
         // TODO : Test update flows with multiple providers
         if (mInfo != null) {
-            Slog.d(TAG, "newServiceInfoLocked, mInfo not null : "
+            Slog.i(TAG, "newServiceInfoLocked, mInfo not null : "
                     + mInfo.getServiceInfo().getComponentName().flattenToString() + " , "
                     + serviceComponent.flattenToString());
         } else {
-            Slog.d(TAG, "newServiceInfoLocked, mInfo null, "
+            Slog.i(TAG, "newServiceInfoLocked, mInfo null, "
                     + serviceComponent.flattenToString());
         }
         mInfo = CredentialProviderInfoFactory.create(
@@ -95,11 +95,11 @@
     public ProviderSession initiateProviderSessionForRequestLocked(
             RequestSession requestSession, List<String> requestOptions) {
         if (!requestOptions.isEmpty() && !isServiceCapableLocked(requestOptions)) {
-            Slog.d(TAG, "Service does not have the required capabilities");
+            Slog.i(TAG, "Service does not have the required capabilities");
             return null;
         }
         if (mInfo == null) {
-            Slog.w(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl, "
+            Slog.w(TAG, "Initiating provider session for request "
                     + "but mInfo is null. This shouldn't happen");
             return null;
         }
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 0271727..1503410 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -72,7 +72,7 @@
                 .createNewSession(mContext, mUserId, providerInfo,
                         this, remoteCredentialService);
         if (providerGetSession != null) {
-            Slog.d(TAG, "In startProviderSession - provider session created and "
+            Slog.i(TAG, "Provider session created and "
                     + "being added for: " + providerInfo.getComponentName());
             mProviders.put(providerGetSession.getComponentName().flattenToString(),
                     providerGetSession);
@@ -114,7 +114,7 @@
     @Override
     public void onFinalResponseReceived(ComponentName componentName,
             @Nullable GetCredentialResponse response) {
-        Slog.d(TAG, "onFinalResponseReceived from: " + componentName.flattenToString());
+        Slog.i(TAG, "onFinalResponseReceived from: " + componentName.flattenToString());
         mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
         mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(
                 mProviders.get(componentName.flattenToString())
@@ -158,7 +158,7 @@
     @Override
     public void onProviderStatusChanged(ProviderSession.Status status,
             ComponentName componentName, ProviderSession.CredentialsSource source) {
-        Slog.d(TAG, "in onStatusChanged for: " + componentName + ", with status: "
+        Slog.i(TAG, "Status changed for: " + componentName + ", with status: "
                 + status + ", and source: " + source);
 
         // Auth entry was selected, and it did not have any underlying credentials
@@ -172,7 +172,7 @@
             // or we need to respond with error. The only other case is the entry being
             // selected after the UI has been invoked which has a separate code path.
             if (isUiInvocationNeeded()) {
-                Slog.d(TAG, "in onProviderStatusChanged - isUiInvocationNeeded");
+                Slog.i(TAG, "Provider status changed - ui invocation is needed");
                 getProviderDataAndInitiateUi();
             } else {
                 respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 50e5163..47502c2 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -19,7 +19,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.util.Log;
+import android.util.Slog;
 
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.credentials.metrics.ApiName;
@@ -27,6 +27,7 @@
 import com.android.server.credentials.metrics.CandidateBrowsingPhaseMetric;
 import com.android.server.credentials.metrics.CandidatePhaseMetric;
 import com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric;
+import com.android.server.credentials.metrics.EntryEnum;
 import com.android.server.credentials.metrics.InitialPhaseMetric;
 
 import java.util.List;
@@ -35,6 +36,7 @@
 /**
  * For all future metric additions, this will contain their names for local usage after importing
  * from {@link com.android.internal.util.FrameworkStatsLog}.
+ * TODO(b/271135048) - Emit all atoms, including all V4 atoms (specifically the rest of track 1).
  */
 public class MetricUtilities {
     private static final boolean LOG_FLAG = true;
@@ -68,7 +70,7 @@
                     componentName.getPackageName(),
                     PackageManager.ApplicationInfoFlags.of(0)).uid;
         } catch (Throwable t) {
-            Log.i(TAG, "Couldn't find required uid");
+            Slog.i(TAG, "Couldn't find required uid");
         }
         return sessUid;
     }
@@ -146,28 +148,28 @@
                             .getFinalFinishTimeNanoseconds()),
                     /* chosen_provider_status */ finalPhaseMetric.getChosenProviderStatus(),
                     /* chosen_provider_has_exception */ finalPhaseMetric.isHasException(),
-                    /* chosen_provider_available_entries */ finalPhaseMetric.getAvailableEntries()
-                            .stream().mapToInt(i -> i).toArray(),
-                    /* chosen_provider_action_entry_count */ finalPhaseMetric.getActionEntryCount(),
-                    /* chosen_provider_credential_entry_count */
-                    finalPhaseMetric.getCredentialEntryCount(),
-                    /* chosen_provider_credential_entry_type_count */
-                    finalPhaseMetric.getCredentialEntryTypeCount(),
-                    /* chosen_provider_remote_entry_count */
-                    finalPhaseMetric.getRemoteEntryCount(),
-                    /* chosen_provider_authentication_entry_count */
-                    finalPhaseMetric.getAuthenticationEntryCount(),
+                    /* chosen_provider_available_entries (deprecated) */ DEFAULT_REPEATED_INT_32,
+                    /* chosen_provider_action_entry_count (deprecated) */ DEFAULT_INT_32,
+                    /* chosen_provider_credential_entry_count (deprecated)*/DEFAULT_INT_32,
+                    /* chosen_provider_credential_entry_type_count (deprecated) */ DEFAULT_INT_32,
+                    /* chosen_provider_remote_entry_count (deprecated) */ DEFAULT_INT_32,
+                    /* chosen_provider_authentication_entry_count (deprecated) */ DEFAULT_INT_32,
                     /* clicked_entries */ browsedClickedEntries,
                     /* provider_of_clicked_entry */ browsedProviderUid,
                     /* api_status */ apiStatus,
-                    DEFAULT_REPEATED_INT_32,
-                    DEFAULT_REPEATED_INT_32,
-                    DEFAULT_REPEATED_STR,
-                    DEFAULT_REPEATED_INT_32,
+                    /* unique_entries */
+                    finalPhaseMetric.getResponseCollective().getUniqueEntries(),
+                    /* per_entry_counts */
+                    finalPhaseMetric.getResponseCollective().getUniqueEntryCounts(),
+                    /* unique_response_classtypes */
+                    finalPhaseMetric.getResponseCollective().getUniqueResponseStrings(),
+                    /* per_classtype_counts */
+                    finalPhaseMetric.getResponseCollective().getUniqueResponseCounts(),
+                    /* framework_exception_unique_classtypes */
                     DEFAULT_STRING
             );
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.w(TAG, "Unexpected error during final provider uid emit: " + e);
         }
     }
 
@@ -222,12 +224,18 @@
                                 metric.getQueryFinishTimeNanoseconds());
                 candidateStatusList[index] = metric.getProviderQueryStatus();
                 candidateHasExceptionList[index] = metric.isHasException();
-                candidateTotalEntryCountList[index] = metric.getNumEntriesTotal();
-                candidateCredentialEntryCountList[index] = metric.getCredentialEntryCount();
-                candidateCredentialTypeCountList[index] = metric.getCredentialEntryTypeCount();
-                candidateActionEntryCountList[index] = metric.getActionEntryCount();
-                candidateAuthEntryCountList[index] = metric.getAuthenticationEntryCount();
-                candidateRemoteEntryCountList[index] = metric.getRemoteEntryCount();
+                candidateTotalEntryCountList[index] = metric.getResponseCollective()
+                        .getNumEntriesTotal();
+                candidateCredentialEntryCountList[index] = metric.getResponseCollective()
+                        .getCountForEntry(EntryEnum.CREDENTIAL_ENTRY);
+                candidateCredentialTypeCountList[index] = metric.getResponseCollective()
+                        .getUniqueResponseStrings().length;
+                candidateActionEntryCountList[index] = metric.getResponseCollective()
+                        .getCountForEntry(EntryEnum.ACTION_ENTRY);
+                candidateAuthEntryCountList[index] = metric.getResponseCollective()
+                        .getCountForEntry(EntryEnum.AUTHENTICATION_ENTRY);
+                candidateRemoteEntryCountList[index] = metric.getResponseCollective()
+                        .getCountForEntry(EntryEnum.REMOTE_ENTRY);
                 frameworkExceptionList[index] = metric.getFrameworkException();
                 index++;
             }
@@ -261,7 +269,7 @@
                     initialPhaseMetric.getUniqueRequestCounts()
             );
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.w(TAG, "Unexpected error during candidate provider uid metric emit: " + e);
         }
     }
 
@@ -297,7 +305,7 @@
                     DEFAULT_INT_32,
                     /* chosen_provider_status */ DEFAULT_INT_32);
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.w(TAG, "Unexpected error during metric logging: " + e);
         }
     }
 
@@ -330,7 +338,7 @@
                     initialPhaseMetric.isOriginSpecified()
             );
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.w(TAG, "Unexpected error during initial metric emit: " + e);
         }
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index 441c87b..36bc8ba 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -66,7 +66,7 @@
     @Override
     public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName,
             ProviderSession.CredentialsSource source) {
-        Slog.d(TAG, "in onProviderStatusChanged with status: " + status + ", and "
+        Slog.i(TAG, "Provider Status changed with status: " + status + ", and "
                 + "source: " + source);
 
         switch (source) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 8af6b56..c1fb92d 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -80,7 +80,7 @@
 
     @Override
     public void onProviderResponseSuccess(@Nullable Void response) {
-        Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
+        Slog.i(TAG, "Remote provider responded with a valid response: " + mComponentName);
         mProviderResponseSet = true;
         updateStatusAndInvokeCallback(Status.COMPLETE,
                 /*source=*/ CredentialsSource.REMOTE_PROVIDER);
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 520b937..4cdc6f4 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -75,7 +75,8 @@
         CreateCredentialRequest providerCreateRequest =
                 createProviderRequest(providerInfo.getCapabilities(),
                         createRequestSession.mClientRequest,
-                        createRequestSession.mClientAppInfo);
+                        createRequestSession.mClientAppInfo,
+                        providerInfo.isSystemProvider());
         if (providerCreateRequest != null) {
             return new ProviderCreateSession(
                     context,
@@ -92,7 +93,7 @@
                     createRequestSession.mHybridService
             );
         }
-        Slog.d(TAG, "Unable to create provider session for: "
+        Slog.i(TAG, "Unable to create provider session for: "
                 + providerInfo.getComponentName());
         return null;
     }
@@ -114,9 +115,16 @@
     }
 
     @Nullable
-    private static CreateCredentialRequest createProviderRequest(List<String> providerCapabilities,
+    private static CreateCredentialRequest createProviderRequest(
+            List<String> providerCapabilities,
             android.credentials.CreateCredentialRequest clientRequest,
-            CallingAppInfo callingAppInfo) {
+            CallingAppInfo callingAppInfo,
+            boolean isSystemProvider) {
+        if (clientRequest.isSystemProviderRequired() && !isSystemProvider) {
+            // Request requires system provider but this session does not correspond to a
+            // system service
+            return null;
+        }
         String capability = clientRequest.getType();
         if (providerCapabilities.contains(capability)) {
             return new CreateCredentialRequest(callingAppInfo, capability,
@@ -145,7 +153,7 @@
     @Override
     public void onProviderResponseSuccess(
             @Nullable BeginCreateCredentialResponse response) {
-        Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
+        Slog.i(TAG, "Remote provider responded with a valid response: " + mComponentName);
         onSetInitialRemoteResponse(response);
     }
 
@@ -200,7 +208,7 @@
     protected CreateCredentialProviderData prepareUiData()
             throws IllegalArgumentException {
         if (!ProviderSession.isUiInvokingStatus(getStatus())) {
-            Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString());
+            Slog.i(TAG, "No data for UI from: " + mComponentName.flattenToString());
             return null;
         }
 
@@ -216,7 +224,7 @@
         switch (entryType) {
             case SAVE_ENTRY_KEY:
                 if (mProviderResponseDataHandler.getCreateEntry(entryKey) == null) {
-                    Slog.w(TAG, "Unexpected save entry key");
+                    Slog.i(TAG, "Unexpected save entry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
@@ -224,14 +232,14 @@
                 break;
             case REMOTE_ENTRY_KEY:
                 if (mProviderResponseDataHandler.getRemoteEntry(entryKey) == null) {
-                    Slog.w(TAG, "Unexpected remote entry key");
+                    Slog.i(TAG, "Unexpected remote entry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
                 onRemoteEntrySelected(providerPendingIntentResponse);
                 break;
             default:
-                Slog.w(TAG, "Unsupported entry type selected");
+                Slog.i(TAG, "Unsupported entry type selected");
                 invokeCallbackOnInternalInvalidState();
         }
     }
@@ -266,7 +274,7 @@
         if (credentialResponse != null) {
             mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
         } else {
-            Slog.w(TAG, "onSaveEntrySelected - no response or error found in pending "
+            Slog.i(TAG, "onSaveEntrySelected - no response or error found in pending "
                     + "intent response");
             invokeCallbackOnInternalInvalidState();
         }
@@ -282,14 +290,14 @@
     private CreateCredentialException maybeGetPendingIntentException(
             ProviderPendingIntentResponse pendingIntentResponse) {
         if (pendingIntentResponse == null) {
-            Slog.w(TAG, "pendingIntentResponse is null");
+            Slog.i(TAG, "pendingIntentResponse is null");
             return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREATE_OPTIONS);
         }
         if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
             CreateCredentialException exception = PendingIntentResultHandler
                     .extractCreateCredentialException(pendingIntentResponse.getResultData());
             if (exception != null) {
-                Slog.d(TAG, "Pending intent contains provider exception");
+                Slog.i(TAG, "Pending intent contains provider exception");
                 return exception;
             }
         } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index a62d9e8..8070fa7 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -40,7 +40,6 @@
 import android.service.credentials.CredentialProviderService;
 import android.service.credentials.GetCredentialRequest;
 import android.service.credentials.RemoteEntry;
-import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 
@@ -115,7 +114,7 @@
                     getRequestSession.mHybridService
             );
         }
-        Slog.d(TAG, "Unable to create provider session for: "
+        Slog.i(TAG, "Unable to create provider session for: "
                 + providerInfo.getComponentName());
         return null;
     }
@@ -147,13 +146,13 @@
             android.credentials.GetCredentialRequest clientRequest,
             CredentialProviderInfo info
     ) {
-        Slog.d(TAG, "Filtering request options for: " + info.getComponentName());
+        Slog.i(TAG, "Filtering request options for: " + info.getComponentName());
         List<CredentialOption> filteredOptions = new ArrayList<>();
         for (CredentialOption option : clientRequest.getCredentialOptions()) {
             if (providerCapabilities.contains(option.getType())
                     && isProviderAllowed(option, info.getComponentName())
                     && checkSystemProviderRequirement(option, info.isSystemProvider())) {
-                Slog.d(TAG, "Option of type: " + option.getType() + " meets all filtering"
+                Slog.i(TAG, "Option of type: " + option.getType() + " meets all filtering"
                         + "conditions");
                 filteredOptions.add(option);
             }
@@ -164,14 +163,14 @@
                     .setCredentialOptions(
                             filteredOptions).build();
         }
-        Slog.d(TAG, "No options filtered");
+        Slog.i(TAG, "No options filtered");
         return null;
     }
 
     private static boolean isProviderAllowed(CredentialOption option, ComponentName componentName) {
         if (!option.getAllowedProviders().isEmpty() && !option.getAllowedProviders().contains(
                 componentName)) {
-            Slog.d(TAG, "Provider allow list specified but does not contain this provider");
+            Slog.i(TAG, "Provider allow list specified but does not contain this provider");
             return false;
         }
         return true;
@@ -180,7 +179,7 @@
     private static boolean checkSystemProviderRequirement(CredentialOption option,
             boolean isSystemProvider) {
         if (option.isSystemProviderRequired() && !isSystemProvider) {
-            Slog.d(TAG, "System provider required, but this service is not a system provider");
+            Slog.i(TAG, "System provider required, but this service is not a system provider");
             return false;
         }
         return true;
@@ -208,7 +207,7 @@
     /** Called when the provider response has been updated by an external source. */
     @Override // Callback from the remote provider
     public void onProviderResponseSuccess(@Nullable BeginGetCredentialResponse response) {
-        Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
+        Slog.i(TAG, "Remote provider responded with a valid response: " + mComponentName);
         onSetInitialRemoteResponse(response);
     }
 
@@ -245,14 +244,14 @@
     @Override // Selection call from the request provider
     protected void onUiEntrySelected(String entryType, String entryKey,
             ProviderPendingIntentResponse providerPendingIntentResponse) {
-        Slog.d(TAG, "onUiEntrySelected with entryType: " + entryType + ", and entryKey: "
+        Slog.i(TAG, "onUiEntrySelected with entryType: " + entryType + ", and entryKey: "
                 + entryKey);
         switch (entryType) {
             case CREDENTIAL_ENTRY_KEY:
                 CredentialEntry credentialEntry = mProviderResponseDataHandler
                         .getCredentialEntry(entryKey);
                 if (credentialEntry == null) {
-                    Slog.w(TAG, "Unexpected credential entry key");
+                    Slog.i(TAG, "Unexpected credential entry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
@@ -261,7 +260,7 @@
             case ACTION_ENTRY_KEY:
                 Action actionEntry = mProviderResponseDataHandler.getActionEntry(entryKey);
                 if (actionEntry == null) {
-                    Slog.w(TAG, "Unexpected action entry key");
+                    Slog.i(TAG, "Unexpected action entry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
@@ -271,21 +270,21 @@
                 Action authenticationEntry = mProviderResponseDataHandler
                         .getAuthenticationAction(entryKey);
                 if (authenticationEntry == null) {
-                    Slog.w(TAG, "Unexpected authenticationEntry key");
+                    Slog.i(TAG, "Unexpected authenticationEntry key");
                     invokeCallbackOnInternalInvalidState();
                     return;
                 }
                 boolean additionalContentReceived =
                         onAuthenticationEntrySelected(providerPendingIntentResponse);
                 if (additionalContentReceived) {
-                    Slog.d(TAG, "Additional content received - removing authentication entry");
+                    Slog.i(TAG, "Additional content received - removing authentication entry");
                     mProviderResponseDataHandler.removeAuthenticationAction(entryKey);
                     if (!mProviderResponseDataHandler.isEmptyResponse()) {
                         updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
                                 /*source=*/ CredentialsSource.AUTH_ENTRY);
                     }
                 } else {
-                    Slog.d(TAG, "Additional content not received from authentication entry");
+                    Slog.i(TAG, "Additional content not received from authentication entry");
                     mProviderResponseDataHandler
                             .updateAuthEntryWithNoCredentialsReceived(entryKey);
                     updateStatusAndInvokeCallback(Status.NO_CREDENTIALS_FROM_AUTH_ENTRY,
@@ -296,12 +295,12 @@
                 if (mProviderResponseDataHandler.getRemoteEntry(entryKey) != null) {
                     onRemoteEntrySelected(providerPendingIntentResponse);
                 } else {
-                    Slog.d(TAG, "Unexpected remote entry key");
+                    Slog.i(TAG, "Unexpected remote entry key");
                     invokeCallbackOnInternalInvalidState();
                 }
                 break;
             default:
-                Slog.w(TAG, "Unsupported entry type selected");
+                Slog.i(TAG, "Unsupported entry type selected");
                 invokeCallbackOnInternalInvalidState();
         }
     }
@@ -323,13 +322,12 @@
     @Nullable
     protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
         if (!ProviderSession.isUiInvokingStatus(getStatus())) {
-            Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString());
+            Slog.i(TAG, "No data for UI from: " + mComponentName.flattenToString());
             return null;
         }
         if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
             return mProviderResponseDataHandler.toGetCredentialProviderData();
         }
-        Slog.d(TAG, "In prepareUiData response null");
         return null;
     }
 
@@ -382,7 +380,7 @@
                     getCredentialResponse);
             return;
         }
-        Slog.d(TAG, "Pending intent response contains no credential, or error "
+        Slog.i(TAG, "Pending intent response contains no credential, or error "
                 + "for a credential entry");
         invokeCallbackOnInternalInvalidState();
     }
@@ -413,11 +411,9 @@
      */
     private boolean onAuthenticationEntrySelected(
             @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
-        Log.i(TAG, "onAuthenticationEntrySelected");
         // Authentication entry is expected to have a BeginGetCredentialResponse instance. If it
         // does not have it, we remove the authentication entry and do not add any more content.
         if (providerPendingIntentResponse == null) {
-            Log.i(TAG, "providerPendingIntentResponse is null");
             // Nothing received. This is equivalent to no content received.
             return false;
         }
@@ -462,7 +458,7 @@
     /** Returns true if either an exception or a response is found. */
     private void onActionEntrySelected(ProviderPendingIntentResponse
             providerPendingIntentResponse) {
-        Slog.d(TAG, "onActionEntrySelected");
+        Slog.i(TAG, "onActionEntrySelected");
         onCredentialEntrySelected(providerPendingIntentResponse);
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index c10f564..b0b72bc 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -171,11 +171,11 @@
     @Override
     protected ProviderData prepareUiData() {
         if (!ProviderSession.isUiInvokingStatus(getStatus())) {
-            Slog.d(TAG, "No date for UI coming from: " + mComponentName.flattenToString());
+            Slog.i(TAG, "No date for UI coming from: " + mComponentName.flattenToString());
             return null;
         }
         if (mProviderResponse == null) {
-            Slog.w(TAG, "In prepareUiData but response is null. This is strange.");
+            Slog.w(TAG, "response is null when preparing ui data. This is strange.");
             return null;
         }
         return new GetCredentialProviderData.Builder(
@@ -196,13 +196,13 @@
             case CREDENTIAL_ENTRY_KEY:
                 CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
                 if (credentialEntry == null) {
-                    Slog.w(TAG, "Unexpected credential entry key");
+                    Slog.i(TAG, "Unexpected credential entry key");
                     return;
                 }
                 onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
                 break;
             default:
-                Slog.w(TAG, "Unsupported entry type selected");
+                Slog.i(TAG, "Unsupported entry type selected");
         }
     }
 
@@ -256,6 +256,7 @@
 
     @Override
     protected void invokeSession() {
+        startCandidateMetrics();
         mProviderResponse = mCredentialDescriptionRegistry
                 .getFilteredResultForProvider(mCredentialProviderPackageName,
                         mElementKeys);
@@ -266,7 +267,7 @@
                 .collect(Collectors.toList());
         updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
                 /*source=*/ CredentialsSource.REGISTRY);
-        // TODO(use metric later)
+        mProviderSessionMetric.collectCandidateEntryMetrics(mCredentialEntries);
     }
 
     @Nullable
@@ -279,7 +280,7 @@
             GetCredentialException exception = PendingIntentResultHandler
                     .extractGetCredentialException(pendingIntentResponse.getResultData());
             if (exception != null) {
-                Slog.d(TAG, "Pending intent contains provider exception");
+                Slog.i(TAG, "Pending intent contains provider exception");
                 return exception;
             }
         } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index d02a8c1..73fdc1c 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -268,12 +268,9 @@
                     /*pId=*/-1, appInfo.uid) == PackageManager.PERMISSION_GRANTED) {
                 return true;
             }
-        } catch (SecurityException e) {
+        } catch (SecurityException | PackageManager.NameNotFoundException e) {
             Slog.e(TAG, "Error getting info for " + mComponentName.flattenToString(), e);
             return false;
-        } catch (PackageManager.NameNotFoundException e) {
-            Slog.i(TAG, "Error getting info for " + mComponentName.flattenToString(), e);
-            return false;
         }
         return false;
     }
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index 0ad73c9..f5e3b86 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -292,13 +292,13 @@
             callback.onProviderResponseSuccess(result);
         } else {
             if (error instanceof TimeoutException) {
-                Slog.d(TAG, "Remote provider response timed tuo for: " + mComponentName);
+                Slog.i(TAG, "Remote provider response timed tuo for: " + mComponentName);
                 dispatchCancellationSignal(cancellationSink.get());
                 callback.onProviderResponseFailure(
                         CredentialProviderErrors.ERROR_TIMEOUT,
                         null);
             } else if (error instanceof CancellationException) {
-                Slog.d(TAG, "Cancellation exception for remote provider: " + mComponentName);
+                Slog.i(TAG, "Cancellation exception for remote provider: " + mComponentName);
                 dispatchCancellationSignal(cancellationSink.get());
                 callback.onProviderResponseFailure(
                         CredentialProviderErrors.ERROR_TASK_CANCELED,
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 15a30e4..7caa921 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -201,7 +201,7 @@
     }
 
     protected void finishSession(boolean propagateCancellation) {
-        Slog.d(TAG, "finishing session with propagateCancellation " + propagateCancellation);
+        Slog.i(TAG, "finishing session with propagateCancellation " + propagateCancellation);
         if (propagateCancellation) {
             mProviders.values().forEach(ProviderSession::cancelProviderRemoteSession);
         }
@@ -265,7 +265,7 @@
 
     @NonNull
     protected ArrayList<ProviderData> getProviderDataForUi() {
-        Slog.d(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size());
+        Slog.i(TAG, "For ui, provider data size: " + mProviders.size());
         ArrayList<ProviderData> providerDataList = new ArrayList<>();
         mRequestSessionMetric.logCandidatePhaseMetrics(mProviders);
 
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
index b99f28d..1930a48 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
@@ -27,7 +27,7 @@
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_UNKNOWN;
 
 import android.credentials.ui.RequestInfo;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.AbstractMap;
 import java.util.Map;
@@ -79,7 +79,7 @@
      */
     public static int getMetricCodeFromRequestInfo(String stringKey) {
         if (!sRequestInfoToMetric.containsKey(stringKey)) {
-            Log.w(TAG, "Attempted to use an unsupported string key request info");
+            Slog.i(TAG, "Attempted to use an unsupported string key request info");
             return UNKNOWN.mInnerMetricCode;
         }
         return sRequestInfoToMetric.get(stringKey);
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
index 0e1e0389..07af654 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
@@ -27,8 +27,6 @@
  * though collection will begin in the candidate phase when the user begins browsing options.
  */
 public class CandidateBrowsingPhaseMetric {
-
-    private static final String TAG = "CandidateBrowsingPhaseMetric";
     // The session id associated with the API Call this candidate provider is a part of, default -1
     private int mSessionId = -1;
     // The EntryEnum that was pressed, defaults to -1
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
index 721d3d7..3ea9b1ce 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
@@ -16,21 +16,18 @@
 
 package com.android.server.credentials.metrics;
 
-import android.util.IntArray;
-import android.util.Log;
+import android.util.Slog;
 
 import com.android.server.credentials.MetricUtilities;
+import com.android.server.credentials.metrics.shared.ResponseCollective;
 
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
+import java.util.Map;
 
 /**
  * The central candidate provider metric object that mimics our defined metric setup.
  * Some types are redundant across these metric collectors, but that has debug use-cases as
  * these data-types are available at different moments of the flow (and typically, one can feed
  * into the next).
- * TODO(b/270403549) - iterate on this in V3+
  */
 public class CandidatePhaseMetric {
 
@@ -56,26 +53,12 @@
     private int mProviderQueryStatus = -1;
     // Indicates if an exception was thrown by this provider, false by default
     private boolean mHasException = false;
-    // Indicates the number of total entries available. We can also locally store the entries, but
-    // cannot emit them in the current split form. TODO(b/271135048) - possibly readjust candidate
-    // entries. Also, it may be okay to remove this and instead aggregate from inner counts.
-    // Defaults to -1
-    private int mNumEntriesTotal = -1;
-    // The count of action entries from this provider, defaults to -1
-    private int mActionEntryCount = -1;
-    // The count of credential entries from this provider, defaults to -1
-    private int mCredentialEntryCount = -1;
-    // The *type-count* of the credential entries, defaults to -1
-    private int mCredentialEntryTypeCount = -1;
-    // The count of remote entries from this provider, defaults to -1
-    private int mRemoteEntryCount = -1;
-    // The count of authentication entries from this provider, defaults to -1
-    private int mAuthenticationEntryCount = -1;
-    // Gathered to pass on to chosen provider when required
-    private final IntArray mAvailableEntries = new IntArray();
-    // The *framework only* exception held by this provider, empty string by default
     private String mFrameworkException = "";
 
+    // Stores the response credential information, as well as the response entry information which
+    // by default, contains empty info
+    private ResponseCollective mResponseCollective = new ResponseCollective(Map.of(), Map.of());
+
     public CandidatePhaseMetric() {
     }
 
@@ -129,7 +112,7 @@
      */
     public int getTimestampFromReferenceStartMicroseconds(long specificTimestamp) {
         if (specificTimestamp < mServiceBeganTimeNanoseconds) {
-            Log.i(TAG, "The timestamp is before service started, falling back to default int");
+            Slog.i(TAG, "The timestamp is before service started, falling back to default int");
             return MetricUtilities.DEFAULT_INT_32;
         }
         return (int) ((specificTimestamp
@@ -186,88 +169,13 @@
         return mHasException;
     }
 
-    /* -------------- Number of Entries ---------------- */
-
-    public void setNumEntriesTotal(int numEntriesTotal) {
-        mNumEntriesTotal = numEntriesTotal;
+    /* -------------- The Entries and Responses Gathered ---------------- */
+    public void setResponseCollective(ResponseCollective responseCollective) {
+        mResponseCollective = responseCollective;
     }
 
-    public int getNumEntriesTotal() {
-        return mNumEntriesTotal;
-    }
-
-    /* -------------- Count of Action Entries ---------------- */
-
-    public void setActionEntryCount(int actionEntryCount) {
-        mActionEntryCount = actionEntryCount;
-    }
-
-    public int getActionEntryCount() {
-        return mActionEntryCount;
-    }
-
-    /* -------------- Count of Credential Entries ---------------- */
-
-    public void setCredentialEntryCount(int credentialEntryCount) {
-        mCredentialEntryCount = credentialEntryCount;
-    }
-
-    public int getCredentialEntryCount() {
-        return mCredentialEntryCount;
-    }
-
-    /* -------------- Count of Credential Entry Types ---------------- */
-
-    public void setCredentialEntryTypeCount(int credentialEntryTypeCount) {
-        mCredentialEntryTypeCount = credentialEntryTypeCount;
-    }
-
-    public int getCredentialEntryTypeCount() {
-        return mCredentialEntryTypeCount;
-    }
-
-    /* -------------- Count of Remote Entries ---------------- */
-
-    public void setRemoteEntryCount(int remoteEntryCount) {
-        mRemoteEntryCount = remoteEntryCount;
-    }
-
-    public int getRemoteEntryCount() {
-        return mRemoteEntryCount;
-    }
-
-    /* -------------- Count of Authentication Entries ---------------- */
-
-    public void setAuthenticationEntryCount(int authenticationEntryCount) {
-        mAuthenticationEntryCount = authenticationEntryCount;
-    }
-
-    public int getAuthenticationEntryCount() {
-        return mAuthenticationEntryCount;
-    }
-
-    /* -------------- The Entries Gathered ---------------- */
-
-    /**
-     * Allows adding an entry record to this metric collector, which can then be propagated to
-     * the final phase to retain information on the data available to the candidate.
-     *
-     * @param e the entry enum collected by the candidate provider associated with this metric
-     *          collector
-     */
-    public void addEntry(EntryEnum e) {
-        mAvailableEntries.add(e.getMetricCode());
-    }
-
-    /**
-     * Returns a safely copied list of the entries captured by this metric collector associated
-     * with a particular candidate provider.
-     *
-     * @return the full collection of entries encountered by the candidate provider associated with
-     * this metric
-     */
-    public List<Integer> getAvailableEntries() {
-        return Arrays.stream(mAvailableEntries.toArray()).boxed().collect(Collectors.toList());
+    public ResponseCollective getResponseCollective() {
+        return mResponseCollective;
     }
 
     /* ------ Framework Exception for this Candidate ------ */
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
index c80cc24..93a8290 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
@@ -16,12 +16,12 @@
 
 package com.android.server.credentials.metrics;
 
-import android.util.Log;
+import android.util.Slog;
 
 import com.android.server.credentials.MetricUtilities;
+import com.android.server.credentials.metrics.shared.ResponseCollective;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Map;
 
 /**
  * The central chosen provider metric object that mimics our defined metric setup. This is used
@@ -29,11 +29,8 @@
  * Some types are redundant across these metric collectors, but that has debug use-cases as
  * these data-types are available at different moments of the flow (and typically, one can feed
  * into the next).
- * TODO(b/270403549) - iterate on this in V3+
  */
 public class ChosenProviderFinalPhaseMetric {
-
-    // TODO(b/270403549) - applies elsewhere, likely removed or replaced w/ some hashed/count index
     private static final String TAG = "ChosenFinalPhaseMetric";
     // The session id associated with this API call, used to unite split emits
     private int mSessionId = -1;
@@ -69,21 +66,10 @@
     private int mChosenProviderStatus = -1;
     // Indicates if an exception was thrown by this provider, false by default
     private boolean mHasException = false;
-    // Indicates the number of total entries available, defaults to -1. Not presently emitted, but
-    // left as a utility
-    private int mNumEntriesTotal = -1;
-    // The count of action entries from this provider, defaults to -1
-    private int mActionEntryCount = -1;
-    // The count of credential entries from this provider, defaults to -1
-    private int mCredentialEntryCount = -1;
-    // The *type-count* of the credential entries, defaults to -1
-    private int mCredentialEntryTypeCount = -1;
-    // The count of remote entries from this provider, defaults to -1
-    private int mRemoteEntryCount = -1;
-    // The count of authentication entries from this provider, defaults to -1
-    private int mAuthenticationEntryCount = -1;
-    // Gathered to pass on to chosen provider when required
-    private List<Integer> mAvailableEntries = new ArrayList<>();
+
+    // Stores the response credential information, as well as the response entry information which
+    // by default, contains empty info
+    private ResponseCollective mResponseCollective = new ResponseCollective(Map.of(), Map.of());
 
 
     public ChosenProviderFinalPhaseMetric() {
@@ -230,7 +216,7 @@
      */
     public int getTimestampFromReferenceStartMicroseconds(long specificTimestamp) {
         if (specificTimestamp < mServiceBeganTimeNanoseconds) {
-            Log.i(TAG, "The timestamp is before service started, falling back to default int");
+            Slog.i(TAG, "The timestamp is before service started, falling back to default int");
             return MetricUtilities.DEFAULT_INT_32;
         }
         return (int) ((specificTimestamp
@@ -267,87 +253,6 @@
         return mUiReturned;
     }
 
-    /* -------------- Number of Entries ---------------- */
-
-    public void setNumEntriesTotal(int numEntriesTotal) {
-        mNumEntriesTotal = numEntriesTotal;
-    }
-
-    public int getNumEntriesTotal() {
-        return mNumEntriesTotal;
-    }
-
-    /* -------------- Count of Action Entries ---------------- */
-
-    public void setActionEntryCount(int actionEntryCount) {
-        mActionEntryCount = actionEntryCount;
-    }
-
-    public int getActionEntryCount() {
-        return mActionEntryCount;
-    }
-
-    /* -------------- Count of Credential Entries ---------------- */
-
-    public void setCredentialEntryCount(int credentialEntryCount) {
-        mCredentialEntryCount = credentialEntryCount;
-    }
-
-    public int getCredentialEntryCount() {
-        return mCredentialEntryCount;
-    }
-
-    /* -------------- Count of Credential Entry Types ---------------- */
-
-    public void setCredentialEntryTypeCount(int credentialEntryTypeCount) {
-        mCredentialEntryTypeCount = credentialEntryTypeCount;
-    }
-
-    public int getCredentialEntryTypeCount() {
-        return mCredentialEntryTypeCount;
-    }
-
-    /* -------------- Count of Remote Entries ---------------- */
-
-    public void setRemoteEntryCount(int remoteEntryCount) {
-        mRemoteEntryCount = remoteEntryCount;
-    }
-
-    public int getRemoteEntryCount() {
-        return mRemoteEntryCount;
-    }
-
-    /* -------------- Count of Authentication Entries ---------------- */
-
-    public void setAuthenticationEntryCount(int authenticationEntryCount) {
-        mAuthenticationEntryCount = authenticationEntryCount;
-    }
-
-    public int getAuthenticationEntryCount() {
-        return mAuthenticationEntryCount;
-    }
-
-    /* -------------- The Entries Gathered ---------------- */
-
-    /**
-     * Sets the collected list of entries from the candidate phase to be retrievable in the
-     * chosen phase in a semantically correct way.
-     */
-    public void setAvailableEntries(List<Integer> entries) {
-        mAvailableEntries = new ArrayList<>(entries); // no alias copy
-    }
-
-    /**
-     * Returns a list of the entries captured by this metric collector associated
-     * with a particular chosen provider.
-     *
-     * @return the full collection of entries encountered by the chosen provider during the
-     * candidate phase.
-     */
-    public List<Integer> getAvailableEntries() {
-        return new ArrayList<>(mAvailableEntries); // no alias copy
-    }
-
     /* -------------- Has Exception ---------------- */
 
     public void setHasException(boolean hasException) {
@@ -357,4 +262,14 @@
     public boolean isHasException() {
         return mHasException;
     }
+
+    /* -------------- The Entries and Responses Gathered ---------------- */
+
+    public void setResponseCollective(ResponseCollective responseCollective) {
+        mResponseCollective = responseCollective;
+    }
+
+    public ResponseCollective getResponseCollective() {
+        return mResponseCollective;
+    }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java b/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
index b9125dd..530f01c 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
@@ -26,7 +26,7 @@
 import static com.android.server.credentials.ProviderGetSession.CREDENTIAL_ENTRY_KEY;
 import static com.android.server.credentials.ProviderGetSession.REMOTE_ENTRY_KEY;
 
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.AbstractMap;
 import java.util.Map;
@@ -77,7 +77,7 @@
      */
     public static int getMetricCodeFromString(String stringKey) {
         if (!sKeyToEntryCode.containsKey(stringKey)) {
-            Log.w(TAG, "Attempted to use an unsupported string key entry type");
+            Slog.i(TAG, "Attempted to use an unsupported string key entry type");
             return UNKNOWN.mInnerMetricCode;
         }
         return sKeyToEntryCode.get(stringKey);
diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
index 0ecd9cc..060e56c 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
@@ -16,8 +16,6 @@
 
 package com.android.server.credentials.metrics;
 
-import android.util.Log;
-
 import java.util.LinkedHashMap;
 import java.util.Map;
 
@@ -26,7 +24,6 @@
  * Some types are redundant across these metric collectors, but that has debug use-cases as
  * these data-types are available at different moments of the flow (and typically, one can feed
  * into the next).
- * TODO(b/270403549) - iterate on this in V3+
  */
 public class InitialPhaseMetric {
     private static final String TAG = "InitialPhaseMetric";
@@ -47,10 +44,9 @@
     private long mCredentialServiceBeginQueryTimeNanoseconds = -1;
 
     // Indicates if the origin was specified when making this API request
-    // TODO(b/271135048) - Emit once metrics approved
     private boolean mOriginSpecified = false;
 
-    // Stores the deduped request information, particularly {"req":5}.
+    // Stores the deduped request information, particularly {"req":5}
     private Map<String, Integer> mRequestCounts = new LinkedHashMap<>();
 
 
@@ -140,26 +136,20 @@
     }
 
     /**
-     * Reruns the unique, deduped, request classtypes for logging.
+     * Returns the unique, deduped, request classtypes for logging.
      * @return a string array for deduped classtypes
      */
     public String[] getUniqueRequestStrings() {
-        if (mRequestCounts.isEmpty()) {
-            Log.w(TAG, "There are no unique string request types collected");
-        }
         String[] result = new String[mRequestCounts.keySet().size()];
         mRequestCounts.keySet().toArray(result);
         return result;
     }
 
     /**
-     * Reruns the unique, deduped, request classtype counts for logging.
+     * Returns the unique, deduped, request classtype counts for logging.
      * @return a string array for deduped classtype counts
      */
     public int[] getUniqueRequestCounts() {
-        if (mRequestCounts.isEmpty()) {
-            Log.w(TAG, "There are no unique string request type counts collected");
-        }
         return mRequestCounts.values().stream().mapToInt(Integer::intValue).toArray();
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
index 9a88255..f011b55 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
@@ -16,15 +16,21 @@
 
 package com.android.server.credentials.metrics;
 
+import static com.android.server.credentials.MetricUtilities.DELTA_CUT;
+import static com.android.server.credentials.MetricUtilities.generateMetricKey;
+
 import android.annotation.NonNull;
 import android.service.credentials.BeginCreateCredentialResponse;
 import android.service.credentials.BeginGetCredentialResponse;
 import android.service.credentials.CredentialEntry;
-import android.util.Log;
+import android.util.Slog;
 
 import com.android.server.credentials.MetricUtilities;
+import com.android.server.credentials.metrics.shared.ResponseCollective;
 
-import java.util.stream.Collectors;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * Provides contextual metric collection for objects generated from
@@ -68,7 +74,7 @@
         try {
             mCandidatePhasePerProviderMetric.setFrameworkException(exceptionType);
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error during candidate exception metric logging: " + e);
         }
     }
 
@@ -97,7 +103,7 @@
                                 .getMetricCode());
             }
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error during candidate update metric logging: " + e);
         }
     }
 
@@ -118,7 +124,7 @@
                     initMetric.getCredentialServiceStartedTimeNanoseconds());
             mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error during candidate setup metric logging: " + e);
         }
     }
 
@@ -138,59 +144,79 @@
                 beginCreateCredentialResponseCollectionCandidateEntryMetrics(
                         (BeginCreateCredentialResponse) response);
             } else {
-                Log.i(TAG, "Your response type is unsupported for metric logging");
+                Slog.i(TAG, "Your response type is unsupported for metric logging");
             }
-
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error during candidate entry metric logging: " + e);
         }
     }
 
+    /**
+     * Once entries are received from the registry, this helps collect their info for metric
+     * purposes.
+     *
+     * @param entries contains matching entries from the Credential Registry.
+     */
+    public void collectCandidateEntryMetrics(List<CredentialEntry> entries) {
+        int numCredEntries = entries.size();
+        int numRemoteEntry = MetricUtilities.ZERO;
+        int numActionEntries = MetricUtilities.ZERO;
+        int numAuthEntries = MetricUtilities.ZERO;
+        Map<EntryEnum, Integer> entryCounts = new LinkedHashMap<>();
+        Map<String, Integer> responseCounts = new LinkedHashMap<>();
+        entryCounts.put(EntryEnum.REMOTE_ENTRY, numRemoteEntry);
+        entryCounts.put(EntryEnum.CREDENTIAL_ENTRY, numCredEntries);
+        entryCounts.put(EntryEnum.ACTION_ENTRY, numActionEntries);
+        entryCounts.put(EntryEnum.AUTHENTICATION_ENTRY, numAuthEntries);
+
+        entries.forEach(entry -> {
+            String entryKey = generateMetricKey(entry.getType(), DELTA_CUT);
+            responseCounts.put(entryKey, responseCounts.getOrDefault(entryKey, 0) + 1);
+        });
+
+        ResponseCollective responseCollective = new ResponseCollective(responseCounts, entryCounts);
+        mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective);
+    }
+
     private void beginCreateCredentialResponseCollectionCandidateEntryMetrics(
             BeginCreateCredentialResponse response) {
+        Map<EntryEnum, Integer> entryCounts = new LinkedHashMap<>();
         var createEntries = response.getCreateEntries();
-        int numRemoteEntry = MetricUtilities.ZERO;
-        if (response.getRemoteCreateEntry() != null) {
-            numRemoteEntry = MetricUtilities.UNIT;
-            mCandidatePhasePerProviderMetric.addEntry(EntryEnum.REMOTE_ENTRY);
-        }
-        int numCreateEntries =
-                createEntries == null ? MetricUtilities.ZERO : createEntries.size();
-        if (numCreateEntries > MetricUtilities.ZERO) {
-            createEntries.forEach(c ->
-                    mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
-        }
-        mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCreateEntries + numRemoteEntry);
-        mCandidatePhasePerProviderMetric.setRemoteEntryCount(numRemoteEntry);
-        mCandidatePhasePerProviderMetric.setCredentialEntryCount(numCreateEntries);
-        mCandidatePhasePerProviderMetric.setCredentialEntryTypeCount(MetricUtilities.UNIT);
+        int numRemoteEntry = response.getRemoteCreateEntry() != null ? MetricUtilities.ZERO :
+                MetricUtilities.UNIT;
+        int numCreateEntries = createEntries.size();
+        entryCounts.put(EntryEnum.REMOTE_ENTRY, numRemoteEntry);
+        entryCounts.put(EntryEnum.CREDENTIAL_ENTRY, numCreateEntries);
+
+        Map<String, Integer> responseCounts = new LinkedHashMap<>();
+        responseCounts.put(MetricUtilities.DEFAULT_STRING, numCreateEntries);
+        // We don't store create response because it's directly related to the request
+        // We do still store the count, however
+
+        ResponseCollective responseCollective = new ResponseCollective(responseCounts, entryCounts);
+        mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective);
     }
 
     private void beginGetCredentialResponseCollectionCandidateEntryMetrics(
             BeginGetCredentialResponse response) {
+        Map<EntryEnum, Integer> entryCounts = new LinkedHashMap<>();
+        Map<String, Integer> responseCounts = new LinkedHashMap<>();
         int numCredEntries = response.getCredentialEntries().size();
         int numActionEntries = response.getActions().size();
         int numAuthEntries = response.getAuthenticationActions().size();
-        int numRemoteEntry = MetricUtilities.ZERO;
-        if (response.getRemoteCredentialEntry() != null) {
-            numRemoteEntry = MetricUtilities.UNIT;
-            mCandidatePhasePerProviderMetric.addEntry(EntryEnum.REMOTE_ENTRY);
-        }
-        response.getCredentialEntries().forEach(c ->
-                mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
-        response.getActions().forEach(c ->
-                mCandidatePhasePerProviderMetric.addEntry(EntryEnum.ACTION_ENTRY));
-        response.getAuthenticationActions().forEach(c ->
-                mCandidatePhasePerProviderMetric.addEntry(EntryEnum.AUTHENTICATION_ENTRY));
-        mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCredEntries + numAuthEntries
-                + numActionEntries + numRemoteEntry);
-        mCandidatePhasePerProviderMetric.setCredentialEntryCount(numCredEntries);
-        int numTypes = (response.getCredentialEntries().stream()
-                .map(CredentialEntry::getType).collect(
-                        Collectors.toSet())).size(); // Dedupe type strings
-        mCandidatePhasePerProviderMetric.setCredentialEntryTypeCount(numTypes);
-        mCandidatePhasePerProviderMetric.setActionEntryCount(numActionEntries);
-        mCandidatePhasePerProviderMetric.setAuthenticationEntryCount(numAuthEntries);
-        mCandidatePhasePerProviderMetric.setRemoteEntryCount(numRemoteEntry);
+        int numRemoteEntry = response.getRemoteCredentialEntry() != null ? MetricUtilities.ZERO :
+                MetricUtilities.UNIT;
+        entryCounts.put(EntryEnum.REMOTE_ENTRY, numRemoteEntry);
+        entryCounts.put(EntryEnum.CREDENTIAL_ENTRY, numCredEntries);
+        entryCounts.put(EntryEnum.ACTION_ENTRY, numActionEntries);
+        entryCounts.put(EntryEnum.AUTHENTICATION_ENTRY, numAuthEntries);
+
+        response.getCredentialEntries().forEach(entry -> {
+            String entryKey = generateMetricKey(entry.getType(), DELTA_CUT);
+            responseCounts.put(entryKey, responseCounts.getOrDefault(entryKey, 0) + 1);
+        });
+
+        ResponseCollective responseCollective = new ResponseCollective(responseCounts, entryCounts);
+        mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective);
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
index 547c09a..4624e0b 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
@@ -24,7 +24,7 @@
 import android.credentials.GetCredentialRequest;
 import android.credentials.ui.UserSelectionDialogResult;
 import android.os.IBinder;
-import android.util.Log;
+import android.util.Slog;
 
 import com.android.server.credentials.ProviderSession;
 
@@ -90,7 +90,7 @@
             mInitialPhaseMetric.setCallerUid(mCallingUid);
             mInitialPhaseMetric.setApiName(metricCode);
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error collecting initial metrics: " + e);
         }
     }
 
@@ -103,7 +103,7 @@
         try {
             mChosenProviderFinalPhaseMetric.setUiReturned(uiReturned);
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error collecting ui end time metric: " + e);
         }
     }
 
@@ -116,7 +116,7 @@
         try {
             mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(uiCallStartTime);
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error collecting ui start metric: " + e);
         }
     }
 
@@ -132,7 +132,7 @@
             mChosenProviderFinalPhaseMetric.setUiReturned(uiReturned);
             mChosenProviderFinalPhaseMetric.setUiCallEndTimeNanoseconds(uiEndTimestamp);
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error collecting ui response metric: " + e);
         }
     }
 
@@ -146,7 +146,7 @@
         try {
             mChosenProviderFinalPhaseMetric.setChosenProviderStatus(status);
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error setting chosen provider status metric: " + e);
         }
     }
 
@@ -159,7 +159,7 @@
         try {
             mInitialPhaseMetric.setOriginSpecified(origin);
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error collecting create flow metric: " + e);
         }
     }
 
@@ -175,7 +175,7 @@
                 uniqueRequestCounts.put(optionKey, uniqueRequestCounts.get(optionKey) + 1);
             });
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during get request metric logging: " + e);
+            Slog.i(TAG, "Unexpected error during get request metric logging: " + e);
         }
         return uniqueRequestCounts;
     }
@@ -190,7 +190,7 @@
             mInitialPhaseMetric.setOriginSpecified(request.getOrigin() != null);
             mInitialPhaseMetric.setRequestCounts(getRequestCountMap(request));
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error collecting get flow metric: " + e);
         }
     }
 
@@ -213,7 +213,7 @@
             browsingPhaseMetric.setProviderUid(selectedProviderPhaseMetric.getCandidateUid());
             mCandidateBrowsingPhaseMetric.add(browsingPhaseMetric);
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error collecting browsing metric: " + e);
         }
     }
 
@@ -226,7 +226,7 @@
         try {
             mChosenProviderFinalPhaseMetric.setHasException(exceptionBitFinalPhase);
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error setting final exception metric: " + e);
         }
     }
 
@@ -244,7 +244,7 @@
             mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
                     finalStatus.getMetricCode());
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error during metric logging: " + e);
         }
     }
 
@@ -272,24 +272,11 @@
                     candidatePhaseMetric.getStartQueryTimeNanoseconds());
             mChosenProviderFinalPhaseMetric.setQueryEndTimeNanoseconds(candidatePhaseMetric
                     .getQueryFinishTimeNanoseconds());
-
-            mChosenProviderFinalPhaseMetric.setNumEntriesTotal(candidatePhaseMetric
-                    .getNumEntriesTotal());
-            mChosenProviderFinalPhaseMetric.setCredentialEntryCount(candidatePhaseMetric
-                    .getCredentialEntryCount());
-            mChosenProviderFinalPhaseMetric.setCredentialEntryTypeCount(
-                    candidatePhaseMetric.getCredentialEntryTypeCount());
-            mChosenProviderFinalPhaseMetric.setActionEntryCount(candidatePhaseMetric
-                    .getActionEntryCount());
-            mChosenProviderFinalPhaseMetric.setRemoteEntryCount(candidatePhaseMetric
-                    .getRemoteEntryCount());
-            mChosenProviderFinalPhaseMetric.setAuthenticationEntryCount(
-                    candidatePhaseMetric.getAuthenticationEntryCount());
-            mChosenProviderFinalPhaseMetric.setAvailableEntries(candidatePhaseMetric
-                    .getAvailableEntries());
+            mChosenProviderFinalPhaseMetric.setResponseCollective(
+                    candidatePhaseMetric.getResponseCollective());
             mChosenProviderFinalPhaseMetric.setFinalFinishTimeNanoseconds(System.nanoTime());
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error during metric candidate to final transfer: " + e);
         }
     }
 
@@ -312,7 +299,7 @@
                         /* apiStatus */ ApiStatus.FAILURE.getMetricCode());
             }
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error during final metric failure emit: " + e);
         }
     }
 
@@ -326,7 +313,7 @@
         try {
             logApiCalledCandidatePhase(providers, ++mSequenceCounter, mInitialPhaseMetric);
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error during candidate metric emit: " + e);
         }
     }
 
@@ -337,15 +324,11 @@
      */
     public void logApiCalledAtFinish(int apiStatus) {
         try {
-            // TODO (b/270403549) - this browsing phase object is fine but also have a new emit
-            // For the returned types by authentication entries - i.e. a CandidatePhase During
-            // Browse
-            // Possibly think of adding in more atoms for other APIs as well.
             logApiCalledFinalPhase(mChosenProviderFinalPhaseMetric, mCandidateBrowsingPhaseMetric,
                     apiStatus,
                     ++mSequenceCounter);
         } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+            Slog.i(TAG, "Unexpected error during final metric emit: " + e);
         }
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java b/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java
new file mode 100644
index 0000000..fd785c2
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials.metrics.shared;
+
+import android.annotation.NonNull;
+
+import com.android.server.credentials.metrics.EntryEnum;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Some data is directly shared between the
+ * {@link com.android.server.credentials.metrics.CandidatePhaseMetric} and the
+ * {@link com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric}. This
+ * aims to create an abstraction that holds that information, to avoid duplication.
+ *
+ * This class should be immutable and threadsafe once generated.
+ */
+public class ResponseCollective {
+    /*
+    Abstract Function (responseCounts, entryCounts) -> A 'ResponseCollective' containing information
+    about a chosen or candidate providers available responses, be they entries or credentials.
+
+    RepInvariant: mResponseCounts and mEntryCounts are always initialized
+
+    Threadsafe and Immutability: Once generated, the maps remain unchangeable. The object is
+    threadsafe and immutable, and safe from external changes. This is threadsafe because it is
+    immutable after creation and only allows reads, not writes.
+    */
+
+    private static final String TAG = "ResponseCollective";
+
+    // Stores the deduped credential response information, eg {"response":5} for this provider
+    private final Map<String, Integer> mResponseCounts;
+    // Stores the deduped entry information, eg {ENTRY_ENUM:5} for this provider
+    private final Map<EntryEnum, Integer> mEntryCounts;
+
+    public ResponseCollective(@NonNull Map<String, Integer> responseCounts,
+            @NonNull Map<EntryEnum, Integer> entryCounts) {
+        mResponseCounts = responseCounts == null ? new LinkedHashMap<>() :
+                new LinkedHashMap<>(responseCounts);
+        mEntryCounts = entryCounts == null ? new LinkedHashMap<>() :
+                new LinkedHashMap<>(entryCounts);
+    }
+
+    /**
+     * Returns the unique, deduped, response classtypes for logging associated with this provider.
+     *
+     * @return a string array for deduped classtypes
+     */
+    public String[] getUniqueResponseStrings() {
+        String[] result = new String[mResponseCounts.keySet().size()];
+        mResponseCounts.keySet().toArray(result);
+        return result;
+    }
+
+    /**
+     * Returns the unique, deduped, response classtype counts for logging associated with this
+     * provider.
+     * @return a string array for deduped classtype counts
+     */
+    public int[] getUniqueResponseCounts() {
+        return mResponseCounts.values().stream().mapToInt(Integer::intValue).toArray();
+    }
+
+    /**
+     * Returns the unique, deduped, entry types for logging associated with this provider.
+     * @return an int array for deduped entries
+     */
+    public int[] getUniqueEntries() {
+        return mEntryCounts.keySet().stream().mapToInt(Enum::ordinal).toArray();
+    }
+
+    /**
+     * Returns the unique, deduped, entry classtype counts for logging associated with this
+     * provider.
+     * @return a string array for deduped classtype counts
+     */
+    public int[] getUniqueEntryCounts() {
+        return mEntryCounts.values().stream().mapToInt(Integer::intValue).toArray();
+    }
+
+    /**
+     * Given a specific {@link EntryEnum}, this provides us with the count of that entry within
+     * this particular provider.
+     * @param e the entry enum with which we want to know the count of
+     * @return a count of this particular entry enum stored by this provider
+     */
+    public int getCountForEntry(EntryEnum e) {
+        return mEntryCounts.get(e);
+    }
+
+    /**
+     * Indicates the total number of existing entries for this provider.
+     * @return a count of the total number of entries for this provider
+     */
+    public int getNumEntriesTotal() {
+        return mEntryCounts.values().stream().mapToInt(Integer::intValue).sum();
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index cf49dcf..0c4830a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -684,6 +684,38 @@
         }
     }
 
+    <V> void transferPolicies(EnforcingAdmin oldAdmin, EnforcingAdmin newAdmin) {
+        Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet());
+        for (PolicyKey policy : globalPolicies) {
+            PolicyState<?> policyState = mGlobalPolicies.get(policy);
+            if (policyState.getPoliciesSetByAdmins().containsKey(oldAdmin)) {
+                PolicyDefinition<V> policyDefinition =
+                        (PolicyDefinition<V>) policyState.getPolicyDefinition();
+                PolicyValue<V> policyValue =
+                        (PolicyValue<V>) policyState.getPoliciesSetByAdmins().get(oldAdmin);
+                setGlobalPolicy(policyDefinition, newAdmin, policyValue);
+            }
+        }
+
+        for (int i = 0; i < mLocalPolicies.size(); i++) {
+            int userId = mLocalPolicies.keyAt(i);
+            Set<PolicyKey> localPolicies = new HashSet<>(
+                    mLocalPolicies.get(userId).keySet());
+            for (PolicyKey policy : localPolicies) {
+                PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
+                if (policyState.getPoliciesSetByAdmins().containsKey(oldAdmin)) {
+                    PolicyDefinition<V> policyDefinition =
+                            (PolicyDefinition<V>) policyState.getPolicyDefinition();
+                    PolicyValue<V> policyValue =
+                            (PolicyValue<V>) policyState.getPoliciesSetByAdmins().get(oldAdmin);
+                    setLocalPolicy(policyDefinition, newAdmin, policyValue, userId);
+                }
+            }
+        }
+
+        removePoliciesForAdmin(oldAdmin);
+    }
+
     private Set<UserRestrictionPolicyKey> getUserRestrictionPolicyKeysForAdminLocked(
             Map<PolicyKey, PolicyState<?>> policies,
             EnforcingAdmin admin) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 231fee3..3578b16 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -39,6 +39,7 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DISPLAY;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_FACTORY_RESET;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_FUN;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_INPUT_METHODS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_KEYGUARD;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCALE;
@@ -873,7 +874,7 @@
 
     // TODO(b/265683382) remove the flag after rollout.
     private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running";
-    private static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = false;
+    private static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = true;
 
     private static final String ENABLE_WORK_PROFILE_TELEPHONY_FLAG =
             "enable_work_profile_telephony";
@@ -3881,6 +3882,17 @@
         final ActiveAdmin adminToTransfer = policy.mAdminMap.get(outgoingReceiver);
         final int oldAdminUid = adminToTransfer.getUid();
 
+        if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
+            EnforcingAdmin oldAdmin =
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                            outgoingReceiver, userHandle, adminToTransfer);
+            EnforcingAdmin newAdmin =
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                            incomingReceiver, userHandle, adminToTransfer);
+
+            mDevicePolicyEngine.transferPolicies(oldAdmin, newAdmin);
+        }
+
         adminToTransfer.transfer(incomingDeviceInfo);
         policy.mAdminMap.remove(outgoingReceiver);
         policy.mAdminMap.put(incomingReceiver, adminToTransfer);
@@ -6050,7 +6062,7 @@
     @Override
     public void lockNow(int flags, String callerPackageName, boolean parent) {
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(callerPackageName);
         } else {
             caller = getCallerIdentity();
@@ -6062,7 +6074,7 @@
             ActiveAdmin admin;
             // Make sure the caller has any active admin with the right policy or
             // the required permission.
-            if (isPermissionCheckFlagEnabled()) {
+            if (isUnicornFlagEnabled()) {
                 admin = enforcePermissionAndGetEnforcingAdmin(
                         /* admin= */ null,
                         /* permission= */ MANAGE_DEVICE_POLICY_LOCK,
@@ -8916,13 +8928,13 @@
         }
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             // The effect of this policy is device-wide.
             enforcePermission(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
         } else {
@@ -8950,13 +8962,13 @@
             return false;
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             enforceCanQuery(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
         } else {
             Objects.requireNonNull(who, "ComponentName is null");
@@ -8979,13 +8991,13 @@
         }
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             // The effect of this policy is device-wide.
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     who,
@@ -9025,13 +9037,13 @@
         }
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             // The effect of this policy is device-wide.
             enforceCanQuery(SET_TIME_ZONE, caller.getPackageName(), UserHandle.USER_ALL);
         } else {
@@ -9334,7 +9346,7 @@
         }
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
@@ -9344,7 +9356,7 @@
         final int userHandle = caller.getUserId();
         int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
         synchronized (getLockObject()) {
-            if (isPermissionCheckFlagEnabled()) {
+            if (isUnicornFlagEnabled()) {
                 // SUPPORT USES_POLICY_DISABLE_KEYGUARD_FEATURES
                 EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
                         who, MANAGE_DEVICE_POLICY_KEYGUARD, caller.getPackageName(),
@@ -9423,13 +9435,14 @@
 
         synchronized (getLockObject()) {
             if (who != null) {
-                if (isPermissionCheckFlagEnabled()) {
-                    EnforcingAdmin admin = getEnforcingAdminForCaller(
-                            who, who.getPackageName());
+                if (isUnicornFlagEnabled()) {
+                    EnforcingAdmin admin = getEnforcingAdminForPackage(
+                            who, who.getPackageName(), userHandle);
                     Integer features = mDevicePolicyEngine.getLocalPolicySetByAdmin(
                             PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
                             admin,
                             affectedUserId);
+
                     return features == null ? 0 : features;
                 } else {
                     ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
@@ -9437,7 +9450,7 @@
                 }
             }
 
-            if (isPermissionCheckFlagEnabled()) {
+            if (isUnicornFlagEnabled()) {
                 Integer features = mDevicePolicyEngine.getResolvedPolicy(
                         PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
                         affectedUserId);
@@ -9998,10 +10011,6 @@
                         "clearDeviceOwner can only be called by the device owner");
             }
             enforceUserUnlocked(deviceOwnerUserId);
-            DevicePolicyData policy = getUserData(deviceOwnerUserId);
-            if (policy.mPasswordTokenHandle != 0) {
-                mLockPatternUtils.removeEscrowToken(policy.mPasswordTokenHandle, deviceOwnerUserId);
-            }
 
             final ActiveAdmin admin = getDeviceOwnerAdminLocked();
             mInjector.binderWithCleanCallingIdentity(() -> {
@@ -10056,6 +10065,10 @@
         }
         final DevicePolicyData policyData = getUserData(userId);
         policyData.mCurrentInputMethodSet = false;
+        if (policyData.mPasswordTokenHandle != 0) {
+            mLockPatternUtils.removeEscrowToken(policyData.mPasswordTokenHandle, userId);
+            policyData.mPasswordTokenHandle = 0;
+        }
         saveSettingsLocked(userId);
         mPolicyCache.onUserRemoved(userId);
         final DevicePolicyData systemPolicyData = getUserData(UserHandle.USER_SYSTEM);
@@ -10747,7 +10760,9 @@
     @VisibleForTesting
     boolean hasDeviceIdAccessUnchecked(String packageName, int uid) {
         final int userId = UserHandle.getUserId(uid);
-        if (isPermissionCheckFlagEnabled()) {
+        // TODO(b/280048070): Introduce a permission to handle device ID access
+        if (isPermissionCheckFlagEnabled()
+                && !(isUidProfileOwnerLocked(uid) || isUidDeviceOwnerLocked(uid))) {
             return hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, packageName, userId);
         } else {
             ComponentName deviceOwner = getDeviceOwnerComponent(true);
@@ -11634,7 +11649,7 @@
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS);
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     who,
                     MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
@@ -12195,7 +12210,7 @@
         }
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
@@ -12205,7 +12220,7 @@
         int userId = getProfileParentUserIfRequested(
                 caller.getUserId(), calledOnParentInstance);
         if (calledOnParentInstance) {
-            if (!isPermissionCheckFlagEnabled()) {
+            if (!isPolicyEngineForFinanceFlagEnabled()) {
                 Preconditions.checkCallAuthorization(
                         isProfileOwnerOfOrganizationOwnedDevice(caller));
             }
@@ -12213,7 +12228,7 @@
                     "Permitted input methods must allow all input methods or only "
                             + "system input methods when called on the parent instance of an "
                             + "organization-owned device");
-        } else if (!isPermissionCheckFlagEnabled()) {
+        } else if (!isPolicyEngineForFinanceFlagEnabled()) {
             Preconditions.checkCallAuthorization(
                     isDefaultDeviceOwner(caller) || isProfileOwner(caller));
         }
@@ -12241,7 +12256,9 @@
 
         synchronized (getLockObject()) {
             if (isPolicyEngineForFinanceFlagEnabled()) {
-                EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackageName);
+                EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
+                        who, MANAGE_DEVICE_POLICY_INPUT_METHODS,
+                        caller.getPackageName(), userId);
                 mDevicePolicyEngine.setLocalPolicy(
                         PolicyDefinition.PERMITTED_INPUT_METHODS,
                         admin,
@@ -13059,7 +13076,7 @@
             String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
                     who,
                     MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
@@ -13129,7 +13146,7 @@
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         ActiveAdmin admin;
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     who,
                     MANAGE_DEVICE_POLICY_PACKAGE_STATE,
@@ -13226,7 +13243,7 @@
     public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             enforcePermission(
                     MANAGE_DEVICE_POLICY_PACKAGE_STATE,
                     caller.getPackageName(),
@@ -13436,6 +13453,13 @@
     public void setUserRestrictionGlobally(String callerPackage, String key) {
         final CallerIdentity caller = getCallerIdentity(callerPackage);
 
+        EnforcingAdmin admin = enforcePermissionForUserRestriction(
+                /* who= */ null,
+                key,
+                caller.getPackageName(),
+                UserHandle.USER_ALL
+        );
+
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION);
 
         if (!isPolicyEngineForFinanceFlagEnabled()) {
@@ -13452,13 +13476,6 @@
             throw new IllegalArgumentException("Invalid restriction key: " + key);
         }
 
-        EnforcingAdmin admin = enforcePermissionForUserRestriction(
-                /* who= */ null,
-                key,
-                caller.getPackageName(),
-                UserHandle.USER_ALL
-        );
-
         setGlobalUserRestrictionInternal(admin, key, /* enabled= */ true);
 
         logUserRestrictionCall(key, /* enabled= */ true, /* parent= */ false, caller);
@@ -13832,7 +13849,7 @@
             boolean hidden, boolean parent) {
         CallerIdentity caller = getCallerIdentity(who, callerPackage);
         final int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId();
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             // TODO: We need to ensure the delegate with DELEGATION_PACKAGE_ACCESS can do this
             enforcePermission(MANAGE_DEVICE_POLICY_PACKAGE_STATE, caller.getPackageName(), userId);
         } else {
@@ -13851,7 +13868,7 @@
         boolean result;
         synchronized (getLockObject()) {
             if (parent) {
-                if (!isPermissionCheckFlagEnabled()) {
+                if (!isPolicyEngineForFinanceFlagEnabled()) {
                     Preconditions.checkCallAuthorization(
                             isProfileOwnerOfOrganizationOwnedDevice(
                                     caller.getUserId()) && isManagedProfile(caller.getUserId()));
@@ -13868,7 +13885,7 @@
                 Slogf.v(LOG_TAG, "calling pm.setApplicationHiddenSettingAsUser(%s, %b, %d)",
                         packageName, hidden, userId);
             }
-            if (isPermissionCheckFlagEnabled()) {
+            if (isPolicyEngineForFinanceFlagEnabled()) {
                 EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage);
                 mDevicePolicyEngine.setLocalPolicy(
                         PolicyDefinition.APPLICATION_HIDDEN(packageName),
@@ -13907,7 +13924,7 @@
             String packageName, boolean parent) {
         CallerIdentity caller = getCallerIdentity(who, callerPackage);
         int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId();
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             // TODO: Also support DELEGATION_PACKAGE_ACCESS
             enforcePermission(MANAGE_DEVICE_POLICY_PACKAGE_STATE, caller.getPackageName(), userId);
         } else {
@@ -13919,7 +13936,7 @@
 
         synchronized (getLockObject()) {
             if (parent) {
-                if (!isPermissionCheckFlagEnabled()) {
+                if (!isPolicyEngineForFinanceFlagEnabled()) {
                     Preconditions.checkCallAuthorization(
                             isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId())
                                     && isManagedProfile(caller.getUserId()));
@@ -14111,13 +14128,13 @@
         enforceMaxStringLength(accountType, "account type");
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
         synchronized (getLockObject()) {
-            if (isPermissionCheckFlagEnabled()) {
+            if (isPolicyEngineForFinanceFlagEnabled()) {
                 int affectedUser = getAffectedUser(parent);
                 EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                         who,
@@ -14180,7 +14197,7 @@
         CallerIdentity caller;
         Preconditions.checkArgumentNonnegative(userId, "Invalid userId");
         final ArraySet<String> resultSet = new ArraySet<>();
-        if (isPermissionCheckFlagEnabled()) {
+        if (isPolicyEngineForFinanceFlagEnabled()) {
             int affectedUser = parent ? getProfileParentId(userId) : userId;
             caller = getCallerIdentity(callerPackageName);
             if (!hasPermission(MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT,
@@ -15551,12 +15568,12 @@
     public boolean setStatusBarDisabled(ComponentName who, String callerPackageName,
             boolean disabled) {
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(who, callerPackageName);
         } else {
             caller = getCallerIdentity(who);
         }
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             enforcePermission(MANAGE_DEVICE_POLICY_STATUS_BAR, caller.getPackageName(),
                     UserHandle.USER_ALL);
         } else {
@@ -15567,7 +15584,7 @@
 
         int userId = caller.getUserId();
         synchronized (getLockObject()) {
-            if (!isPermissionCheckFlagEnabled()) {
+            if (!isUnicornFlagEnabled()) {
                 Preconditions.checkCallAuthorization(isUserAffiliatedWithDeviceLocked(userId),
                         "Admin " + who + " is neither the device owner or affiliated "
                                 + "user's profile owner.");
@@ -15626,7 +15643,7 @@
     @Override
     public boolean isStatusBarDisabled(String callerPackage) {
         final CallerIdentity caller = getCallerIdentity(callerPackage);
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             enforceCanQuery(
                     MANAGE_DEVICE_POLICY_STATUS_BAR, caller.getPackageName(), caller.getUserId());
         } else {
@@ -15636,7 +15653,7 @@
 
         int userId = caller.getUserId();
         synchronized (getLockObject()) {
-            if (!isPermissionCheckFlagEnabled()) {
+            if (!isUnicornFlagEnabled()) {
                 Preconditions.checkCallAuthorization(isUserAffiliatedWithDeviceLocked(userId),
                         "Admin " + callerPackage
                                 + " is neither the device owner or affiliated user's profile owner.");
@@ -16371,7 +16388,8 @@
                     // TODO(b/128928355): if this restriction is enforced by multiple DPCs, return
                     // the admin for the calling user.
                     Slogf.w(LOG_TAG, "getEnforcingAdminAndUserDetailsInternal(%d, %s): multiple "
-                            + "sources for restriction %s on user %d", restriction, userId);
+                            + "sources for restriction %s on user %d",
+                            userId, restriction, restriction, userId);
                     result = new Bundle();
                     result.putInt(Intent.EXTRA_USER_ID, userId);
                     return result;
@@ -16796,7 +16814,7 @@
             }
         }
         EnforcingAdmin enforcingAdmin;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
@@ -16967,7 +16985,7 @@
     public int getPermissionGrantState(ComponentName admin, String callerPackage,
             String packageName, String permission) throws RemoteException {
         final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             enforceCanQuery(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, caller.getPackageName(),
                     caller.getUserId());
         } else {
@@ -19105,14 +19123,14 @@
             throw new IllegalArgumentException("token must be at least 32-byte long");
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(admin, callerPackageName);
         } else {
             caller = getCallerIdentity(admin);
         }
         final int userId = caller.getUserId();
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19168,7 +19186,7 @@
             return false;
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(admin, callerPackageName);
         } else {
             caller = getCallerIdentity(admin);
@@ -19176,7 +19194,7 @@
         final int userId = caller.getUserId();
         boolean result = false;
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19215,14 +19233,14 @@
             return false;
         }
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(admin, callerPackageName);
         } else {
             caller = getCallerIdentity(admin);
         }
         int userId = caller.getUserId();
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19264,7 +19282,7 @@
         Objects.requireNonNull(token);
 
         CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             caller = getCallerIdentity(admin, callerPackageName);
         } else {
             caller = getCallerIdentity(admin);
@@ -19274,7 +19292,7 @@
         boolean result = false;
         final String password = passwordOrNull != null ? passwordOrNull : "";
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (isUnicornFlagEnabled()) {
             EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                     admin,
                     MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19305,7 +19323,7 @@
         }
 
         if (result) {
-            if (isPermissionCheckFlagEnabled()) {
+            if (isUnicornFlagEnabled()) {
                 DevicePolicyEventLogger
                         .createEvent(DevicePolicyEnums.RESET_PASSWORD_WITH_TOKEN)
                         .setAdmin(callerPackageName)
@@ -22839,6 +22857,7 @@
             MANAGE_DEVICE_POLICY_DISPLAY,
             MANAGE_DEVICE_POLICY_FACTORY_RESET,
             MANAGE_DEVICE_POLICY_FUN,
+            MANAGE_DEVICE_POLICY_INPUT_METHODS,
             MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES,
             MANAGE_DEVICE_POLICY_KEYGUARD,
             MANAGE_DEVICE_POLICY_LOCALE,
@@ -22914,15 +22933,18 @@
                     MANAGE_DEVICE_POLICY_BLUETOOTH,
                     MANAGE_DEVICE_POLICY_CALLS,
                     MANAGE_DEVICE_POLICY_CAMERA,
+                    MANAGE_DEVICE_POLICY_CERTIFICATES,
                     MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
                     MANAGE_DEVICE_POLICY_DISPLAY,
                     MANAGE_DEVICE_POLICY_FACTORY_RESET,
+                    MANAGE_DEVICE_POLICY_INPUT_METHODS,
                     MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES,
                     MANAGE_DEVICE_POLICY_KEYGUARD,
                     MANAGE_DEVICE_POLICY_LOCALE,
                     MANAGE_DEVICE_POLICY_LOCATION,
                     MANAGE_DEVICE_POLICY_LOCK,
                     MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
+                    MANAGE_DEVICE_POLICY_CERTIFICATES,
                     MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION,
                     MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
                     MANAGE_DEVICE_POLICY_PACKAGE_STATE,
@@ -22949,7 +22971,6 @@
                     MANAGE_DEVICE_POLICY_ACROSS_USERS,
                     MANAGE_DEVICE_POLICY_AIRPLANE_MODE,
                     MANAGE_DEVICE_POLICY_APPS_CONTROL,
-                    MANAGE_DEVICE_POLICY_CERTIFICATES,
                     MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
                     MANAGE_DEVICE_POLICY_DEFAULT_SMS,
                     MANAGE_DEVICE_POLICY_LOCALE,
@@ -23074,11 +23095,12 @@
     //Map of Permission to Delegate Scope.
     private static final HashMap<String, String> DELEGATE_SCOPES = new HashMap<>();
     {
-        DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, DELEGATION_PERMISSION_GRANT);
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, DELEGATION_APP_RESTRICTIONS);
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL, DELEGATION_BLOCK_UNINSTALL);
-        DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, DELEGATION_SECURITY_LOGGING);
+        DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_CERTIFICATES, DELEGATION_CERT_INSTALL);
         DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_PACKAGE_STATE, DELEGATION_PACKAGE_ACCESS);
+        DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, DELEGATION_PERMISSION_GRANT);
+        DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, DELEGATION_SECURITY_LOGGING);
     }
 
     private static final HashMap<String, String> CROSS_USER_PERMISSIONS =
@@ -23559,6 +23581,30 @@
         return  EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId, admin);
     }
 
+    private EnforcingAdmin getEnforcingAdminForPackage(@Nullable ComponentName who,
+            String packageName, int userId) {
+        ActiveAdmin admin;
+        if (who != null) {
+            if (isDeviceOwner(who, userId) || isProfileOwner(who, userId)) {
+                synchronized (getLockObject()) {
+                    admin = getActiveAdminUncheckedLocked(who, userId);
+                }
+                if (admin != null) {
+                    return EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId, admin);
+                }
+            } else {
+                // Check for non-DPC active admins.
+                admin = getActiveAdminUncheckedLocked(who, userId);
+                if (admin != null) {
+                    return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId, admin);
+                }
+            }
+        }
+
+        admin = getUserData(userId).createOrGetPermissionBasedAdmin(userId);
+        return  EnforcingAdmin.createEnforcingAdmin(packageName, userId, admin);
+    }
+
     private int getAffectedUser(boolean calledOnParent) {
         int callingUserId = mInjector.userHandleGetCallingUserId();
         return calledOnParent ? getProfileParentId(callingUserId) : callingUserId;
@@ -23614,6 +23660,10 @@
                 DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
     }
 
+    private boolean isUnicornFlagEnabled() {
+        return false;
+    }
+
     private boolean isWorkProfileTelephonyEnabled() {
         return isWorkProfileTelephonyDevicePolicyManagerFlagEnabled()
                 && isWorkProfileTelephonySubscriptionManagerFlagEnabled();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b1d6131..a8a1c03 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -593,8 +593,8 @@
      * Spawn a thread that monitors for fd leaks.
      */
     private static void spawnFdLeakCheckThread() {
-        final int enableThreshold = SystemProperties.getInt(SYSPROP_FDTRACK_ENABLE_THRESHOLD, 1024);
-        final int abortThreshold = SystemProperties.getInt(SYSPROP_FDTRACK_ABORT_THRESHOLD, 2048);
+        final int enableThreshold = SystemProperties.getInt(SYSPROP_FDTRACK_ENABLE_THRESHOLD, 1600);
+        final int abortThreshold = SystemProperties.getInt(SYSPROP_FDTRACK_ABORT_THRESHOLD, 3000);
         final int checkInterval = SystemProperties.getInt(SYSPROP_FDTRACK_INTERVAL, 120);
 
         new Thread(() -> {
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index f05b1d4..475966e 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -20,9 +20,11 @@
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
 import android.app.job.JobService;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.Handler;
 import android.os.IBinder.DeathRecipient;
 import android.os.Looper;
@@ -53,7 +55,8 @@
     public static final String LOG_TAG = "ProfcollectForwardingService";
 
     private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
-
+    private static final String INTENT_UPLOAD_PROFILES =
+            "com.android.server.profcollect.UPLOAD_PROFILES";
     private static final long BG_PROCESS_PERIOD = TimeUnit.HOURS.toMillis(4); // every 4 hours.
 
     private IProfCollectd mIProfcollect;
@@ -66,6 +69,16 @@
         }
     };
 
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction() == INTENT_UPLOAD_PROFILES) {
+                Log.d(LOG_TAG, "Received broadcast to pack and upload reports");
+                packAndUploadReport();
+            }
+        }
+    };
+
     public ProfcollectForwardingService(Context context) {
         super(context);
 
@@ -73,6 +86,10 @@
             throw new AssertionError("only one service instance allowed");
         }
         sSelfService = this;
+
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(INTENT_UPLOAD_PROFILES);
+        context.registerReceiver(mBroadcastReceiver, filter);
     }
 
     /**
@@ -296,7 +313,7 @@
                 }
 
                 if (status == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT) {
-                    packProfileReport();
+                    packAndUploadReport();
                 }
             }
 
@@ -307,7 +324,7 @@
         });
     }
 
-    private void packProfileReport() {
+    private void packAndUploadReport() {
         if (mIProfcollect == null) {
             return;
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 3a47b47..2b57c59 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -154,6 +154,7 @@
         // Freeze time for testing.
         long nowElapsed;
         boolean useMotionSensor = true;
+        boolean isLocationPrefetchEnabled = true;
 
         InjectorForTest(Context ctx) {
             super(ctx);
@@ -223,6 +224,11 @@
         }
 
         @Override
+        boolean isLocationPrefetchEnabled() {
+            return isLocationPrefetchEnabled;
+        }
+
+        @Override
         PowerManager getPowerManager() {
             return mPowerManager;
         }
@@ -991,6 +997,43 @@
     }
 
     @Test
+    public void testStepIdleStateLocked_ValidStates_LocationPrefetchDisabled() {
+        mInjector.locationManager = mLocationManager;
+        mInjector.isLocationPrefetchEnabled = false;
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+        doReturn(mock(LocationProvider.class)).when(mLocationManager).getProvider(anyString());
+        // Make sure the controller doesn't think there's a wake-from-idle alarm coming soon.
+        setAlarmSoon(false);
+        // Set state to INACTIVE.
+        mDeviceIdleController.becomeActiveLocked("testing", 0);
+        setChargingOn(false);
+        setScreenOn(false);
+        verifyStateConditions(STATE_INACTIVE);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_IDLE_PENDING);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_SENSING);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        // Prefetch location is off, so SENSING should go straight through to IDLE.
+        verifyStateConditions(STATE_IDLE);
+
+        // Should just alternate between IDLE and IDLE_MAINTENANCE now.
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_IDLE_MAINTENANCE);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_IDLE);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_IDLE_MAINTENANCE);
+    }
+
+    @Test
     public void testStepIdleStateLocked_ValidStates_WithLocationManager_NoProviders() {
         // Make sure the controller doesn't think there's a wake-from-idle alarm coming soon.
         setAlarmSoon(false);
@@ -1024,6 +1067,46 @@
     }
 
     @Test
+    public void testStepIdleStateLocked_ValidStates_WithLocationManager_MissingProviders() {
+        mInjector.locationManager = mLocationManager;
+        doReturn(null).when(mLocationManager)
+                .getProvider(eq(LocationManager.FUSED_PROVIDER));
+        doReturn(null).when(mLocationManager)
+                .getProvider(eq(LocationManager.GPS_PROVIDER));
+        doReturn(mock(LocationProvider.class)).when(mLocationManager)
+                .getProvider(eq(LocationManager.NETWORK_PROVIDER));
+        // Make sure the controller doesn't think there's a wake-from-idle alarm coming soon.
+        setAlarmSoon(false);
+        // Set state to INACTIVE.
+        mDeviceIdleController.becomeActiveLocked("testing", 0);
+        setChargingOn(false);
+        setScreenOn(false);
+        verifyStateConditions(STATE_INACTIVE);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_IDLE_PENDING);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_SENSING);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        // Location manager exists, but the required providers don't exist,
+        // so SENSING should go straight through to IDLE.
+        verifyStateConditions(STATE_IDLE);
+
+        // Should just alternate between IDLE and IDLE_MAINTENANCE now.
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_IDLE_MAINTENANCE);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_IDLE);
+
+        mDeviceIdleController.stepIdleStateLocked("testing");
+        verifyStateConditions(STATE_IDLE_MAINTENANCE);
+    }
+
+    @Test
     public void testStepIdleStateLocked_ValidStates_WithLocationManager_WithProviders() {
         mInjector.locationManager = mLocationManager;
         doReturn(mock(LocationProvider.class)).when(mLocationManager).getProvider(anyString());
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java
deleted file mode 100644
index 17fba9f..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display;
-
-import static com.android.internal.display.RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.when;
-
-import android.hardware.display.DisplayManager;
-import android.provider.Settings;
-import android.testing.TestableContext;
-import android.view.Display;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.display.RefreshRateSettingsUtils;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RefreshRateSettingsUtilsTest {
-
-    @Rule
-    public final TestableContext mContext = new TestableContext(
-            InstrumentationRegistry.getInstrumentation().getContext());
-
-    @Mock
-    private DisplayManager mDisplayManagerMock;
-    @Mock
-    private Display mDisplayMock;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mContext.addMockSystemService(DisplayManager.class, mDisplayManagerMock);
-
-        Display.Mode[] modes = new Display.Mode[] {
-                new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
-                        /* refreshRate= */ 60),
-                new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
-                        /* refreshRate= */ 120),
-                new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
-                        /* refreshRate= */ 90)
-        };
-
-        when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
-        when(mDisplayMock.getSupportedModes()).thenReturn(modes);
-    }
-
-    @Test
-    public void testFindHighestRefreshRateForDefaultDisplay() {
-        when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(null);
-        assertEquals(DEFAULT_REFRESH_RATE,
-                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
-                /* delta= */ 0);
-
-        when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
-        assertEquals(120,
-                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
-                /* delta= */ 0);
-    }
-
-    @Test
-    public void testGetMinRefreshRate() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.FORCE_PEAK_REFRESH_RATE, -1);
-        assertEquals(0, RefreshRateSettingsUtils.getMinRefreshRate(mContext), /* delta= */ 0);
-
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.FORCE_PEAK_REFRESH_RATE, 0);
-        assertEquals(0, RefreshRateSettingsUtils.getMinRefreshRate(mContext), /* delta= */ 0);
-
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.FORCE_PEAK_REFRESH_RATE, 1);
-        assertEquals(120, RefreshRateSettingsUtils.getMinRefreshRate(mContext), /* delta= */ 0);
-    }
-
-    @Test
-    public void testGetPeakRefreshRate() {
-        float defaultPeakRefreshRate = 100;
-
-        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY, -1);
-        assertEquals(defaultPeakRefreshRate,
-                RefreshRateSettingsUtils.getPeakRefreshRate(mContext, defaultPeakRefreshRate),
-                /* delta= */ 0);
-
-        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY, 0);
-        assertEquals(DEFAULT_REFRESH_RATE,
-                RefreshRateSettingsUtils.getPeakRefreshRate(mContext, defaultPeakRefreshRate),
-                /* delta= */ 0);
-
-        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY, 1);
-        assertEquals(120,
-                RefreshRateSettingsUtils.getPeakRefreshRate(mContext, defaultPeakRefreshRate),
-                /* delta= */ 0);
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
index 941a3a4..3faf394 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
@@ -72,18 +72,28 @@
 
         mResources = InstrumentationRegistry.getContext().getResources();
         // These Resources are common to all tests.
-        doReturn(mResources.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin))
+        doReturn(4000)
             .when(mMockedResources)
             .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin);
-        doReturn(mResources.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax))
+        doReturn(8000)
             .when(mMockedResources)
             .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax);
-        doReturn(mResources.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault))
+        doReturn(6500)
             .when(mMockedResources)
             .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault);
-        doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite))
-            .when(mMockedResources)
-            .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
+        doReturn(new String[] {"0.950456", "1.000000", "1.089058"})
+                .when(mMockedResources)
+                .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
+        doReturn(6500)
+                .when(mMockedResources)
+                .getInteger(R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
+        doReturn(new int[] {0})
+                .when(mMockedResources)
+                .getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
+        doReturn(new int[] {20})
+                .when(mMockedResources)
+                .getIntArray(R.array.config_displayWhiteBalanceDisplayRangeMinimums);
+
         doReturn(mMockedResources).when(mMockedContext).getResources();
 
         mDisplayToken = new Binder();
@@ -195,7 +205,7 @@
      * Matrix should match the precalculated one for given cct and display primaries.
      */
     @Test
-    public void displayWhiteBalance_validateTransformMatrix() {
+    public void displayWhiteBalance_getAndSetMatrix_validateTransformMatrix() {
         DisplayPrimaries displayPrimaries = new DisplayPrimaries();
         displayPrimaries.red = new CieXyz();
         displayPrimaries.red.X = 0.412315f;
@@ -223,10 +233,12 @@
 
         final int cct = 6500;
         mDisplayWhiteBalanceTintController.setMatrix(cct);
+        mDisplayWhiteBalanceTintController.setAppliedCct(
+                mDisplayWhiteBalanceTintController.getTargetCct());
+
         assertWithMessage("Failed to set temperature")
                 .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
                 .isEqualTo(cct);
-
         float[] matrixDwb = mDisplayWhiteBalanceTintController.getMatrix();
         final float[] expectedMatrixDwb = {
             0.971848f,   -0.001421f,  0.000491f, 0.0f,
@@ -238,6 +250,54 @@
             1e-6f /* tolerance */);
     }
 
+    /**
+     * Matrix should match the precalculated one for given cct and display primaries.
+     */
+    @Test
+    public void displayWhiteBalance_targetApplied_validateTransformMatrix() {
+        DisplayPrimaries displayPrimaries = new DisplayPrimaries();
+        displayPrimaries.red = new CieXyz();
+        displayPrimaries.red.X = 0.412315f;
+        displayPrimaries.red.Y = 0.212600f;
+        displayPrimaries.red.Z = 0.019327f;
+        displayPrimaries.green = new CieXyz();
+        displayPrimaries.green.X = 0.357600f;
+        displayPrimaries.green.Y = 0.715200f;
+        displayPrimaries.green.Z = 0.119200f;
+        displayPrimaries.blue = new CieXyz();
+        displayPrimaries.blue.X = 0.180500f;
+        displayPrimaries.blue.Y = 0.072200f;
+        displayPrimaries.blue.Z = 0.950633f;
+        displayPrimaries.white = new CieXyz();
+        displayPrimaries.white.X = 0.950456f;
+        displayPrimaries.white.Y = 1.000000f;
+        displayPrimaries.white.Z = 1.089058f;
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+                .thenReturn(displayPrimaries);
+
+        setUpTintController();
+        assertWithMessage("Setup with valid SurfaceControl failed")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isTrue();
+
+        final int cct = 6500;
+        mDisplayWhiteBalanceTintController.setTargetCct(cct);
+        final float[] matrixDwb = mDisplayWhiteBalanceTintController.computeMatrixForCct(cct);
+        mDisplayWhiteBalanceTintController.setAppliedCct(cct);
+
+        assertWithMessage("Failed to set temperature")
+                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+                .isEqualTo(cct);
+        final float[] expectedMatrixDwb = {
+                0.971848f,   -0.001421f,  0.000491f, 0.0f,
+                0.028193f,    0.945798f,  0.003207f, 0.0f,
+                -0.000042f,  -0.000989f,  0.988659f, 0.0f,
+                0.0f,         0.0f,       0.0f,      1.0f
+        };
+        assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
+                1e-6f /* tolerance */);
+    }
+
     private void setUpTintController() {
         mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
                 mDisplayManagerInternal);
diff --git a/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java
index f788c92..46974cf7 100644
--- a/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java
@@ -28,6 +28,8 @@
 import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
@@ -81,7 +83,7 @@
     public void testEnqueueUidChange() {
         int change = mUidObserverController.enqueueUidChange(null, TEST_UID1,
                 UidRecord.CHANGE_ACTIVE, PROCESS_STATE_FOREGROUND_SERVICE,
-                PROCESS_CAPABILITY_ALL, 0, false);
+                UNKNOWN_ADJ, PROCESS_CAPABILITY_ALL, 0, false);
         assertEquals("expected=ACTIVE,actual=" + changeToStr(change),
                 UidRecord.CHANGE_ACTIVE, change);
         assertPendingChange(TEST_UID1, UidRecord.CHANGE_ACTIVE, PROCESS_STATE_FOREGROUND_SERVICE,
@@ -91,8 +93,8 @@
 
         final ChangeRecord record2 = new ChangeRecord();
         change = mUidObserverController.enqueueUidChange(record2, TEST_UID2,
-                UidRecord.CHANGE_CACHED, PROCESS_STATE_CACHED_RECENT, PROCESS_CAPABILITY_NONE,
-                99, true);
+                UidRecord.CHANGE_CACHED, PROCESS_STATE_CACHED_RECENT, UNKNOWN_ADJ,
+                PROCESS_CAPABILITY_NONE, 99, true);
         assertEquals("expected=ACTIVE,actual=" + changeToStr(change),
                 UidRecord.CHANGE_CACHED, change);
         assertPendingChange(TEST_UID1, UidRecord.CHANGE_ACTIVE, PROCESS_STATE_FOREGROUND_SERVICE,
@@ -101,7 +103,8 @@
                 PROCESS_CAPABILITY_NONE, 99, true, record2);
 
         change = mUidObserverController.enqueueUidChange(record1, TEST_UID1,
-                UidRecord.CHANGE_UNCACHED, PROCESS_STATE_TOP, PROCESS_CAPABILITY_ALL, 0, false);
+                UidRecord.CHANGE_UNCACHED, PROCESS_STATE_TOP, UNKNOWN_ADJ,
+                PROCESS_CAPABILITY_ALL, 0, false);
         assertEquals("expected=ACTIVE|UNCACHED,actual=" + changeToStr(change),
                 UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_UNCACHED, change);
         assertPendingChange(TEST_UID1, UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_UNCACHED,
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index d12741a..317fd58 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -100,6 +100,7 @@
 import com.android.server.FgThread;
 import com.android.server.SystemService;
 import com.android.server.am.UserState.KeyEvictedCallback;
+import com.android.server.pm.UserJourneyLogger;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerService;
 import com.android.server.wm.WindowManagerService;
@@ -199,6 +200,7 @@
             mUserController.setAllowUserUnlocking(true);
             setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
             setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated= */ true, null);
+            mInjector.mRelevantUser = null;
         });
     }
 
@@ -232,6 +234,25 @@
     }
 
     @Test
+    public void testStartUser_background_duringBootHsum() {
+        mockIsHeadlessSystemUserMode(true);
+        mUserController.setAllowUserUnlocking(false);
+        mInjector.mRelevantUser = TEST_USER_ID;
+        boolean started = mUserController.startUser(TEST_USER_ID, USER_START_MODE_BACKGROUND);
+        assertWithMessage("startUser(%s, foreground=false)", TEST_USER_ID).that(started).isTrue();
+
+        // ACTION_LOCKED_BOOT_COMPLETED not sent yet
+        startUserAssertions(newArrayList(Intent.ACTION_USER_STARTED, Intent.ACTION_USER_STARTING),
+                START_BACKGROUND_USER_MESSAGE_CODES);
+
+        mUserController.onBootComplete(null);
+
+        startUserAssertions(newArrayList(Intent.ACTION_USER_STARTED, Intent.ACTION_USER_STARTING,
+                        Intent.ACTION_LOCKED_BOOT_COMPLETED),
+                START_BACKGROUND_USER_MESSAGE_CODES);
+    }
+
+    @Test
     public void testStartUser_sendsNoBroadcastsForSystemUserInNonHeadlessMode() {
         setUpUser(SYSTEM_USER_ID, UserInfo.FLAG_SYSTEM, /* preCreated= */ false,
                 UserManager.USER_TYPE_FULL_SYSTEM);
@@ -1074,8 +1095,12 @@
         private final KeyguardManager mKeyguardManagerMock;
         private final LockPatternUtils mLockPatternUtilsMock;
 
+        private final UserJourneyLogger mUserJourneyLoggerMock;
+
         private final Context mCtx;
 
+        private Integer mRelevantUser;
+
         TestInjector(Context ctx) {
             super(null);
             mCtx = ctx;
@@ -1090,6 +1115,7 @@
             mKeyguardManagerMock = mock(KeyguardManager.class);
             when(mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
             mLockPatternUtilsMock = mock(LockPatternUtils.class);
+            mUserJourneyLoggerMock = mock(UserJourneyLogger.class);
         }
 
         @Override
@@ -1162,7 +1188,9 @@
                 boolean sticky, int callingPid, int callingUid, int realCallingUid,
                 int realCallingPid, int userId) {
             Log.i(TAG, "broadcastIntentLocked " + intent);
-            mSentIntents.add(intent);
+            if (mRelevantUser == null || mRelevantUser == userId || userId == UserHandle.USER_ALL) {
+                mSentIntents.add(intent);
+            }
             return 0;
         }
 
@@ -1220,6 +1248,11 @@
         void onSystemUserVisibilityChanged(boolean visible) {
             Log.i(TAG, "onSystemUserVisibilityChanged(" + visible + ")");
         }
+
+        @Override
+        protected UserJourneyLogger getUserJourneyLogger() {
+            return mUserJourneyLoggerMock;
+        }
     }
 
     private static class TestHandler extends Handler {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorListTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorListTest.java
new file mode 100644
index 0000000..3d80916b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorListTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.IActivityManager;
+import android.app.SynchronousUserSwitchObserver;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.sensors.face.aidl.Sensor;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class SensorListTest {
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Mock
+    Sensor mSensor;
+    @Mock
+    IActivityManager mActivityManager;
+    @Mock
+    SynchronousUserSwitchObserver mUserSwitchObserver;
+
+    SensorList<Sensor> mSensorList;
+
+    @Before
+    public void setUp() throws RemoteException {
+        mSensorList = new SensorList<>(mActivityManager);
+    }
+
+    @Test
+    public void testAddingSensor() throws RemoteException {
+        mSensorList.addSensor(0, mSensor, UserHandle.USER_NULL, mUserSwitchObserver);
+
+        verify(mUserSwitchObserver).onUserSwitching(UserHandle.USER_SYSTEM);
+        verify(mActivityManager).registerUserSwitchObserver(eq(mUserSwitchObserver), anyString());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 41f7433..31a58cd 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -30,6 +32,7 @@
 import android.hardware.biometrics.face.ISession;
 import android.hardware.biometrics.face.SensorProps;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 
@@ -38,6 +41,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -98,16 +102,34 @@
                 mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext);
     }
 
+    @Test
+    public void testAddingSensors() {
+        waitForIdle();
+
+        for (SensorProps prop : mSensorProps) {
+            final BiometricScheduler scheduler =
+                    mFaceProvider.mFaceSensors.get(prop.commonProps.sensorId)
+                            .getScheduler();
+            BaseClientMonitor currentClient = scheduler.getCurrentClient();
+
+            assertThat(currentClient).isInstanceOf(FaceInternalCleanupClient.class);
+            assertThat(currentClient.getSensorId()).isEqualTo(prop.commonProps.sensorId);
+            assertThat(currentClient.getTargetUserId()).isEqualTo(UserHandle.USER_SYSTEM);
+        }
+    }
+
     @SuppressWarnings("rawtypes")
     @Test
     public void halServiceDied_resetsAllSchedulers() {
+        waitForIdle();
+
         assertEquals(mSensorProps.length, mFaceProvider.getSensorProperties().size());
 
         // Schedule N operations on each sensor
         final int numFakeOperations = 10;
         for (SensorProps prop : mSensorProps) {
             final BiometricScheduler scheduler =
-                    mFaceProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
+                    mFaceProvider.mFaceSensors.get(prop.commonProps.sensorId).getScheduler();
             for (int i = 0; i < numFakeOperations; i++) {
                 final HalClientMonitor testMonitor = mock(HalClientMonitor.class);
                 when(testMonitor.getFreshDaemon()).thenReturn(new Object());
@@ -119,8 +141,8 @@
         // The right amount of pending and current operations are scheduled
         for (SensorProps prop : mSensorProps) {
             final BiometricScheduler scheduler =
-                    mFaceProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
-            assertEquals(numFakeOperations - 1, scheduler.getCurrentPendingCount());
+                    mFaceProvider.mFaceSensors.get(prop.commonProps.sensorId).getScheduler();
+            assertEquals(numFakeOperations, scheduler.getCurrentPendingCount());
             assertNotNull(scheduler.getCurrentClient());
         }
 
@@ -132,7 +154,7 @@
         // No pending operations, no current operation.
         for (SensorProps prop : mSensorProps) {
             final BiometricScheduler scheduler =
-                    mFaceProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
+                    mFaceProvider.mFaceSensors.get(prop.commonProps.sensorId).getScheduler();
             assertNull(scheduler.getCurrentClient());
             assertEquals(0, scheduler.getCurrentPendingCount());
         }
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
index 9c9d3f8..25bd9bc 100644
--- 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
@@ -29,9 +29,9 @@
 import android.content.Context;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.common.CommonProps;
-import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.FaceSensorPropertiesInternal;
 import android.os.Handler;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
@@ -42,7 +42,6 @@
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -82,17 +81,13 @@
     @Mock
     private AuthSessionCoordinator mAuthSessionCoordinator;
     @Mock
-    private IFace mDaemon;
-    @Mock
-    private BiometricStateCallback mBiometricStateCallback;
+    FaceProvider mFaceProvider;
 
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
 
     private UserAwareBiometricScheduler mScheduler;
     private Sensor.HalSessionCallback mHalCallback;
-    private FaceProvider mFaceProvider;
-    private SensorProps[] mSensorProps;
 
     @Before
     public void setUp() {
@@ -113,16 +108,6 @@
                 TAG, mScheduler, SENSOR_ID,
                 USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
                 mHalSessionCallback);
-
-        final SensorProps sensor1 = new SensorProps();
-        sensor1.commonProps = new CommonProps();
-        sensor1.commonProps.sensorId = 0;
-        final SensorProps sensor2 = new SensorProps();
-        sensor2.commonProps = new CommonProps();
-        sensor2.commonProps.sensorId = 1;
-        mSensorProps = new SensorProps[]{sensor1, sensor2};
-        mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
-                mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext);
     }
 
     @Test
@@ -154,14 +139,26 @@
 
     @Test
     public void onBinderDied_noErrorOnNullClient() {
-        mScheduler.reset();
-        assertNull(mScheduler.getCurrentClient());
-        mFaceProvider.binderDied();
+        mLooper.dispatchAll();
 
-        for (int i = 0; i < mFaceProvider.mSensors.size(); i++) {
-            final Sensor sensor = mFaceProvider.mSensors.valueAt(i);
-            assertNull(sensor.getSessionForUser(USER_ID));
-        }
+        final SensorProps sensorProps = new SensorProps();
+        sensorProps.commonProps = new CommonProps();
+        sensorProps.commonProps.sensorId = 1;
+        final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+                sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
+                sensorProps.commonProps.maxEnrollmentsPerUser, null,
+                sensorProps.sensorType, sensorProps.supportsDetectInteraction,
+                sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
+        final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
+                internalProp, mLockoutResetDispatcher, mBiometricContext);
+
+        mScheduler.reset();
+
+        assertNull(mScheduler.getCurrentClient());
+
+        sensor.onBinderDied();
+
+        assertNull(sensor.getSessionForUser(USER_ID));
     }
 
     private void verifyNotLocked() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index c6ddf27..9c01de6 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -33,6 +35,7 @@
 import android.hardware.biometrics.fingerprint.SensorLocation;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 
@@ -41,6 +44,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -111,16 +115,38 @@
                 mGestureAvailabilityDispatcher, mBiometricContext);
     }
 
+    @Test
+    public void testAddingSensors() {
+        mFingerprintProvider = new TestableFingerprintProvider(mDaemon, mContext,
+                mBiometricStateCallback, mSensorProps, TAG, mLockoutResetDispatcher,
+                mGestureAvailabilityDispatcher, mBiometricContext);
+
+        waitForIdle();
+
+        for (SensorProps prop : mSensorProps) {
+            final BiometricScheduler scheduler =
+                    mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId)
+                            .getScheduler();
+            BaseClientMonitor currentClient = scheduler.getCurrentClient();
+
+            assertThat(currentClient).isInstanceOf(FingerprintInternalCleanupClient.class);
+            assertThat(currentClient.getSensorId()).isEqualTo(prop.commonProps.sensorId);
+            assertThat(currentClient.getTargetUserId()).isEqualTo(UserHandle.USER_SYSTEM);
+        }
+    }
+
     @SuppressWarnings("rawtypes")
     @Test
     public void halServiceDied_resetsAllSchedulers() {
+        waitForIdle();
         assertEquals(mSensorProps.length, mFingerprintProvider.getSensorProperties().size());
 
         // Schedule N operations on each sensor
         final int numFakeOperations = 10;
         for (SensorProps prop : mSensorProps) {
             final BiometricScheduler scheduler =
-                    mFingerprintProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
+                    mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId)
+                            .getScheduler();
             for (int i = 0; i < numFakeOperations; i++) {
                 final HalClientMonitor testMonitor = mock(HalClientMonitor.class);
                 when(testMonitor.getFreshDaemon()).thenReturn(new Object());
@@ -132,8 +158,9 @@
         // The right amount of pending and current operations are scheduled
         for (SensorProps prop : mSensorProps) {
             final BiometricScheduler scheduler =
-                    mFingerprintProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
-            assertEquals(numFakeOperations - 1, scheduler.getCurrentPendingCount());
+                    mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId)
+                            .getScheduler();
+            assertEquals(numFakeOperations, scheduler.getCurrentPendingCount());
             assertNotNull(scheduler.getCurrentClient());
         }
 
@@ -145,7 +172,8 @@
         // No pending operations, no current operation.
         for (SensorProps prop : mSensorProps) {
             final BiometricScheduler scheduler =
-                    mFingerprintProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
+                    mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId)
+                            .getScheduler();
             assertNull(scheduler.getCurrentClient());
             assertEquals(0, scheduler.getCurrentPendingCount());
         }
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java
index c5a9af7..dcd06c9 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java
@@ -30,7 +30,7 @@
     @Test
     public void call_writeToParcel_fromParcel_reconstructsSuccessfully() {
         final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
-        final long id = 5;
+        final String id = "5";
         final String callerId = "callerId";
         final byte[] appIcon = "appIcon".getBytes();
         final String appName = "appName";
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallServiceTest.java
index a488ab4..afddf3c 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallServiceTest.java
@@ -47,24 +47,24 @@
 
     @Test
     public void getCallForId_invalid() {
-        when(mMockCrossDeviceCall.getId()).thenReturn(-1L);
-        final CrossDeviceCall call = mSyncInCallService.getCallForId(-1L,
+        when(mMockCrossDeviceCall.getId()).thenReturn(null);
+        final CrossDeviceCall call = mSyncInCallService.getCallForId(null,
                 List.of(mMockCrossDeviceCall));
         assertWithMessage("Unexpectedly found a match for call id").that(call).isNull();
     }
 
     @Test
     public void getCallForId_noMatch() {
-        when(mMockCrossDeviceCall.getId()).thenReturn(5L);
-        final CrossDeviceCall call = mSyncInCallService.getCallForId(1L,
+        when(mMockCrossDeviceCall.getId()).thenReturn("123abc");
+        final CrossDeviceCall call = mSyncInCallService.getCallForId("abc123",
                 List.of(mMockCrossDeviceCall));
         assertWithMessage("Unexpectedly found a match for call id").that(call).isNull();
     }
 
     @Test
     public void getCallForId_hasMatch() {
-        when(mMockCrossDeviceCall.getId()).thenReturn(5L);
-        final CrossDeviceCall call = mSyncInCallService.getCallForId(5L,
+        when(mMockCrossDeviceCall.getId()).thenReturn("123abc");
+        final CrossDeviceCall call = mSyncInCallService.getCallForId("123abc",
                 List.of(mMockCrossDeviceCall));
         assertWithMessage("Unexpectedly did not find a match for call id").that(call).isNotNull();
     }
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
index 9d42a5b..5a0646c 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
@@ -62,9 +62,9 @@
         assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.RINGING);
         assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.ACCEPT,
-                        android.companion.Telecom.Call.REJECT,
-                        android.companion.Telecom.Call.SILENCE));
+                .isEqualTo(Set.of(android.companion.Telecom.ACCEPT,
+                        android.companion.Telecom.REJECT,
+                        android.companion.Telecom.SILENCE));
     }
 
     @Test
@@ -77,9 +77,9 @@
         assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.ONGOING);
         assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.END,
-                        android.companion.Telecom.Call.MUTE,
-                        android.companion.Telecom.Call.PUT_ON_HOLD));
+                .isEqualTo(Set.of(android.companion.Telecom.END,
+                        android.companion.Telecom.MUTE,
+                        android.companion.Telecom.PUT_ON_HOLD));
     }
 
     @Test
@@ -92,8 +92,8 @@
         assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.ON_HOLD);
         assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.END,
-                        android.companion.Telecom.Call.TAKE_OFF_HOLD));
+                .isEqualTo(Set.of(android.companion.Telecom.END,
+                        android.companion.Telecom.TAKE_OFF_HOLD));
     }
 
     @Test
@@ -106,8 +106,8 @@
         assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.ONGOING);
         assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.END,
-                        android.companion.Telecom.Call.MUTE));
+                .isEqualTo(Set.of(android.companion.Telecom.END,
+                        android.companion.Telecom.MUTE));
     }
 
     @Test
@@ -120,8 +120,8 @@
         assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.ONGOING);
         assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.END,
-                        android.companion.Telecom.Call.PUT_ON_HOLD));
+                .isEqualTo(Set.of(android.companion.Telecom.END,
+                        android.companion.Telecom.PUT_ON_HOLD));
     }
 
     @Test
@@ -134,17 +134,17 @@
         assertWithMessage("Wrong status for ringing state").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.RINGING);
         assertWithMessage("Wrong controls for ringing state").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.ACCEPT,
-                        android.companion.Telecom.Call.REJECT,
-                        android.companion.Telecom.Call.SILENCE));
+                .isEqualTo(Set.of(android.companion.Telecom.ACCEPT,
+                        android.companion.Telecom.REJECT,
+                        android.companion.Telecom.SILENCE));
         crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_ACTIVE,
                 Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
         assertWithMessage("Wrong status for active state").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.ONGOING);
         assertWithMessage("Wrong controls for active state").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.END,
-                        android.companion.Telecom.Call.MUTE,
-                        android.companion.Telecom.Call.PUT_ON_HOLD));
+                .isEqualTo(Set.of(android.companion.Telecom.END,
+                        android.companion.Telecom.MUTE,
+                        android.companion.Telecom.PUT_ON_HOLD));
     }
 
     @Test
@@ -158,8 +158,8 @@
         assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.RINGING_SILENCED);
         assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.ACCEPT,
-                        android.companion.Telecom.Call.REJECT));
+                .isEqualTo(Set.of(android.companion.Telecom.ACCEPT,
+                        android.companion.Telecom.REJECT));
     }
 
     @Test
@@ -173,9 +173,9 @@
         assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
                 .isEqualTo(android.companion.Telecom.Call.ONGOING);
         assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
-                .isEqualTo(Set.of(android.companion.Telecom.Call.END,
-                        android.companion.Telecom.Call.MUTE,
-                        android.companion.Telecom.Call.PUT_ON_HOLD));
+                .isEqualTo(Set.of(android.companion.Telecom.END,
+                        android.companion.Telecom.MUTE,
+                        android.companion.Telecom.PUT_ON_HOLD));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java
new file mode 100644
index 0000000..25b0ae4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.datatransfer.contextsync;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.companion.transport.CompanionTransportManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+public class CrossDeviceSyncControllerTest {
+
+    private CrossDeviceSyncController mCrossDeviceSyncController;
+    @Mock
+    private CompanionTransportManager mMockCompanionTransportManager;
+    @Mock
+    private CrossDeviceCall mMockCrossDeviceCall;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mCrossDeviceSyncController = new CrossDeviceSyncController(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                mMockCompanionTransportManager);
+    }
+
+    @Test
+    public void processTelecomDataFromSync_createCallUpdateMessage_emptyCallsAndRequests() {
+        final byte[] data = mCrossDeviceSyncController.createCallUpdateMessage(new HashSet<>(),
+                InstrumentationRegistry.getInstrumentation().getContext().getUserId());
+        final CallMetadataSyncData callMetadataSyncData =
+                mCrossDeviceSyncController.processTelecomDataFromSync(data);
+        assertWithMessage("Unexpectedly found a call").that(
+                callMetadataSyncData.getCalls()).isEmpty();
+        assertWithMessage("Unexpectedly found a request").that(
+                callMetadataSyncData.getRequests()).isEmpty();
+    }
+
+    @Test
+    public void processTelecomDataFromSync_createEmptyMessage_emptyCallsAndRequests() {
+        final byte[] data = CrossDeviceSyncController.createEmptyMessage();
+        final CallMetadataSyncData callMetadataSyncData =
+                mCrossDeviceSyncController.processTelecomDataFromSync(data);
+        assertWithMessage("Unexpectedly found a call").that(
+                callMetadataSyncData.getCalls()).isEmpty();
+        assertWithMessage("Unexpectedly found a request").that(
+                callMetadataSyncData.getRequests()).isEmpty();
+    }
+
+    @Test
+    public void processTelecomDataFromSync_createCallUpdateMessage_hasCalls() {
+        when(mMockCrossDeviceCall.getId()).thenReturn("123abc");
+        final String callerId = "Firstname Lastname";
+        when(mMockCrossDeviceCall.getReadableCallerId(anyBoolean())).thenReturn(callerId);
+        final String appName = "AppName";
+        when(mMockCrossDeviceCall.getCallingAppName()).thenReturn(appName);
+        final String appIcon = "ABCD";
+        when(mMockCrossDeviceCall.getCallingAppIcon()).thenReturn(appIcon.getBytes());
+        when(mMockCrossDeviceCall.getStatus()).thenReturn(android.companion.Telecom.Call.RINGING);
+        final Set<Integer> controls = Set.of(
+                android.companion.Telecom.ACCEPT,
+                android.companion.Telecom.REJECT,
+                android.companion.Telecom.SILENCE);
+        when(mMockCrossDeviceCall.getControls()).thenReturn(controls);
+        final byte[] data = mCrossDeviceSyncController.createCallUpdateMessage(
+                new HashSet<>(List.of(mMockCrossDeviceCall)),
+                InstrumentationRegistry.getInstrumentation().getContext().getUserId());
+        final CallMetadataSyncData callMetadataSyncData =
+                mCrossDeviceSyncController.processTelecomDataFromSync(data);
+        assertWithMessage("Wrong number of active calls").that(
+                callMetadataSyncData.getCalls()).hasSize(1);
+        final CallMetadataSyncData.Call call =
+                callMetadataSyncData.getCalls().stream().findAny().orElseThrow();
+        assertWithMessage("Wrong id").that(call.getId()).isEqualTo("123abc");
+        assertWithMessage("Wrong app icon").that(new String(call.getAppIcon())).isEqualTo(appIcon);
+        assertWithMessage("Wrong app name").that(call.getAppName()).isEqualTo(appName);
+        assertWithMessage("Wrong caller id").that(call.getCallerId()).isEqualTo(callerId);
+        assertWithMessage("Wrong status").that(call.getStatus())
+                .isEqualTo(android.companion.Telecom.Call.RINGING);
+        assertWithMessage("Wrong controls").that(call.getControls()).isEqualTo(controls);
+    }
+
+    @Test
+    public void processTelecomDataFromMessage_createCallControlMessage_hasCallControlRequest() {
+        final byte[] data = CrossDeviceSyncController.createCallControlMessage(
+                /* callId= */ "123abc", /* status= */ android.companion.Telecom.ACCEPT);
+        final CallMetadataSyncData callMetadataSyncData =
+                mCrossDeviceSyncController.processTelecomDataFromSync(data);
+        assertWithMessage("Wrong number of requests").that(
+                callMetadataSyncData.getRequests()).hasSize(1);
+        final CallMetadataSyncData.Call call =
+                callMetadataSyncData.getRequests().stream().findAny().orElseThrow();
+        assertWithMessage("Wrong id").that(call.getId()).isEqualTo("123abc");
+        assertWithMessage("Wrong app icon").that(call.getAppIcon()).isNull();
+        assertWithMessage("Wrong app name").that(call.getAppName()).isNull();
+        assertWithMessage("Wrong caller id").that(call.getCallerId()).isNull();
+        assertWithMessage("Wrong status").that(call.getStatus())
+                .isEqualTo(android.companion.Telecom.Call.UNKNOWN_STATUS);
+        assertWithMessage("Wrong controls").that(call.getControls())
+                .isEqualTo(Set.of(android.companion.Telecom.ACCEPT));
+        assertWithMessage("Unexpectedly has active calls").that(
+                callMetadataSyncData.getCalls()).isEmpty();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 1e342f5..57755a9 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1512,6 +1512,7 @@
      * Validates that when the device owner is removed, the reset password token is cleared
      */
     @Test
+    @Ignore("b/277916462")
     public void testClearDeviceOwner_clearResetPasswordToken() throws Exception {
         mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
@@ -2602,6 +2603,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetApplicationHiddenWithDO() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -2627,6 +2629,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetApplicationHiddenWithPOOfOrganizationOwnedDevice() throws Exception {
         final int MANAGED_PROFILE_USER_ID = CALLER_USER_HANDLE;
         final int MANAGED_PROFILE_ADMIN_UID =
@@ -4373,6 +4376,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeZoneEnabledModifiesSetting() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -4384,6 +4388,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeZoneEnabledWithPOOnUser0() throws Exception {
         mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         setupProfileOwnerOnUser0();
@@ -4395,6 +4400,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeZoneEnabledFailWithPONotOnUser0() throws Exception {
         setupProfileOwner();
         assertExpectException(SecurityException.class, null,
@@ -4404,6 +4410,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeZoneEnabledWithPOOfOrganizationOwnedDevice() throws Exception {
         setupProfileOwner();
         configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
@@ -5377,6 +5384,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testResetPasswordWithToken() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -5411,6 +5419,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void resetPasswordWithToken_NumericPin() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -5431,6 +5440,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void resetPasswordWithToken_EmptyPassword() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -7251,6 +7261,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testCanProfileOwnerResetPasswordWhenLocked() throws Exception {
         setDeviceEncryptionPerUser();
         setupProfileOwner();
@@ -7314,6 +7325,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAccountTypesWithManagementDisabledOnManagedProfile() throws Exception {
         setupProfileOwner();
 
@@ -7333,6 +7345,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAccountTypesWithManagementDisabledOnOrgOwnedManagedProfile()
             throws Exception {
         mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS);
diff --git a/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java
new file mode 100644
index 0000000..b96666a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.color;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CctEvaluatorTest {
+
+    @Test
+    public void noEntriesInParallelArrays_setsEverythingToOne() {
+        final CctEvaluator evaluator = new CctEvaluator(0, 5, new int[]{}, new int[]{});
+        assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(new int[]{1, 1, 1, 1, 1, 1});
+        assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+                new int[]{0, 1, 2, 3, 4, 5});
+    }
+
+    @Test
+    public void unevenNumberOfEntriesInParallelArrays_setsEverythingToOne() {
+        final CctEvaluator evaluator = new CctEvaluator(0, 5, new int[]{0}, new int[]{});
+        assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(new int[]{1, 1, 1, 1, 1, 1});
+        assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+                new int[]{0, 1, 2, 3, 4, 5});
+    }
+
+    @Test
+    public void singleEntryInParallelArray_computesCorrectly() {
+        final CctEvaluator evaluator = new CctEvaluator(0, 5, new int[]{0}, new int[]{2});
+        assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(new int[]{2, 2, 2, 2, 2, 2});
+        assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+                new int[]{0, 0, 2, 2, 4, 4});
+    }
+
+    @Test
+    public void minimumIsBelowFirstRange_computesCorrectly() {
+        final CctEvaluator evaluator = new CctEvaluator(3000, 3005, new int[]{3002},
+                new int[]{20});
+        assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(new int[]{20, 20, 20, 20, 20, 20});
+        assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+                new int[]{3000, 3000, 3000, 3000, 3000, 3000});
+    }
+
+    @Test
+    public void minimumIsAboveFirstRange_computesCorrectly() {
+        final CctEvaluator evaluator = new CctEvaluator(3000, 3008, new int[]{3002},
+                new int[]{20});
+        assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(
+                new int[]{20, 20, 20, 20, 20, 20, 20, 20, 20});
+        assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+                new int[]{3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000});
+    }
+
+    @Test
+    public void multipleStepsStartsAtThreshold_computesCorrectly() {
+        final CctEvaluator evaluator = new CctEvaluator(5, 20, new int[]{0, 4, 5, 10, 18},
+                new int[]{11, 7, 2, 15, 9});
+        assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(
+                new int[]{2, 2, 2, 2, 2, 15, 15, 15, 15, 15, 15, 15, 15, 9, 9, 9});
+        assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+                new int[]{5, 5, 7, 7, 9, 9, 9, 9, 9, 9, 9, 9, 9, 18, 18, 18});
+    }
+
+    @Test
+    public void multipleStepsStartsInBetween_computesCorrectly() {
+        final CctEvaluator evaluator = new CctEvaluator(4, 20, new int[]{0, 5, 10, 18},
+                new int[]{14, 2, 15, 9});
+        assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(
+                new int[]{14, 2, 2, 2, 2, 2, 15, 15, 15, 15, 15, 15, 15, 15, 9, 9, 9});
+        assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+                new int[]{4, 4, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 18, 18, 18});
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index e492252..b2a3a57 100644
--- a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -84,7 +84,6 @@
 
 import com.android.internal.R;
 import com.android.internal.display.BrightnessSynchronizer;
-import com.android.internal.display.RefreshRateSettingsUtils;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.test.FakeSettingsProvider;
@@ -159,9 +158,6 @@
         LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
-
-        clearSmoothDisplaySetting();
-        clearForcePeakRefreshRateSetting();
     }
 
     private DisplayModeDirector createDirectorFromRefreshRateArray(
@@ -922,6 +918,7 @@
     public void testLockFpsForLowZone() throws Exception {
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90);
         director.getSettingsObserver().setDefaultRefreshRate(90);
         director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
 
@@ -929,7 +926,6 @@
         config.setRefreshRateInLowZone(90);
         config.setLowDisplayBrightnessThresholds(new int[] { 10 });
         config.setLowAmbientBrightnessThresholds(new int[] { 20 });
-        config.setDefaultPeakRefreshRate(90);
 
         Sensor lightSensor = createLightSensor();
         SensorManager sensorManager = createMockSensorManager(lightSensor);
@@ -980,6 +976,7 @@
     public void testLockFpsForHighZone() throws Exception {
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90 /*fps*/);
         director.getSettingsObserver().setDefaultRefreshRate(90);
         director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
 
@@ -987,7 +984,6 @@
         config.setRefreshRateInHighZone(60);
         config.setHighDisplayBrightnessThresholds(new int[] { 255 });
         config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
-        config.setDefaultPeakRefreshRate(90);
 
         Sensor lightSensor = createLightSensor();
         SensorManager sensorManager = createMockSensorManager(lightSensor);
@@ -1035,123 +1031,16 @@
     }
 
     @Test
-    public void testSmoothDisplay() {
-        float defaultRefreshRate = 60;
-        int defaultPeakRefreshRate = 100;
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
-        director.getSettingsObserver().setDefaultRefreshRate(defaultRefreshRate);
-        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
-
-        final FakeDeviceConfig config = mInjector.getDeviceConfig();
-        config.setDefaultPeakRefreshRate(defaultPeakRefreshRate);
-
-        Sensor lightSensor = createLightSensor();
-        SensorManager sensorManager = createMockSensorManager(lightSensor);
-        director.start(sensorManager);
-
-        // Default value of the setting
-
-        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                defaultPeakRefreshRate);
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                Float.POSITIVE_INFINITY);
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                defaultRefreshRate);
-
-        setSmoothDisplayEnabled(false);
-
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                Float.POSITIVE_INFINITY);
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                defaultRefreshRate);
-
-        setSmoothDisplayEnabled(true);
-
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext));
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                Float.POSITIVE_INFINITY);
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                defaultRefreshRate);
-    }
-
-    @Test
-    public void testForcePeakRefreshRate() {
-        float defaultRefreshRate = 60;
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
-        director.getSettingsObserver().setDefaultRefreshRate(defaultRefreshRate);
-        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
-
-        Sensor lightSensor = createLightSensor();
-        SensorManager sensorManager = createMockSensorManager(lightSensor);
-        director.start(sensorManager);
-
-        setForcePeakRefreshRateEnabled(false);
-        setSmoothDisplayEnabled(false);
-
-        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
-                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                defaultRefreshRate);
-
-        setForcePeakRefreshRateEnabled(true);
-
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext));
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */
-                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
-                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
-        vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                defaultRefreshRate);
-    }
-
-    @Test
     public void testSensorRegistration() {
         // First, configure brightness zones or DMD won't register for sensor data.
         final FakeDeviceConfig config = mInjector.getDeviceConfig();
         config.setRefreshRateInHighZone(60);
         config.setHighDisplayBrightnessThresholds(new int[] { 255 });
         config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
-        config.setDefaultPeakRefreshRate(90);
 
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90 /*fps*/);
         director.getSettingsObserver().setDefaultRefreshRate(90);
         director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
 
@@ -2527,10 +2416,10 @@
         config.setRefreshRateInHighZone(60);
         config.setHighDisplayBrightnessThresholds(new int[] { 255 });
         config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
-        config.setDefaultPeakRefreshRate(90);
 
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90 /*fps*/);
         director.getSettingsObserver().setDefaultRefreshRate(90);
         director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
 
@@ -2828,30 +2717,10 @@
         listener.onDisplayChanged(DISPLAY_ID);
     }
 
-    private void setSmoothDisplayEnabled(boolean enabled) {
-        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY,
-                enabled ? 1 : 0);
-        mInjector.notifySmoothDisplaySettingChanged();
-        waitForIdleSync();
-    }
-
-    private void clearSmoothDisplaySetting() {
-        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY, -1);
-        mInjector.notifySmoothDisplaySettingChanged();
-        waitForIdleSync();
-    }
-
-    private void setForcePeakRefreshRateEnabled(boolean enabled) {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.FORCE_PEAK_REFRESH_RATE, enabled ? 1 : 0);
-        mInjector.notifyForcePeakRefreshRateSettingChanged();
-        waitForIdleSync();
-    }
-
-    private void clearForcePeakRefreshRateSetting() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.FORCE_PEAK_REFRESH_RATE, -1);
-        mInjector.notifySmoothDisplaySettingChanged();
+    private void setPeakRefreshRate(float fps) {
+        Settings.System.putFloat(mContext.getContentResolver(), Settings.System.PEAK_REFRESH_RATE,
+                 fps);
+        mInjector.notifyPeakRefreshRateChanged();
         waitForIdleSync();
     }
 
@@ -2900,8 +2769,7 @@
         private final Display mDisplay;
         private boolean mDisplayInfoValid = true;
         private ContentObserver mBrightnessObserver;
-        private ContentObserver mSmoothDisplaySettingObserver;
-        private ContentObserver mForcePeakRefreshRateSettingObserver;
+        private ContentObserver mPeakRefreshRateObserver;
 
         FakesInjector() {
             mDeviceConfig = new FakeDeviceConfig();
@@ -2918,15 +2786,9 @@
         }
 
         @Override
-        public void registerSmoothDisplayObserver(@NonNull ContentResolver cr,
+        public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
                 @NonNull ContentObserver observer) {
-            mSmoothDisplaySettingObserver = observer;
-        }
-
-        @Override
-        public void registerForcePeakRefreshRateObserver(@NonNull ContentResolver cr,
-                @NonNull ContentObserver observer) {
-            mForcePeakRefreshRateSettingObserver = observer;
+            mPeakRefreshRateObserver = observer;
         }
 
         @Override
@@ -2976,17 +2838,10 @@
                     ApplicationProvider.getApplicationContext().getResources());
         }
 
-        void notifySmoothDisplaySettingChanged() {
-            if (mSmoothDisplaySettingObserver != null) {
-                mSmoothDisplaySettingObserver.dispatchChange(false /*selfChange*/,
-                        SMOOTH_DISPLAY_URI);
-            }
-        }
-
-        void notifyForcePeakRefreshRateSettingChanged() {
-            if (mForcePeakRefreshRateSettingObserver != null) {
-                mForcePeakRefreshRateSettingObserver.dispatchChange(false /*selfChange*/,
-                        FORCE_PEAK_REFRESH_RATE_URI);
+        void notifyPeakRefreshRateChanged() {
+            if (mPeakRefreshRateObserver != null) {
+                mPeakRefreshRateObserver.dispatchChange(false /*selfChange*/,
+                        PEAK_REFRESH_RATE_URI);
             }
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index a446e10..c53a7a7 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_DESTINATION;
 import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER;
+import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_LONG;
 import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_SHORT;
 import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_SOURCE;
 import static com.android.server.hdmi.HdmiCecMessageValidator.OK;
@@ -145,11 +146,12 @@
     @Test
     public void isValid_systemAudioModeStatus() {
         assertMessageValidity("40:7E:00").isEqualTo(OK);
-        assertMessageValidity("40:7E:01:01").isEqualTo(OK);
+        assertMessageValidity("40:7E:01").isEqualTo(OK);
 
         assertMessageValidity("0F:7E:00").isEqualTo(ERROR_DESTINATION);
         assertMessageValidity("F0:7E").isEqualTo(ERROR_SOURCE);
         assertMessageValidity("40:7E").isEqualTo(ERROR_PARAMETER_SHORT);
+        assertMessageValidity("40:7E:01:1F:28").isEqualTo(ERROR_PARAMETER_LONG);
         assertMessageValidity("40:7E:02").isEqualTo(ERROR_PARAMETER);
     }
 
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 4dd5e94..fd6eb92 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -761,6 +761,13 @@
 
         assertThat(mHdmiControlServiceSpy.handleCecCommand(message))
                 .isEqualTo(Constants.ABORT_INVALID_OPERAND);
+
+        // Validating ERROR_PARAMETER_LONG will generate ABORT_INVALID_OPERAND.
+        // Taken from HdmiCecMessageValidatorTest#isValid_systemAudioModeStatus
+        HdmiCecMessage systemAudioModeStatus = HdmiUtils.buildMessage("40:7E:01:1F:28");
+
+        assertThat(mHdmiControlServiceSpy.handleCecCommand(systemAudioModeStatus))
+                .isEqualTo(Constants.ABORT_INVALID_OPERAND);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index d0d28c3..55c45df 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -85,6 +85,9 @@
         const val DEVICE_ID = 1
         const val VENDOR_SPECIFIC_DEVICE_ID = 2
         const val ENGLISH_DVORAK_DEVICE_ID = 3
+        const val ENGLISH_QWERTY_DEVICE_ID = 4
+        const val DEFAULT_VENDOR_ID = 123
+        const val DEFAULT_PRODUCT_ID = 456
         const val USER_ID = 4
         const val IME_ID = "ime_id"
         const val PACKAGE_NAME = "KeyboardLayoutManagerTests"
@@ -122,6 +125,7 @@
     private lateinit var keyboardDevice: InputDevice
     private lateinit var vendorSpecificKeyboardDevice: InputDevice
     private lateinit var englishDvorakKeyboardDevice: InputDevice
+    private lateinit var englishQwertyKeyboardDevice: InputDevice
 
     @Before
     fun setup() {
@@ -150,17 +154,26 @@
         Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
             .thenReturn(inputManager)
 
-        keyboardDevice = createKeyboard(DEVICE_ID, 0, 0, "", "")
+        keyboardDevice = createKeyboard(DEVICE_ID, DEFAULT_VENDOR_ID, DEFAULT_PRODUCT_ID, "", "")
         vendorSpecificKeyboardDevice = createKeyboard(VENDOR_SPECIFIC_DEVICE_ID, 1, 1, "", "")
-        englishDvorakKeyboardDevice =
-            createKeyboard(ENGLISH_DVORAK_DEVICE_ID, 0, 0, "en", "dvorak")
+        englishDvorakKeyboardDevice = createKeyboard(ENGLISH_DVORAK_DEVICE_ID, DEFAULT_VENDOR_ID,
+                DEFAULT_PRODUCT_ID, "en", "dvorak")
+        englishQwertyKeyboardDevice = createKeyboard(ENGLISH_QWERTY_DEVICE_ID, DEFAULT_VENDOR_ID,
+                DEFAULT_PRODUCT_ID, "en", "qwerty")
         Mockito.`when`(iInputManager.inputDeviceIds)
-            .thenReturn(intArrayOf(DEVICE_ID, VENDOR_SPECIFIC_DEVICE_ID, ENGLISH_DVORAK_DEVICE_ID))
+            .thenReturn(intArrayOf(
+                DEVICE_ID,
+                VENDOR_SPECIFIC_DEVICE_ID,
+                ENGLISH_DVORAK_DEVICE_ID,
+                ENGLISH_QWERTY_DEVICE_ID
+            ))
         Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
         Mockito.`when`(iInputManager.getInputDevice(VENDOR_SPECIFIC_DEVICE_ID))
             .thenReturn(vendorSpecificKeyboardDevice)
         Mockito.`when`(iInputManager.getInputDevice(ENGLISH_DVORAK_DEVICE_ID))
             .thenReturn(englishDvorakKeyboardDevice)
+        Mockito.`when`(iInputManager.getInputDevice(ENGLISH_QWERTY_DEVICE_ID))
+                .thenReturn(englishQwertyKeyboardDevice)
     }
 
     private fun setupBroadcastReceiver() {
@@ -778,14 +791,23 @@
     @Test
     fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withHwLanguageTagAndLayoutType() {
         NewSettingsApiFlag(true).use {
-            // Should return English dvorak even if IME current layout is qwerty, since HW says the
+            val frenchSubtype = createImeSubtypeForLanguageTagAndLayoutType("fr", "azerty")
+            // Should return English dvorak even if IME current layout is French, since HW says the
             // keyboard is a Dvorak keyboard
             assertCorrectLayout(
                 englishDvorakKeyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("en", "qwerty"),
+                frenchSubtype,
                 createLayoutDescriptor("keyboard_layout_english_us_dvorak")
             )
 
+            // Back to back changing HW keyboards with same product and vendor ID but different
+            // language and layout type should configure the layouts correctly.
+            assertCorrectLayout(
+                englishQwertyKeyboardDevice,
+                frenchSubtype,
+                createLayoutDescriptor("keyboard_layout_english_us")
+            )
+
             // Fallback to IME information if the HW provided layout script is incompatible with the
             // provided IME subtype
             assertCorrectLayout(
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index d07831d..fd65807 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -20,16 +20,25 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
+import android.platform.test.annotations.Presubmit;
+
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+@Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class InputMethodManagerServiceTests {
@@ -87,4 +96,25 @@
                 InputMethodManagerService.computeImeDisplayIdForTarget(
                         SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker));
     }
+
+    @Test
+    public void testSoftInputShowHideHistoryDump_withNulls_doesntThrow() {
+        var writer = new StringWriter();
+        var history = new InputMethodManagerService.SoftInputShowHideHistory();
+        history.addEntry(new InputMethodManagerService.SoftInputShowHideHistory.Entry(
+                null,
+                null,
+                null,
+                SOFT_INPUT_STATE_UNSPECIFIED,
+                SoftInputShowHideReason.SHOW_SOFT_INPUT,
+                false,
+                null,
+                null,
+                null,
+                null));
+
+        history.dump(new PrintWriter(writer), "" /* prefix */);
+
+        // Asserts that dump doesn't throw an NPE.
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index e65f8cf..7c1845f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -201,7 +201,7 @@
         mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL + 50;
         assertResetTimes(START_TIME + 4 * INTERVAL, START_TIME + 5 * INTERVAL);
 
-        mService.saveBaseStateLocked();
+        mService.saveBaseState();
 
         dumpBaseStateFile();
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 01e56a0..1cfaf7c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -228,6 +228,15 @@
                 });
     }
 
+    public void testShortcutIdTruncated() {
+        ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(),
+                "s".repeat(Short.MAX_VALUE)).build();
+
+        assertTrue(
+                "id must be truncated to MAX_ID_LENGTH",
+                si.getId().length() <= ShortcutInfo.MAX_ID_LENGTH);
+    }
+
     public void testShortcutInfoParcel() {
         setCaller(CALLING_PACKAGE_1, USER_10);
         ShortcutInfo si = parceled(new ShortcutInfo.Builder(mClientContext)
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserJourneyLoggerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserJourneyLoggerTest.java
new file mode 100644
index 0000000..20e2692
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserJourneyLoggerTest.java
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_INCOMPLETE_OR_TIMEOUT;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_NULL_USER_INFO;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_UNSPECIFIED;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_BEGIN;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_CANCEL;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_ERROR;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_FINISH;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_NONE;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_GRANT_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_CREATE;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_REMOVE;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_START;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_STOP;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_CREATE_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_REMOVE_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_REVOKE_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_START_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_STOP_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.pm.UserInfo;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class UserJourneyLoggerTest {
+
+    public static final int FULL_USER_ADMIN_FLAG = 0x00000402;
+    private UserJourneyLogger mUserJourneyLogger;
+
+    @Before
+    public void setup() throws Exception {
+        mUserJourneyLogger = spy(new UserJourneyLogger());
+    }
+
+    @Test
+    public void testUserStartLifecycleJourneyReported() {
+        final UserLifecycleJourneyReportedCaptor report1 = new UserLifecycleJourneyReportedCaptor();
+        final UserJourneyLogger.UserJourneySession session = new UserJourneyLogger
+                .UserJourneySession(10, USER_JOURNEY_USER_START);
+
+        report1.captureLogAndAssert(mUserJourneyLogger, session,
+                USER_JOURNEY_USER_START, 0, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY, 1,
+                ERROR_CODE_UNSPECIFIED);
+    }
+
+
+    @Test
+    public void testUserLifecycleEventOccurred() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = new UserJourneyLogger
+                .UserJourneySession(10, USER_JOURNEY_USER_START);
+
+        report1.captureLogAndAssert(mUserJourneyLogger, session, 0,
+                USER_LIFECYCLE_EVENT_START_USER, EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED);
+    }
+
+    @Test
+    public void testLogUserLifecycleEvent() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_START_USER, EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        mUserJourneyLogger.logUserLifecycleEvent(10, USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
+                EVENT_STATE_NONE);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
+                EVENT_STATE_NONE, ERROR_CODE_UNSPECIFIED, 2);
+    }
+
+
+    @Test
+    public void testCreateUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(-1, USER_JOURNEY_USER_CREATE);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, -1,
+                USER_LIFECYCLE_EVENT_CREATE_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        UserInfo targetUser = new UserInfo(10, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserCreateJourneyFinish(0, targetUser);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_CREATE_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                USER_JOURNEY_USER_CREATE, 0, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+    }
+
+    @Test
+    public void testRemoveUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_USER_REMOVE);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_REMOVE_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final UserInfo targetUser = new UserInfo(10, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
+                USER_JOURNEY_USER_REMOVE);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_REMOVE_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                USER_JOURNEY_USER_REMOVE, 0, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+    }
+
+    @Test
+    public void testStartUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_START_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final UserInfo targetUser = new UserInfo(10, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
+                USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_START_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                USER_JOURNEY_USER_START, 0, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+    }
+
+    @Test
+    public void testStopUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_USER_STOP);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_STOP_USER, EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final UserInfo targetUser = new UserInfo(10, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
+                USER_JOURNEY_USER_STOP);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_STOP_USER, EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                USER_JOURNEY_USER_STOP, 0, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+    }
+
+    @Test
+    public void testAbortStopUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_USER_STOP);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_STOP_USER, EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final UserInfo targetUser = new UserInfo(10, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+
+        mUserJourneyLogger.logUserJourneyFinishWithError(-1, targetUser,
+                USER_JOURNEY_USER_STOP, ERROR_CODE_ABORTED);
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_STOP_USER,
+                EVENT_STATE_CANCEL, ERROR_CODE_ABORTED, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                USER_JOURNEY_USER_STOP, -1, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_ABORTED, 1);
+    }
+
+    @Test
+    public void testIncompleteStopUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_USER_STOP);
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_STOP_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        mUserJourneyLogger.finishAndClearIncompleteUserJourney(10, USER_JOURNEY_USER_STOP);
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_STOP_USER,
+                EVENT_STATE_ERROR,
+                ERROR_CODE_INCOMPLETE_OR_TIMEOUT, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                USER_JOURNEY_USER_STOP, -1, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN,
+                -1, // information about user are incomplete
+                ERROR_CODE_INCOMPLETE_OR_TIMEOUT, 1);
+    }
+
+    @Test
+    public void testGrantAdminUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_GRANT_ADMIN);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_GRANT_ADMIN,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final UserInfo targetUser = new UserInfo(10, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
+                USER_JOURNEY_GRANT_ADMIN);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_GRANT_ADMIN,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                USER_JOURNEY_GRANT_ADMIN, 0, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+    }
+
+    @Test
+    public void testNullUserErrorGrantAdminUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+
+        UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_GRANT_ADMIN);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_GRANT_ADMIN,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        mUserJourneyLogger.logNullUserJourneyError(USER_JOURNEY_GRANT_ADMIN,
+                0, 10, "", -1);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_GRANT_ADMIN,
+                EVENT_STATE_ERROR, ERROR_CODE_NULL_USER_INFO, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                USER_JOURNEY_GRANT_ADMIN, 0, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN,
+                -1, ERROR_CODE_NULL_USER_INFO, 1);
+    }
+
+    @Test
+    public void testRevokeAdminUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(10, UserJourneyLogger.USER_JOURNEY_REVOKE_ADMIN);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_REVOKE_ADMIN,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final UserInfo targetUser = new UserInfo(10, "test target user", UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
+                UserJourneyLogger.USER_JOURNEY_REVOKE_ADMIN);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_REVOKE_ADMIN,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                UserJourneyLogger.USER_JOURNEY_REVOKE_ADMIN, 0, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000400, ERROR_CODE_UNSPECIFIED, 1);
+    }
+
+    @Test
+    public void testSwitchFGUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(11, UserJourneyLogger.USER_JOURNEY_USER_SWITCH_FG);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserJourneyLogger.UserJourneySession session2 = mUserJourneyLogger
+                .logUserJourneyBegin(11, USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 11,
+                USER_LIFECYCLE_EVENT_START_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 2);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final UserInfo targetUser = new UserInfo(11, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(10, targetUser,
+                USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 11,
+                USER_LIFECYCLE_EVENT_START_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 3);
+
+        report2.captureAndAssert(mUserJourneyLogger, session2.mSessionId,
+                USER_JOURNEY_USER_START, 10, 11,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+
+        mUserJourneyLogger.logUserSwitchJourneyFinish(10, targetUser);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 4);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                UserJourneyLogger.USER_JOURNEY_USER_SWITCH_FG, 10, 11,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 2);
+    }
+
+
+    @Test
+    public void testSwitchUIUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(11, UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        final UserJourneyLogger.UserJourneySession session2 = mUserJourneyLogger
+                .logUserJourneyBegin(11, USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 11,
+                USER_LIFECYCLE_EVENT_START_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 2);
+
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        UserInfo targetUser = new UserInfo(11, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(10, targetUser,
+                USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 11,
+                USER_LIFECYCLE_EVENT_START_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 3);
+
+        report2.captureAndAssert(mUserJourneyLogger, session2.mSessionId,
+                USER_JOURNEY_USER_START, 10, 11,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+
+        mUserJourneyLogger.logUserSwitchJourneyFinish(10, targetUser);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+                EVENT_STATE_FINISH,
+                ERROR_CODE_UNSPECIFIED, 4);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI, 10, 11,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 2);
+    }
+
+
+    @Test
+    public void testSwitchWithStopUIUserJourney() {
+        final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+
+        // BEGIN USER SWITCH
+        final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+                .logUserJourneyBegin(11, UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+        // BEGIN USER STOP
+        final  UserJourneyLogger.UserJourneySession session2 = mUserJourneyLogger
+                .logUserJourneyBegin(10, USER_JOURNEY_USER_STOP);
+
+        report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_STOP_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 2);
+
+        // BEGIN USER START
+        UserJourneyLogger.UserJourneySession session3 = mUserJourneyLogger
+                .logUserJourneyBegin(11, USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session3.mSessionId, 11,
+                USER_LIFECYCLE_EVENT_START_USER,
+                EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 3);
+
+
+        // FINISH USER STOP
+        final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+        final UserInfo targetUser = new UserInfo(10, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(-1, targetUser,
+                USER_JOURNEY_USER_STOP);
+
+        report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 10,
+                USER_LIFECYCLE_EVENT_STOP_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 4);
+
+        report2.captureAndAssert(mUserJourneyLogger, session2.mSessionId,
+                USER_JOURNEY_USER_STOP, -1, 10,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                FULL_USER_ADMIN_FLAG, ERROR_CODE_UNSPECIFIED, 1);
+
+        // FINISH USER START
+        final UserInfo targetUser2 = new UserInfo(11, "test target user",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+        mUserJourneyLogger.logUserJourneyFinish(10, targetUser2,
+                USER_JOURNEY_USER_START);
+
+        report1.captureAndAssert(mUserJourneyLogger, session3.mSessionId, 11,
+                USER_LIFECYCLE_EVENT_START_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 5);
+
+        report2.captureAndAssert(mUserJourneyLogger, session3.mSessionId,
+                USER_JOURNEY_USER_START, 10, 11,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                FULL_USER_ADMIN_FLAG, ERROR_CODE_UNSPECIFIED, 2);
+
+
+        // FINISH USER SWITCH
+        mUserJourneyLogger.logUserSwitchJourneyFinish(10, targetUser2);
+
+        report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+                UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+                EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 6);
+
+        report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+                UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI, 10, 11,
+                FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+                0x00000402, ERROR_CODE_UNSPECIFIED, 3);
+    }
+
+    static class UserLifecycleJourneyReportedCaptor {
+        ArgumentCaptor<Long> mSessionId = ArgumentCaptor.forClass(Long.class);
+        ArgumentCaptor<Integer> mJourney = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> mOriginalUserId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> mTargetUserId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> mUserType = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> mUserFlags = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> mErrorCode = ArgumentCaptor.forClass(Integer.class);
+
+        public void captureAndAssert(UserJourneyLogger mUserJourneyLogger,
+                long sessionId, int journey, int originalUserId,
+                int targetUserId, int userType, int userFlags, int errorCode, int times) {
+            verify(mUserJourneyLogger, times(times))
+                    .writeUserLifecycleJourneyReported(mSessionId.capture(),
+                            mJourney.capture(),
+                            mOriginalUserId.capture(),
+                            mTargetUserId.capture(),
+                            mUserType.capture(),
+                            mUserFlags.capture(),
+                            mErrorCode.capture());
+
+            assertThat(mSessionId.getValue()).isEqualTo(sessionId);
+            assertThat(mJourney.getValue()).isEqualTo(journey);
+            assertThat(mOriginalUserId.getValue()).isEqualTo(originalUserId);
+            assertThat(mTargetUserId.getValue()).isEqualTo(targetUserId);
+            assertThat(mUserType.getValue()).isEqualTo(userType);
+            assertThat(mUserFlags.getValue()).isEqualTo(userFlags);
+            assertThat(mErrorCode.getValue()).isEqualTo(errorCode);
+        }
+
+
+        public void captureLogAndAssert(UserJourneyLogger mUserJourneyLogger,
+                UserJourneyLogger.UserJourneySession session, int journey, int originalUserId,
+                int targetUserId, int userType, int userFlags, int errorCode) {
+            mUserJourneyLogger.logUserLifecycleJourneyReported(session, journey, originalUserId,
+                    targetUserId, userType, userFlags, errorCode);
+
+            captureAndAssert(mUserJourneyLogger, session.mSessionId, journey, originalUserId,
+                    targetUserId, userType, userFlags, errorCode, 1);
+        }
+    }
+
+
+    static class UserLifecycleEventOccurredCaptor {
+        ArgumentCaptor<Long> mSessionId = ArgumentCaptor.forClass(Long.class);
+        ArgumentCaptor<Integer> mTargetUserId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> mEvent = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> mStste = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> mErrorCode = ArgumentCaptor.forClass(Integer.class);
+
+
+        public void captureAndAssert(UserJourneyLogger mUserJourneyLogger,
+                long sessionId, int targetUserId, int event, int state, int errorCode, int times) {
+            verify(mUserJourneyLogger, times(times))
+                    .writeUserLifecycleEventOccurred(mSessionId.capture(),
+                            mTargetUserId.capture(),
+                            mEvent.capture(),
+                            mStste.capture(),
+                            mErrorCode.capture());
+
+            assertThat(mSessionId.getValue()).isEqualTo(sessionId);
+            assertThat(mTargetUserId.getValue()).isEqualTo(targetUserId);
+            assertThat(mEvent.getValue()).isEqualTo(event);
+            assertThat(mStste.getValue()).isEqualTo(state);
+            assertThat(mErrorCode.getValue()).isEqualTo(errorCode);
+        }
+
+
+        public void captureLogAndAssert(UserJourneyLogger mUserJourneyLogger,
+                UserJourneyLogger.UserJourneySession session, int targetUserId, int event,
+                int state, int errorCode) {
+            mUserJourneyLogger.logUserLifecycleEventOccurred(session, targetUserId, event,
+                    state, errorCode);
+
+            captureAndAssert(mUserJourneyLogger, session.mSessionId, targetUserId, event,
+                    state, errorCode, 1);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 52bf244..ae3ceb1 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.os.PowerManager.USER_ACTIVITY_EVENT_BUTTON;
 import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
 import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
 import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
@@ -41,6 +42,7 @@
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -112,6 +114,7 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
 
+import java.time.Duration;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.Executor;
@@ -2468,4 +2471,18 @@
         verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(),
                 anyInt(), any(), any(), same(callback2));
     }
+
+    @Test
+    public void testUserActivity_futureEventsAreIgnored() {
+        createService();
+        startSystem();
+        // Starting the system triggers a user activity event, so clear that before calling
+        // userActivity() directly.
+        clearInvocations(mNotifierMock);
+        final long eventTime = mClock.now() + Duration.ofHours(10).toMillis();
+        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, eventTime,
+                USER_ACTIVITY_EVENT_BUTTON, /* flags= */ 0);
+        verify(mNotifierMock, never()).onUserActivity(anyInt(),  anyInt(), anyInt());
+    }
+
 }
diff --git a/services/tests/servicestests/utils/com/android/server/testutils/OWNERS b/services/tests/servicestests/utils/com/android/server/testutils/OWNERS
new file mode 100644
index 0000000..bdacf7f
--- /dev/null
+++ b/services/tests/servicestests/utils/com/android/server/testutils/OWNERS
@@ -0,0 +1 @@
+per-file *Transaction.java  = file:/services/core/java/com/android/server/wm/OWNERS
\ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
similarity index 98%
rename from services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
rename to services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
index 31546e8..34e8ff2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm;
+package com.android.server.testutils;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -30,6 +30,8 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 
+import com.android.server.testutils.StubTransaction;
+
 import java.util.HashSet;
 import java.util.concurrent.Executor;
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index 397e3c1..539f329 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -36,6 +36,7 @@
 import static org.mockito.Mockito.when;
 
 import android.Manifest;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
@@ -47,7 +48,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -67,7 +67,7 @@
 public class PermissionHelperTest extends UiServiceTestCase {
 
     @Mock
-    private PermissionManagerServiceInternal mPmi;
+    private Context mContext;
     @Mock
     private IPackageManager mPackageManager;
     @Mock
@@ -80,7 +80,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mPermissionHelper = new PermissionHelper(mPmi, mPackageManager, mPermManager);
+        mPermissionHelper = new PermissionHelper(mContext, mPackageManager, mPermManager);
         PackageInfo testPkgInfo = new PackageInfo();
         testPkgInfo.requestedPermissions = new String[]{ Manifest.permission.POST_NOTIFICATIONS };
         when(mPackageManager.getPackageInfo(anyString(), anyLong(), anyInt()))
@@ -89,12 +89,12 @@
 
     @Test
     public void testHasPermission() throws Exception {
-        when(mPmi.checkUidPermission(anyInt(), anyString()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_GRANTED);
 
         assertThat(mPermissionHelper.hasPermission(1)).isTrue();
 
-        when(mPmi.checkUidPermission(anyInt(), anyString()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_DENIED);
 
         assertThat(mPermissionHelper.hasPermission(1)).isFalse();
@@ -241,7 +241,7 @@
 
     @Test
     public void testSetNotificationPermission_grantUserSet() throws Exception {
-        when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_DENIED);
         mPermissionHelper.setNotificationPermission("pkg", 10, true, true);
 
@@ -255,7 +255,7 @@
     @Test
     public void testSetNotificationPermission_pkgPerm_grantedByDefaultPermSet_allUserSet()
             throws Exception {
-        when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_DENIED);
         when(mPermManager.getPermissionFlags(anyString(),
                 eq(Manifest.permission.POST_NOTIFICATIONS),
@@ -273,7 +273,7 @@
 
     @Test
     public void testSetNotificationPermission_revokeUserSet() throws Exception {
-        when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_GRANTED);
 
         mPermissionHelper.setNotificationPermission("pkg", 10, false, true);
@@ -287,7 +287,7 @@
 
     @Test
     public void testSetNotificationPermission_grantNotUserSet() throws Exception {
-        when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_DENIED);
 
         mPermissionHelper.setNotificationPermission("pkg", 10, true, false);
@@ -300,7 +300,7 @@
 
     @Test
     public void testSetNotificationPermission_revokeNotUserSet() throws Exception {
-        when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_GRANTED);
 
         mPermissionHelper.setNotificationPermission("pkg", 10, false, false);
@@ -340,7 +340,7 @@
 
     @Test
     public void testSetNotificationPermission_alreadyGrantedNotRegranted() throws Exception {
-        when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_GRANTED);
         mPermissionHelper.setNotificationPermission("pkg", 10, true, false);
 
@@ -350,7 +350,7 @@
 
     @Test
     public void testSetNotificationPermission_alreadyRevokedNotRerevoked() throws Exception {
-        when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_DENIED);
         mPermissionHelper.setNotificationPermission("pkg", 10, false, false);
 
@@ -360,16 +360,19 @@
 
     @Test
     public void testSetNotificationPermission_doesntRequestNotChanged() throws Exception {
-        when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+        int testUid = -1;
+        when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
                 .thenReturn(PERMISSION_GRANTED);
+        when(mPackageManager.getPackageUid(anyString(), anyInt(), anyInt()))
+                .thenReturn(testUid);
         PackageInfo testPkgInfo = new PackageInfo();
         testPkgInfo.requestedPermissions = new String[]{ Manifest.permission.RECORD_AUDIO };
         when(mPackageManager.getPackageInfo(anyString(), anyLong(), anyInt()))
                 .thenReturn(testPkgInfo);
         mPermissionHelper.setNotificationPermission("pkg", 10, false, false);
 
-        verify(mPmi, never()).checkPermission(
-                eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10));
+        verify(mContext, never()).checkPermission(
+                eq(Manifest.permission.POST_NOTIFICATIONS), eq(-1), eq(testUid));
         verify(mPermManager, never()).revokeRuntimePermission(
                 eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
     }
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/DeviceStateHandlerTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/DeviceStateHandlerTest.java
new file mode 100644
index 0000000..089bd45
--- /dev/null
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/DeviceStateHandlerTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger;
+
+import static android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_DISABLED;
+import static android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
+import static android.os.PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY;
+
+import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState;
+import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState.*;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.SystemClock;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.utils.EventLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public final class DeviceStateHandlerTest {
+    private final long CONFIRM_NO_EVENT_WAIT_MS = 1000;
+    // A wait substantially less than the duration we delay phone notifications by
+    private final long PHONE_DELAY_BRIEF_WAIT_MS =
+            DeviceStateHandler.CALL_INACTIVE_MSG_DELAY_MS / 4;
+
+    private DeviceStateHandler mHandler;
+    private DeviceStateHandler.DeviceStateListener mDeviceStateCallback;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private SoundTriggerDeviceState mState;
+
+    @GuardedBy("mLock")
+    private CountDownLatch mLatch;
+
+    private EventLogger mEventLogger;
+
+    @Before
+    public void setup() {
+        // Reset the state prior to each test
+        mEventLogger = new EventLogger(256, "test logger");
+        synchronized (mLock) {
+            mLatch = new CountDownLatch(1);
+        }
+        mDeviceStateCallback =
+                (SoundTriggerDeviceState state) -> {
+                    synchronized (mLock) {
+                        mState = state;
+                        mLatch.countDown();
+                    }
+                };
+        mHandler = new DeviceStateHandler(Runnable::run, mEventLogger);
+        mHandler.onPhoneCallStateChanged(false);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+        mHandler.registerListener(mDeviceStateCallback);
+        try {
+            waitAndAssertState(ENABLE);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void waitAndAssertState(SoundTriggerDeviceState state) throws InterruptedException {
+        CountDownLatch latch;
+        synchronized (mLock) {
+            latch = mLatch;
+        }
+        latch.await();
+        synchronized (mLock) {
+            assertThat(mState).isEqualTo(state);
+            mLatch = new CountDownLatch(1);
+        }
+    }
+
+    private void waitToConfirmNoEventReceived() throws InterruptedException {
+        CountDownLatch latch;
+        synchronized (mLock) {
+            latch = mLatch;
+        }
+        // Check that we time out
+        assertThat(latch.await(CONFIRM_NO_EVENT_WAIT_MS, TimeUnit.MILLISECONDS)).isFalse();
+    }
+
+    @Test
+    public void onPowerModeChangedCritical_receiveStateChange() throws Exception {
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitAndAssertState(CRITICAL);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+        waitAndAssertState(ENABLE);
+    }
+
+    @Test
+    public void onPowerModeChangedDisabled_receiveStateChange() throws Exception {
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+        waitAndAssertState(DISABLE);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+        waitAndAssertState(ENABLE);
+    }
+
+    @Test
+    public void onPowerModeChangedMultiple_receiveStateChange() throws Exception {
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+        waitAndAssertState(DISABLE);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitAndAssertState(CRITICAL);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+        waitAndAssertState(DISABLE);
+    }
+
+    @Test
+    public void onPowerModeSameState_noStateChange() throws Exception {
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+        waitAndAssertState(DISABLE);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+        waitToConfirmNoEventReceived();
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitAndAssertState(CRITICAL);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitToConfirmNoEventReceived();
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+        waitAndAssertState(ENABLE);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+        waitToConfirmNoEventReceived();
+    }
+
+    @Test
+    public void onPhoneCall_receiveStateChange() throws Exception {
+        mHandler.onPhoneCallStateChanged(true);
+        waitAndAssertState(DISABLE);
+        mHandler.onPhoneCallStateChanged(false);
+        waitAndAssertState(ENABLE);
+    }
+
+    @Test
+    public void onPhoneCall_receiveStateChangeIsDelayed() throws Exception {
+        mHandler.onPhoneCallStateChanged(true);
+        waitAndAssertState(DISABLE);
+        long beforeTime = SystemClock.uptimeMillis();
+        mHandler.onPhoneCallStateChanged(false);
+        waitAndAssertState(ENABLE);
+        long afterTime = SystemClock.uptimeMillis();
+        assertThat(afterTime - beforeTime).isAtLeast(DeviceStateHandler.CALL_INACTIVE_MSG_DELAY_MS);
+    }
+
+    @Test
+    public void onPhoneCallEnterExitEnter_receiveNoStateChange() throws Exception {
+        mHandler.onPhoneCallStateChanged(true);
+        waitAndAssertState(DISABLE);
+        mHandler.onPhoneCallStateChanged(false);
+        SystemClock.sleep(PHONE_DELAY_BRIEF_WAIT_MS);
+        mHandler.onPhoneCallStateChanged(true);
+        waitToConfirmNoEventReceived();
+    }
+
+    @Test
+    public void onBatteryCallbackDuringPhoneWait_receiveStateChangeDelayed() throws Exception {
+        mHandler.onPhoneCallStateChanged(true);
+        waitAndAssertState(DISABLE);
+        mHandler.onPhoneCallStateChanged(false);
+        SystemClock.sleep(PHONE_DELAY_BRIEF_WAIT_MS);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitAndAssertState(CRITICAL);
+        // Ensure we don't get an ENABLE event after
+        waitToConfirmNoEventReceived();
+    }
+
+    @Test
+    public void onBatteryChangeWhenInPhoneCall_receiveNoStateChange() throws Exception {
+        mHandler.onPhoneCallStateChanged(true);
+        waitAndAssertState(DISABLE);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+        waitToConfirmNoEventReceived();
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitToConfirmNoEventReceived();
+    }
+
+    @Test
+    public void whenBatteryCriticalChangeDuringCallAfterPhoneCall_receiveCriticalStateChange()
+            throws Exception {
+        mHandler.onPhoneCallStateChanged(true);
+        waitAndAssertState(DISABLE);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitToConfirmNoEventReceived();
+        mHandler.onPhoneCallStateChanged(false);
+        waitAndAssertState(CRITICAL);
+    }
+
+    @Test
+    public void whenBatteryDisableDuringCallAfterPhoneCallBatteryEnable_receiveStateChange()
+            throws Exception {
+        mHandler.onPhoneCallStateChanged(true);
+        waitAndAssertState(DISABLE);
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+        waitToConfirmNoEventReceived();
+        mHandler.onPhoneCallStateChanged(false);
+        waitToConfirmNoEventReceived();
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitAndAssertState(CRITICAL);
+    }
+
+    @Test
+    public void whenPhoneCallDuringBatteryDisable_receiveNoStateChange() throws Exception {
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+        waitAndAssertState(DISABLE);
+        mHandler.onPhoneCallStateChanged(true);
+        waitToConfirmNoEventReceived();
+        mHandler.onPhoneCallStateChanged(false);
+        waitToConfirmNoEventReceived();
+    }
+
+    @Test
+    public void whenPhoneCallDuringBatteryCritical_receiveStateChange() throws Exception {
+        mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+        waitAndAssertState(CRITICAL);
+        mHandler.onPhoneCallStateChanged(true);
+        waitAndAssertState(DISABLE);
+        mHandler.onPhoneCallStateChanged(false);
+        waitAndAssertState(CRITICAL);
+    }
+
+    // This test could be flaky, but we want to verify that we only delay notification if
+    // we are exiting a call, NOT if we are entering a call.
+    @FlakyTest
+    @Test
+    public void whenPhoneCallReceived_receiveStateChangeFast() throws Exception {
+        mHandler.onPhoneCallStateChanged(true);
+        CountDownLatch latch;
+        synchronized (mLock) {
+            latch = mLatch;
+        }
+        assertThat(latch.await(PHONE_DELAY_BRIEF_WAIT_MS, TimeUnit.MILLISECONDS)).isTrue();
+        synchronized (mLock) {
+            assertThat(mState).isEqualTo(DISABLE);
+        }
+    }
+}
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
index 2d0755d..3ac9a27 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.soundtrigger_middleware;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -34,12 +36,12 @@
 import static org.mockito.Mockito.when;
 
 import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.Properties;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger.Status;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.HwParcel;
 import android.os.IBinder;
 import android.os.IHwBinder;
@@ -617,13 +619,16 @@
             final int handle = 85;
             final int status =
                     android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.ABORT;
-            ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                    RecognitionEvent.class);
+            ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                    RecognitionEventSys.class);
 
             hwCallback.recognitionCallback(TestUtil.createRecognitionEvent_2_0(handle, status), 99);
             mCanonical.flushCallbacks();
             verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
-            TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.ABORTED,
+            RecognitionEventSys lastEvent = eventCaptor.getValue();
+            assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+            TestUtil.validateRecognitionEvent(lastEvent.recognitionEvent,
+                    RecognitionStatus.ABORTED,
                     false);
         }
 
@@ -631,14 +636,16 @@
             final int handle = 92;
             final int status =
                     android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS;
-            ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                    PhraseRecognitionEvent.class);
+            ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                    PhraseRecognitionEventSys.class);
 
             hwCallback.phraseRecognitionCallback(
                     TestUtil.createPhraseRecognitionEvent_2_0(handle, status), 99);
             mCanonical.flushCallbacks();
             verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
-            TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
+            PhraseRecognitionEventSys lastEvent = eventCaptor.getValue();
+            assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+            TestUtil.validatePhraseRecognitionEvent(lastEvent.phraseRecognitionEvent,
                     RecognitionStatus.SUCCESS, false);
         }
         verifyNoMoreInteractions(canonicalCallback);
@@ -652,28 +659,34 @@
             final int handle = 85;
             final int status =
                     android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.ABORT;
-            ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                    RecognitionEvent.class);
+            ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                    RecognitionEventSys.class);
 
             hwCallback.recognitionCallback_2_1(TestUtil.createRecognitionEvent_2_1(handle, status),
                     99);
             mCanonical.flushCallbacks();
             verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
-            TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.ABORTED,
+            RecognitionEventSys lastEvent = eventCaptor.getValue();
+            assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+            TestUtil.validateRecognitionEvent(lastEvent.recognitionEvent,
+                    RecognitionStatus.ABORTED,
                     false);
         }
 
         {
             final int handle = 87;
             final int status = 3; // FORCED;
-            ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                    RecognitionEvent.class);
+            ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                    RecognitionEventSys.class);
 
             hwCallback.recognitionCallback_2_1(TestUtil.createRecognitionEvent_2_1(handle, status),
                     99);
             mCanonical.flushCallbacks();
             verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
-            TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.FORCED,
+            RecognitionEventSys lastEvent = eventCaptor.getValue();
+            assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+            TestUtil.validateRecognitionEvent(lastEvent.recognitionEvent,
+                    RecognitionStatus.FORCED,
                     true);
         }
 
@@ -681,28 +694,32 @@
             final int handle = 92;
             final int status =
                     android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS;
-            ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                    PhraseRecognitionEvent.class);
+            ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                    PhraseRecognitionEventSys.class);
 
             hwCallback.phraseRecognitionCallback_2_1(
                     TestUtil.createPhraseRecognitionEvent_2_1(handle, status), 99);
             mCanonical.flushCallbacks();
             verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
-            TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
+            PhraseRecognitionEventSys lastEvent = eventCaptor.getValue();
+            assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+            TestUtil.validatePhraseRecognitionEvent(lastEvent.phraseRecognitionEvent,
                     RecognitionStatus.SUCCESS, false);
         }
 
         {
             final int handle = 102;
             final int status = 3; // FORCED;
-            ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                    PhraseRecognitionEvent.class);
+            ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                    PhraseRecognitionEventSys.class);
 
             hwCallback.phraseRecognitionCallback_2_1(
                     TestUtil.createPhraseRecognitionEvent_2_1(handle, status), 99);
             mCanonical.flushCallbacks();
             verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
-            TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
+            PhraseRecognitionEventSys lastEvent = eventCaptor.getValue();
+            assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+            TestUtil.validatePhraseRecognitionEvent(lastEvent.phraseRecognitionEvent,
                     RecognitionStatus.FORCED, true);
         }
         verifyNoMoreInteractions(canonicalCallback);
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
index 6198925..9a59ede 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
@@ -30,8 +30,8 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.RecognitionStatus;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 
 import androidx.annotation.NonNull;
 
@@ -68,13 +68,14 @@
 
         mNotifier.setActive(true);
         verify(mUnderlying).stopRecognition(handle);
-        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                RecognitionEvent.class);
+        ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEventSys.class);
         Thread.sleep(50);
         verify(callback).recognitionCallback(eq(handle), eventCaptor.capture());
-        RecognitionEvent event = eventCaptor.getValue();
-        assertEquals(event.status, RecognitionStatus.ABORTED);
-        assertFalse(event.recognitionStillActive);
+        RecognitionEventSys event = eventCaptor.getValue();
+        assertEquals(event.halEventReceivedMillis, -1);
+        assertEquals(event.recognitionEvent.status, RecognitionStatus.ABORTED);
+        assertFalse(event.recognitionEvent.recognitionStillActive);
         verifyZeroInteractions(mGlobalCallback);
         clearInvocations(callback, mUnderlying);
 
@@ -116,8 +117,11 @@
 
         mNotifier.setActive(true);
         verify(mUnderlying, times(1)).stopRecognition(handle);
+        ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEventSys.class);
         mHandler.stopRecognition(handle);
-        verify(callback, times(1)).recognitionCallback(eq(handle), any());
+        verify(callback).recognitionCallback(eq(handle), eventCaptor.capture());
+        assertEquals(eventCaptor.getValue().halEventReceivedMillis, -1);
     }
 
     @Test(timeout = 200)
@@ -133,19 +137,21 @@
         verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
 
         doAnswer(invocation -> {
-            RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.ABORTED,
+            RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+            recognitionEventSys.recognitionEvent = TestUtil.createRecognitionEvent(
+                    RecognitionStatus.ABORTED,
                     false);
+            recognitionEventSys.halEventReceivedMillis = 12345;
             // Call the callback from a different thread to detect deadlocks by preventing recursive
             // locking from working.
-            runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+            runOnSeparateThread(
+                    () -> modelCallback.recognitionCallback(handle, recognitionEventSys));
             return null;
         }).when(mUnderlying).stopRecognition(handle);
         mHandler.stopRecognition(handle);
         verify(mUnderlying, times(1)).stopRecognition(handle);
 
-        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                RecognitionEvent.class);
-        verify(callback, atMost(1)).recognitionCallback(eq(handle), eventCaptor.capture());
+        verify(callback, atMost(1)).recognitionCallback(eq(handle), any(RecognitionEventSys.class));
     }
 
     @Test(timeout = 200)
@@ -162,11 +168,15 @@
 
         doAnswer(invocation -> {
             // The stop request causes a callback to be flushed.
-            RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+            RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+            recognitionEventSys.recognitionEvent = TestUtil.createRecognitionEvent(
+                    RecognitionStatus.FORCED,
                     true);
+            recognitionEventSys.halEventReceivedMillis = 12345;
             // Call the callback from a different thread to detect deadlocks by preventing recursive
             // locking from working.
-            runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+            runOnSeparateThread(
+                    () -> modelCallback.recognitionCallback(handle, recognitionEventSys));
             // While the HAL is processing the stop request, capture state becomes active.
             new Thread(() -> mNotifier.setActive(true)).start();
             Thread.sleep(50);
@@ -194,11 +204,15 @@
 
         doAnswer(invocation -> {
             // The stop request causes a callback to be flushed.
-            RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+            RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+            recognitionEventSys.recognitionEvent = TestUtil.createRecognitionEvent(
+                    RecognitionStatus.FORCED,
                     true);
+            recognitionEventSys.halEventReceivedMillis = 12345;
             // Call the callback from a different thread to detect deadlocks by preventing recursive
             // locking from working.
-            runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+            runOnSeparateThread(
+                    () -> modelCallback.recognitionCallback(handle, recognitionEventSys));
             // While the HAL is processing the stop request, client requests stop.
             new Thread(() -> mHandler.stopRecognition(handle)).start();
             Thread.sleep(50);
@@ -223,23 +237,22 @@
         verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
 
         doAnswer(invocation -> {
-            RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.SUCCESS,
+            RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+            recognitionEventSys.recognitionEvent = TestUtil.createRecognitionEvent(
+                    RecognitionStatus.SUCCESS,
                     false);
+            recognitionEventSys.halEventReceivedMillis = 12345;
             // Call the callback from a different thread to detect deadlocks by preventing recursive
             // locking from working.
-            runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+            runOnSeparateThread(
+                    () -> modelCallback.recognitionCallback(handle, recognitionEventSys));
             return null;
         }).when(mUnderlying).stopRecognition(handle);
         mNotifier.setActive(true);
         verify(mUnderlying, times(1)).stopRecognition(handle);
         Thread.sleep(50);
 
-        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                RecognitionEvent.class);
-        verify(callback, atMost(2)).recognitionCallback(eq(handle), eventCaptor.capture());
-        RecognitionEvent lastEvent = eventCaptor.getValue();
-        assertEquals(lastEvent.status, RecognitionStatus.ABORTED);
-        assertFalse(lastEvent.recognitionStillActive);
+        verify(callback, atMost(2)).recognitionCallback(eq(handle), any());
     }
 
 
@@ -256,11 +269,15 @@
         verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
 
         doAnswer(invocation -> {
-            RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+            RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+            recognitionEventSys.recognitionEvent = TestUtil.createRecognitionEvent(
+                    RecognitionStatus.FORCED,
                     true);
+            recognitionEventSys.halEventReceivedMillis = 12345;
             // Call the callback from a different thread to detect deadlocks by preventing recursive
             // locking from working.
-            runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+            runOnSeparateThread(
+                    () -> modelCallback.recognitionCallback(handle, recognitionEventSys));
 
             return null;
         }).when(mUnderlying).stopRecognition(handle);
@@ -268,12 +285,7 @@
         verify(mUnderlying, times(1)).stopRecognition(handle);
 
         Thread.sleep(50);
-        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                RecognitionEvent.class);
-        verify(callback, atMost(2)).recognitionCallback(eq(handle), eventCaptor.capture());
-        RecognitionEvent lastEvent = eventCaptor.getValue();
-        assertEquals(lastEvent.status, RecognitionStatus.ABORTED);
-        assertFalse(lastEvent.recognitionStillActive);
+        verify(callback, atMost(2)).recognitionCallback(eq(handle), any());
     }
 
     private static void runOnSeparateThread(Runnable runnable) {
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index 3bebc94..5a2451f 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -30,18 +30,19 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.ElapsedRealtimeLong;
 import android.media.soundtrigger.ModelParameter;
 import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
 import android.media.soundtrigger.Properties;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.Status;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.os.RemoteException;
 import android.util.Pair;
@@ -224,10 +225,12 @@
         // Stop the recognition.
         stopRecognition(module, handle, hwHandle);
 
-        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                RecognitionEvent.class);
+        ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEventSys.class);
         verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
-        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
+        RecognitionEventSys lastEvent = eventCaptor.getValue();
+        assertEquals(-1, lastEvent.halEventReceivedMillis);
+        assertEquals(RecognitionStatus.ABORTED, lastEvent.recognitionEvent.status);
 
         // Unload the model.
         unloadModel(module, handle, hwHandle);
@@ -273,10 +276,12 @@
         // Stop the recognition.
         stopRecognition(module, handle, hwHandle);
 
-        ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                PhraseRecognitionEvent.class);
+        ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                PhraseRecognitionEventSys.class);
         verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture(), eq(101));
-        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
+        PhraseRecognitionEventSys lastEvent = eventCaptor.getValue();
+        assertEquals(-1, lastEvent.halEventReceivedMillis);
+        assertEquals(RecognitionStatus.ABORTED, lastEvent.phraseRecognitionEvent.common.status);
 
         // Unload the model.
         unloadModel(module, handle, hwHandle);
@@ -299,11 +304,11 @@
 
         {
             // Signal a capture from the driver (with "still active").
-            RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
-                    RecognitionStatus.SUCCESS, true);
+            RecognitionEventSys event = hwCallback.sendRecognitionEvent(hwHandle,
+                    RecognitionStatus.SUCCESS, true, 12345);
 
-            ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                    RecognitionEvent.class);
+            ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                    RecognitionEventSys.class);
             verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
             // Validate the event.
@@ -312,11 +317,11 @@
 
         {
             // Signal a capture from the driver (without "still active").
-            RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
-                    RecognitionStatus.SUCCESS, false);
+            RecognitionEventSys event = hwCallback.sendRecognitionEvent(hwHandle,
+                    RecognitionStatus.SUCCESS, false, 12345);
 
-            ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                    RecognitionEvent.class);
+            ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                    RecognitionEventSys.class);
             verify(callback, times(2)).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
             // Validate the event.
@@ -343,11 +348,11 @@
         startRecognition(module, handle, hwHandle);
 
         // Signal a capture from the driver.
-        PhraseRecognitionEvent event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
-                RecognitionStatus.SUCCESS, false);
+        PhraseRecognitionEventSys event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
+                RecognitionStatus.SUCCESS, false, 12345);
 
-        ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                PhraseRecognitionEvent.class);
+        ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                PhraseRecognitionEventSys.class);
         verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
         // Validate the event.
@@ -377,11 +382,11 @@
         verify(mHalDriver).forceRecognitionEvent(hwHandle);
 
         // Signal a capture from the driver.
-        RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
-                RecognitionStatus.FORCED, true);
+        RecognitionEventSys event = hwCallback.sendRecognitionEvent(hwHandle,
+                RecognitionStatus.FORCED, true, 12345);
 
-        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                RecognitionEvent.class);
+        ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEventSys.class);
         verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
         // Validate the event.
@@ -445,11 +450,11 @@
         verify(mHalDriver).forceRecognitionEvent(hwHandle);
 
         // Signal a capture from the driver.
-        PhraseRecognitionEvent event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
-                RecognitionStatus.FORCED, true);
+        PhraseRecognitionEventSys event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
+                RecognitionStatus.FORCED, true, 12345);
 
-        ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                PhraseRecognitionEvent.class);
+        ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                PhraseRecognitionEventSys.class);
         verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
         // Validate the event.
@@ -510,14 +515,16 @@
         startRecognition(module, handle, hwHandle);
 
         // Abort.
-        hwCallback.sendRecognitionEvent(hwHandle, RecognitionStatus.ABORTED, false);
+        hwCallback.sendRecognitionEvent(hwHandle, RecognitionStatus.ABORTED, false, 12345);
 
-        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                RecognitionEvent.class);
+        ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEventSys.class);
         verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
         // Validate the event.
-        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
+        RecognitionEventSys lastEvent = eventCaptor.getValue();
+        assertEquals(12345, lastEvent.halEventReceivedMillis);
+        assertEquals(RecognitionStatus.ABORTED, lastEvent.recognitionEvent.status);
 
         // Unload the model.
         unloadModel(module, handle, hwHandle);
@@ -540,14 +547,16 @@
         startRecognition(module, handle, hwHandle);
 
         // Abort.
-        hwCallback.sendPhraseRecognitionEvent(hwHandle, RecognitionStatus.ABORTED, false);
+        hwCallback.sendPhraseRecognitionEvent(hwHandle, RecognitionStatus.ABORTED, false, 12345);
 
-        ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                PhraseRecognitionEvent.class);
+        ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+                PhraseRecognitionEventSys.class);
         verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
         // Validate the event.
-        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
+        PhraseRecognitionEventSys lastEvent = eventCaptor.getValue();
+        assertEquals(12345, lastEvent.halEventReceivedMillis);
+        assertEquals(RecognitionStatus.ABORTED, lastEvent.phraseRecognitionEvent.common.status);
 
         // Unload the model.
         unloadModel(module, handle, hwHandle);
@@ -630,18 +639,24 @@
             mCallback = callback;
         }
 
-        private RecognitionEvent sendRecognitionEvent(int hwHandle, @RecognitionStatus int status,
-                boolean recognitionStillActive) {
-            RecognitionEvent event = TestUtil.createRecognitionEvent(status,
+        private RecognitionEventSys sendRecognitionEvent(int hwHandle,
+                @RecognitionStatus int status,
+                boolean recognitionStillActive, @ElapsedRealtimeLong long halEventReceivedMillis) {
+            RecognitionEventSys event = new RecognitionEventSys();
+            event.recognitionEvent = TestUtil.createRecognitionEvent(status,
                     recognitionStillActive);
+            event.halEventReceivedMillis = halEventReceivedMillis;
             mCallback.recognitionCallback(hwHandle, event);
             return event;
         }
 
-        private PhraseRecognitionEvent sendPhraseRecognitionEvent(int hwHandle,
-                @RecognitionStatus int status, boolean recognitionStillActive) {
-            PhraseRecognitionEvent event = TestUtil.createPhraseRecognitionEvent(status,
+        private PhraseRecognitionEventSys sendPhraseRecognitionEvent(int hwHandle,
+                @RecognitionStatus int status, boolean recognitionStillActive,
+                @ElapsedRealtimeLong long halEventReceivedMillis) {
+            PhraseRecognitionEventSys event = new PhraseRecognitionEventSys();
+            event.phraseRecognitionEvent = TestUtil.createPhraseRecognitionEvent(status,
                     recognitionStillActive);
+            event.halEventReceivedMillis = halEventReceivedMillis;
             mCallback.phraseRecognitionCallback(hwHandle, event);
             return event;
         }
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
new file mode 100644
index 0000000..cc357d7
--- /dev/null
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+
+import android.Manifest;
+import android.app.ActivityThread;
+import android.media.permission.Identity;
+import android.media.permission.IdentityContext;
+import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger.PhraseRecognitionExtra;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionStatus;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.os.BatteryStatsInternal;
+import android.os.Process;
+import android.os.RemoteException;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.util.FakeLatencyTracker;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class SoundTriggerMiddlewareLoggingLatencyTest {
+
+    private FakeLatencyTracker mLatencyTracker;
+    @Mock
+    private BatteryStatsInternal mBatteryStatsInternal;
+    @Mock
+    private ISoundTriggerMiddlewareInternal mDelegateMiddleware;
+    @Mock
+    private ISoundTriggerCallback mISoundTriggerCallback;
+    private SoundTriggerMiddlewareLogging mSoundTriggerMiddlewareLogging;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.WRITE_DEVICE_CONFIG,
+                        Manifest.permission.READ_DEVICE_CONFIG);
+
+        Identity identity = new Identity();
+        identity.uid = Process.myUid();
+        identity.pid = Process.myPid();
+        identity.packageName = ActivityThread.currentOpPackageName();
+        IdentityContext.create(identity);
+
+        mLatencyTracker = FakeLatencyTracker.create();
+        mLatencyTracker.forceEnabled(ACTION_SHOW_VOICE_INTERACTION, -1);
+        mSoundTriggerMiddlewareLogging = new SoundTriggerMiddlewareLogging(mLatencyTracker,
+                () -> mBatteryStatsInternal,
+                mDelegateMiddleware);
+    }
+
+    @After
+    public void tearDown() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @Test
+    @FlakyTest(bugId = 275113847)
+    public void testSetUpAndTearDown() {
+    }
+
+    @Test
+    @FlakyTest(bugId = 275113847)
+    public void testOnPhraseRecognitionStartsLatencyTrackerWithSuccessfulPhraseIdTrigger()
+            throws RemoteException {
+        ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
+                ISoundTriggerCallback.class);
+        mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
+        verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
+
+        triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
+                RecognitionStatus.SUCCESS, 100 /* keyphraseId */);
+
+        assertThat(mLatencyTracker.getActiveActionStartTime(
+                ACTION_SHOW_VOICE_INTERACTION)).isGreaterThan(-1);
+    }
+
+    @Test
+    @FlakyTest(bugId = 275113847)
+    public void testOnPhraseRecognitionRestartsActiveSession() throws RemoteException {
+        ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
+                ISoundTriggerCallback.class);
+        mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
+        verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
+
+        triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
+                RecognitionStatus.SUCCESS, 100 /* keyphraseId */);
+        long firstTriggerSessionStartTime = mLatencyTracker.getActiveActionStartTime(
+                ACTION_SHOW_VOICE_INTERACTION);
+        triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
+                RecognitionStatus.SUCCESS, 100 /* keyphraseId */);
+        assertThat(mLatencyTracker.getActiveActionStartTime(
+                ACTION_SHOW_VOICE_INTERACTION)).isGreaterThan(-1);
+        assertThat(mLatencyTracker.getActiveActionStartTime(
+                ACTION_SHOW_VOICE_INTERACTION)).isNotEqualTo(firstTriggerSessionStartTime);
+    }
+
+    @Test
+    @FlakyTest(bugId = 275113847)
+    public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNonSuccessEvent()
+            throws RemoteException {
+        ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
+                ISoundTriggerCallback.class);
+        mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
+        verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
+
+        triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
+                RecognitionStatus.ABORTED, 100 /* keyphraseId */);
+
+        assertThat(
+                mLatencyTracker.getActiveActionStartTime(ACTION_SHOW_VOICE_INTERACTION)).isEqualTo(
+                -1);
+    }
+
+    @Test
+    @FlakyTest(bugId = 275113847)
+    public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNoKeyphraseId()
+            throws RemoteException {
+        ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
+                ISoundTriggerCallback.class);
+        mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
+        verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
+
+        triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
+                RecognitionStatus.SUCCESS);
+
+        assertThat(
+                mLatencyTracker.getActiveActionStartTime(ACTION_SHOW_VOICE_INTERACTION)).isEqualTo(
+                -1);
+    }
+
+    private void triggerPhraseRecognitionEvent(ISoundTriggerCallback callback,
+            @RecognitionStatus int triggerEventStatus) throws RemoteException {
+        triggerPhraseRecognitionEvent(callback, triggerEventStatus, -1 /* keyphraseId */);
+    }
+
+    private void triggerPhraseRecognitionEvent(ISoundTriggerCallback callback,
+            @RecognitionStatus int triggerEventStatus, int keyphraseId) throws RemoteException {
+        // trigger a phrase recognition to start a latency tracker session
+        PhraseRecognitionEvent successEventWithKeyphraseId = new PhraseRecognitionEvent();
+        successEventWithKeyphraseId.common = new RecognitionEvent();
+        successEventWithKeyphraseId.common.status = triggerEventStatus;
+        if (keyphraseId > 0) {
+            PhraseRecognitionExtra recognitionExtra = new PhraseRecognitionExtra();
+            recognitionExtra.id = keyphraseId;
+            successEventWithKeyphraseId.phraseExtras =
+                    new PhraseRecognitionExtra[]{recognitionExtra};
+        }
+        PhraseRecognitionEventSys phraseRecognitionEventSys = new PhraseRecognitionEventSys();
+        phraseRecognitionEventSys.phraseRecognitionEvent = successEventWithKeyphraseId;
+        phraseRecognitionEventSys.halEventReceivedMillis = 12345;
+        callback.onPhraseRecognition(0 /* modelHandle */, phraseRecognitionEventSys,
+                0 /* captureSession */);
+    }
+
+}
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java
index eb117d1..f92e0db 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java
@@ -18,186 +18,27 @@
 
 import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.ServiceEvent;
 import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent;
-import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.verify;
-
-import android.Manifest;
-import android.app.ActivityThread;
-import android.media.permission.Identity;
-import android.media.permission.IdentityContext;
-import android.media.soundtrigger.PhraseRecognitionEvent;
-import android.media.soundtrigger.PhraseRecognitionExtra;
-import android.media.soundtrigger.RecognitionEvent;
-import android.media.soundtrigger.RecognitionStatus;
-import android.media.soundtrigger_middleware.ISoundTriggerCallback;
-import android.media.soundtrigger_middleware.ISoundTriggerModule;
-import android.os.BatteryStatsInternal;
-import android.os.Process;
-import android.os.RemoteException;
-
-import androidx.test.filters.FlakyTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.internal.util.FakeLatencyTracker;
-
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
 
 @RunWith(JUnit4.class)
 public class SoundTriggerMiddlewareLoggingTest {
     private static final ServiceEvent.Type SERVICE_TYPE = ServiceEvent.Type.ATTACH;
     private static final SessionEvent.Type SESSION_TYPE = SessionEvent.Type.LOAD_MODEL;
 
-    private FakeLatencyTracker mLatencyTracker;
-    @Mock
-    private BatteryStatsInternal mBatteryStatsInternal;
-    @Mock
-    private ISoundTriggerMiddlewareInternal mDelegateMiddleware;
-    @Mock
-    private ISoundTriggerCallback mISoundTriggerCallback;
-    @Mock
-    private ISoundTriggerModule mSoundTriggerModule;
-    private SoundTriggerMiddlewareLogging mSoundTriggerMiddlewareLogging;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                .adoptShellPermissionIdentity(Manifest.permission.WRITE_DEVICE_CONFIG,
-                        Manifest.permission.READ_DEVICE_CONFIG);
-
-        Identity identity = new Identity();
-        identity.uid = Process.myUid();
-        identity.pid = Process.myPid();
-        identity.packageName = ActivityThread.currentOpPackageName();
-        IdentityContext.create(identity);
-
-        mLatencyTracker = FakeLatencyTracker.create();
-        mLatencyTracker.forceEnabled(ACTION_SHOW_VOICE_INTERACTION, -1);
-        mSoundTriggerMiddlewareLogging = new SoundTriggerMiddlewareLogging(mLatencyTracker,
-                () -> mBatteryStatsInternal,
-                mDelegateMiddleware);
-    }
-
-    @After
-    public void tearDown() {
-        InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                .dropShellPermissionIdentity();
-    }
-
-    @Test
-    @FlakyTest(bugId = 275113847)
-    public void testSetUpAndTearDown() {
-    }
-
-    @Test
-    @FlakyTest(bugId = 275113847)
-    public void testOnPhraseRecognitionStartsLatencyTrackerWithSuccessfulPhraseIdTrigger()
-            throws RemoteException {
-        ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
-                ISoundTriggerCallback.class);
-        mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
-        verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
-
-        triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
-                RecognitionStatus.SUCCESS, Optional.of(100) /* keyphraseId */);
-
-        assertThat(mLatencyTracker.getActiveActionStartTime(
-                ACTION_SHOW_VOICE_INTERACTION)).isGreaterThan(-1);
-    }
-
-    @Test
-    @FlakyTest(bugId = 275113847)
-    public void testOnPhraseRecognitionRestartsActiveSession() throws RemoteException {
-        ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
-                ISoundTriggerCallback.class);
-        mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
-        verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
-
-        triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
-                RecognitionStatus.SUCCESS, Optional.of(100) /* keyphraseId */);
-        long firstTriggerSessionStartTime = mLatencyTracker.getActiveActionStartTime(
-                ACTION_SHOW_VOICE_INTERACTION);
-        triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
-                RecognitionStatus.SUCCESS, Optional.of(100) /* keyphraseId */);
-        assertThat(mLatencyTracker.getActiveActionStartTime(
-                ACTION_SHOW_VOICE_INTERACTION)).isGreaterThan(-1);
-        assertThat(mLatencyTracker.getActiveActionStartTime(
-                ACTION_SHOW_VOICE_INTERACTION)).isNotEqualTo(firstTriggerSessionStartTime);
-    }
-
-    @Test
-    @FlakyTest(bugId = 275113847)
-    public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNonSuccessEvent()
-            throws RemoteException {
-        ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
-                ISoundTriggerCallback.class);
-        mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
-        verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
-
-        triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
-                RecognitionStatus.ABORTED, Optional.of(100) /* keyphraseId */);
-
-        assertThat(
-                mLatencyTracker.getActiveActionStartTime(ACTION_SHOW_VOICE_INTERACTION)).isEqualTo(
-                -1);
-    }
-
-    @Test
-    @FlakyTest(bugId = 275113847)
-    public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNoKeyphraseId()
-            throws RemoteException {
-        ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
-                ISoundTriggerCallback.class);
-        mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
-        verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
-
-        triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
-                RecognitionStatus.SUCCESS, Optional.empty() /* keyphraseId */);
-
-        assertThat(
-                mLatencyTracker.getActiveActionStartTime(ACTION_SHOW_VOICE_INTERACTION)).isEqualTo(
-                -1);
-    }
-
-    private void triggerPhraseRecognitionEvent(ISoundTriggerCallback callback,
-            @RecognitionStatus int triggerEventStatus, Optional<Integer> optionalKeyphraseId)
-            throws RemoteException {
-        // trigger a phrase recognition to start a latency tracker session
-        PhraseRecognitionEvent successEventWithKeyphraseId = new PhraseRecognitionEvent();
-        successEventWithKeyphraseId.common = new RecognitionEvent();
-        successEventWithKeyphraseId.common.status = triggerEventStatus;
-        if (optionalKeyphraseId.isPresent()) {
-            PhraseRecognitionExtra recognitionExtra = new PhraseRecognitionExtra();
-            recognitionExtra.id = optionalKeyphraseId.get();
-            successEventWithKeyphraseId.phraseExtras =
-                    new PhraseRecognitionExtra[]{recognitionExtra};
-        }
-        callback.onPhraseRecognition(0 /* modelHandle */, successEventWithKeyphraseId,
-                0 /* captureSession */);
-    }
-
     @Test
     public void serviceEventException_getStringContainsInfo() {
         String packageName = "com.android.test";
         Exception exception = new Exception("test");
         Object param1 = new Object();
         Object param2 = new Object();
-        final var event = ServiceEvent.createForException(
-                SERVICE_TYPE, packageName, exception, param1, param2);
+        final var event =
+                ServiceEvent.createForException(
+                        SERVICE_TYPE, packageName, exception, param1, param2);
         final var stringRep = event.eventToString();
         assertThat(stringRep).contains(SERVICE_TYPE.name());
         assertThat(stringRep).contains(packageName);
@@ -211,8 +52,7 @@
     public void serviceEventExceptionNoArgs_getStringContainsInfo() {
         String packageName = "com.android.test";
         Exception exception = new Exception("test");
-        final var event = ServiceEvent.createForException(
-                SERVICE_TYPE, packageName, exception);
+        final var event = ServiceEvent.createForException(SERVICE_TYPE, packageName, exception);
         final var stringRep = event.eventToString();
         assertThat(stringRep).contains(SERVICE_TYPE.name());
         assertThat(stringRep).contains(packageName);
@@ -226,8 +66,8 @@
         Object param1 = new Object();
         Object param2 = new Object();
         Object retValue = new Object();
-        final var event = ServiceEvent.createForReturn(
-                SERVICE_TYPE, packageName, retValue, param1, param2);
+        final var event =
+                ServiceEvent.createForReturn(SERVICE_TYPE, packageName, retValue, param1, param2);
         final var stringRep = event.eventToString();
         assertThat(stringRep).contains(SERVICE_TYPE.name());
         assertThat(stringRep).contains(packageName);
@@ -241,8 +81,7 @@
     public void serviceEventReturnNoArgs_getStringContainsInfo() {
         String packageName = "com.android.test";
         Object retValue = new Object();
-        final var event = ServiceEvent.createForReturn(
-                SERVICE_TYPE, packageName, retValue);
+        final var event = ServiceEvent.createForReturn(SERVICE_TYPE, packageName, retValue);
         final var stringRep = event.eventToString();
         assertThat(stringRep).contains(SERVICE_TYPE.name());
         assertThat(stringRep).contains(packageName);
@@ -255,8 +94,7 @@
         Object param1 = new Object();
         Object param2 = new Object();
         Exception exception = new Exception("test");
-        final var event = SessionEvent.createForException(
-                SESSION_TYPE, exception, param1, param2);
+        final var event = SessionEvent.createForException(SESSION_TYPE, exception, param1, param2);
         final var stringRep = event.eventToString();
         assertThat(stringRep).contains(SESSION_TYPE.name());
         assertThat(stringRep).contains(exception.toString());
@@ -268,8 +106,7 @@
     @Test
     public void sessionEventExceptionNoArgs_getStringContainsInfo() {
         Exception exception = new Exception("test");
-        final var event = SessionEvent.createForException(
-                SESSION_TYPE, exception);
+        final var event = SessionEvent.createForException(SESSION_TYPE, exception);
         final var stringRep = event.eventToString();
         assertThat(stringRep).contains(SESSION_TYPE.name());
         assertThat(stringRep).contains(exception.toString());
@@ -281,8 +118,7 @@
         Object param1 = new Object();
         Object param2 = new Object();
         Object retValue = new Object();
-        final var event = SessionEvent.createForReturn(
-                SESSION_TYPE, retValue, param1, param2);
+        final var event = SessionEvent.createForReturn(SESSION_TYPE, retValue, param1, param2);
         final var stringRep = event.eventToString();
         assertThat(stringRep).contains(SESSION_TYPE.name());
         assertThat(stringRep).contains(retValue.toString());
@@ -294,8 +130,7 @@
     @Test
     public void sessionEventReturnNoArgs_getStringContainsInfo() {
         Object retValue = new Object();
-        final var event = SessionEvent.createForReturn(
-                SESSION_TYPE, retValue);
+        final var event = SessionEvent.createForReturn(SESSION_TYPE, retValue);
         final var stringRep = event.eventToString();
         assertThat(stringRep).contains(SESSION_TYPE.name());
         assertThat(stringRep).contains(retValue.toString());
@@ -306,8 +141,7 @@
     public void sessionEventVoid_getStringContainsInfo() {
         Object param1 = new Object();
         Object param2 = new Object();
-        final var event = SessionEvent.createForVoid(
-                SESSION_TYPE, param1, param2);
+        final var event = SessionEvent.createForVoid(SESSION_TYPE, param1, param2);
         final var stringRep = event.eventToString();
         assertThat(stringRep).contains(SESSION_TYPE.name());
         assertThat(stringRep).contains(param1.toString());
@@ -317,8 +151,7 @@
 
     @Test
     public void sessionEventVoidNoArgs_getStringContainsInfo() {
-        final var event = SessionEvent.createForVoid(
-                SESSION_TYPE);
+        final var event = SessionEvent.createForVoid(SESSION_TYPE);
         final var stringRep = event.eventToString();
         assertThat(stringRep).contains(SESSION_TYPE.name());
         assertThat(stringRep).ignoringCase().doesNotContain("error");
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 5282585e9..f235d15 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -36,6 +36,7 @@
 import android.view.SurfaceSession;
 
 import com.android.server.wm.SurfaceAnimator.AnimationType;
+import com.android.server.testutils.StubTransaction;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index d400a4c..d2494ff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -37,6 +37,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.testutils.StubTransaction;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.invocation.InvocationOnMock;
diff --git a/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java b/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java
new file mode 100644
index 0000000..cc8dab9
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
+import static com.android.server.wm.DeviceStateController.DeviceState.FOLDED;
+import static com.android.server.wm.DeviceStateController.DeviceState.HALF_FOLDED;
+import static com.android.server.wm.DeviceStateController.DeviceState.OPEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.window.TransitionRequestInfo.DisplayChange;
+
+import static com.android.internal.R.bool.config_unfoldTransitionEnabled;
+import static com.android.server.wm.DeviceStateController.DeviceState.REAR;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the {@link WindowToken} class.
+ *
+ * Build/Install/Run:
+ * atest WmTests:PhysicalDisplaySwitchTransitionLauncherTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class PhysicalDisplaySwitchTransitionLauncherTest extends WindowTestsBase {
+
+    @Mock
+    DisplayContent mDisplayContent;
+    @Mock
+    Context mContext;
+    @Mock
+    Resources mResources;
+    @Mock
+    ActivityTaskManagerService mActivityTaskManagerService;
+    @Mock
+    TransitionController mTransitionController;
+
+    private PhysicalDisplaySwitchTransitionLauncher mTarget;
+    private float mOriginalAnimationScale;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getResources()).thenReturn(mResources);
+        mTarget = new PhysicalDisplaySwitchTransitionLauncher(mDisplayContent,
+                mActivityTaskManagerService, mContext, mTransitionController);
+        mOriginalAnimationScale = ValueAnimator.getDurationScale();
+    }
+
+    @After
+    public void after() {
+        ValueAnimator.setDurationScale(mOriginalAnimationScale);
+    }
+
+    @Test
+    public void testDisplaySwitchAfterUnfoldToOpen_animationsEnabled_requestsTransition() {
+        givenAllAnimationsEnabled();
+        mTarget.foldStateChanged(FOLDED);
+
+        mTarget.foldStateChanged(OPEN);
+        mTarget.requestDisplaySwitchTransitionIfNeeded(
+                /* displayId= */ 123,
+                /* oldDisplayWidth= */ 100,
+                /* oldDisplayHeight= */ 150,
+                /* newDisplayWidth= */ 200,
+                /* newDisplayHeight= */ 250
+        );
+
+        ArgumentCaptor<DisplayChange> displayChangeArgumentCaptor =
+                ArgumentCaptor.forClass(DisplayChange.class);
+        verify(mTransitionController).requestTransitionIfNeeded(eq(TRANSIT_CHANGE), /* flags= */
+                eq(0), eq(mDisplayContent), eq(mDisplayContent), /* remoteTransition= */ isNull(),
+                displayChangeArgumentCaptor.capture());
+        assertThat(displayChangeArgumentCaptor.getValue().getDisplayId()).isEqualTo(123);
+        assertThat(displayChangeArgumentCaptor.getValue().getStartAbsBounds()).isEqualTo(
+                new Rect(0, 0, 100, 150));
+        assertThat(displayChangeArgumentCaptor.getValue().getEndAbsBounds()).isEqualTo(
+                new Rect(0, 0, 200, 250));
+    }
+
+    @Test
+    public void testDisplaySwitchAfterFolding_animationEnabled_doesNotRequestTransition() {
+        givenAllAnimationsEnabled();
+        mTarget.foldStateChanged(OPEN);
+
+        mTarget.foldStateChanged(FOLDED);
+        requestDisplaySwitch();
+
+        assertTransitionNotRequested();
+    }
+
+    @Test
+    public void testDisplaySwitchAfterUnfoldingToHalf_animationEnabled_requestsTransition() {
+        givenAllAnimationsEnabled();
+        mTarget.foldStateChanged(FOLDED);
+
+        mTarget.foldStateChanged(HALF_FOLDED);
+        requestDisplaySwitch();
+
+        assertTransitionRequested();
+    }
+
+    @Test
+    public void testDisplaySwitchSecondTimeAfterUnfolding_animationEnabled_noTransition() {
+        givenAllAnimationsEnabled();
+        mTarget.foldStateChanged(FOLDED);
+        mTarget.foldStateChanged(OPEN);
+        requestDisplaySwitch();
+        clearInvocations(mTransitionController);
+
+        requestDisplaySwitch();
+
+        assertTransitionNotRequested();
+    }
+
+
+    @Test
+    public void testDisplaySwitchAfterGoingToRearAndBack_animationEnabled_noTransition() {
+        givenAllAnimationsEnabled();
+        mTarget.foldStateChanged(OPEN);
+
+        mTarget.foldStateChanged(REAR);
+        mTarget.foldStateChanged(OPEN);
+        requestDisplaySwitch();
+
+        assertTransitionNotRequested();
+    }
+
+    @Test
+    public void testDisplaySwitchAfterUnfoldingAndFolding_animationEnabled_noTransition() {
+        givenAllAnimationsEnabled();
+        mTarget.foldStateChanged(FOLDED);
+        mTarget.foldStateChanged(OPEN);
+        // No request display switch event (simulate very fast fold after unfold, even before
+        // the displays switched)
+        mTarget.foldStateChanged(FOLDED);
+
+        requestDisplaySwitch();
+
+        assertTransitionNotRequested();
+    }
+
+    @Test
+    public void testDisplaySwitch_whenShellTransitionsNotEnabled_noTransition() {
+        givenAllAnimationsEnabled();
+        givenShellTransitionsEnabled(false);
+        mTarget.foldStateChanged(FOLDED);
+
+        mTarget.foldStateChanged(OPEN);
+        requestDisplaySwitch();
+
+        assertTransitionNotRequested();
+    }
+
+    @Test
+    public void testDisplaySwitch_whenAnimationsDisabled_noTransition() {
+        givenAllAnimationsEnabled();
+        givenAnimationsEnabled(false);
+        mTarget.foldStateChanged(FOLDED);
+
+        mTarget.foldStateChanged(OPEN);
+        requestDisplaySwitch();
+
+        assertTransitionNotRequested();
+    }
+
+    @Test
+    public void testDisplaySwitch_whenUnfoldAnimationDisabled_noTransition() {
+        givenAllAnimationsEnabled();
+        givenUnfoldTransitionEnabled(false);
+        mTarget.foldStateChanged(FOLDED);
+
+        mTarget.foldStateChanged(OPEN);
+        requestDisplaySwitch();
+
+        assertTransitionNotRequested();
+    }
+
+    @Test
+    public void testDisplaySwitch_whenNoContentInDisplayContent_noTransition() {
+        givenAllAnimationsEnabled();
+        givenDisplayContentHasContent(false);
+        mTarget.foldStateChanged(FOLDED);
+
+        mTarget.foldStateChanged(OPEN);
+        requestDisplaySwitch();
+
+        assertTransitionNotRequested();
+    }
+
+    private void assertTransitionRequested() {
+        verify(mTransitionController).requestTransitionIfNeeded(anyInt(), anyInt(), any(), any(),
+                any(), any());
+    }
+
+    private void assertTransitionNotRequested() {
+        verify(mTransitionController, never()).requestTransitionIfNeeded(anyInt(), anyInt(), any(),
+                any(), any(), any());
+    }
+
+    private void requestDisplaySwitch() {
+        mTarget.requestDisplaySwitchTransitionIfNeeded(
+                /* displayId= */ 123,
+                /* oldDisplayWidth= */ 100,
+                /* oldDisplayHeight= */ 150,
+                /* newDisplayWidth= */ 200,
+                /* newDisplayHeight= */ 250
+        );
+    }
+
+    private void givenAllAnimationsEnabled() {
+        givenAnimationsEnabled(true);
+        givenUnfoldTransitionEnabled(true);
+        givenShellTransitionsEnabled(true);
+        givenDisplayContentHasContent(true);
+    }
+
+    private void givenUnfoldTransitionEnabled(boolean enabled) {
+        when(mResources.getBoolean(config_unfoldTransitionEnabled)).thenReturn(enabled);
+    }
+
+    private void givenAnimationsEnabled(boolean enabled) {
+        ValueAnimator.setDurationScale(enabled ? 1.0f : 0.0f);
+    }
+
+    private void givenShellTransitionsEnabled(boolean enabled) {
+        when(mTransitionController.isShellTransitionsEnabled()).thenReturn(enabled);
+    }
+
+    private void givenDisplayContentHasContent(boolean hasContent) {
+        when(mDisplayContent.getLastHasContent()).thenReturn(hasContent);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index dfc453f..d173ce9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -1134,7 +1134,7 @@
         TaskChangeNotificationController controller = mAtm.getTaskChangeNotificationController();
         spyOn(controller);
         mWm.mRoot.lockAllProfileTasks(profileUserId);
-        verify(controller).notifyTaskProfileLocked(any());
+        verify(controller).notifyTaskProfileLocked(any(), eq(profileUserId));
 
         // Create the work lock activity on top of the task
         final ActivityRecord workLockActivity = new ActivityBuilder(mAtm).setTask(task).build();
@@ -1144,7 +1144,7 @@
         // Make sure the listener won't be notified again.
         clearInvocations(controller);
         mWm.mRoot.lockAllProfileTasks(profileUserId);
-        verify(controller, never()).notifyTaskProfileLocked(any());
+        verify(controller, never()).notifyTaskProfileLocked(any(), anyInt());
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index e30206e..d84620b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -30,6 +30,9 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.testutils.StubTransaction;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -50,6 +53,11 @@
         SurfaceSyncGroup.setTransactionFactory(StubTransaction::new);
     }
 
+    @After
+    public void tearDown() {
+        SurfaceSyncGroup.setTransactionFactory(SurfaceControl.Transaction::new);
+    }
+
     @Test
     public void testSyncOne() throws InterruptedException {
         final CountDownLatch finishedLatch = new CountDownLatch(1);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index ddd630e..a3a3684 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -42,6 +42,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.testutils.StubTransaction;
 import com.android.server.testutils.TestHandler;
 
 import org.junit.Before;
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 013c6d5..7edfd9a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -88,6 +88,7 @@
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.testutils.StubTransaction;
 import com.android.server.uri.UriGrantsManagerInternal;
 
 import org.junit.rules.TestRule;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java
index e2f1334..608d7c9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java
@@ -38,6 +38,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.testutils.StubTransaction;
+
 import org.junit.Test;
 
 /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index ad606cb..2d8ddfa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -1443,10 +1443,8 @@
         final InsetsFrameProvider provider2 =
                 new InsetsFrameProvider(null, 2, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(genericOverlayInsetsRect2);
-        final int sourceId1 = InsetsSource.createId(
-                provider1.getOwner(), provider1.getIndex(), provider1.getType());
-        final int sourceId2 = InsetsSource.createId(
-                provider2.getOwner(), provider2.getIndex(), provider2.getType());
+        final int sourceId1 = provider1.getId();
+        final int sourceId2 = provider2.getId();
 
         rootTask.addLocalInsetsFrameProvider(provider1);
         container.addLocalInsetsFrameProvider(provider2);
@@ -1504,10 +1502,8 @@
         final InsetsFrameProvider provider2 =
                 new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(genericOverlayInsetsRect2);
-        final int sourceId1 = InsetsSource.createId(
-                provider1.getOwner(), provider1.getIndex(), provider1.getType());
-        final int sourceId2 = InsetsSource.createId(
-                provider2.getOwner(), provider2.getIndex(), provider2.getType());
+        final int sourceId1 = provider1.getId();
+        final int sourceId2 = provider2.getId();
 
         rootTask.addLocalInsetsFrameProvider(provider1);
         activity0.forAllWindows(window -> {
@@ -1566,10 +1562,8 @@
         final InsetsFrameProvider provider2 =
                 new InsetsFrameProvider(null, 2, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(navigationBarInsetsRect2);
-        final int sourceId1 = InsetsSource.createId(
-                provider1.getOwner(), provider1.getIndex(), provider1.getType());
-        final int sourceId2 = InsetsSource.createId(
-                provider2.getOwner(), provider2.getIndex(), provider2.getType());
+        final int sourceId1 = provider1.getId();
+        final int sourceId2 = provider2.getId();
 
         rootTask.addLocalInsetsFrameProvider(provider1);
         container.addLocalInsetsFrameProvider(provider2);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerThumbnailTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerThumbnailTest.java
index 2ae1172..849072e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerThumbnailTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerThumbnailTest.java
@@ -28,6 +28,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.testutils.StubTransaction;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 460a603..ee1afcf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -110,6 +110,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.testutils.StubTransaction;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index a63807d..a4cad5e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -359,9 +359,12 @@
                     new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()),
                     new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures())
             };
-            for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
-                mNavBarWindow.mAttrs.paramsForRotation[rot] =
-                        getNavBarLayoutParamsForRotation(rot, owner);
+            // If the navigation bar cannot move then it is always at the bottom.
+            if (mDisplayContent.getDisplayPolicy().navigationBarCanMove()) {
+                for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+                    mNavBarWindow.mAttrs.paramsForRotation[rot] =
+                            getNavBarLayoutParamsForRotation(rot, owner);
+                }
             }
         }
         if (addAll || ArrayUtils.contains(requestedWindows, W_DOCK_DIVIDER)) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index a98429a..ef13594 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2539,10 +2539,20 @@
         }
 
         @Override
-        public void reportChooserSelection(String packageName, int userId, String contentType,
-                                           String[] annotations, String action) {
+        public void reportChooserSelection(@NonNull String packageName, int userId,
+                @NonNull String contentType, String[] annotations, @NonNull String action) {
             if (packageName == null) {
-                Slog.w(TAG, "Event report user selecting a null package");
+                throw new IllegalArgumentException("Package selection must not be null.");
+            }
+            if (contentType == null) {
+                throw new IllegalArgumentException("Content type for selection must not be null.");
+            }
+            if (action == null) {
+                throw new IllegalArgumentException("Selection action must not be null.");
+            }
+            // Verify if this package exists before reporting an event for it.
+            if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) < 0) {
+                Slog.w(TAG, "Event report user selecting an invalid package");
                 return;
             }
 
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/DeviceStateHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger/DeviceStateHandler.java
new file mode 100644
index 0000000..6605449
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/DeviceStateHandler.java
@@ -0,0 +1,279 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger;
+
+import static android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_DISABLED;
+import static android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
+import static android.os.PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.utils.EventLogger;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Manages device state events which require pausing SoundTrigger recognition
+ *
+ * @hide
+ */
+public class DeviceStateHandler implements PhoneCallStateHandler.Callback {
+
+    public static final long CALL_INACTIVE_MSG_DELAY_MS = 1000;
+
+    public interface DeviceStateListener {
+        void onSoundTriggerDeviceStateUpdate(SoundTriggerDeviceState state);
+    }
+
+    public enum SoundTriggerDeviceState {
+        DISABLE, // The device state requires all SoundTrigger sessions are disabled
+        CRITICAL, // The device state requires all non-critical SoundTrigger sessions are disabled
+        ENABLE // The device state permits all SoundTrigger sessions
+    }
+
+    private final Object mLock = new Object();
+
+    private final EventLogger mEventLogger;
+
+    @GuardedBy("mLock")
+    SoundTriggerDeviceState mSoundTriggerDeviceState = SoundTriggerDeviceState.ENABLE;
+
+    // Individual components of the SoundTriggerDeviceState
+    @GuardedBy("mLock")
+    private int mSoundTriggerPowerSaveMode = SOUND_TRIGGER_MODE_ALL_ENABLED;
+
+    @GuardedBy("mLock")
+    private boolean mIsPhoneCallOngoing = false;
+
+    // There can only be one pending notify at any given time.
+    // If any phone state change comes in between, we will cancel the previous pending
+    // task.
+    @GuardedBy("mLock")
+    private NotificationTask mPhoneStateChangePendingNotify = null;
+
+    private Set<DeviceStateListener> mCallbackSet = ConcurrentHashMap.newKeySet(4);
+
+    private final Executor mDelayedNotificationExecutor = Executors.newSingleThreadExecutor();
+
+    private final Executor mCallbackExecutor;
+
+    public void onPowerModeChanged(int soundTriggerPowerSaveMode) {
+        mEventLogger.enqueue(new SoundTriggerPowerEvent(soundTriggerPowerSaveMode));
+        synchronized (mLock) {
+            if (soundTriggerPowerSaveMode == mSoundTriggerPowerSaveMode) {
+                // No state change, nothing to do
+                return;
+            }
+            mSoundTriggerPowerSaveMode = soundTriggerPowerSaveMode;
+            evaluateStateChange();
+        }
+    }
+
+    @Override
+    public void onPhoneCallStateChanged(boolean isInPhoneCall) {
+        mEventLogger.enqueue(new PhoneCallEvent(isInPhoneCall));
+        synchronized (mLock) {
+            if (mIsPhoneCallOngoing == isInPhoneCall) {
+                // no change, nothing to do
+                return;
+            }
+            // Clear any pending notification
+            if (mPhoneStateChangePendingNotify != null) {
+                mPhoneStateChangePendingNotify.cancel();
+                mPhoneStateChangePendingNotify = null;
+            }
+            mIsPhoneCallOngoing = isInPhoneCall;
+            if (!mIsPhoneCallOngoing) {
+                // State has changed from call to no call, delay notification
+                mPhoneStateChangePendingNotify = new NotificationTask(
+                        new Runnable() {
+                            @Override
+                            public void run() {
+                                synchronized (mLock) {
+                                    if (mPhoneStateChangePendingNotify != null &&
+                                            mPhoneStateChangePendingNotify.runnableEquals(this)) {
+
+                                        mPhoneStateChangePendingNotify = null;
+                                        evaluateStateChange();
+                                    }
+                                }
+                            }
+                        },
+                        CALL_INACTIVE_MSG_DELAY_MS);
+                mDelayedNotificationExecutor.execute(mPhoneStateChangePendingNotify);
+            } else {
+                evaluateStateChange();
+            }
+        }
+    }
+
+    /** Note, we expect initial callbacks immediately following construction */
+    public DeviceStateHandler(Executor callbackExecutor, EventLogger eventLogger) {
+        mCallbackExecutor = Objects.requireNonNull(callbackExecutor);
+        mEventLogger = Objects.requireNonNull(eventLogger);
+    }
+
+    public SoundTriggerDeviceState getDeviceState() {
+        synchronized (mLock) {
+            return mSoundTriggerDeviceState;
+        }
+    }
+
+    public void registerListener(DeviceStateListener callback) {
+        final var state = getDeviceState();
+        mCallbackExecutor.execute(
+                () -> callback.onSoundTriggerDeviceStateUpdate(state));
+        mCallbackSet.add(callback);
+    }
+
+    public void unregisterListener(DeviceStateListener callback) {
+        mCallbackSet.remove(callback);
+    }
+
+    void dump(PrintWriter pw) {
+        synchronized (mLock) {
+            pw.println("DeviceState: " + mSoundTriggerDeviceState.name());
+            pw.println("PhoneState: " + mIsPhoneCallOngoing);
+            pw.println("PowerSaveMode: " + mSoundTriggerPowerSaveMode);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void evaluateStateChange() {
+        // We should wait until any pending delays are complete to update.
+        // We will eventually get called by the notification task, or something which
+        // cancels it.
+        // Additionally, if there isn't a state change, there is nothing to update.
+        SoundTriggerDeviceState newState = computeState();
+        if (mPhoneStateChangePendingNotify != null || mSoundTriggerDeviceState == newState) {
+            return;
+        }
+
+        mSoundTriggerDeviceState = newState;
+        mEventLogger.enqueue(new DeviceStateEvent(mSoundTriggerDeviceState));
+        final var state = mSoundTriggerDeviceState;
+        for (var callback : mCallbackSet) {
+            mCallbackExecutor.execute(
+                    () -> callback.onSoundTriggerDeviceStateUpdate(state));
+        }
+    }
+
+    @GuardedBy("mLock")
+    private SoundTriggerDeviceState computeState() {
+        if (mIsPhoneCallOngoing) {
+            return SoundTriggerDeviceState.DISABLE;
+        }
+        return switch (mSoundTriggerPowerSaveMode) {
+            case SOUND_TRIGGER_MODE_ALL_ENABLED -> SoundTriggerDeviceState.ENABLE;
+            case SOUND_TRIGGER_MODE_CRITICAL_ONLY -> SoundTriggerDeviceState.CRITICAL;
+            case SOUND_TRIGGER_MODE_ALL_DISABLED -> SoundTriggerDeviceState.DISABLE;
+            default -> throw new IllegalStateException(
+                    "Received unexpected power state code" + mSoundTriggerPowerSaveMode);
+        };
+    }
+
+    /**
+     * One-shot, cancellable task which runs after a delay. Run must only be called once, from a
+     * single thread. Cancel can be called from any other thread.
+     */
+    private static class NotificationTask implements Runnable {
+        private final Runnable mRunnable;
+        private final long mWaitInMillis;
+
+        private final CountDownLatch mCancelLatch = new CountDownLatch(1);
+
+        NotificationTask(Runnable r, long waitInMillis) {
+            mRunnable = r;
+            mWaitInMillis = waitInMillis;
+        }
+
+        void cancel() {
+            mCancelLatch.countDown();
+        }
+
+        // Used for determining task equality.
+        boolean runnableEquals(Runnable runnable) {
+            return mRunnable == runnable;
+        }
+
+        public void run() {
+            try {
+                if (!mCancelLatch.await(mWaitInMillis, TimeUnit.MILLISECONDS)) {
+                    mRunnable.run();
+                }
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new AssertionError("Unexpected InterruptedException", e);
+            }
+        }
+    }
+
+    private static class PhoneCallEvent extends EventLogger.Event {
+        final boolean mIsInPhoneCall;
+
+        PhoneCallEvent(boolean isInPhoneCall) {
+            mIsInPhoneCall = isInPhoneCall;
+        }
+
+        @Override
+        public String eventToString() {
+            return "PhoneCallChange - inPhoneCall: " + mIsInPhoneCall;
+        }
+    }
+
+    private static class SoundTriggerPowerEvent extends EventLogger.Event {
+        final int mSoundTriggerPowerState;
+
+        SoundTriggerPowerEvent(int soundTriggerPowerState) {
+            mSoundTriggerPowerState = soundTriggerPowerState;
+        }
+
+        @Override
+        public String eventToString() {
+            return "SoundTriggerPowerChange: " + stateToString();
+        }
+
+        private String stateToString() {
+            return switch (mSoundTriggerPowerState) {
+                case SOUND_TRIGGER_MODE_ALL_ENABLED -> "All enabled";
+                case SOUND_TRIGGER_MODE_CRITICAL_ONLY -> "Critical only";
+                case SOUND_TRIGGER_MODE_ALL_DISABLED -> "All disabled";
+                default -> "Unknown power state: " + mSoundTriggerPowerState;
+            };
+        }
+    }
+
+    private static class DeviceStateEvent extends EventLogger.Event {
+        final SoundTriggerDeviceState mSoundTriggerDeviceState;
+
+        DeviceStateEvent(SoundTriggerDeviceState soundTriggerDeviceState) {
+            mSoundTriggerDeviceState = soundTriggerDeviceState;
+        }
+
+        @Override
+        public String eventToString() {
+            return "DeviceStateChange: " + mSoundTriggerDeviceState.name();
+        }
+    }
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
new file mode 100644
index 0000000..8773cab
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
@@ -0,0 +1,158 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger;
+
+import android.telephony.Annotation;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Handles monitoring telephony call state across active subscriptions.
+ *
+ * @hide
+ */
+public class PhoneCallStateHandler {
+
+    public interface Callback {
+        void onPhoneCallStateChanged(boolean isInPhoneCall);
+    }
+
+    private final Object mLock = new Object();
+
+    // Actually never contended due to executor.
+    @GuardedBy("mLock")
+    private final List<MyCallStateListener> mListenerList = new ArrayList<>();
+
+    private final AtomicBoolean mIsPhoneCallOngoing = new AtomicBoolean(false);
+
+    private final SubscriptionManager mSubscriptionManager;
+    private final TelephonyManager mTelephonyManager;
+    private final Callback mCallback;
+
+    private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+
+    public PhoneCallStateHandler(
+            SubscriptionManager subscriptionManager,
+            TelephonyManager telephonyManager,
+            Callback callback) {
+        mSubscriptionManager = Objects.requireNonNull(subscriptionManager);
+        mTelephonyManager = Objects.requireNonNull(telephonyManager);
+        mCallback = Objects.requireNonNull(callback);
+        mSubscriptionManager.addOnSubscriptionsChangedListener(
+                mExecutor,
+                new SubscriptionManager.OnSubscriptionsChangedListener() {
+                    @Override
+                    public void onSubscriptionsChanged() {
+                        updateTelephonyListeners();
+                    }
+
+                    @Override
+                    public void onAddListenerFailed() {
+                        Slog.wtf(
+                                "SoundTriggerPhoneCallStateHandler",
+                                "Failed to add a telephony listener");
+                    }
+                });
+    }
+
+    private final class MyCallStateListener extends TelephonyCallback
+            implements TelephonyCallback.CallStateListener {
+
+        final TelephonyManager mTelephonyManagerForSubId;
+
+        // Manager corresponding to the sub-id
+        MyCallStateListener(TelephonyManager telephonyManager) {
+            mTelephonyManagerForSubId = telephonyManager;
+        }
+
+        void cleanup() {
+            mExecutor.execute(() -> mTelephonyManagerForSubId.unregisterTelephonyCallback(this));
+        }
+
+        @Override
+        public void onCallStateChanged(int unused) {
+            updateCallStatus();
+        }
+    }
+
+    /** Compute the current call status, and dispatch callback if it has changed. */
+    private void updateCallStatus() {
+        boolean callStatus = checkCallStatus();
+        if (mIsPhoneCallOngoing.compareAndSet(!callStatus, callStatus)) {
+            mCallback.onPhoneCallStateChanged(callStatus);
+        }
+    }
+
+    /**
+     * Synchronously query the current telephony call state across all subscriptions
+     *
+     * @return - {@code true} if in call, {@code false} if not in call.
+     */
+    private boolean checkCallStatus() {
+        List<SubscriptionInfo> infoList = mSubscriptionManager.getActiveSubscriptionInfoList();
+        if (infoList == null) return false;
+        return infoList.stream()
+                .filter(s -> (s.getSubscriptionId() != SubscriptionManager.INVALID_SUBSCRIPTION_ID))
+                .anyMatch(s -> isCallOngoingFromState(
+                                        mTelephonyManager
+                                                .createForSubscriptionId(s.getSubscriptionId())
+                                                .getCallStateForSubscription()));
+    }
+
+    private void updateTelephonyListeners() {
+        synchronized (mLock) {
+            for (var listener : mListenerList) {
+                listener.cleanup();
+            }
+            mListenerList.clear();
+            List<SubscriptionInfo> infoList = mSubscriptionManager.getActiveSubscriptionInfoList();
+            if (infoList == null) return;
+            infoList.stream()
+                    .filter(s -> s.getSubscriptionId()
+                                            != SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+                    .map(s -> mTelephonyManager.createForSubscriptionId(s.getSubscriptionId()))
+                    .forEach(manager -> {
+                        synchronized (mLock) {
+                            var listener = new MyCallStateListener(manager);
+                            mListenerList.add(listener);
+                            manager.registerTelephonyCallback(mExecutor, listener);
+                        }
+                    });
+        }
+    }
+
+    private static boolean isCallOngoingFromState(@Annotation.CallState int callState) {
+        return switch (callState) {
+            case TelephonyManager.CALL_STATE_IDLE, TelephonyManager.CALL_STATE_RINGING -> false;
+            case TelephonyManager.CALL_STATE_OFFHOOK -> true;
+            default -> throw new IllegalStateException(
+                    "Received unexpected call state from Telephony Manager: " + callState);
+        };
+    }
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 255db1e..b4066ab 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -16,15 +16,12 @@
 
 package com.android.server.soundtrigger;
 
+import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState;
 import static com.android.server.soundtrigger.SoundTriggerEvent.SessionEvent.Type;
 import static com.android.server.utils.EventLogger.Event.ALOGW;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.ModelParams;
 import android.hardware.soundtrigger.SoundTrigger;
@@ -45,11 +42,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.PowerManager;
-import android.os.PowerManager.SoundTriggerPowerSaveMode;
 import android.os.RemoteException;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -99,37 +92,20 @@
     private SoundTriggerModule mModule;
     private final Object mLock = new Object();
     private final Context mContext;
-    private final TelephonyManager mTelephonyManager;
-    private final PhoneStateListener mPhoneStateListener;
-    private final PowerManager mPowerManager;
 
     // The SoundTriggerManager layer handles multiple recognition models of type generic and
     // keyphrase. We store the ModelData here in a hashmap.
-    private final HashMap<UUID, ModelData> mModelDataMap;
+    private final HashMap<UUID, ModelData> mModelDataMap = new HashMap<>();
 
     // An index of keyphrase sound models so that we can reach them easily. We support indexing
     // keyphrase sound models with a keyphrase ID. Sound model with the same keyphrase ID will
     // replace an existing model, thus there is a 1:1 mapping from keyphrase ID to a voice
     // sound model.
-    private HashMap<Integer, UUID> mKeyphraseUuidMap;
-
-    private boolean mCallActive = false;
-    private @SoundTriggerPowerSaveMode int mSoundTriggerPowerSaveMode =
-            PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
+    private final HashMap<Integer, UUID> mKeyphraseUuidMap = new HashMap<>();
 
     // Whether ANY recognition (keyphrase or generic) has been requested.
     private boolean mRecognitionRequested = false;
 
-    private PowerSaveModeListener mPowerSaveModeListener;
-
-
-    // Handler to process call state changes will delay to allow time for the audio
-    // and sound trigger HALs to process the end of call notifications
-    // before we re enable pending recognition requests.
-    private final Handler mHandler;
-    private static final int MSG_CALL_STATE_CHANGED = 0;
-    private static final int CALL_INACTIVE_MSG_DELAY_MS = 1000;
-
     // TODO(b/269366605) Temporary solution to query correct moduleProperties
     private final int mModuleId;
     private final Function<SoundTrigger.StatusListener, SoundTriggerModule> mModuleProvider;
@@ -139,16 +115,15 @@
     @GuardedBy("mLock")
     private boolean mIsDetached = false;
 
+    @GuardedBy("mLock")
+    private SoundTriggerDeviceState mDeviceState = SoundTriggerDeviceState.DISABLE;
+
     SoundTriggerHelper(Context context, EventLogger eventLogger,
             @NonNull Function<SoundTrigger.StatusListener, SoundTriggerModule> moduleProvider,
             int moduleId,
             @NonNull Supplier<List<ModuleProperties>> modulePropertiesProvider) {
         mModuleId = moduleId;
         mContext = context;
-        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-        mModelDataMap = new HashMap<UUID, ModelData>();
-        mKeyphraseUuidMap = new HashMap<Integer, UUID>();
         mModuleProvider = moduleProvider;
         mEventLogger = eventLogger;
         mModulePropertiesProvider = modulePropertiesProvider;
@@ -157,31 +132,6 @@
         } else {
             mModule = mModuleProvider.apply(this);
         }
-        Looper looper = Looper.myLooper();
-        if (looper == null) {
-            looper = Looper.getMainLooper();
-        }
-        mPhoneStateListener = new MyCallStateListener(looper);
-        if (looper != null) {
-            mHandler = new Handler(looper) {
-                @Override
-                public void handleMessage(Message msg) {
-                    switch (msg.what) {
-                        case MSG_CALL_STATE_CHANGED:
-                            synchronized (mLock) {
-                                onCallStateChangedLocked(
-                                        TelephonyManager.CALL_STATE_OFFHOOK == msg.arg1);
-                            }
-                            break;
-                        default:
-                            Slog.e(TAG, "unknown message in handler:" + msg.what);
-                            break;
-                    }
-                }
-            };
-        } else {
-            mHandler = null;
-        }
     }
 
     /**
@@ -373,7 +323,6 @@
             modelData.setSoundModel(soundModel);
 
             if (!isRecognitionAllowedByDeviceState(modelData)) {
-                initializeDeviceStateListeners();
                 return STATUS_OK;
             }
 
@@ -497,11 +446,6 @@
             modelData.setLoaded();
             modelData.clearCallback();
             modelData.setRecognitionConfig(null);
-
-            if (!computeRecognitionRequestedLocked()) {
-                internalClearGlobalStateLocked();
-            }
-
             return status;
         }
     }
@@ -638,6 +582,17 @@
         }
     }
 
+    public void onDeviceStateChanged(SoundTriggerDeviceState state) {
+        synchronized (mLock) {
+            if (mIsDetached || mDeviceState == state) {
+                // Nothing to update
+                return;
+            }
+            mDeviceState = state;
+            updateAllRecognitionsLocked();
+        }
+    }
+
     public int getGenericModelState(UUID modelId) {
         synchronized (mLock) {
             MetricsLogger.count(mContext, "sth_get_generic_model_state", 1);
@@ -880,25 +835,6 @@
         }
     }
 
-    private void onCallStateChangedLocked(boolean callActive) {
-        if (mCallActive == callActive) {
-            // We consider multiple call states as being active
-            // so we check if something really changed or not here.
-            return;
-        }
-        mCallActive = callActive;
-        updateAllRecognitionsLocked();
-    }
-
-    private void onPowerSaveModeChangedLocked(
-            @SoundTriggerPowerSaveMode int soundTriggerPowerSaveMode) {
-        if (mSoundTriggerPowerSaveMode == soundTriggerPowerSaveMode) {
-            return;
-        }
-        mSoundTriggerPowerSaveMode = soundTriggerPowerSaveMode;
-        updateAllRecognitionsLocked();
-    }
-
     private void onModelUnloadedLocked(int modelHandle) {
         ModelData modelData = getModelDataForLocked(modelHandle);
         if (modelData != null) {
@@ -1011,10 +947,6 @@
                 return status;
             }
             status = startRecognitionLocked(model, notifyClientOnError);
-            // Initialize power save, call active state monitoring logic.
-            if (status == STATUS_OK) {
-                initializeDeviceStateListeners();
-            }
             return status;
         } else {
             return stopRecognitionLocked(model, notifyClientOnError);
@@ -1040,7 +972,6 @@
             }
         } finally {
             internalClearModelStateLocked();
-            internalClearGlobalStateLocked();
             if (mModule != null) {
                 mModule.detach();
                 try {
@@ -1054,24 +985,6 @@
         }
     }
 
-    // internalClearGlobalStateLocked() cleans up the telephony and power save listeners.
-    private void internalClearGlobalStateLocked() {
-        // Unregister from call state changes.
-        final long token = Binder.clearCallingIdentity();
-        try {
-            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-
-        // Unregister from power save mode changes.
-        if (mPowerSaveModeListener != null) {
-            mContext.unregisterReceiver(mPowerSaveModeListener);
-            mPowerSaveModeListener = null;
-        }
-        mRecognitionRequested = false;
-    }
-
     // Clears state for all models (generic and keyphrase).
     private void internalClearModelStateLocked() {
         for (ModelData modelData : mModelDataMap.values()) {
@@ -1079,67 +992,6 @@
         }
     }
 
-    class MyCallStateListener extends PhoneStateListener {
-        MyCallStateListener(@NonNull Looper looper) {
-            super(Objects.requireNonNull(looper));
-        }
-
-        @Override
-        public void onCallStateChanged(int state, String arg1) {
-
-            if (mHandler != null) {
-                synchronized (mLock) {
-                    mHandler.removeMessages(MSG_CALL_STATE_CHANGED);
-                    Message msg = mHandler.obtainMessage(MSG_CALL_STATE_CHANGED, state, 0);
-                    mHandler.sendMessageDelayed(
-                            msg, (TelephonyManager.CALL_STATE_OFFHOOK == state) ? 0
-                                    : CALL_INACTIVE_MSG_DELAY_MS);
-                }
-            }
-        }
-    }
-
-    class PowerSaveModeListener extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
-                return;
-            }
-            @SoundTriggerPowerSaveMode int soundTriggerPowerSaveMode =
-                    mPowerManager.getSoundTriggerPowerSaveMode();
-            synchronized (mLock) {
-                onPowerSaveModeChangedLocked(soundTriggerPowerSaveMode);
-            }
-        }
-    }
-
-    private void initializeDeviceStateListeners() {
-        if (mRecognitionRequested) {
-            return;
-        }
-        final long token = Binder.clearCallingIdentity();
-        try {
-            // Get the current call state synchronously for the first recognition.
-            mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
-
-            // Register for call state changes when the first call to start recognition occurs.
-            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
-
-            // Register for power saver mode changes when the first call to start recognition
-            // occurs.
-            if (mPowerSaveModeListener == null) {
-                mPowerSaveModeListener = new PowerSaveModeListener();
-                mContext.registerReceiver(mPowerSaveModeListener,
-                        new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
-            }
-            mSoundTriggerPowerSaveMode = mPowerManager.getSoundTriggerPowerSaveMode();
-
-            mRecognitionRequested = true;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
     /**
      * Stops and unloads all models. This is intended as a clean-up call with the expectation that
      * this instance is not used after.
@@ -1153,7 +1005,6 @@
                 forceStopAndUnloadModelLocked(model, null);
             }
             mModelDataMap.clear();
-            internalClearGlobalStateLocked();
             if (mModule != null) {
                 mModule.detach();
                 mModule = null;
@@ -1305,28 +1156,14 @@
      * @param modelData Model data to be used for recognition
      * @return True if recognition is allowed to run at this time. False if not.
      */
+    @GuardedBy("mLock")
     private boolean isRecognitionAllowedByDeviceState(ModelData modelData) {
-        // if mRecognitionRequested is false, call and power state listeners are not registered so
-        // we read current state directly from services
-        if (!mRecognitionRequested) {
-            mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
-            mSoundTriggerPowerSaveMode = mPowerManager.getSoundTriggerPowerSaveMode();
-        }
-
-        return !mCallActive && isRecognitionAllowedByPowerState(
-                modelData);
-    }
-
-    /**
-     * Helper function to validate if a recognition should run based on the current power state
-     *
-     * @param modelData Model data to be used for recognition
-     * @return True if device state allows recognition to run, false if not.
-     */
-    private boolean isRecognitionAllowedByPowerState(ModelData modelData) {
-        return mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED
-                || (mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY
-                && modelData.shouldRunInBatterySaverMode());
+        return switch (mDeviceState) {
+            case DISABLE -> false;
+            case CRITICAL -> modelData.shouldRunInBatterySaverMode();
+            case ENABLE -> true;
+            default -> throw new AssertionError("Enum changed between compile and runtime");
+        };
     }
 
     // A single routine that implements the start recognition logic for both generic and keyphrase
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index b062e6b..3151781 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -35,14 +35,18 @@
 import static com.android.server.utils.EventLogger.Event.ALOGW;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.server.soundtrigger.DeviceStateHandler.DeviceStateListener;
+import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState;
 
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.PermissionChecker;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
@@ -85,6 +89,8 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.SparseArray;
@@ -112,6 +118,8 @@
 import java.util.TreeMap;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingDeque;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -131,8 +139,8 @@
     private static final boolean DEBUG = true;
     private static final int SESSION_MAX_EVENT_SIZE = 128;
 
-    final Context mContext;
-    private Object mLock;
+    private final Context mContext;
+    private final Object mLock = new Object();
     private final SoundTriggerServiceStub mServiceStub;
     private final LocalSoundTriggerService mLocalSoundTriggerService;
 
@@ -140,6 +148,7 @@
     private SoundTriggerDbHelper mDbHelper;
 
     private final EventLogger mServiceEventLogger = new EventLogger(256, "Service");
+    private final EventLogger mDeviceEventLogger = new EventLogger(256, "Device Event");
 
     private final Set<EventLogger> mSessionEventLoggers = ConcurrentHashMap.newKeySet(4);
     private final Deque<EventLogger> mDetachedSessionEventLoggers = new LinkedBlockingDeque<>(4);
@@ -223,13 +232,18 @@
     @GuardedBy("mLock")
     private final ArrayMap<String, NumOps> mNumOpsPerPackage = new ArrayMap<>();
 
+    private final DeviceStateHandler mDeviceStateHandler;
+    private final Executor mDeviceStateHandlerExecutor = Executors.newSingleThreadExecutor();
+    private PhoneCallStateHandler mPhoneCallStateHandler;
+
     public SoundTriggerService(Context context) {
         super(context);
         mContext = context;
         mServiceStub = new SoundTriggerServiceStub();
         mLocalSoundTriggerService = new LocalSoundTriggerService(context);
-        mLock = new Object();
         mSoundModelStatTracker = new SoundModelStatTracker();
+        mDeviceStateHandler = new DeviceStateHandler(mDeviceStateHandlerExecutor,
+                mDeviceEventLogger);
     }
 
     @Override
@@ -243,6 +257,29 @@
         Slog.d(TAG, "onBootPhase: " + phase + " : " + isSafeMode());
         if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
             mDbHelper = new SoundTriggerDbHelper(mContext);
+            final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
+            // Hook up power state listener
+            mContext.registerReceiver(
+                    new BroadcastReceiver() {
+                        @Override
+                        public void onReceive(Context context, Intent intent) {
+                            if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED
+                                    .equals(intent.getAction())) {
+                                return;
+                            }
+                            mDeviceStateHandler.onPowerModeChanged(
+                                    powerManager.getSoundTriggerPowerSaveMode());
+                        }
+                    }, new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
+            // Initialize the initial power state
+            // Do so after registering the listener so we ensure that we don't drop any events
+            mDeviceStateHandler.onPowerModeChanged(powerManager.getSoundTriggerPowerSaveMode());
+
+            // PhoneCallStateHandler initializes the original call state
+            mPhoneCallStateHandler = new PhoneCallStateHandler(
+                      mContext.getSystemService(SubscriptionManager.class),
+                      mContext.getSystemService(TelephonyManager.class),
+                      mDeviceStateHandler);
         }
         mMiddlewareService = ISoundTriggerMiddlewareService.Stub.asInterface(
                 ServiceManager.waitForService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE));
@@ -296,7 +333,10 @@
 
     // Helper to add session logger to the capacity limited detached list.
     // If we are at capacity, remove the oldest, and retry
-    private void addDetachedSessionLogger(EventLogger logger) {
+    private void detachSessionLogger(EventLogger logger) {
+        if (!mSessionEventLoggers.remove(logger)) {
+            return;
+        }
         // Attempt to push to the top of the queue
         while (!mDetachedSessionEventLoggers.offerFirst(logger)) {
             // Remove the oldest element, if one still exists
@@ -377,6 +417,9 @@
             // Event loggers
             pw.println("##Service-Wide logs:");
             mServiceEventLogger.dump(pw, /* indent = */ "  ");
+            pw.println("\n##Device state logs:");
+            mDeviceStateHandler.dump(pw);
+            mDeviceEventLogger.dump(pw, /* indent = */ "  ");
 
             pw.println("\n##Active Session dumps:\n");
             for (var sessionLogger : mSessionEventLoggers) {
@@ -400,6 +443,7 @@
 
     class SoundTriggerSessionStub extends ISoundTriggerSession.Stub {
         private final SoundTriggerHelper mSoundTriggerHelper;
+        private final DeviceStateListener mListener;
         // Used to detect client death.
         private final IBinder mClient;
         private final Identity mOriginatorIdentity;
@@ -421,6 +465,9 @@
             } catch (RemoteException e) {
                 clientDied();
             }
+            mListener = (SoundTriggerDeviceState state)
+                    -> mSoundTriggerHelper.onDeviceStateChanged(state);
+            mDeviceStateHandler.registerListener(mListener);
         }
 
         @Override
@@ -871,9 +918,9 @@
         }
 
         private void detach() {
+            mDeviceStateHandler.unregisterListener(mListener);
             mSoundTriggerHelper.detach();
-            mSessionEventLoggers.remove(mEventLogger);
-            addDetachedSessionLogger(mEventLogger);
+            detachSessionLogger(mEventLogger);
         }
 
         private void enforceCallingPermission(String permission) {
@@ -888,7 +935,8 @@
         private void enforceDetectionPermissions(ComponentName detectionService) {
             PackageManager packageManager = mContext.getPackageManager();
             String packageName = detectionService.getPackageName();
-            if (packageManager.checkPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName)
+            if (packageManager.checkPermission(
+                        Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName)
                     != PackageManager.PERMISSION_GRANTED) {
                 throw new SecurityException(detectionService.getPackageName() + " does not have"
                         + " permission " + Manifest.permission.CAPTURE_AUDIO_HOTWORD);
@@ -1574,6 +1622,7 @@
             private final @NonNull IBinder mClient;
             private final EventLogger mEventLogger;
             private final Identity mOriginatorIdentity;
+            private final @NonNull DeviceStateListener mListener;
 
             private final SparseArray<UUID> mModelUuid = new SparseArray<>(1);
 
@@ -1592,6 +1641,9 @@
                 } catch (RemoteException e) {
                     clientDied();
                 }
+                mListener = (SoundTriggerDeviceState state)
+                        -> mSoundTriggerHelper.onDeviceStateChanged(state);
+                mDeviceStateHandler.registerListener(mListener);
             }
 
             @Override
@@ -1659,8 +1711,8 @@
 
             private void detachInternal() {
                 mEventLogger.enqueue(new SessionEvent(Type.DETACH, null));
-                mSessionEventLoggers.remove(mEventLogger);
-                addDetachedSessionLogger(mEventLogger);
+                detachSessionLogger(mEventLogger);
+                mDeviceStateHandler.unregisterListener(mListener);
                 mSoundTriggerHelper.detach();
             }
         }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/AidlUtil.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/AidlUtil.java
index f3457f5..56a159e 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/AidlUtil.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/AidlUtil.java
@@ -21,6 +21,8 @@
 import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger.SoundModelType;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 
 /**
  * Utilities for working with sound trigger related AIDL generated types.
@@ -49,23 +51,29 @@
 
     /**
      * Creates a new generic abort event.
+     *
      * @return The new event.
      */
-    static RecognitionEvent newAbortEvent() {
-        RecognitionEvent event = newEmptyRecognitionEvent();
-        event.type = SoundModelType.GENERIC;
-        event.status = RecognitionStatus.ABORTED;
-        return event;
+    static RecognitionEventSys newAbortEvent() {
+        RecognitionEvent recognitionEvent = newEmptyRecognitionEvent();
+        recognitionEvent.type = SoundModelType.GENERIC;
+        recognitionEvent.status = RecognitionStatus.ABORTED;
+        RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+        recognitionEventSys.recognitionEvent = recognitionEvent;
+        return recognitionEventSys;
     }
 
     /**
      * Creates a new generic phrase event.
+     *
      * @return The new event.
      */
-    static PhraseRecognitionEvent newAbortPhraseEvent() {
-        PhraseRecognitionEvent event = newEmptyPhraseRecognitionEvent();
-        event.common.type = SoundModelType.KEYPHRASE;
-        event.common.status = RecognitionStatus.ABORTED;
-        return event;
+    static PhraseRecognitionEventSys newAbortPhraseEvent() {
+        PhraseRecognitionEvent recognitionEvent = newEmptyPhraseRecognitionEvent();
+        recognitionEvent.common.type = SoundModelType.KEYPHRASE;
+        recognitionEvent.common.status = RecognitionStatus.ABORTED;
+        PhraseRecognitionEventSys phraseRecognitionEventSys = new PhraseRecognitionEventSys();
+        phraseRecognitionEventSys.phraseRecognitionEvent = recognitionEvent;
+        return phraseRecognitionEventSys;
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerHal.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerHal.java
index 75206e6..6f4a946 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerHal.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerHal.java
@@ -20,12 +20,12 @@
 import android.hardware.soundtrigger3.ISoundTriggerHwCallback;
 import android.hardware.soundtrigger3.ISoundTriggerHwGlobalCallback;
 import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
 import android.media.soundtrigger.Properties;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.IBinder;
 
 /**
@@ -173,14 +173,19 @@
      */
     interface ModelCallback {
         /**
-         * @see ISoundTriggerHwCallback#recognitionCallback(int, RecognitionEvent)
+         * Decorated callback of
+         * {@link ISoundTriggerHwCallback#recognitionCallback(int, RecognitionEvent)} where
+         * {@link RecognitionEventSys} is decorating the returned {@link RecognitionEvent}
          */
-        void recognitionCallback(int modelHandle, RecognitionEvent event);
+        void recognitionCallback(int modelHandle, RecognitionEventSys event);
 
         /**
-         * @see ISoundTriggerHwCallback#phraseRecognitionCallback(int, PhraseRecognitionEvent)
+         * Decorated callback of
+         * {@link ISoundTriggerHwCallback#phraseRecognitionCallback(int, PhraseRecognitionEvent)}
+         * where {@link PhraseRecognitionEventSys} is decorating the returned
+         * {@link PhraseRecognitionEvent}
          */
-        void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEvent event);
+        void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEventSys event);
 
         /**
          * @see ISoundTriggerHwCallback#modelUnloaded(int)
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
index 8c7cabe..d8ef2b6 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
@@ -19,14 +19,14 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
 import android.media.soundtrigger.Properties;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.SoundModelType;
 import android.media.soundtrigger.Status;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.IBinder;
 
 import java.util.HashSet;
@@ -238,13 +238,13 @@
         }
 
         @Override
-        public void recognitionCallback(int modelHandle, RecognitionEvent event) {
+        public void recognitionCallback(int modelHandle, RecognitionEventSys event) {
             synchronized (mActiveModels) {
                 if (!mActiveModels.contains(modelHandle)) {
                     // Discard the event.
                     return;
                 }
-                if (!event.recognitionStillActive) {
+                if (!event.recognitionEvent.recognitionStillActive) {
                     mActiveModels.remove(modelHandle);
                 }
                 // A recognition event must be the last one for its model, unless it indicates that
@@ -255,13 +255,13 @@
         }
 
         @Override
-        public void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEvent event) {
+        public void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEventSys event) {
             synchronized (mActiveModels) {
                 if (!mActiveModels.contains(modelHandle)) {
                     // Discard the event.
                     return;
                 }
-                if (!event.common.recognitionStillActive) {
+                if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
                     mActiveModels.remove(modelHandle);
                 }
                 // A recognition event must be the last one for its model, unless it indicates that
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
index 24741e1..bac2466 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
@@ -17,14 +17,14 @@
 package com.android.server.soundtrigger_middleware;
 
 import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
 import android.media.soundtrigger.Properties;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.Status;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.DeadObjectException;
 import android.os.IBinder;
 import android.util.Log;
@@ -253,7 +253,7 @@
         }
 
         @Override
-        public void recognitionCallback(int model, RecognitionEvent event) {
+        public void recognitionCallback(int model, RecognitionEventSys event) {
             synchronized (mModelStates) {
                 ModelState state = mModelStates.get(model);
                 if (state == null || state == ModelState.INACTIVE) {
@@ -261,15 +261,16 @@
                     reboot();
                     return;
                 }
-                if (event.recognitionStillActive && event.status != RecognitionStatus.SUCCESS
-                        && event.status != RecognitionStatus.FORCED) {
+                if (event.recognitionEvent.recognitionStillActive
+                        && event.recognitionEvent.status != RecognitionStatus.SUCCESS
+                        && event.recognitionEvent.status != RecognitionStatus.FORCED) {
                     Log.wtfStack(TAG,
                             "recognitionStillActive is only allowed when the recognition status "
                                     + "is SUCCESS");
                     reboot();
                     return;
                 }
-                if (!event.recognitionStillActive) {
+                if (!event.recognitionEvent.recognitionStillActive) {
                     mModelStates.replace(model, ModelState.INACTIVE);
                 }
             }
@@ -278,7 +279,7 @@
         }
 
         @Override
-        public void phraseRecognitionCallback(int model, PhraseRecognitionEvent event) {
+        public void phraseRecognitionCallback(int model, PhraseRecognitionEventSys event) {
             synchronized (mModelStates) {
                 ModelState state = mModelStates.get(model);
                 if (state == null || state == ModelState.INACTIVE) {
@@ -286,16 +287,16 @@
                     reboot();
                     return;
                 }
-                if (event.common.recognitionStillActive
-                        && event.common.status != RecognitionStatus.SUCCESS
-                        && event.common.status != RecognitionStatus.FORCED) {
+                if (event.phraseRecognitionEvent.common.recognitionStillActive
+                        && event.phraseRecognitionEvent.common.status != RecognitionStatus.SUCCESS
+                        && event.phraseRecognitionEvent.common.status != RecognitionStatus.FORCED) {
                     Log.wtfStack(TAG,
                             "recognitionStillActive is only allowed when the recognition status "
                                     + "is SUCCESS");
                     reboot();
                     return;
                 }
-                if (!event.common.recognitionStillActive) {
+                if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
                     mModelStates.replace(model, ModelState.INACTIVE);
                 }
             }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
index c67bdd7..df2e9b4 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -25,9 +25,12 @@
 import android.media.soundtrigger.RecognitionConfig;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.Status;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.IBinder;
 import android.os.IHwBinder;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.system.OsConstants;
 import android.util.Log;
 
@@ -570,16 +573,20 @@
         public void recognitionCallback_2_1(
                 android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event,
                 int cookie) {
-            mDelegate.recognitionCallback(event.header.model,
-                    ConversionUtil.hidl2aidlRecognitionEvent(event));
+            RecognitionEventSys eventSys = new RecognitionEventSys();
+            eventSys.recognitionEvent = ConversionUtil.hidl2aidlRecognitionEvent(event);
+            eventSys.halEventReceivedMillis = SystemClock.elapsedRealtime();
+            mDelegate.recognitionCallback(event.header.model, eventSys);
         }
 
         @Override
         public void phraseRecognitionCallback_2_1(
                 android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
                 int cookie) {
-            mDelegate.phraseRecognitionCallback(event.common.header.model,
-                    ConversionUtil.hidl2aidlPhraseRecognitionEvent(event));
+            PhraseRecognitionEventSys eventSys = new PhraseRecognitionEventSys();
+            eventSys.phraseRecognitionEvent = ConversionUtil.hidl2aidlPhraseRecognitionEvent(event);
+            eventSys.halEventReceivedMillis = SystemClock.elapsedRealtime();
+            mDelegate.phraseRecognitionCallback(event.common.header.model, eventSys);
         }
 
         @Override
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
index 8bb5eb1..b1165bb 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
@@ -29,9 +29,12 @@
 import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.Status;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
+import android.os.SystemClock;
 
 public class SoundTriggerHw3Compat implements ISoundTriggerHal {
     private final @NonNull ISoundTriggerHw mDriver;
@@ -244,14 +247,20 @@
         public void phraseRecognitionCallback(int model, PhraseRecognitionEvent event) {
             // A FORCED status implies that recognition is still active after the event.
             event.common.recognitionStillActive |= event.common.status == RecognitionStatus.FORCED;
-            mDelegate.phraseRecognitionCallback(model, event);
+            PhraseRecognitionEventSys phraseRecognitionEventSys = new PhraseRecognitionEventSys();
+            phraseRecognitionEventSys.phraseRecognitionEvent = event;
+            phraseRecognitionEventSys.halEventReceivedMillis = SystemClock.elapsedRealtimeNanos();
+            mDelegate.phraseRecognitionCallback(model, phraseRecognitionEventSys);
         }
 
         @Override
         public void recognitionCallback(int model, RecognitionEvent event) {
             // A FORCED status implies that recognition is still active after the event.
             event.recognitionStillActive |= event.status == RecognitionStatus.FORCED;
-            mDelegate.recognitionCallback(model, event);
+            RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+            recognitionEventSys.recognitionEvent = event;
+            recognitionEventSys.halEventReceivedMillis = SystemClock.elapsedRealtimeNanos();
+            mDelegate.recognitionCallback(model, recognitionEventSys);
         }
 
         @Override
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
index 0e796d1..2ee4e3c 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -16,12 +16,24 @@
 
 package com.android.server.soundtrigger_middleware;
 
-import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.*;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.DETACH;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.FORCE_RECOGNITION;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.GET_MODEL_PARAMETER;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.LOAD_MODEL;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.LOAD_PHRASE_MODEL;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.MODEL_UNLOADED;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.MODULE_DIED;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.QUERY_MODEL_PARAMETER;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.RECOGNITION;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.RESOURCES_AVAILABLE;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.SET_MODEL_PARAMETER;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.START_RECOGNITION;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.STOP_RECOGNITION;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.UNLOAD_MODEL;
 import static com.android.server.utils.EventLogger.Event.ALOGI;
 import static com.android.server.utils.EventLogger.Event.ALOGW;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.Context;
 import android.media.permission.Identity;
 import android.media.permission.IdentityContext;
@@ -29,11 +41,12 @@
 import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.os.BatteryStatsInternal;
 import android.os.IBinder;
@@ -45,19 +58,18 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.LatencyTracker;
 import com.android.server.LocalServices;
-import com.android.server.utils.EventLogger.Event;
 import com.android.server.utils.EventLogger;
-
+import com.android.server.utils.EventLogger.Event;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.Deque;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.LinkedBlockingDeque;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Supplier;
-import java.util.Deque;
 
 
 /**
@@ -370,7 +382,7 @@
         }
 
         @Override
-        public void onRecognition(int modelHandle, RecognitionEvent event, int captureSession)
+        public void onRecognition(int modelHandle, RecognitionEventSys event, int captureSession)
                 throws RemoteException {
             try {
                 mBatteryStatsInternalSupplier.get().noteWakingSoundTrigger(
@@ -388,13 +400,13 @@
         }
 
         @Override
-        public void onPhraseRecognition(int modelHandle, PhraseRecognitionEvent event,
+        public void onPhraseRecognition(int modelHandle, PhraseRecognitionEventSys event,
                 int captureSession)
                 throws RemoteException {
             try {
                 mBatteryStatsInternalSupplier.get().noteWakingSoundTrigger(
                         SystemClock.elapsedRealtime(), mOriginatorIdentity.uid);
-                startKeyphraseEventLatencyTracking(event);
+                startKeyphraseEventLatencyTracking(event.phraseRecognitionEvent);
                 mCallbackDelegate.onPhraseRecognition(modelHandle, event, captureSession);
                 mEventLogger.enqueue(SessionEvent.createForVoid(
                             RECOGNITION, modelHandle, event, captureSession)
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
index 13fe14c..00b894e 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
@@ -27,15 +27,15 @@
 import android.media.permission.IdentityContext;
 import android.media.permission.PermissionUtil;
 import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.Status;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -78,7 +78,7 @@
     public @NonNull
     SoundTriggerModuleDescriptor[] listModules() {
         Identity identity = getIdentity();
-        enforcePermissionsForPreflight(identity);
+        enforcePermissionForPreflight(mContext, identity, CAPTURE_AUDIO_HOTWORD);
         return mDelegate.listModules();
     }
 
@@ -307,16 +307,15 @@
             }
 
             @Override
-            public void onRecognition(int modelHandle, RecognitionEvent event, int captureSession)
-                    throws RemoteException {
+            public void onRecognition(int modelHandle, RecognitionEventSys event,
+                    int captureSession) throws RemoteException {
                 enforcePermissions("Sound trigger recognition.");
                 mDelegate.onRecognition(modelHandle, event, captureSession);
             }
 
             @Override
-            public void onPhraseRecognition(int modelHandle, PhraseRecognitionEvent event,
-                    int captureSession)
-                    throws RemoteException {
+            public void onPhraseRecognition(int modelHandle, PhraseRecognitionEventSys event,
+                    int captureSession) throws RemoteException {
                 enforcePermissions("Sound trigger phrase recognition.");
                 mDelegate.onPhraseRecognition(modelHandle, event, captureSession);
             }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index 15c9ba9..f208c03 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -21,17 +21,17 @@
 import android.media.permission.Identity;
 import android.media.permission.IdentityContext;
 import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
 import android.media.soundtrigger.Properties;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.Status;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -710,8 +710,7 @@
             }
         }
 
-        class CallbackWrapper implements ISoundTriggerCallback,
-                IBinder.DeathRecipient {
+        class CallbackWrapper implements ISoundTriggerCallback, IBinder.DeathRecipient {
             private final ISoundTriggerCallback mCallback;
 
             CallbackWrapper(ISoundTriggerCallback callback) {
@@ -728,11 +727,11 @@
             }
 
             @Override
-            public void onRecognition(int modelHandle, @NonNull RecognitionEvent event,
+            public void onRecognition(int modelHandle, @NonNull RecognitionEventSys event,
                     int captureSession) {
                 synchronized (SoundTriggerMiddlewareValidation.this) {
                     ModelState modelState = mLoadedModels.get(modelHandle);
-                    if (!event.recognitionStillActive) {
+                    if (!event.recognitionEvent.recognitionStillActive) {
                         modelState.activityState = ModelState.Activity.LOADED;
                     }
                 }
@@ -744,7 +743,7 @@
                     Log.w(TAG, "Client callback exception.", e);
                     synchronized (SoundTriggerMiddlewareValidation.this) {
                         ModelState modelState = mLoadedModels.get(modelHandle);
-                        if (event.status != RecognitionStatus.FORCED) {
+                        if (event.recognitionEvent.status != RecognitionStatus.FORCED) {
                             modelState.activityState = ModelState.Activity.INTERCEPTED;
                             // If we failed to deliver an actual event to the client, they would
                             // never know to restart it whenever circumstances change. Thus, we
@@ -758,10 +757,10 @@
 
             @Override
             public void onPhraseRecognition(int modelHandle,
-                    @NonNull PhraseRecognitionEvent event, int captureSession) {
+                    @NonNull PhraseRecognitionEventSys event, int captureSession) {
                 synchronized (SoundTriggerMiddlewareValidation.this) {
                     ModelState modelState = mLoadedModels.get(modelHandle);
-                    if (!event.common.recognitionStillActive) {
+                    if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
                         modelState.activityState = ModelState.Activity.LOADED;
                     }
                 }
@@ -773,7 +772,7 @@
                     Log.w(TAG, "Client callback exception.", e);
                     synchronized (SoundTriggerMiddlewareValidation.this) {
                         ModelState modelState = mLoadedModels.get(modelHandle);
-                        if (!event.common.recognitionStillActive) {
+                        if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
                             modelState.activityState = ModelState.Activity.INTERCEPTED;
                             // If we failed to deliver an actual event to the client, they would
                             // never know to restart it whenever circumstances change. Thus, we
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 6223b2e..84cec55 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -19,16 +19,16 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
 import android.media.soundtrigger.PhraseSoundModel;
 import android.media.soundtrigger.Properties;
 import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.SoundModelType;
 import android.media.soundtrigger.Status;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -499,10 +499,10 @@
 
             @Override
             public void recognitionCallback(int modelHandle,
-                    @NonNull RecognitionEvent recognitionEvent) {
+                    @NonNull RecognitionEventSys event) {
                 ISoundTriggerCallback callback;
                 synchronized (SoundTriggerModule.this) {
-                    if (!recognitionEvent.recognitionStillActive) {
+                    if (!event.recognitionEvent.recognitionStillActive) {
                         setState(ModelState.LOADED);
                     }
                     callback = mCallback;
@@ -510,7 +510,7 @@
                 // The callback must be invoked outside of the lock.
                 try {
                     if (callback != null) {
-                        callback.onRecognition(mHandle, recognitionEvent, mSession.mSessionHandle);
+                        callback.onRecognition(mHandle, event, mSession.mSessionHandle);
                     }
                 } catch (RemoteException e) {
                     // We're not expecting any exceptions here.
@@ -520,10 +520,10 @@
 
             @Override
             public void phraseRecognitionCallback(int modelHandle,
-                    @NonNull PhraseRecognitionEvent phraseRecognitionEvent) {
+                    @NonNull PhraseRecognitionEventSys event) {
                 ISoundTriggerCallback callback;
                 synchronized (SoundTriggerModule.this) {
-                    if (!phraseRecognitionEvent.common.recognitionStillActive) {
+                    if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
                         setState(ModelState.LOADED);
                     }
                     callback = mCallback;
@@ -532,8 +532,7 @@
                 // The callback must be invoked outside of the lock.
                 try {
                     if (callback != null) {
-                        mCallback.onPhraseRecognition(mHandle, phraseRecognitionEvent,
-                                mSession.mSessionHandle);
+                        mCallback.onPhraseRecognition(mHandle, event, mSession.mSessionHandle);
                     }
                 } catch (RemoteException e) {
                     // We're not expecting any exceptions here.
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 486945d..edaaf3f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -913,8 +913,10 @@
         }
         // Handle case where all hotword detector sessions are destroyed with only the visual
         // detector session left
-        if (mDetectorSessions.size() == 1
-                && mDetectorSessions.get(0) instanceof VisualQueryDetectorSession) {
+        boolean allHotwordDetectionServiceSessionsRemoved = mDetectorSessions.size() == 0
+                || (mDetectorSessions.size() == 1 && mDetectorSessions.get(0)
+                instanceof VisualQueryDetectorSession);
+        if (allHotwordDetectionServiceSessionsRemoved) {
             unbindHotwordDetectionService();
         }
     }
diff --git a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/RemoteWallpaperEffectsGenerationService.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/RemoteWallpaperEffectsGenerationService.java
index c228daf..61f34c2 100644
--- a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/RemoteWallpaperEffectsGenerationService.java
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/RemoteWallpaperEffectsGenerationService.java
@@ -40,6 +40,8 @@
 
     private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
 
+    private static final long TIMEOUT_IDLE_BIND_MILLIS = 120 * DateUtils.SECOND_IN_MILLIS;
+
     private final RemoteWallpaperEffectsGenerationServiceCallback mCallback;
 
     public RemoteWallpaperEffectsGenerationService(Context context,
@@ -62,7 +64,7 @@
 
     @Override
     protected long getTimeoutIdleBindMillis() {
-        return PERMANENT_BOUND_TIMEOUT_MS;
+        return TIMEOUT_IDLE_BIND_MILLIS;
     }
 
     @Override
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 2704418..6997f3c7 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -360,6 +360,11 @@
      */
     public static final int INCOMING_AUTO_REJECTED = 81;
 
+    /**
+     * Indicates that the call was unable to be made because the satellite modem is enabled.
+     * @hide
+     */
+    public static final int SATELLITE_ENABLED = 82;
 
     //*********************************************************************************************
     // When adding a disconnect type:
@@ -379,168 +384,170 @@
     @UnsupportedAppUsage
     public static @NonNull String toString(int cause) {
         switch (cause) {
-        case NOT_DISCONNECTED:
-            return "NOT_DISCONNECTED";
-        case INCOMING_MISSED:
-            return "INCOMING_MISSED";
-        case NORMAL:
-            return "NORMAL";
-        case LOCAL:
-            return "LOCAL";
-        case BUSY:
-            return "BUSY";
-        case CONGESTION:
-            return "CONGESTION";
-        case INVALID_NUMBER:
-            return "INVALID_NUMBER";
-        case NUMBER_UNREACHABLE:
-            return "NUMBER_UNREACHABLE";
-        case SERVER_UNREACHABLE:
-            return "SERVER_UNREACHABLE";
-        case INVALID_CREDENTIALS:
-            return "INVALID_CREDENTIALS";
-        case OUT_OF_NETWORK:
-            return "OUT_OF_NETWORK";
-        case SERVER_ERROR:
-            return "SERVER_ERROR";
-        case TIMED_OUT:
-            return "TIMED_OUT";
-        case LOST_SIGNAL:
-            return "LOST_SIGNAL";
-        case LIMIT_EXCEEDED:
-            return "LIMIT_EXCEEDED";
-        case INCOMING_REJECTED:
-            return "INCOMING_REJECTED";
-        case POWER_OFF:
-            return "POWER_OFF";
-        case OUT_OF_SERVICE:
-            return "OUT_OF_SERVICE";
-        case ICC_ERROR:
-            return "ICC_ERROR";
-        case CALL_BARRED:
-            return "CALL_BARRED";
-        case FDN_BLOCKED:
-            return "FDN_BLOCKED";
-        case CS_RESTRICTED:
-            return "CS_RESTRICTED";
-        case CS_RESTRICTED_NORMAL:
-            return "CS_RESTRICTED_NORMAL";
-        case CS_RESTRICTED_EMERGENCY:
-            return "CS_RESTRICTED_EMERGENCY";
-        case UNOBTAINABLE_NUMBER:
-            return "UNOBTAINABLE_NUMBER";
-        case CDMA_LOCKED_UNTIL_POWER_CYCLE:
-            return "CDMA_LOCKED_UNTIL_POWER_CYCLE";
-        case CDMA_DROP:
-            return "CDMA_DROP";
-        case CDMA_INTERCEPT:
-            return "CDMA_INTERCEPT";
-        case CDMA_REORDER:
-            return "CDMA_REORDER";
-        case CDMA_SO_REJECT:
-            return "CDMA_SO_REJECT";
-        case CDMA_RETRY_ORDER:
-            return "CDMA_RETRY_ORDER";
-        case CDMA_ACCESS_FAILURE:
-            return "CDMA_ACCESS_FAILURE";
-        case CDMA_PREEMPTED:
-            return "CDMA_PREEMPTED";
-        case CDMA_NOT_EMERGENCY:
-            return "CDMA_NOT_EMERGENCY";
-        case CDMA_ACCESS_BLOCKED:
-            return "CDMA_ACCESS_BLOCKED";
-        case EMERGENCY_ONLY:
-            return "EMERGENCY_ONLY";
-        case NO_PHONE_NUMBER_SUPPLIED:
-            return "NO_PHONE_NUMBER_SUPPLIED";
-        case DIALED_MMI:
-            return "DIALED_MMI";
-        case VOICEMAIL_NUMBER_MISSING:
-            return "VOICEMAIL_NUMBER_MISSING";
-        case CDMA_CALL_LOST:
-            return "CDMA_CALL_LOST";
-        case EXITED_ECM:
-            return "EXITED_ECM";
-        case DIAL_MODIFIED_TO_USSD:
-            return "DIAL_MODIFIED_TO_USSD";
-        case DIAL_MODIFIED_TO_SS:
-            return "DIAL_MODIFIED_TO_SS";
-        case DIAL_MODIFIED_TO_DIAL:
-            return "DIAL_MODIFIED_TO_DIAL";
-        case DIAL_MODIFIED_TO_DIAL_VIDEO:
-            return "DIAL_MODIFIED_TO_DIAL_VIDEO";
-        case DIAL_VIDEO_MODIFIED_TO_SS:
-            return "DIAL_VIDEO_MODIFIED_TO_SS";
-        case DIAL_VIDEO_MODIFIED_TO_USSD:
-            return "DIAL_VIDEO_MODIFIED_TO_USSD";
-        case DIAL_VIDEO_MODIFIED_TO_DIAL:
-            return "DIAL_VIDEO_MODIFIED_TO_DIAL";
-        case DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO:
-            return "DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO";
-        case ERROR_UNSPECIFIED:
-            return "ERROR_UNSPECIFIED";
-        case OUTGOING_FAILURE:
-            return "OUTGOING_FAILURE";
-        case OUTGOING_CANCELED:
-            return "OUTGOING_CANCELED";
-        case IMS_MERGED_SUCCESSFULLY:
-            return "IMS_MERGED_SUCCESSFULLY";
-        case CDMA_ALREADY_ACTIVATED:
-            return "CDMA_ALREADY_ACTIVATED";
-        case VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED:
-            return "VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED";
-        case CALL_PULLED:
-            return "CALL_PULLED";
-        case ANSWERED_ELSEWHERE:
-            return "ANSWERED_ELSEWHERE";
-        case MAXIMUM_NUMBER_OF_CALLS_REACHED:
-            return "MAXIMUM_NUMER_OF_CALLS_REACHED";
-        case DATA_DISABLED:
-            return "DATA_DISABLED";
-        case DATA_LIMIT_REACHED:
-            return "DATA_LIMIT_REACHED";
-        case DIALED_CALL_FORWARDING_WHILE_ROAMING:
-            return "DIALED_CALL_FORWARDING_WHILE_ROAMING";
-        case IMEI_NOT_ACCEPTED:
-            return "IMEI_NOT_ACCEPTED";
-        case WIFI_LOST:
-            return "WIFI_LOST";
-        case IMS_ACCESS_BLOCKED:
-            return "IMS_ACCESS_BLOCKED";
-        case LOW_BATTERY:
-            return "LOW_BATTERY";
-        case DIAL_LOW_BATTERY:
-            return "DIAL_LOW_BATTERY";
-        case EMERGENCY_TEMP_FAILURE:
-            return "EMERGENCY_TEMP_FAILURE";
-        case EMERGENCY_PERM_FAILURE:
-            return "EMERGENCY_PERM_FAILURE";
-        case NORMAL_UNSPECIFIED:
-            return "NORMAL_UNSPECIFIED";
-        case IMS_SIP_ALTERNATE_EMERGENCY_CALL:
-            return "IMS_SIP_ALTERNATE_EMERGENCY_CALL";
-        case ALREADY_DIALING:
-            return "ALREADY_DIALING";
-        case CANT_CALL_WHILE_RINGING:
-            return "CANT_CALL_WHILE_RINGING";
-        case CALLING_DISABLED:
-            return "CALLING_DISABLED";
-        case TOO_MANY_ONGOING_CALLS:
-            return "TOO_MANY_ONGOING_CALLS";
-        case OTASP_PROVISIONING_IN_PROCESS:
-            return "OTASP_PROVISIONING_IN_PROCESS";
-        case MEDIA_TIMEOUT:
-            return "MEDIA_TIMEOUT";
-        case EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE:
-            return "EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE";
-        case WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION:
-            return "WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION";
-        case OUTGOING_EMERGENCY_CALL_PLACED:
-            return "OUTGOING_EMERGENCY_CALL_PLACED";
+            case NOT_DISCONNECTED:
+                return "NOT_DISCONNECTED";
+            case INCOMING_MISSED:
+                return "INCOMING_MISSED";
+            case NORMAL:
+                return "NORMAL";
+            case LOCAL:
+                return "LOCAL";
+            case BUSY:
+                return "BUSY";
+            case CONGESTION:
+                return "CONGESTION";
+            case INVALID_NUMBER:
+                return "INVALID_NUMBER";
+            case NUMBER_UNREACHABLE:
+                return "NUMBER_UNREACHABLE";
+            case SERVER_UNREACHABLE:
+                return "SERVER_UNREACHABLE";
+            case INVALID_CREDENTIALS:
+                return "INVALID_CREDENTIALS";
+            case OUT_OF_NETWORK:
+                return "OUT_OF_NETWORK";
+            case SERVER_ERROR:
+                return "SERVER_ERROR";
+            case TIMED_OUT:
+                return "TIMED_OUT";
+            case LOST_SIGNAL:
+                return "LOST_SIGNAL";
+            case LIMIT_EXCEEDED:
+                return "LIMIT_EXCEEDED";
+            case INCOMING_REJECTED:
+                return "INCOMING_REJECTED";
+            case POWER_OFF:
+                return "POWER_OFF";
+            case OUT_OF_SERVICE:
+                return "OUT_OF_SERVICE";
+            case ICC_ERROR:
+                return "ICC_ERROR";
+            case CALL_BARRED:
+                return "CALL_BARRED";
+            case FDN_BLOCKED:
+                return "FDN_BLOCKED";
+            case CS_RESTRICTED:
+                return "CS_RESTRICTED";
+            case CS_RESTRICTED_NORMAL:
+                return "CS_RESTRICTED_NORMAL";
+            case CS_RESTRICTED_EMERGENCY:
+                return "CS_RESTRICTED_EMERGENCY";
+            case UNOBTAINABLE_NUMBER:
+                return "UNOBTAINABLE_NUMBER";
+            case CDMA_LOCKED_UNTIL_POWER_CYCLE:
+                return "CDMA_LOCKED_UNTIL_POWER_CYCLE";
+            case CDMA_DROP:
+                return "CDMA_DROP";
+            case CDMA_INTERCEPT:
+                return "CDMA_INTERCEPT";
+            case CDMA_REORDER:
+                return "CDMA_REORDER";
+            case CDMA_SO_REJECT:
+                return "CDMA_SO_REJECT";
+            case CDMA_RETRY_ORDER:
+                return "CDMA_RETRY_ORDER";
+            case CDMA_ACCESS_FAILURE:
+                return "CDMA_ACCESS_FAILURE";
+            case CDMA_PREEMPTED:
+                return "CDMA_PREEMPTED";
+            case CDMA_NOT_EMERGENCY:
+                return "CDMA_NOT_EMERGENCY";
+            case CDMA_ACCESS_BLOCKED:
+                return "CDMA_ACCESS_BLOCKED";
+            case EMERGENCY_ONLY:
+                return "EMERGENCY_ONLY";
+            case NO_PHONE_NUMBER_SUPPLIED:
+                return "NO_PHONE_NUMBER_SUPPLIED";
+            case DIALED_MMI:
+                return "DIALED_MMI";
+            case VOICEMAIL_NUMBER_MISSING:
+                return "VOICEMAIL_NUMBER_MISSING";
+            case CDMA_CALL_LOST:
+                return "CDMA_CALL_LOST";
+            case EXITED_ECM:
+                return "EXITED_ECM";
+            case DIAL_MODIFIED_TO_USSD:
+                return "DIAL_MODIFIED_TO_USSD";
+            case DIAL_MODIFIED_TO_SS:
+                return "DIAL_MODIFIED_TO_SS";
+            case DIAL_MODIFIED_TO_DIAL:
+                return "DIAL_MODIFIED_TO_DIAL";
+            case DIAL_MODIFIED_TO_DIAL_VIDEO:
+                return "DIAL_MODIFIED_TO_DIAL_VIDEO";
+            case DIAL_VIDEO_MODIFIED_TO_SS:
+                return "DIAL_VIDEO_MODIFIED_TO_SS";
+            case DIAL_VIDEO_MODIFIED_TO_USSD:
+                return "DIAL_VIDEO_MODIFIED_TO_USSD";
+            case DIAL_VIDEO_MODIFIED_TO_DIAL:
+                return "DIAL_VIDEO_MODIFIED_TO_DIAL";
+            case DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO:
+                return "DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO";
+            case ERROR_UNSPECIFIED:
+                return "ERROR_UNSPECIFIED";
+            case OUTGOING_FAILURE:
+                return "OUTGOING_FAILURE";
+            case OUTGOING_CANCELED:
+                return "OUTGOING_CANCELED";
+            case IMS_MERGED_SUCCESSFULLY:
+                return "IMS_MERGED_SUCCESSFULLY";
+            case CDMA_ALREADY_ACTIVATED:
+                return "CDMA_ALREADY_ACTIVATED";
+            case VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED:
+                return "VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED";
+            case CALL_PULLED:
+                return "CALL_PULLED";
+            case ANSWERED_ELSEWHERE:
+                return "ANSWERED_ELSEWHERE";
+            case MAXIMUM_NUMBER_OF_CALLS_REACHED:
+                return "MAXIMUM_NUMER_OF_CALLS_REACHED";
+            case DATA_DISABLED:
+                return "DATA_DISABLED";
+            case DATA_LIMIT_REACHED:
+                return "DATA_LIMIT_REACHED";
+            case DIALED_CALL_FORWARDING_WHILE_ROAMING:
+                return "DIALED_CALL_FORWARDING_WHILE_ROAMING";
+            case IMEI_NOT_ACCEPTED:
+                return "IMEI_NOT_ACCEPTED";
+            case WIFI_LOST:
+                return "WIFI_LOST";
+            case IMS_ACCESS_BLOCKED:
+                return "IMS_ACCESS_BLOCKED";
+            case LOW_BATTERY:
+                return "LOW_BATTERY";
+            case DIAL_LOW_BATTERY:
+                return "DIAL_LOW_BATTERY";
+            case EMERGENCY_TEMP_FAILURE:
+                return "EMERGENCY_TEMP_FAILURE";
+            case EMERGENCY_PERM_FAILURE:
+                return "EMERGENCY_PERM_FAILURE";
+            case NORMAL_UNSPECIFIED:
+                return "NORMAL_UNSPECIFIED";
+            case IMS_SIP_ALTERNATE_EMERGENCY_CALL:
+                return "IMS_SIP_ALTERNATE_EMERGENCY_CALL";
+            case ALREADY_DIALING:
+                return "ALREADY_DIALING";
+            case CANT_CALL_WHILE_RINGING:
+                return "CANT_CALL_WHILE_RINGING";
+            case CALLING_DISABLED:
+                return "CALLING_DISABLED";
+            case TOO_MANY_ONGOING_CALLS:
+                return "TOO_MANY_ONGOING_CALLS";
+            case OTASP_PROVISIONING_IN_PROCESS:
+                return "OTASP_PROVISIONING_IN_PROCESS";
+            case MEDIA_TIMEOUT:
+                return "MEDIA_TIMEOUT";
+            case EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE:
+                return "EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE";
+            case WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION:
+                return "WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION";
+            case OUTGOING_EMERGENCY_CALL_PLACED:
+                return "OUTGOING_EMERGENCY_CALL_PLACED";
             case INCOMING_AUTO_REJECTED:
                 return "INCOMING_AUTO_REJECTED";
-        default:
-            return "INVALID: " + cause;
+            case SATELLITE_ENABLED:
+                return "SATELLITE_ENABLED";
+            default:
+                return "INVALID: " + cause;
         }
     }
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index c5830b8..431e9e6 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -1503,6 +1503,31 @@
         }
     }
 
+    /**
+     * Inform whether the device is aligned with the satellite for demo mode.
+     *
+     * @param isAligned {@true} Device is aligned with the satellite for demo mode
+     *                  {@false} Device is not aligned with the satellite for demo mode
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+
+    public void onDeviceAlignedWithSatellite(boolean isAligned) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                telephony.onDeviceAlignedWithSatellite(mSubId, isAligned);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("informDeviceAlignedToSatellite() RemoteException:" + ex);
+            ex.rethrowFromSystemServer();
+        }
+    }
+
     private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
                 .getTelephonyServiceManager()
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 18e4c37..21aad73 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2982,6 +2982,16 @@
     void requestTimeForNextSatelliteVisibility(int subId, in ResultReceiver receiver);
 
     /**
+     * Inform whether the device is aligned with the satellite within in margin for demo mode.
+     *
+     * @param isAligned {@true} Device is aligned with the satellite for demo mode
+     *                  {@false} Device is not aligned with the satellite for demo mode
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void onDeviceAlignedWithSatellite(int subId, in boolean isAligned);
+
+    /**
      * This API can be used by only CTS to update satellite vendor service package name.
      *
      * @param servicePackageName The package name of the satellite vendor service.
@@ -3018,4 +3028,13 @@
      * {@code false} otherwise.
      */
     boolean setSatellitePointingUiClassName(in String packageName, in String className);
+
+    /**
+     * This API can be used by only CTS to update the timeout duration in milliseconds whether
+     * the device is aligned with the satellite for demo mode
+     *
+     * @param timeoutMillis The timeout duration in millisecond.
+     * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
+     */
+    boolean setSatelliteDeviceAlignedTimeoutDuration(long timeoutMillis);
 }
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index 7272abb..32ff243 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -21,6 +21,9 @@
         <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
         <!-- Ensure output directory is empty at the start -->
         <option name="run-command" value="rm -rf /sdcard/flicker" />
+        <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+        <option name="run-command" value="cmd window tracing size 20480" />
+        <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index 6053f1d..daecfe7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -17,8 +17,8 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
+import android.tools.common.PlatformConsts
 import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.common.traces.wm.WindowManagerState.Companion.STATE_RESUMED
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.helpers.FIND_TIMEOUT
 import android.tools.device.traces.parsers.WindowManagerStateHelper
@@ -54,8 +54,8 @@
         launchButton.click()
         wmHelper
             .StateSyncBuilder()
-            .withActivityState(SECONDARY_ACTIVITY_COMPONENT, STATE_RESUMED)
-            .withActivityState(MAIN_ACTIVITY_COMPONENT, STATE_RESUMED)
+            .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
             .waitForAndVerify()
     }
 
@@ -73,8 +73,8 @@
         launchButton.click()
         wmHelper
             .StateSyncBuilder()
-            .withActivityState(PLACEHOLDER_PRIMARY_COMPONENT, STATE_RESUMED)
-            .withActivityState(PLACEHOLDER_SECONDARY_COMPONENT, STATE_RESUMED)
+            .withActivityState(PLACEHOLDER_PRIMARY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .withActivityState(PLACEHOLDER_SECONDARY_COMPONENT, PlatformConsts.STATE_RESUMED)
             .waitForAndVerify()
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
index a3fb73b..496165a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
@@ -18,6 +18,11 @@
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.Timestamp
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.common.flicker.subject.exceptions.ExceptionMessageBuilder
+import android.tools.common.flicker.subject.exceptions.InvalidPropertyException
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
@@ -36,7 +41,7 @@
 /**
  * Test IME window layer will become visible when switching from the fixed orientation activity
  * (e.g. Launcher activity). To run this test: `atest
- * FlickerTests:OpenImeWindowFromFixedOrientationAppTest`
+ * FlickerTests:ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
@@ -77,6 +82,49 @@
         flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
     }
 
+    @Postsubmit
+    @Test
+    fun imeLayerAlphaOneAfterSnapshotStartingWindowRemoval() {
+        // Check if the snapshot appeared during the trace
+        var imeSnapshotRemovedTimestamp: Timestamp? = null
+
+        val layerTrace = flicker.reader.readLayersTrace()
+        val layerTraceEntries = layerTrace?.entries?.toList() ?: emptyList()
+
+        layerTraceEntries.zipWithNext { prev, next ->
+            val prevSnapshotLayerVisible =
+                    ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(prev.visibleLayers)
+            val nextSnapshotLayerVisible =
+                    ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(next.visibleLayers)
+
+            if (imeSnapshotRemovedTimestamp == null &&
+                    (prevSnapshotLayerVisible && !nextSnapshotLayerVisible)) {
+                imeSnapshotRemovedTimestamp = next.timestamp
+            }
+        }
+
+        // if so, make an assertion
+        imeSnapshotRemovedTimestamp?.let { timestamp ->
+            val stateAfterSnapshot = layerTrace?.getEntryAt(timestamp)
+                    ?: error("State not found for $timestamp")
+
+            val imeLayers = ComponentNameMatcher.IME
+                    .filterLayers(stateAfterSnapshot.visibleLayers.toList())
+
+            require(imeLayers.isNotEmpty()) { "IME layer not found" }
+            if (imeLayers.any { it.color.a != 1.0f }) {
+                val errorMsgBuilder = ExceptionMessageBuilder()
+                        .setTimestamp(timestamp)
+                        .forInvalidProperty("IME layer alpha")
+                        .setExpected("is 1.0")
+                        .setActual("not 1.0")
+                        .addExtraDescription("Filter",
+                                ComponentNameMatcher.IME.toLayerIdentifier())
+                throw InvalidPropertyException(errorMsgBuilder)
+            }
+        }
+    }
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
index a658293..3a80c66 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.launch
 
+import android.platform.test.annotations.FlakyTest
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
@@ -24,6 +25,7 @@
 import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
 import androidx.test.filters.RequiresDevice
 import org.junit.FixMethodOrder
+import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -76,6 +78,131 @@
             teardown { testApp.exit(wmHelper) }
         }
 
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun focusChanges() {
+        super.focusChanges()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowReplacesLauncherAsTopWindow() {
+        super.appWindowReplacesLauncherAsTopWindow()
+    }
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowAsTopWindowAtEnd() {
+        super.appWindowAsTopWindowAtEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowBecomesTopWindow() {
+        super.appWindowBecomesTopWindow()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowBecomesVisible() {
+        super.appWindowBecomesVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowIsTopWindowAtEnd() {
+        super.appWindowIsTopWindowAtEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appLayerBecomesVisible() {
+        super.appLayerBecomesVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appLayerReplacesLauncher() {
+        super.appLayerReplacesLauncher()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun cujCompleted() {
+        super.cujCompleted()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun entireScreenCovered() {
+        super.entireScreenCovered()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() {
+        super.navBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() {
+        super.navBarLayerPositionAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarWindowIsAlwaysVisible() {
+        super.navBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarWindowIsVisibleAtStartAndEnd() {
+        super.navBarWindowIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() {
+        super.statusBarLayerPositionAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() {
+        super.statusBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {
+        super.taskBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() {
+        super.taskBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+    }
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
index 017de60..e2d17cd 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
@@ -26,39 +26,57 @@
 import android.graphics.PixelFormat;
 import android.graphics.RenderNode;
 import android.graphics.Shader;
+import android.graphics.SurfaceTexture;
 import android.hardware.HardwareBuffer;
 import android.media.Image;
 import android.media.ImageWriter;
 import android.os.Bundle;
+import android.view.Surface;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
+import android.view.TextureView;
 import android.view.View;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.Spinner;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
 
 import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.FutureTask;
 
 @SuppressWarnings({"UnusedDeclaration"})
 public class ColorBitmapActivity extends Activity implements SurfaceHolder.Callback,
-        AdapterView.OnItemSelectedListener {
+        TextureView.SurfaceTextureListener {
 
     private static final int WIDTH = 512;
     private static final int HEIGHT = 512;
 
     private ImageView mImageView;
     private SurfaceView mSurfaceView;
+    private TextureView mTextureView;
     private HardwareBuffer mGradientBuffer;
-    private ImageWriter mImageWriter;
+    private Map<View, ImageWriter> mImageWriters = new HashMap<>();
     private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
     private String[] mColorNames = {"sRGB", "BT2020_HLG", "BT2020_PQ"};
-    private String mCurrentColorName = "sRGB";
 
-    private FutureTask<HardwareBuffer> authorGradientBuffer(HardwareBuffer buffer) {
+    private int mGradientEndColor = 0xFFFFFFFF;
+
+    private int[] mGradientEndColors = {0xFFFFFFFF, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF};
+    private String[] mGradientColorNames = {"Grayscale", "Red", "Green", "Blue"};
+
+    private final ExecutorService mBufferFenceExecutor = Executors.newFixedThreadPool(1);
+    private final ExecutorService mBufferExecutor = Executors.newFixedThreadPool(1);
+
+    private FutureTask<HardwareBuffer> authorGradientBuffer(
+            HardwareBuffer buffer, int gradentEndColor) {
         HardwareBufferRenderer renderer = new HardwareBufferRenderer(buffer);
         RenderNode node = new RenderNode("content");
         node.setPosition(0, 0, buffer.getWidth(), buffer.getHeight());
@@ -66,9 +84,10 @@
         Canvas canvas = node.beginRecording();
         LinearGradient gradient = new LinearGradient(
                 0, 0, buffer.getWidth(), buffer.getHeight(), 0xFF000000,
-                0xFFFFFFFF, Shader.TileMode.CLAMP);
+                gradentEndColor, Shader.TileMode.CLAMP);
         Paint paint = new Paint();
         paint.setShader(gradient);
+        paint.setDither(true);
         canvas.drawRect(0f, 0f, buffer.getWidth(), buffer.getHeight(), paint);
         node.endRecording();
 
@@ -78,7 +97,7 @@
         FutureTask<HardwareBuffer> resolvedBuffer = new FutureTask<>(() -> buffer);
         renderer.obtainRenderRequest()
                 .setColorSpace(colorSpace)
-                .draw(Executors.newSingleThreadExecutor(), result -> {
+                .draw(mBufferFenceExecutor, result -> {
                     result.getFence().await(Duration.ofSeconds(3));
                     resolvedBuffer.run();
                 });
@@ -90,7 +109,7 @@
                 WIDTH, HEIGHT, PixelFormat.RGBA_8888, 1,
                 HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
                         | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT);
-        return authorGradientBuffer(buffer);
+        return authorGradientBuffer(buffer, mGradientEndColor);
     }
 
     @Override
@@ -101,29 +120,70 @@
 
             mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
 
-            ArrayAdapter<String> adapter = new ArrayAdapter<>(
+            ArrayAdapter<String> colorSpaceAdapter = new ArrayAdapter<>(
                     this, android.R.layout.simple_spinner_item, mColorNames);
 
-            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-            Spinner spinner = new Spinner(this);
-            spinner.setAdapter(adapter);
-            spinner.setOnItemSelectedListener(this);
+            colorSpaceAdapter
+                    .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+            Spinner colorSpaceSpinner = new Spinner(this);
+            colorSpaceSpinner.setAdapter(colorSpaceAdapter);
+            colorSpaceSpinner.setOnItemSelectedListener(new ColorSpaceOnItemSelectedListener());
+
+            ArrayAdapter<String> gradientColorAdapter = new ArrayAdapter<>(
+                    this, android.R.layout.simple_spinner_item, mGradientColorNames);
+
+            gradientColorAdapter
+                    .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+            Spinner gradientColorSpinner = new Spinner(this);
+            gradientColorSpinner.setAdapter(gradientColorAdapter);
+            gradientColorSpinner
+                    .setOnItemSelectedListener(new GradientColorOnItemSelectedListener());
 
             mGradientBuffer = getGradientBuffer().get();
 
             LinearLayout linearLayout = new LinearLayout(this);
             linearLayout.setOrientation(LinearLayout.VERTICAL);
 
+            TextView imageViewText = new TextView(this);
+            imageViewText.setText("ImageView");
             mImageView = new ImageView(this);
 
+            TextView textureViewText = new TextView(this);
+            textureViewText.setText("TextureView");
+            mTextureView = new TextureView(this);
+            mTextureView.setSurfaceTextureListener(this);
+
+            TextView surfaceViewText = new TextView(this);
+            surfaceViewText.setText("SurfaceView");
             mSurfaceView = new SurfaceView(this);
             mSurfaceView.getHolder().addCallback(this);
 
-            linearLayout.addView(spinner, new LinearLayout.LayoutParams(
+            LinearLayout spinnerLayout = new LinearLayout(this);
+            spinnerLayout.setOrientation(LinearLayout.HORIZONTAL);
+
+            spinnerLayout.addView(colorSpaceSpinner, new LinearLayout.LayoutParams(
                     LinearLayout.LayoutParams.WRAP_CONTENT,
                     LinearLayout.LayoutParams.WRAP_CONTENT));
 
+            spinnerLayout.addView(gradientColorSpinner, new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.WRAP_CONTENT,
+                    LinearLayout.LayoutParams.WRAP_CONTENT));
+
+            linearLayout.addView(spinnerLayout, new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.WRAP_CONTENT,
+                    LinearLayout.LayoutParams.WRAP_CONTENT));
+
+            linearLayout.addView(imageViewText,  new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.WRAP_CONTENT,
+                    LinearLayout.LayoutParams.WRAP_CONTENT));
             linearLayout.addView(mImageView, new LinearLayout.LayoutParams(WIDTH, HEIGHT));
+            linearLayout.addView(textureViewText,  new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.WRAP_CONTENT,
+                    LinearLayout.LayoutParams.WRAP_CONTENT));
+            linearLayout.addView(mTextureView, new LinearLayout.LayoutParams(WIDTH, HEIGHT));
+            linearLayout.addView(surfaceViewText,  new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.WRAP_CONTENT,
+                    LinearLayout.LayoutParams.WRAP_CONTENT));
             linearLayout.addView(mSurfaceView, new LinearLayout.LayoutParams(WIDTH, HEIGHT));
 
             setContentView(linearLayout);
@@ -145,16 +205,24 @@
     }
 
     private void populateBuffers() {
-        Bitmap bitmap = Bitmap.wrapHardwareBuffer(
-                mGradientBuffer, ColorSpace.get(ColorSpace.Named.SRGB));
-        Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, false);
-        copy.setColorSpace(mColorSpace);
-        mImageView.setImageBitmap(copy);
+        try {
+            Bitmap bitmap = Bitmap.wrapHardwareBuffer(
+                    getGradientBuffer().get(), ColorSpace.get(ColorSpace.Named.SRGB));
+            Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, false);
+            copy.setColorSpace(mColorSpace);
+            mImageView.setImageBitmap(copy);
 
-        try (Image image = mImageWriter.dequeueInputImage()) {
-            authorGradientBuffer(image.getHardwareBuffer()).get();
-            image.setDataSpace(mColorSpace.getDataSpace());
-            mImageWriter.queueInputImage(image);
+            for (ImageWriter writer : mImageWriters.values()) {
+                mBufferExecutor.execute(() -> {
+                    try (Image image = writer.dequeueInputImage()) {
+                        authorGradientBuffer(image.getHardwareBuffer(), mGradientEndColor).get();
+                        image.setDataSpace(mColorSpace.getDataSpace());
+                        writer.queueInputImage(image);
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                });
+            }
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -167,30 +235,81 @@
 
     @Override
     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-        mImageWriter = new ImageWriter.Builder(holder.getSurface())
+        mImageWriters.put(mSurfaceView, new ImageWriter.Builder(holder.getSurface())
                 .setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
                         | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT
                         | HardwareBuffer.USAGE_COMPOSER_OVERLAY)
-                .build();
+                .build());
         populateBuffers();
     }
 
     @Override
     public void surfaceDestroyed(SurfaceHolder holder) {
-        mImageWriter.close();
-        mImageWriter = null;
+        if (mImageWriters.containsKey(mSurfaceView)) {
+            mImageWriters.remove(mSurfaceView);
+        }
     }
 
-
     @Override
-    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-        mCurrentColorName = mColorNames[position];
-        mColorSpace = getFromName(mCurrentColorName);
+    public void onSurfaceTextureAvailable(
+            @NonNull SurfaceTexture surface, int width, int height) {
+        mImageWriters.put(mTextureView, new ImageWriter.Builder(new Surface(surface))
+                .setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+                        | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT)
+                .build());
         populateBuffers();
     }
 
     @Override
-    public void onNothingSelected(AdapterView<?> parent) {
+    public void onSurfaceTextureSizeChanged(
+            @NonNull SurfaceTexture surface, int width, int height) {
 
     }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
+        if (mImageWriters.containsKey(mTextureView)) {
+            mImageWriters.remove(mTextureView);
+        }
+        return false;
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
+
+    }
+
+    private final class ColorSpaceOnItemSelectedListener
+            implements AdapterView.OnItemSelectedListener {
+
+        @Override
+        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+            ColorBitmapActivity.this.mColorSpace =
+                    getFromName(ColorBitmapActivity.this.mColorNames[position]);
+            ColorBitmapActivity.this.getMainExecutor()
+                    .execute(ColorBitmapActivity.this::populateBuffers);
+        }
+
+        @Override
+        public void onNothingSelected(AdapterView<?> parent) {
+
+        }
+    }
+
+    private final class GradientColorOnItemSelectedListener
+            implements AdapterView.OnItemSelectedListener {
+
+        @Override
+        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+            ColorBitmapActivity.this.mGradientEndColor =
+                    ColorBitmapActivity.this.mGradientEndColors[position];
+            ColorBitmapActivity.this.getMainExecutor()
+                    .execute(ColorBitmapActivity.this::populateBuffers);
+        }
+
+        @Override
+        public void onNothingSelected(AdapterView<?> parent) {
+
+        }
+    }
 }
diff --git a/tests/SilkFX/res/layout/gainmap_image.xml b/tests/SilkFX/res/layout/gainmap_image.xml
index 89bbb70..b0ed914 100644
--- a/tests/SilkFX/res/layout/gainmap_image.xml
+++ b/tests/SilkFX/res/layout/gainmap_image.xml
@@ -34,7 +34,7 @@
                          android:layout_width="wrap_content"
                          android:layout_weight="1"
                          android:layout_height="wrap_content"
-                         android:text="SDR original" />
+                         android:text="SDR" />
 
             <RadioButton android:id="@+id/output_gainmap"
                          android:layout_width="wrap_content"
@@ -46,13 +46,34 @@
                          android:layout_width="wrap_content"
                          android:layout_weight="1"
                          android:layout_height="wrap_content"
-                         android:text="HDR (sdr+gainmap)" />
+                         android:text="HDR" />
+
+            <RadioButton android:id="@+id/output_hdr_test"
+                         android:layout_width="wrap_content"
+                         android:layout_weight="1"
+                         android:layout_height="wrap_content"
+                         android:text="HDR (test)" />
         </RadioGroup>
 
-        <Spinner
-            android:id="@+id/image_selection"
+        <LinearLayout
             android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <Spinner
+                android:id="@+id/image_selection"
+                android:layout_width="match_parent"
+                android:layout_weight="1"
+                android:layout_height="wrap_content" />
+
+            <Button
+                android:id="@+id/gainmap_metadata"
+                android:layout_width="match_parent"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:text="Gainmap Metadata..." />
+
+        </LinearLayout>
 
         <TextView
             android:id="@+id/error_msg"
@@ -67,4 +88,4 @@
 
     </LinearLayout>
 
-</com.android.test.silkfx.hdr.GainmapImage>
\ No newline at end of file
+</com.android.test.silkfx.hdr.GainmapImage>
diff --git a/tests/SilkFX/res/layout/gainmap_metadata.xml b/tests/SilkFX/res/layout/gainmap_metadata.xml
new file mode 100644
index 0000000..0dabaca
--- /dev/null
+++ b/tests/SilkFX/res/layout/gainmap_metadata.xml
@@ -0,0 +1,264 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout
+        android:layout_width="350dp"
+        android:layout_height="300dp"
+        android:layout_centerHorizontal="true"
+        android:padding="8dp"
+        android:orientation="vertical"
+        android:background="#444444">
+
+        <TextView
+            android:id="@+id/gainmap_metadata_title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="10dp"
+            android:text="Metadata for &quot;HDR (test)&quot; (values in linear space):" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="10dp"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/gainmap_metadata_gainmapmin_text"
+                android:layout_width="match_parent"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:text="Gain Map Min:" />
+
+            <TextView
+                android:id="@+id/gainmap_metadata_gainmapmin_val"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ems="4"
+                android:text="TODO" />
+
+            <SeekBar
+                android:id="@+id/gainmap_metadata_gainmapmin"
+                android:min="0"
+                android:max="100"
+                android:layout_width="150dp"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="10dp"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/gainmap_metadata_gainmapmax_text"
+                android:layout_width="match_parent"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:text="Gain Map Max:" />
+
+            <TextView
+                android:id="@+id/gainmap_metadata_gainmapmax_val"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ems="4"
+                android:text="TODO" />
+
+            <SeekBar
+                android:id="@+id/gainmap_metadata_gainmapmax"
+                android:min="0"
+                android:max="100"
+                android:layout_width="150dp"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="10dp"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/gainmap_metadata_capacitymin_text"
+                android:layout_width="match_parent"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:text="Capacity Min:" />
+
+            <TextView
+                android:id="@+id/gainmap_metadata_capacitymin_val"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ems="4"
+                android:text="TODO" />
+
+            <SeekBar
+                android:id="@+id/gainmap_metadata_capacitymin"
+                android:min="0"
+                android:max="100"
+                android:layout_width="150dp"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="10dp"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/gainmap_metadata_capacitymax_text"
+                android:layout_width="match_parent"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:text="Capacity Max:" />
+
+            <TextView
+                android:id="@+id/gainmap_metadata_capacitymax_val"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ems="4"
+                android:text="TODO" />
+
+            <SeekBar
+                android:id="@+id/gainmap_metadata_capacitymax"
+                android:min="0"
+                android:max="100"
+                android:layout_width="150dp"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="10dp"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/gainmap_metadata_gamma_text"
+                android:layout_width="match_parent"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:text="Gamma:" />
+
+            <TextView
+                android:id="@+id/gainmap_metadata_gamma_val"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ems="4"
+                android:text="TODO" />
+
+            <SeekBar
+                android:id="@+id/gainmap_metadata_gamma"
+                android:min="0"
+                android:max="100"
+                android:layout_width="150dp"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="10dp"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/gainmap_metadata_offsetsdr_text"
+                android:layout_width="match_parent"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:text="Offset SDR:" />
+
+            <TextView
+                android:id="@+id/gainmap_metadata_offsetsdr_val"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ems="4"
+                android:text="TODO" />
+
+            <SeekBar
+                android:id="@+id/gainmap_metadata_offsetsdr"
+                android:min="0"
+                android:max="100"
+                android:layout_width="150dp"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="10dp"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/gainmap_metadata_offsethdr_text"
+                android:layout_width="match_parent"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:text="Offset HDR:" />
+
+            <TextView
+                android:id="@+id/gainmap_metadata_offsethdr_val"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ems="4"
+                android:text="TODO" />
+
+            <SeekBar
+                android:id="@+id/gainmap_metadata_offsethdr"
+                android:min="0"
+                android:max="100"
+                android:layout_width="150dp"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <Button
+                android:id="@+id/gainmap_metadata_reset"
+                android:layout_width="match_parent"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:text="Reset" />
+
+            <Button
+                android:id="@+id/gainmap_metadata_done"
+                android:layout_width="match_parent"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:text="Done" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</RelativeLayout>
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt
index 78bc4c4..7cf69b7 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt
@@ -27,6 +27,7 @@
 import android.view.View
 import android.widget.AdapterView
 import android.widget.ArrayAdapter
+import android.widget.Button
 import android.widget.FrameLayout
 import android.widget.RadioGroup
 import android.widget.Spinner
@@ -44,6 +45,7 @@
     private var gainmap: Gainmap? = null
     private var gainmapVisualizer: Bitmap? = null
     private lateinit var imageView: SubsamplingScaleImageView
+    private lateinit var gainmapMetadataEditor: GainmapMetadataEditor
 
     init {
         gainmapImages = context.assets.list("gainmaps")!!
@@ -58,6 +60,7 @@
         super.onFinishInflate()
 
         imageView = findViewById(R.id.image)!!
+        gainmapMetadataEditor = GainmapMetadataEditor(this, imageView)
 
         findViewById<RadioGroup>(R.id.output_mode)!!.also {
             it.check(outputMode)
@@ -92,6 +95,10 @@
             }
         }
 
+        findViewById<Button>(R.id.gainmap_metadata)!!.setOnClickListener {
+            gainmapMetadataEditor.openEditor()
+        }
+
         setImage(0)
 
         imageView.apply {
@@ -132,6 +139,7 @@
             findViewById<RadioGroup>(R.id.output_mode)!!.visibility = View.VISIBLE
 
             gainmap = bitmap!!.gainmap
+            gainmapMetadataEditor.setGainmap(gainmap)
             val map = gainmap!!.gainmapContents
             if (map.config != Bitmap.Config.ALPHA_8) {
                 gainmapVisualizer = map
@@ -175,7 +183,15 @@
 
         imageView.setImage(ImageSource.cachedBitmap(when (outputMode) {
             R.id.output_hdr -> {
-                bitmap!!.gainmap = gainmap; bitmap!!
+                gainmapMetadataEditor.useOriginalMetadata()
+                bitmap!!.gainmap = gainmap
+                bitmap!!
+            }
+
+            R.id.output_hdr_test -> {
+                gainmapMetadataEditor.useEditMetadata()
+                bitmap!!.gainmap = gainmap
+                bitmap!!
             }
 
             R.id.output_sdr -> {
@@ -186,4 +202,4 @@
             else -> throw IllegalStateException()
         }))
     }
-}
\ No newline at end of file
+}
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt
new file mode 100644
index 0000000..8a65304
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.silkfx.hdr
+
+import android.graphics.Gainmap
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.PopupWindow
+import android.widget.SeekBar
+import android.widget.TextView
+import com.android.test.silkfx.R
+
+data class GainmapMetadata(
+    var ratioMin: Float,
+    var ratioMax: Float,
+    var capacityMin: Float,
+    var capacityMax: Float,
+    var gamma: Float,
+    var offsetSdr: Float,
+    var offsetHdr: Float
+)
+
+class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) {
+    private var gainmap: Gainmap? = null
+    private var showingEdits = false
+
+    private var metadataPopup: PopupWindow? = null
+
+    private var originalMetadata: GainmapMetadata = GainmapMetadata(
+        1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f)
+    private var currentMetadata: GainmapMetadata = originalMetadata.copy()
+
+    private val maxProgress = 100.0f
+
+    private val minRatioMin = .001f
+    private val maxRatioMin = 1.0f
+    private val minRatioMax = 1.0f
+    private val maxRatioMax = 16.0f
+    private val minCapacityMin = 1.0f
+    private val maxCapacityMin = maxRatioMax
+    private val minCapacityMax = 1.001f
+    private val maxCapacityMax = maxRatioMax
+    private val minGamma = 0.1f
+    private val maxGamma = 3.0f
+    // Min and max offsets are 0.0 and 1.0 respectively
+
+    fun setGainmap(newGainmap: Gainmap?) {
+        gainmap = newGainmap
+        originalMetadata = GainmapMetadata(gainmap!!.getRatioMin()[0],
+            gainmap!!.getRatioMax()[0], gainmap!!.getMinDisplayRatioForHdrTransition(),
+            gainmap!!.getDisplayRatioForFullHdr(), gainmap!!.getGamma()[0],
+            gainmap!!.getEpsilonSdr()[0], gainmap!!.getEpsilonHdr()[0])
+        currentMetadata = originalMetadata.copy()
+    }
+
+    fun useOriginalMetadata() {
+        showingEdits = false
+        applyMetadata(originalMetadata)
+    }
+
+    fun useEditMetadata() {
+        showingEdits = true
+        applyMetadata(currentMetadata)
+    }
+
+    fun closeEditor() {
+        metadataPopup?.let {
+            it.dismiss()
+            metadataPopup = null
+        }
+    }
+
+    fun openEditor() {
+        if (metadataPopup != null) return
+
+        val view = LayoutInflater.from(parent.getContext()).inflate(R.layout.gainmap_metadata, null)
+
+        metadataPopup = PopupWindow(view, ViewGroup.LayoutParams.WRAP_CONTENT,
+            ViewGroup.LayoutParams.WRAP_CONTENT)
+        metadataPopup!!.showAtLocation(view, Gravity.CENTER, 0, 0)
+
+        (view.getParent() as ViewGroup).removeView(view)
+        parent.addView(view)
+
+        view.findViewById<Button>(R.id.gainmap_metadata_done)!!.setOnClickListener {
+            closeEditor()
+        }
+
+        view.findViewById<Button>(R.id.gainmap_metadata_reset)!!.setOnClickListener {
+            resetGainmapMetadata()
+        }
+
+        updateMetadataUi()
+
+        val gainmapMinSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmin)
+        val gainmapMaxSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmax)
+        val capacityMinSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymin)
+        val capacityMaxSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymax)
+        val gammaSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_gamma)
+        val offsetSdrSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr)
+        val offsetHdrSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_offsethdr)
+        arrayOf(gainmapMinSeek, gainmapMaxSeek, capacityMinSeek, capacityMaxSeek, gammaSeek,
+            offsetSdrSeek, offsetHdrSeek).forEach {
+            it.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{
+                override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+                    if (!fromUser) return
+                    val normalized = progress.toFloat() / maxProgress
+                    when (seekBar) {
+                        gainmapMinSeek -> updateGainmapMin(normalized)
+                        gainmapMaxSeek -> updateGainmapMax(normalized)
+                        capacityMinSeek -> updateCapacityMin(normalized)
+                        capacityMaxSeek -> updateCapacityMax(normalized)
+                        gammaSeek -> updateGamma(normalized)
+                        offsetSdrSeek -> updateOffsetSdr(normalized)
+                        offsetHdrSeek -> updateOffsetHdr(normalized)
+                    }
+                }
+
+                override fun onStartTrackingTouch(seekBar: SeekBar) {}
+                override fun onStopTrackingTouch(seekBar: SeekBar) {}
+            })
+        }
+    }
+
+    private fun updateMetadataUi() {
+        val gainmapMinSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmin)
+        val gainmapMaxSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmax)
+        val capacityMinSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymin)
+        val capacityMaxSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymax)
+        val gammaSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_gamma)
+        val offsetSdrSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr)
+        val offsetHdrSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_offsethdr)
+
+        gainmapMinSeek.setProgress(
+            ((currentMetadata.ratioMin - minRatioMin) / maxRatioMin * maxProgress).toInt())
+        gainmapMaxSeek.setProgress(
+            ((currentMetadata.ratioMax - minRatioMax) / maxRatioMax * maxProgress).toInt())
+        capacityMinSeek.setProgress(
+            ((currentMetadata.capacityMin - minCapacityMin) / maxCapacityMin * maxProgress).toInt())
+        capacityMaxSeek.setProgress(
+            ((currentMetadata.capacityMax - minCapacityMax) / maxCapacityMax * maxProgress).toInt())
+        gammaSeek.setProgress(
+            ((currentMetadata.gamma - minGamma) / maxGamma * maxProgress).toInt())
+        // Log base 3 via: log_b(x) = log_y(x) / log_y(b)
+        offsetSdrSeek.setProgress(
+            ((1.0 - Math.log(currentMetadata.offsetSdr.toDouble() / Math.log(3.0)) / -11.0)
+             .toFloat() * maxProgress).toInt())
+        offsetHdrSeek.setProgress(
+            ((1.0 - Math.log(currentMetadata.offsetHdr.toDouble() / Math.log(3.0)) / -11.0)
+             .toFloat() * maxProgress).toInt())
+
+        parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val)!!.setText(
+            "%.3f".format(currentMetadata.ratioMin))
+        parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val)!!.setText(
+            "%.3f".format(currentMetadata.ratioMax))
+        parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymin_val)!!.setText(
+            "%.3f".format(currentMetadata.capacityMin))
+        parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymax_val)!!.setText(
+            "%.3f".format(currentMetadata.capacityMax))
+        parent.findViewById<TextView>(R.id.gainmap_metadata_gamma_val)!!.setText(
+            "%.3f".format(currentMetadata.gamma))
+        parent.findViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val)!!.setText(
+            "%.5f".format(currentMetadata.offsetSdr))
+        parent.findViewById<TextView>(R.id.gainmap_metadata_offsethdr_val)!!.setText(
+            "%.5f".format(currentMetadata.offsetHdr))
+    }
+
+    private fun resetGainmapMetadata() {
+        currentMetadata = originalMetadata.copy()
+        applyMetadata(currentMetadata)
+        updateMetadataUi()
+    }
+
+    private fun applyMetadata(newMetadata: GainmapMetadata) {
+        gainmap!!.setRatioMin(newMetadata.ratioMin, newMetadata.ratioMin, newMetadata.ratioMin)
+        gainmap!!.setRatioMax(newMetadata.ratioMax, newMetadata.ratioMax, newMetadata.ratioMax)
+        gainmap!!.setMinDisplayRatioForHdrTransition(newMetadata.capacityMin)
+        gainmap!!.setDisplayRatioForFullHdr(newMetadata.capacityMax)
+        gainmap!!.setGamma(newMetadata.gamma, newMetadata.gamma, newMetadata.gamma)
+        gainmap!!.setEpsilonSdr(newMetadata.offsetSdr, newMetadata.offsetSdr, newMetadata.offsetSdr)
+        gainmap!!.setEpsilonHdr(newMetadata.offsetHdr, newMetadata.offsetHdr, newMetadata.offsetHdr)
+        renderView.invalidate()
+    }
+
+    private fun updateGainmapMin(normalized: Float) {
+        val newValue = minRatioMin + normalized * (maxRatioMin - minRatioMin)
+        parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val)!!.setText(
+            "%.3f".format(newValue))
+        currentMetadata.ratioMin = newValue
+        if (showingEdits) {
+            gainmap!!.setRatioMin(newValue, newValue, newValue)
+            renderView.invalidate()
+        }
+    }
+
+    private fun updateGainmapMax(normalized: Float) {
+        val newValue = minRatioMax + normalized * (maxRatioMax - minRatioMax)
+        parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val)!!.setText(
+            "%.3f".format(newValue))
+        currentMetadata.ratioMax = newValue
+        if (showingEdits) {
+            gainmap!!.setRatioMax(newValue, newValue, newValue)
+            renderView.invalidate()
+        }
+    }
+
+    private fun updateCapacityMin(normalized: Float) {
+        val newValue = minCapacityMin + normalized * (maxCapacityMin - minCapacityMin)
+        parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymin_val)!!.setText(
+            "%.3f".format(newValue))
+        currentMetadata.capacityMin = newValue
+        if (showingEdits) {
+            gainmap!!.setMinDisplayRatioForHdrTransition(newValue)
+            renderView.invalidate()
+        }
+    }
+
+    private fun updateCapacityMax(normalized: Float) {
+        val newValue = minCapacityMax + normalized * (maxCapacityMax - minCapacityMax)
+        parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymax_val)!!.setText(
+            "%.3f".format(newValue))
+        currentMetadata.capacityMax = newValue
+        if (showingEdits) {
+            gainmap!!.setDisplayRatioForFullHdr(newValue)
+            renderView.invalidate()
+        }
+    }
+
+    private fun updateGamma(normalized: Float) {
+        val newValue = minGamma + normalized * (maxGamma - minGamma)
+        parent.findViewById<TextView>(R.id.gainmap_metadata_gamma_val)!!.setText(
+            "%.3f".format(newValue))
+        currentMetadata.gamma = newValue
+        if (showingEdits) {
+            gainmap!!.setGamma(newValue, newValue, newValue)
+            renderView.invalidate()
+        }
+    }
+
+    private fun updateOffsetSdr(normalized: Float) {
+        var newValue = 0.0f
+        if (normalized > 0.0f ) {
+            newValue = Math.pow(3.0, (1.0 - normalized.toDouble()) * -11.0).toFloat()
+        }
+        parent.findViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val)!!.setText(
+            "%.5f".format(newValue))
+        currentMetadata.offsetSdr = newValue
+        if (showingEdits) {
+            gainmap!!.setEpsilonSdr(newValue, newValue, newValue)
+            renderView.invalidate()
+        }
+    }
+
+    private fun updateOffsetHdr(normalized: Float) {
+        var newValue = 0.0f
+        if (normalized > 0.0f ) {
+            newValue = Math.pow(3.0, (1.0 - normalized.toDouble()) * -11.0).toFloat()
+        }
+        parent.findViewById<TextView>(R.id.gainmap_metadata_offsethdr_val)!!.setText(
+            "%.5f".format(newValue))
+        currentMetadata.offsetHdr = newValue
+        if (showingEdits) {
+            gainmap!!.setEpsilonHdr(newValue, newValue, newValue)
+            renderView.invalidate()
+        }
+    }
+}