diff --git a/Ravenwood.bp b/Ravenwood.bp
index 1582266203c6a13e6c0a11b4e7434726dcc8d27c..3310898ccfb5d94b5b1e800126f5bd923cb03e35 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -77,10 +77,17 @@ android_ravenwood_libgroup {
         "framework-minus-apex.ravenwood",
         "hoststubgen-helper-runtime.ravenwood",
         "hoststubgen-helper-framework-runtime.ravenwood",
+        "junit",
+        "truth",
+        "ravenwood-junit",
     ],
 }
 
 android_ravenwood_libgroup {
     name: "ravenwood-utils",
-    libs: [],
+    libs: [
+        "junit",
+        "truth",
+        "ravenwood-junit",
+    ],
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
index 5fc77451e832761eac4f97813d2a537ec98399a1..e08200b055d86406bdc3c42418214f2f68359ed2 100644
--- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
@@ -23,7 +23,6 @@ import android.app.AppOpsManager;
 import android.app.AppOpsManager.PackageOps;
 import android.app.IActivityManager;
 import android.app.usage.UsageStatsManager;
-import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -747,10 +746,8 @@ public class AppStateTrackerImpl implements AppStateTracker {
         public void opChanged(int op, int uid, String packageName) throws RemoteException {
             boolean restricted = false;
             try {
-                final AttributionSource attributionSource =
-                        new AttributionSource.Builder(uid).setPackageName(packageName).build();
-                restricted = mAppOpsService.checkOperationWithState(TARGET_OP,
-                        attributionSource.asState()) != AppOpsManager.MODE_ALLOWED;
+                restricted = mAppOpsService.checkOperation(TARGET_OP,
+                        uid, packageName) != AppOpsManager.MODE_ALLOWED;
             } catch (RemoteException e) {
                 // Shouldn't happen
             }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 95f901c4a365e7cb5cd6ef6462f0467ae4db8cd3..b8397d2cd1b47fab0410b7ee7d536648ebc11e98 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -38,7 +38,6 @@ import android.app.tare.EconomyManager;
 import android.app.tare.IEconomyManager;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
-import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -231,11 +230,8 @@ public class InternalResourceService extends SystemService {
         public void opChanged(int op, int uid, String packageName) {
             boolean restricted = false;
             try {
-                final AttributionSource attributionSource = new AttributionSource.Builder(uid)
-                        .setPackageName(packageName)
-                        .build();
-                restricted = mAppOpsService.checkOperationWithState(
-                        AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, attributionSource.asState())
+                restricted = mAppOpsService.checkOperation(
+                        AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName)
                         != AppOpsManager.MODE_ALLOWED;
             } catch (RemoteException e) {
                 // Shouldn't happen
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index ee9c464219d9e1b4ab681ea113ff821b98ff656f..2d235331a672c2587a76bfe7d5f20b7b750f8d85 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -142,13 +142,22 @@ int main(int argc, char** argv)
             case 'p':
                 png = true;
                 break;
-            case 'd':
-                displayIdOpt = DisplayId::fromValue(atoll(optarg));
+            case 'd': {
+                errno = 0;
+                char* end = nullptr;
+                const uint64_t id = strtoull(optarg, &end, 10);
+                if (!end || *end != '\0' || errno == ERANGE) {
+                    fprintf(stderr, "Invalid display ID: Out of range [0, 2^64).\n");
+                    return 1;
+                }
+
+                displayIdOpt = DisplayId::fromValue(id);
                 if (!displayIdOpt) {
-                    fprintf(stderr, "Invalid display ID: %s\n", optarg);
+                    fprintf(stderr, "Invalid display ID: Incorrect encoding.\n");
                     return 1;
                 }
                 break;
+            }
             case '?':
             case 'h':
                 if (ids.size() == 1) {
diff --git a/core/api/current.txt b/core/api/current.txt
index 14869dba96460722fa27b63f57403251bd18b934..323f9e9cd7c6b0e227c1bf1c50bb62d50b963e7c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4464,6 +4464,7 @@ package android.app {
     method public boolean onPreparePanel(int, @Nullable android.view.View, @NonNull android.view.Menu);
     method public void onProvideAssistContent(android.app.assist.AssistContent);
     method public void onProvideAssistData(android.os.Bundle);
+    method public void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
     method public android.net.Uri onProvideReferrer();
     method public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[]);
     method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[], int);
@@ -13973,6 +13974,7 @@ package android.database {
     method public String getColumnName(int);
     method public android.os.Bundle getExtras();
     method public android.net.Uri getNotificationUri();
+    method public java.util.List<android.net.Uri> getNotificationUris();
     method public final int getPosition();
     method public int getType(int);
     method @Deprecated protected Object getUpdatedField(int);
@@ -13998,6 +14000,7 @@ package android.database {
     method public android.os.Bundle respond(android.os.Bundle);
     method public void setExtras(android.os.Bundle);
     method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
+    method public void setNotificationUris(@NonNull android.content.ContentResolver, @NonNull java.util.List<android.net.Uri>);
     method public void unregisterContentObserver(android.database.ContentObserver);
     method public void unregisterDataSetObserver(android.database.DataSetObserver);
     field @Deprecated protected boolean mClosed;
@@ -14129,6 +14132,7 @@ package android.database {
     method public boolean hasNext();
     method public java.util.Iterator<android.database.CursorJoiner.Result> iterator();
     method public android.database.CursorJoiner.Result next();
+    method public void remove();
   }
 
   public enum CursorJoiner.Result {
@@ -14196,6 +14200,7 @@ package android.database {
     method public int getInt(int);
     method public long getLong(int);
     method public android.net.Uri getNotificationUri();
+    method public java.util.List<android.net.Uri> getNotificationUris();
     method public int getPosition();
     method public short getShort(int);
     method public String getString(int);
@@ -14220,6 +14225,7 @@ package android.database {
     method public android.os.Bundle respond(android.os.Bundle);
     method public void setExtras(android.os.Bundle);
     method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
+    method public void setNotificationUris(android.content.ContentResolver, java.util.List<android.net.Uri>);
     method public void unregisterContentObserver(android.database.ContentObserver);
     method public void unregisterDataSetObserver(android.database.DataSetObserver);
   }
@@ -22213,6 +22219,9 @@ package android.media {
     ctor public MediaCodec.CryptoException(int, @Nullable String);
     method @Nullable public android.media.MediaCodec.CryptoInfo getCryptoInfo();
     method public int getErrorCode();
+    method public int getErrorContext();
+    method public int getOemError();
+    method public int getVendorError();
     field @Deprecated public static final int ERROR_FRAME_TOO_LARGE = 8; // 0x8
     field @Deprecated public static final int ERROR_INSUFFICIENT_OUTPUT_PROTECTION = 4; // 0x4
     field @Deprecated public static final int ERROR_INSUFFICIENT_SECURITY = 7; // 0x7
@@ -22725,6 +22734,9 @@ package android.media {
 
   public final class MediaCryptoException extends java.lang.Exception implements android.media.MediaDrmThrowable {
     ctor public MediaCryptoException(@Nullable String);
+    method public int getErrorContext();
+    method public int getOemError();
+    method public int getVendorError();
   }
 
   public abstract class MediaDataSource implements java.io.Closeable {
@@ -22949,6 +22961,9 @@ package android.media {
   public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException implements android.media.MediaDrmThrowable {
     method @NonNull public String getDiagnosticInfo();
     method public int getErrorCode();
+    method public int getErrorContext();
+    method public int getOemError();
+    method public int getVendorError();
     method public boolean isTransient();
   }
 
@@ -23022,6 +23037,9 @@ package android.media {
   public static final class MediaDrm.SessionException extends java.lang.RuntimeException implements android.media.MediaDrmThrowable {
     ctor public MediaDrm.SessionException(int, @Nullable String);
     method @Deprecated public int getErrorCode();
+    method public int getErrorContext();
+    method public int getOemError();
+    method public int getVendorError();
     method public boolean isTransient();
     field @Deprecated public static final int ERROR_RESOURCE_CONTENTION = 1; // 0x1
     field @Deprecated public static final int ERROR_UNKNOWN = 0; // 0x0
@@ -23029,6 +23047,9 @@ package android.media {
 
   public class MediaDrmException extends java.lang.Exception implements android.media.MediaDrmThrowable {
     ctor public MediaDrmException(String);
+    method public int getErrorContext();
+    method public int getOemError();
+    method public int getVendorError();
   }
 
   public class MediaDrmResetException extends java.lang.IllegalStateException implements android.media.MediaDrmThrowable {
@@ -31918,6 +31939,7 @@ package android.opengl {
     method public void surfaceCreated(android.view.SurfaceHolder);
     method public void surfaceDestroyed(android.view.SurfaceHolder);
     method @Deprecated public void surfaceRedrawNeeded(android.view.SurfaceHolder);
+    method public void surfaceRedrawNeededAsync(android.view.SurfaceHolder, Runnable);
     field public static final int DEBUG_CHECK_GL_ERROR = 1; // 0x1
     field public static final int DEBUG_LOG_GL_CALLS = 2; // 0x2
     field public static final int RENDERMODE_CONTINUOUSLY = 1; // 0x1
@@ -47459,6 +47481,7 @@ package android.text {
     method public boolean hasNext();
     method public java.util.Iterator<java.lang.String> iterator();
     method public String next();
+    method public void remove();
     method public void setString(String);
   }
 
@@ -48924,6 +48947,7 @@ package android.util {
     method public void ensureCapacity(int);
     method public java.util.Set<java.util.Map.Entry<K,V>> entrySet();
     method public boolean equals(@Nullable Object);
+    method public void forEach(java.util.function.BiConsumer<? super K,? super V>);
     method public V get(Object);
     method public int hashCode();
     method public int indexOfKey(Object);
@@ -48937,6 +48961,7 @@ package android.util {
     method public V remove(Object);
     method public boolean removeAll(java.util.Collection<?>);
     method public V removeAt(int);
+    method public void replaceAll(java.util.function.BiFunction<? super K,? super V,? extends V>);
     method public boolean retainAll(java.util.Collection<?>);
     method public V setValueAt(int, V);
     method public int size();
@@ -48967,6 +48992,7 @@ package android.util {
     method public boolean removeAll(android.util.ArraySet<? extends E>);
     method public boolean removeAll(java.util.Collection<?>);
     method public E removeAt(int);
+    method public boolean removeIf(java.util.function.Predicate<? super E>);
     method public boolean retainAll(java.util.Collection<?>);
     method public int size();
     method public Object[] toArray();
@@ -52834,6 +52860,7 @@ package android.view {
     method protected void dispatchThawSelfOnly(android.util.SparseArray<android.os.Parcelable>);
     method protected boolean drawChild(@NonNull android.graphics.Canvas, android.view.View, long);
     method public void endViewTransition(android.view.View);
+    method @Nullable public android.window.OnBackInvokedDispatcher findOnBackInvokedDispatcherForChild(@NonNull android.view.View, @NonNull android.view.View);
     method public android.view.View focusSearch(android.view.View, int);
     method public void focusableViewAvailable(android.view.View);
     method protected android.view.ViewGroup.LayoutParams generateDefaultLayoutParams();
@@ -52875,6 +52902,7 @@ package android.view {
     method public void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int);
     method public final void offsetDescendantRectToMyCoords(android.view.View, android.graphics.Rect);
     method public final void offsetRectIntoDescendantCoords(android.view.View, android.graphics.Rect);
+    method @CallSuper public void onDescendantInvalidated(@NonNull android.view.View, @NonNull android.view.View);
     method public boolean onInterceptHoverEvent(android.view.MotionEvent);
     method public boolean onInterceptTouchEvent(android.view.MotionEvent);
     method protected abstract void onLayout(boolean, int, int, int, int);
@@ -55058,12 +55086,14 @@ package android.view.inputmethod {
     method @Nullable public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int);
     method @Nullable public android.os.Handler getHandler();
     method @Nullable public CharSequence getSelectedText(int);
+    method @Nullable public android.view.inputmethod.SurroundingText getSurroundingText(@IntRange(from=0) int, @IntRange(from=0) int, int);
     method @Nullable public CharSequence getTextAfterCursor(@IntRange(from=0) int, int);
     method @Nullable public CharSequence getTextBeforeCursor(@IntRange(from=0) int, int);
     method public boolean performContextMenuAction(int);
     method public boolean performEditorAction(int);
     method public boolean performPrivateCommand(String, android.os.Bundle);
     method public static final void removeComposingSpans(@NonNull android.text.Spannable);
+    method public boolean replaceText(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute);
     method public boolean reportFullscreenMode(boolean);
     method public boolean requestCursorUpdates(int);
     method public boolean sendKeyEvent(android.view.KeyEvent);
@@ -55071,6 +55101,7 @@ package android.view.inputmethod {
     method public static void setComposingSpans(@NonNull android.text.Spannable);
     method public boolean setComposingText(CharSequence, int);
     method public boolean setSelection(int, int);
+    method @Nullable public android.view.inputmethod.TextSnapshot takeSnapshot();
   }
 
   public final class CompletionInfo implements android.os.Parcelable {
@@ -55407,6 +55438,7 @@ package android.view.inputmethod {
     method public boolean commitContent(android.view.inputmethod.InputContentInfo, int, android.os.Bundle);
     method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public boolean commitText(CharSequence, int);
+    method public boolean commitText(@NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute);
     method public boolean deleteSurroundingText(int, int);
     method public boolean deleteSurroundingTextInCodePoints(int, int);
     method public boolean endBatchEdit();
@@ -55415,18 +55447,29 @@ package android.view.inputmethod {
     method public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int);
     method public android.os.Handler getHandler();
     method public CharSequence getSelectedText(int);
+    method @Nullable public android.view.inputmethod.SurroundingText getSurroundingText(int, int, int);
     method @Nullable public CharSequence getTextAfterCursor(@IntRange(from=0) int, int);
     method @Nullable public CharSequence getTextBeforeCursor(@IntRange(from=0) int, int);
     method public boolean performContextMenuAction(int);
     method public boolean performEditorAction(int);
+    method public void performHandwritingGesture(@NonNull android.view.inputmethod.HandwritingGesture, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.IntConsumer);
     method public boolean performPrivateCommand(String, android.os.Bundle);
+    method public boolean performSpellCheck();
+    method public boolean previewHandwritingGesture(@NonNull android.view.inputmethod.PreviewableHandwritingGesture, @Nullable android.os.CancellationSignal);
+    method public boolean replaceText(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute);
     method public boolean reportFullscreenMode(boolean);
     method public boolean requestCursorUpdates(int);
+    method public boolean requestCursorUpdates(int, int);
+    method public void requestTextBoundsInfo(@NonNull android.graphics.RectF, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.inputmethod.TextBoundsInfoResult>);
     method public boolean sendKeyEvent(android.view.KeyEvent);
     method public boolean setComposingRegion(int, int);
+    method public boolean setComposingRegion(int, int, @Nullable android.view.inputmethod.TextAttribute);
     method public boolean setComposingText(CharSequence, int);
+    method public boolean setComposingText(@NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute);
+    method public boolean setImeConsumesInput(boolean);
     method public boolean setSelection(int, int);
     method public void setTarget(android.view.inputmethod.InputConnection);
+    method @Nullable public android.view.inputmethod.TextSnapshot takeSnapshot();
   }
 
   public final class InputContentInfo implements android.os.Parcelable {
@@ -57993,6 +58036,7 @@ package android.widget {
   public abstract class BaseAdapter implements android.widget.ListAdapter android.widget.SpinnerAdapter {
     ctor public BaseAdapter();
     method public boolean areAllItemsEnabled();
+    method public CharSequence[] getAutofillOptions();
     method public android.view.View getDropDownView(int, android.view.View, android.view.ViewGroup);
     method public int getItemViewType(int);
     method public int getViewTypeCount();
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
index 1e6aa498f4176e109364bf03147222fa38248712..483d2a4d26b241e9d7ff5d6c28bdd2184c927530 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -1,4 +1,40 @@
 // Baseline format: 1.0
+BannedThrow: android.database.sqlite.SQLiteRawStatement#bindBlob(int, byte[]):
+    Methods must not throw unchecked exceptions
+BannedThrow: android.database.sqlite.SQLiteRawStatement#bindBlob(int, byte[], int, int):
+    Methods must not throw unchecked exceptions
+BannedThrow: android.database.sqlite.SQLiteRawStatement#bindDouble(int, double):
+    Methods must not throw unchecked exceptions
+BannedThrow: android.database.sqlite.SQLiteRawStatement#bindInt(int, int):
+    Methods must not throw unchecked exceptions
+BannedThrow: android.database.sqlite.SQLiteRawStatement#bindLong(int, long):
+    Methods must not throw unchecked exceptions
+BannedThrow: android.database.sqlite.SQLiteRawStatement#bindNull(int):
+    Methods must not throw unchecked exceptions
+BannedThrow: android.database.sqlite.SQLiteRawStatement#bindText(int, String):
+    Methods must not throw unchecked exceptions
+BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnBlob(int):
+    Methods must not throw unchecked exceptions
+BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnDouble(int):
+    Methods must not throw unchecked exceptions
+BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnInt(int):
+    Methods must not throw unchecked exceptions
+BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnLength(int):
+    Methods must not throw unchecked exceptions
+BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnLong(int):
+    Methods must not throw unchecked exceptions
+BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnName(int):
+    Methods must not throw unchecked exceptions
+BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnText(int):
+    Methods must not throw unchecked exceptions
+BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnType(int):
+    Methods must not throw unchecked exceptions
+BannedThrow: android.database.sqlite.SQLiteRawStatement#readColumnBlob(int, byte[], int, int, int):
+    Methods must not throw unchecked exceptions
+BannedThrow: android.database.sqlite.SQLiteRawStatement#step():
+    Methods must not throw unchecked exceptions
+
+
 BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED:
     Field 'ACTION_NEXT_ALARM_CLOCK_CHANGED' is missing @BroadcastBehavior
 BroadcastBehavior: android.app.AlarmManager#ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED:
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 07374963bea05cc45f60536f93f14b0683956287..df466ab9b5bbe30bbc047f8c3e26ec4633394e2a 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -144,7 +144,6 @@ package android.content.pm {
   }
 
   public abstract class PackageManager {
-    method @NonNull public String getPermissionControllerPackageName();
     method @NonNull public String getSdkSandboxPackageName();
     method @RequiresPermission(android.Manifest.permission.MAKE_UID_VISIBLE) public void makeUidVisible(int, int);
     field public static final String EXTRA_VERIFICATION_ROOT_HASH = "android.content.pm.extra.VERIFICATION_ROOT_HASH";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 739fdc59ed5d92dff52eff327df5a50c37bae0e1..eefa36eb4f2d0bbbe3ab92b1e078b3b6ac0cc7b2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3916,6 +3916,7 @@ package android.content.pm {
     method @Deprecated @NonNull public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(@NonNull String);
     method @Deprecated @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public int getPackageUidAsUser(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public String getPermissionControllerPackageName();
     method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] getUnsuspendablePackages(@NonNull String[]);
     method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
@@ -9733,6 +9734,7 @@ package android.nfc.cardemulation {
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getUid();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean hasCategory(@NonNull String);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean isOnHost();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean isOtherServiceEnabled();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadAppLabel(@NonNull android.content.pm.PackageManager);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadBanner(@NonNull android.content.pm.PackageManager);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager);
@@ -9743,6 +9745,7 @@ package android.nfc.cardemulation {
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void resetOffHostSecureElement();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setDynamicAidGroup(@NonNull android.nfc.cardemulation.AidGroup);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setOffHostSecureElement(@NonNull String);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setOtherServiceEnabled(boolean);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeToParcel(@NonNull android.os.Parcel, int);
     field @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.ApduServiceInfo> CREATOR;
   }
@@ -10225,6 +10228,7 @@ package android.os {
     ctor public ParcelableHolder(int);
     method public int describeContents();
     method @Nullable public <T extends android.os.Parcelable> T getParcelable(@NonNull Class<T>);
+    method public int getStability();
     method public void readFromParcel(@NonNull android.os.Parcel);
     method public void setParcelable(@Nullable android.os.Parcelable);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 559db514dabf29c4fd99450903e52bbac1c13ceb..3a91e25de9e6d9a6ee5c090d4f2278fa9753397c 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -1,4 +1,8 @@
 // Baseline format: 1.0
+BannedThrow: android.os.vibrator.persistence.VibrationXmlSerializer#serialize(android.os.VibrationEffect, java.io.Writer):
+    Methods must not throw unchecked exceptions
+
+
 BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED:
     Field 'ACTION_NEXT_ALARM_CLOCK_CHANGED' is missing @BroadcastBehavior
 BroadcastBehavior: android.app.AlarmManager#ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED:
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index a99dfa6054079a4909ec1aeb713223220b521dcd..3d54449971077fd5904ba73f072d28ae4d77ce59 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -8357,9 +8357,7 @@ public class AppOpsManager {
      */
     public int unsafeCheckOpRawNoThrow(int op, int uid, @NonNull String packageName) {
         try {
-            final AttributionSource attributionSource =
-                    new AttributionSource.Builder(uid).setPackageName(packageName).build();
-            return mService.checkOperationWithStateRaw(op, attributionSource.asState());
+            return mService.checkOperationRaw(op, uid, packageName, null);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -8522,12 +8520,7 @@ public class AppOpsManager {
                 }
             }
 
-            final AttributionSource attributionSource =
-                    new AttributionSource.Builder(uid)
-                            .setPackageName(packageName)
-                            .setAttributionTag(attributionTag)
-                            .build();
-            SyncNotedAppOp syncOp = mService.noteOperationWithState(op, attributionSource.asState(),
+            SyncNotedAppOp syncOp = mService.noteOperation(op, uid, packageName, attributionTag,
                     collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
 
             if (syncOp.getOpMode() == MODE_ALLOWED) {
@@ -8767,9 +8760,7 @@ public class AppOpsManager {
     @UnsupportedAppUsage
     public int checkOp(int op, int uid, String packageName) {
         try {
-            final AttributionSource attributionSource =
-                    new AttributionSource.Builder(uid).setPackageName(packageName).build();
-            int mode = mService.checkOperationWithState(op, attributionSource.asState());
+            int mode = mService.checkOperation(op, uid, packageName);
             if (mode == MODE_ERRORED) {
                 throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
             }
@@ -8790,9 +8781,7 @@ public class AppOpsManager {
     @UnsupportedAppUsage
     public int checkOpNoThrow(int op, int uid, String packageName) {
         try {
-            final AttributionSource attributionSource =
-                    new AttributionSource.Builder(uid).setPackageName(packageName).build();
-            int mode = mService.checkOperationWithState(op, attributionSource.asState());
+            int mode = mService.checkOperation(op, uid, packageName);
             return mode == AppOpsManager.MODE_FOREGROUND ? AppOpsManager.MODE_ALLOWED : mode;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -9037,14 +9026,8 @@ public class AppOpsManager {
                 }
             }
 
-            final AttributionSource attributionSource =
-                    new AttributionSource.Builder(uid)
-                            .setPackageName(packageName)
-                            .setAttributionTag(attributionTag)
-                            .build();
-            SyncNotedAppOp syncOp = mService.startOperationWithState(token, op,
-                    attributionSource.asState(), startIfModeDefault,
-                    collectionMode == COLLECT_ASYNC, message,
+            SyncNotedAppOp syncOp = mService.startOperation(token, op, uid, packageName,
+                    attributionTag, startIfModeDefault, collectionMode == COLLECT_ASYNC, message,
                     shouldCollectMessage, attributionFlags, attributionChainId);
 
             if (syncOp.getOpMode() == MODE_ALLOWED) {
@@ -9257,12 +9240,7 @@ public class AppOpsManager {
     public void finishOp(IBinder token, int op, int uid, @NonNull String packageName,
             @Nullable String attributionTag) {
         try {
-            final AttributionSource attributionSource =
-                    new AttributionSource.Builder(uid)
-                            .setPackageName(packageName)
-                            .setAttributionTag(attributionTag)
-                            .build();
-            mService.finishOperationWithState(token, op, attributionSource.asState());
+            mService.finishOperation(token, op, uid, packageName, attributionTag);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
index a3de8faa12734f1880e29ee9e23ca1a33472c607..43023fe9c2abb23c982652ad4fd158ad26646d89 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -26,12 +26,11 @@ import android.util.SparseArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.util.function.HeptFunction;
 import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.NonaFunction;
 import com.android.internal.util.function.QuadFunction;
+import com.android.internal.util.function.QuintConsumer;
 import com.android.internal.util.function.QuintFunction;
-import com.android.internal.util.function.TriConsumer;
-import com.android.internal.util.function.TriFunction;
 import com.android.internal.util.function.UndecFunction;
 
 /**
@@ -46,13 +45,15 @@ public abstract class AppOpsManagerInternal {
          * Allows overriding check operation behavior.
          *
          * @param code The op code to check.
-         * @param attributionSource the {@link AttributionSource} responsible for data access
+         * @param uid The UID for which to check.
+         * @param packageName The package for which to check.
+         * @param attributionTag The attribution tag for which to check.
          * @param raw Whether to check the raw op i.e. not interpret the mode based on UID state.
          * @param superImpl The super implementation.
          * @return The app op check result.
          */
-        int checkOperation(int code, AttributionSource attributionSource,
-                boolean raw, TriFunction<Integer, AttributionSource, Boolean, Integer>
+        int checkOperation(int code, int uid, String packageName, @Nullable String attributionTag,
+                boolean raw, QuintFunction<Integer, Integer, String, String, Boolean, Integer>
                 superImpl);
 
         /**
@@ -72,23 +73,25 @@ public abstract class AppOpsManagerInternal {
          * Allows overriding note operation behavior.
          *
          * @param code The op code to note.
-         * @param attributionSource the {@link AttributionSource} responsible for data access
+         * @param uid The UID for which to note.
+         * @param packageName The package for which to note. {@code null} for system package.
+         * @param featureId Id of the feature in the package
          * @param shouldCollectAsyncNotedOp If an {@link AsyncNotedAppOp} should be collected
          * @param message The message in the async noted op
          * @param superImpl The super implementation.
          * @return The app op note result.
          */
-        SyncNotedAppOp noteOperation(int code, AttributionSource attributionSource,
-                boolean shouldCollectAsyncNotedOp,
+        SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
+                @Nullable String featureId, boolean shouldCollectAsyncNotedOp,
                 @Nullable String message, boolean shouldCollectMessage,
-                @NonNull QuintFunction<Integer, AttributionSource, Boolean, String, Boolean,
+                @NonNull HeptFunction<Integer, Integer, String, String, Boolean, String, Boolean,
                         SyncNotedAppOp> superImpl);
 
         /**
          * Allows overriding note proxy operation behavior.
          *
          * @param code The op code to note.
-         * @param attributionSource the {@link AttributionSource} responsible for data access
+         * @param attributionSource The permission identity of the caller.
          * @param shouldCollectAsyncNotedOp If an {@link AsyncNotedAppOp} should be collected
          * @param message The message in the async noted op
          * @param shouldCollectMessage whether to collect messages
@@ -107,7 +110,9 @@ public abstract class AppOpsManagerInternal {
          *
          * @param token The client state.
          * @param code The op code to start.
-         * @param attributionSource the {@link AttributionSource} responsible for data access
+         * @param uid The UID for which to note.
+         * @param packageName The package for which to note. {@code null} for system package.
+         * @param attributionTag the attribution tag.
          * @param startIfModeDefault Whether to start the op of the mode is default.
          * @param shouldCollectAsyncNotedOp If an {@link AsyncNotedAppOp} should be collected
          * @param message The message in the async noted op
@@ -117,12 +122,12 @@ public abstract class AppOpsManagerInternal {
          * @param superImpl The super implementation.
          * @return The app op note result.
          */
-        SyncNotedAppOp startOperation(IBinder token, int code,
-                AttributionSource attributionSource,
+        SyncNotedAppOp startOperation(IBinder token, int code, int uid,
+                @Nullable String packageName, @Nullable String attributionTag,
                 boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
                 @Nullable String message, boolean shouldCollectMessage,
                 @AttributionFlags int attributionFlags, int attributionChainId,
-                @NonNull NonaFunction<IBinder, Integer, AttributionSource, Boolean,
+                @NonNull UndecFunction<IBinder, Integer, Integer, String, String, Boolean,
                         Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl);
 
         /**
@@ -130,7 +135,7 @@ public abstract class AppOpsManagerInternal {
          *
          * @param clientId The client calling start, represented by an IBinder
          * @param code The op code to start.
-         * @param attributionSource the {@link AttributionSource} responsible for data access
+         * @param attributionSource The permission identity of the caller.
          * @param startIfModeDefault Whether to start the op of the mode is default.
          * @param shouldCollectAsyncNotedOp If an {@link AsyncNotedAppOp} should be collected
          * @param message The message in the async noted op
@@ -156,19 +161,21 @@ public abstract class AppOpsManagerInternal {
          *
          * @param clientId The client state.
          * @param code The op code to finish.
-         * @param attributionSource the {@link AttributionSource} responsible for data access
+         * @param uid The UID for which the op was noted.
+         * @param packageName The package for which it was noted. {@code null} for system package.
+         * @param attributionTag the attribution tag.
          */
-        default void finishOperation(IBinder clientId, int code,
-                AttributionSource attributionSource,
-                @NonNull TriConsumer<IBinder, Integer, AttributionSource> superImpl) {
-            superImpl.accept(clientId, code, attributionSource);
+        default void finishOperation(IBinder clientId, int code, int uid, String packageName,
+                String attributionTag,
+                @NonNull QuintConsumer<IBinder, Integer, Integer, String, String> superImpl) {
+            superImpl.accept(clientId, code, uid, packageName, attributionTag);
         }
 
         /**
          * Allows overriding finish proxy op.
          *
          * @param code The op code to finish.
-         * @param attributionSource the {@link AttributionSource} responsible for data access
+         * @param attributionSource The permission identity of the caller.
          * @param skipProxyOperation Whether to skip the proxy in the proxy/proxied operation
          * @param clientId The client calling finishProxyOperation
          * @param superImpl The "standard" implementation to potentially call
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 1f8784bf72dd4124097bb475af6c712baff27151..df6badcffe8e9a0b2f254843d48946fc8ee9ef76 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -269,7 +269,10 @@ interface IActivityTaskManager {
      * task snapshot cache only if requested.
      *
      * @param taskId the id of the task to take a snapshot of
-     * @param updateCache whether to store the new snapshot in the system's task snapshot cache
+     * @param updateCache Whether to store the new snapshot in the system's task snapshot cache.
+     *                    If it is true, the snapshot can be either real content or app-theme mode
+     *                    depending on the attributes of app. Otherwise, the snapshot will be taken
+     *                    with real content.
      * @return a graphic buffer representing a screenshot of a task
      */
     android.window.TaskSnapshot takeTaskSnapshot(int taskId, boolean updateCache);
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 27f6a266597cd4a502a918a3b4fd3b8b509fef9c..ec181dac6b36b845d4e1fff3d13a3fcb93e9a86d 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -155,6 +155,22 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW
         mInteractionHandler = getHandler(handler);
     }
 
+    /**
+     * @hide
+     */
+    public static class AdapterChildHostView extends AppWidgetHostView {
+
+        public AdapterChildHostView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public Context getRemoteContextEnsuringCorrectCachedApkPath() {
+            // To reduce noise in error messages
+            return null;
+        }
+    }
+
     /**
      * Set the AppWidget that will be displayed by this view. This method also adds default padding
      * to widgets, as described in {@link #getDefaultPaddingForWidget(Context, ComponentName, Rect)}
@@ -921,17 +937,31 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW
         setColorResources(RemoteViews.ColorResources.create(mContext, colorMapping));
     }
 
+    private void setColorResourcesStates(RemoteViews.ColorResources colorResources) {
+        mColorResources = colorResources;
+        mColorMappingChanged = true;
+        mViewMode = VIEW_MODE_NOINIT;
+    }
+
     /** @hide **/
     public void setColorResources(RemoteViews.ColorResources colorResources) {
         if (colorResources == mColorResources) {
             return;
         }
-        mColorResources = colorResources;
-        mColorMappingChanged = true;
-        mViewMode = VIEW_MODE_NOINIT;
+        setColorResourcesStates(colorResources);
         reapplyLastRemoteViews();
     }
 
+    /**
+     * @hide
+     */
+    public void setColorResourcesNoReapply(RemoteViews.ColorResources colorResources) {
+        if (colorResources == mColorResources) {
+            return;
+        }
+        setColorResourcesStates(colorResources);
+    }
+
     /** Check if, in the current context, the two color mappings are equivalent. */
     private boolean isSameColorMapping(SparseIntArray oldColors, SparseIntArray newColors) {
         if (oldColors.size() != newColors.size()) {
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index c2bc974a42ae5d0a06b923557a053783523ba351..4b2cee698df21902a915feb6066a5bfdc4a8f35e 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -234,12 +234,6 @@ public final class AttributionSource implements Parcelable {
         return withToken(sDefaultToken);
     }
 
-    /** @hide */
-    public AttributionSource withUid(int uid) {
-        return new AttributionSource(uid, getPid(), getPackageName(), getAttributionTag(),
-                getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext());
-    }
-
     /** @hide */
     public AttributionSource withPid(int pid) {
         return new AttributionSource(getUid(), pid, getPackageName(), getAttributionTag(),
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index e2a5747a4bcb21ea286449d52d58f74a3ca3b01d..72e1066395448fea597c723d098f2660b542dfec 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -6279,9 +6279,9 @@ public abstract class PackageManager {
      * @hide
      */
     @NonNull
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @SystemApi
     @TestApi
-    @UnsupportedAppUsage
+    @SuppressLint("UnflaggedApi") // Promoting from @SystemApi(MODULE_LIBRARIES)
     public String getPermissionControllerPackageName() {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
     }
diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
index 6b78d05c848d9fd4cbae3542ff6225c5132ade03..5c5083a691be256a4420667a5670fb125a6e8761 100644
--- a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
@@ -6,3 +6,10 @@ flag {
     description: "Enable USB data compliance warnings when set"
     bug: "296119135"
 }
+
+flag {
+    name: "enable_input_power_limited_warning"
+    namespace: "system_sw_usb"
+    description: "Flag incompatible charging on COMPLIANCE_WARNING_INPUT_POWER_LIMITED instead of COMPLIANCE_WARNING_OTHER when enabled"
+    bug: "308700954"
+}
diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java
index dbb312720373217b6647d3445d7a3c0c5c459473..19ba6a1d22ede4647579630ce58ae9e601809cdb 100644
--- a/core/java/android/net/NetworkStack.java
+++ b/core/java/android/net/NetworkStack.java
@@ -23,6 +23,7 @@ import android.content.Context;
 import android.os.IBinder;
 import android.os.ServiceManager;
 
+import com.android.net.flags.Flags;
 import com.android.net.module.util.PermissionUtils;
 /**
  * Constants and utilities for client code communicating with the network stack service.
@@ -103,4 +104,16 @@ public class NetworkStack {
             final @NonNull String... otherPermissions) {
         PermissionUtils.enforceNetworkStackPermissionOr(context, otherPermissions);
     }
+
+    /**
+     * Get setting of the "set_data_saver_via_cm" flag.
+     *
+     * @hide
+     */
+    // A workaround for aconfig. Currently, aconfig value read from platform and mainline code can
+    // be inconsistent. To avoid the problem, CTS for mainline code can get the flag value by this
+    // method.
+    public static boolean getDataSaverViaCmFlag() {
+        return Flags.setDataSaverViaCm();
+    }
 }
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index 9cf8c4ddc53bdb14d39a630a0d46ad56c776e594..597c948bd51516cf2323883f52549ab6813684ab 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -129,7 +129,7 @@ public final class ApduServiceInfo implements Parcelable {
     /**
      * State of the service for CATEGORY_OTHER selection
      */
-    private boolean mOtherServiceSelectionState;
+    private boolean mOtherServiceEnabled;
 
     /**
      * @hide
@@ -150,10 +150,10 @@ public final class ApduServiceInfo implements Parcelable {
             List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
             boolean requiresUnlock, int bannerResource, int uid,
             String settingsActivityName, String offHost, String staticOffHost,
-            boolean isSelected) {
+            boolean isEnabled) {
         this(info, onHost, description, staticAidGroups, dynamicAidGroups,
                 requiresUnlock, onHost ? true : false, bannerResource, uid,
-                settingsActivityName, offHost, staticOffHost, isSelected);
+                settingsActivityName, offHost, staticOffHost, isEnabled);
     }
 
     /**
@@ -162,7 +162,7 @@ public final class ApduServiceInfo implements Parcelable {
     public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
             List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
             boolean requiresUnlock, boolean requiresScreenOn, int bannerResource, int uid,
-            String settingsActivityName, String offHost, String staticOffHost, boolean isSelected) {
+            String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled) {
         this.mService = info;
         this.mDescription = description;
         this.mStaticAidGroups = new HashMap<String, AidGroup>();
@@ -181,7 +181,7 @@ public final class ApduServiceInfo implements Parcelable {
         this.mBannerResourceId = bannerResource;
         this.mUid = uid;
         this.mSettingsActivityName = settingsActivityName;
-        this.mOtherServiceSelectionState = isSelected;
+        this.mOtherServiceEnabled = isEnabled;
 
     }
 
@@ -372,7 +372,7 @@ public final class ApduServiceInfo implements Parcelable {
         // Set uid
         mUid = si.applicationInfo.uid;
 
-        mOtherServiceSelectionState = false;    // support other category
+        mOtherServiceEnabled = false;    // support other category
 
     }
 
@@ -744,7 +744,7 @@ public final class ApduServiceInfo implements Parcelable {
         dest.writeInt(mUid);
         dest.writeString(mSettingsActivityName);
 
-        dest.writeInt(mOtherServiceSelectionState ? 1 : 0);
+        dest.writeInt(mOtherServiceEnabled ? 1 : 0);
     };
 
     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
@@ -772,11 +772,11 @@ public final class ApduServiceInfo implements Parcelable {
                     int bannerResource = source.readInt();
                     int uid = source.readInt();
                     String settingsActivityName = source.readString();
-                    boolean isSelected = source.readInt() != 0;
+                    boolean isEnabled = source.readInt() != 0;
                     return new ApduServiceInfo(info, onHost, description, staticAidGroups,
                             dynamicAidGroups, requiresUnlock, requiresScreenOn, bannerResource, uid,
                             settingsActivityName, offHostName, staticOffHostName,
-                            isSelected);
+                            isEnabled);
                 }
 
                 @Override
@@ -807,7 +807,7 @@ public final class ApduServiceInfo implements Parcelable {
         pw.println("    Static AID groups:");
         for (AidGroup group : mStaticAidGroups.values()) {
             pw.println("        Category: " + group.getCategory()
-                    + "(selected: " + mOtherServiceSelectionState + ")");
+                    + "(enabled: " + mOtherServiceEnabled + ")");
             for (String aid : group.getAids()) {
                 pw.println("            AID: " + aid);
             }
@@ -815,7 +815,7 @@ public final class ApduServiceInfo implements Parcelable {
         pw.println("    Dynamic AID groups:");
         for (AidGroup group : mDynamicAidGroups.values()) {
             pw.println("        Category: " + group.getCategory()
-                    + "(selected: " + mOtherServiceSelectionState + ")");
+                    + "(enabled: " + mOtherServiceEnabled + ")");
             for (String aid : group.getAids()) {
                 pw.println("            AID: " + aid);
             }
@@ -827,18 +827,24 @@ public final class ApduServiceInfo implements Parcelable {
 
 
     /**
-     * @hide
+     * Enable or disable this CATEGORY_OTHER service.
+     *
+     * @param enabled true to indicate if user has enabled this service
      */
-    public void setOtherServiceState(boolean selected) {
-        mOtherServiceSelectionState = selected;
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void setOtherServiceEnabled(boolean enabled) {
+        mOtherServiceEnabled = enabled;
     }
 
 
     /**
-     * @hide
+     * Returns whether this CATEGORY_OTHER service is enabled or not.
+     *
+     * @return true to indicate if user has enabled this service
      */
-    public boolean isSelectedOtherService() {
-        return mOtherServiceSelectionState;
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public boolean isOtherServiceEnabled() {
+        return mOtherServiceEnabled;
     }
 
     /**
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 3db1cb0500f97f1cbf71a7104a57bfe11831c99a..995622004fa6b7e6fdc5cd0c823c00ece131ea27 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -333,6 +333,12 @@ public class Binder implements IBinder {
     @CriticalNative
     public static final native boolean isDirectlyHandlingTransactionNative();
 
+    /** @hide */
+    public static final boolean isDirectlyHandlingTransactionNative$ravenwood() {
+        // Ravenwood doesn't support IPC
+        return false;
+    }
+
     private static boolean sIsHandlingBinderTransaction = false;
 
     /**
@@ -715,7 +721,9 @@ public class Binder implements IBinder {
      */
     public Binder(@Nullable String descriptor) {
         mObject = getNativeBBinderHolder();
-        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mObject);
+        if (mObject != 0L) {
+            NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mObject);
+        }
 
         if (FIND_POTENTIAL_LEAKS) {
             final Class<? extends Binder> klass = getClass();
@@ -1277,6 +1285,10 @@ public class Binder implements IBinder {
 
     private static native long getNativeBBinderHolder();
 
+    private static long getNativeBBinderHolder$ravenwood() {
+        return 0L;
+    }
+
     /**
      * By default, we use the calling UID since we can always trust it.
      */
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 352c9d2ba81f1186a389a57a7e7325c82e4bfe22..86628d95b25ae398ac29b58200d827307c655d8c 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1558,7 +1558,7 @@ public final class Parcel {
         ensureWithinMemoryLimit(typeSize, totalObjects);
     }
 
-    private void ensureWithinMemoryLimit(int typeSize, @NonNull int length) {
+    private void ensureWithinMemoryLimit(int typeSize, int length) {
         int estimatedAllocationSize = 0;
         try {
             estimatedAllocationSize = Math.multiplyExact(typeSize, length);
@@ -2956,6 +2956,14 @@ public final class Parcel {
         }
     }
 
+    /** @hide */
+    public final void writeException$ravenwood(@NonNull Exception e) {
+        // Ravenwood doesn't support IPC, no transaction headers needed
+        writeInt(getExceptionCode(e));
+        writeString(e.getMessage());
+        writeInt(0);
+    }
+
     /** @hide */
     public static int getExceptionCode(@NonNull Throwable e) {
         int code = 0;
@@ -3039,6 +3047,12 @@ public final class Parcel {
         }
     }
 
+    /** @hide */
+    public final void writeNoException$ravenwood() {
+        // Ravenwood doesn't support IPC, no transaction headers needed
+        writeInt(0);
+    }
+
     /**
      * Special function for reading an exception result from the header of
      * a parcel, to be used after receiving the result of a transaction.  This
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 0133bd8221828a1130b0f14a71791d7c59d7de05..9bdd0c2db7795d7572da66b72de2838023d48da6 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -36,3 +36,10 @@ flag {
     description: "Collect sepolicy hash from sysfs"
     bug: "308471499"
 }
+
+flag {
+    name: "extend_ecm_to_all_settings"
+    namespace: "responsible_apis"
+    description: "Allow all app settings to be restrictable via configuration"
+    bug: "297372999"
+}
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 0d5704eed4b300ffe214eb67af99b754a9d1803a..1acc384b18dac1fbbde209ddfe2d5c81ed082ee6 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -18,6 +18,7 @@ package android.view;
 
 import static android.view.InsetsSourceProto.FRAME;
 import static android.view.InsetsSourceProto.TYPE;
+import static android.view.InsetsSourceProto.TYPE_NUMBER;
 import static android.view.InsetsSourceProto.VISIBLE;
 import static android.view.InsetsSourceProto.VISIBLE_FRAME;
 import static android.view.WindowInsets.Type.captionBar;
@@ -352,6 +353,7 @@ public class InsetsSource implements Parcelable {
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         proto.write(TYPE, WindowInsets.Type.toString(mType));
+        proto.write(TYPE_NUMBER, mType);
         mFrame.dumpDebug(proto, FRAME);
         if (mVisibleFrame != null) {
             mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME);
diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS
index 7f0a651c6420b7720941c454938dbe56f29b183a..e20357fa2dd9abe0132accf7d47aa34b629a07ff 100644
--- a/core/java/android/widget/OWNERS
+++ b/core/java/android/widget/OWNERS
@@ -12,6 +12,6 @@ per-file TextView*,Edit*,Selection* = file:../text/OWNERS
 
 per-file SpellChecker.java = file:../view/inputmethod/OWNERS
 
-per-file RemoteViews* = file:../appwidget/OWNERS
+per-file Remote* = file:../appwidget/OWNERS
 
 per-file Toast.java = juliacr@google.com, jeffdq@google.com
diff --git a/core/java/android/widget/RemoteCollectionItemsAdapter.java b/core/java/android/widget/RemoteCollectionItemsAdapter.java
index d84330828bf7238ba4a6f0d5cda70c1ae83de3de..9b396aeea9eb2c5fd69cf6ec33245fb94284def9 100644
--- a/core/java/android/widget/RemoteCollectionItemsAdapter.java
+++ b/core/java/android/widget/RemoteCollectionItemsAdapter.java
@@ -18,6 +18,7 @@ package android.widget;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.appwidget.AppWidgetHostView;
 import android.util.SparseIntArray;
 import android.view.View;
 import android.view.ViewGroup;
@@ -25,8 +26,6 @@ import android.widget.RemoteViews.ColorResources;
 import android.widget.RemoteViews.InteractionHandler;
 import android.widget.RemoteViews.RemoteCollectionItems;
 
-import com.android.internal.R;
-
 import java.util.stream.IntStream;
 
 /**
@@ -178,40 +177,14 @@ class RemoteCollectionItemsAdapter extends BaseAdapter {
 
         RemoteViews item = mItems.getItemView(position);
         item.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
-        View reapplyView = getViewToReapply(convertView, item);
-
-        // Reapply the RemoteViews if we can.
-        if (reapplyView != null) {
-            try {
-                item.reapply(
-                        parent.getContext(),
-                        reapplyView,
-                        mInteractionHandler,
-                        null /* size */,
-                        mColorResources);
-                return reapplyView;
-            } catch (RuntimeException e) {
-                // We can't reapply for some reason, we'll fallback to an apply and inflate a
-                // new view.
-            }
-        }
-
-        return item.apply(
-                parent.getContext(),
-                parent,
-                mInteractionHandler,
-                null /* size */,
-                mColorResources);
-    }
-
-    /** Returns {@code convertView} if it can be used to reapply {@code item}, or null otherwise. */
-    @Nullable
-    private static View getViewToReapply(@Nullable View convertView, @NonNull RemoteViews item) {
-        if (convertView == null) return null;
-
-        Object layoutIdTag = convertView.getTag(R.id.widget_frame);
-        if (!(layoutIdTag instanceof Integer)) return null;
 
-        return item.getLayoutId() == (Integer) layoutIdTag ? convertView : null;
+        AppWidgetHostView newView = convertView instanceof AppWidgetHostView.AdapterChildHostView
+                widgetChildView
+                ? widgetChildView
+                : new AppWidgetHostView.AdapterChildHostView(parent.getContext());
+        newView.setInteractionHandler(mInteractionHandler);
+        newView.setColorResourcesNoReapply(mColorResources);
+        newView.updateAppWidget(item);
+        return newView;
     }
 }
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 403b403be9612c807a11073e77e81b6175ba3915..a919c0079cea1b39b4f8095de770b8679e13a9d1 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -6958,13 +6958,13 @@ public class RemoteViews implements Parcelable, Filter {
             View parent = (View) view.getParent();
             // Break the for loop on the first encounter of:
             //    1) an AdapterView,
-            //    2) an AppWidgetHostView that is not a RemoteViewsFrameLayout, or
+            //    2) an AppWidgetHostView that is not a child of an adapter view, or
             //    3) a null parent.
             // 2) and 3) are unexpected and catch the case where a child is not
             // correctly parented in an AdapterView.
             while (parent != null && !(parent instanceof AdapterView<?>)
                     && !((parent instanceof AppWidgetHostView)
-                    && !(parent instanceof RemoteViewsAdapter.RemoteViewsFrameLayout))) {
+                            && !(parent instanceof AppWidgetHostView.AdapterChildHostView))) {
                 parent = (View) parent.getParent();
             }
 
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 61a7599e8f730fc415dea4c8a40de0c1c0d89a6e..2f28a8704cd3d5d2e18b08d91fa8b6bf363d6efd 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -368,7 +368,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
      * A FrameLayout which contains a loading view, and manages the re/applying of RemoteViews when
      * they are loaded.
      */
-    static class RemoteViewsFrameLayout extends AppWidgetHostView {
+    static class RemoteViewsFrameLayout extends AppWidgetHostView.AdapterChildHostView {
         private final FixedSizeRemoteViewsCache mCache;
 
         public int cacheIndex = -1;
@@ -407,11 +407,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
             return loadingTextView;
         }
 
-        @Override
-        protected Context getRemoteContextEnsuringCorrectCachedApkPath() {
-            return null;
-        }
-
         @Override
         protected View getErrorView() {
             // Use the default loading view as the error view.
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 24dc6dbfede846d968c543f2061fe57c9ba0502a..0ad6c99422b81a6cb904c0dbdfcb3c603e6cbdb2 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -29,3 +29,10 @@ flag {
     description: "Whether to dispatch window resize through ClientTransaction is enabled"
     bug: "301870955"
 }
+
+flag {
+    namespace: "windowing_sdk"
+    name: "fullscreen_dim_flag"
+    description: "Whether to allow showing fullscreen dim on ActivityEmbedding split"
+    bug: "253533308"
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 1b05982a7569d16a15bc25ce4bbf321489769568..492e2ac7cc288f3f10e9f4a37b56a2be954f59e0 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -42,22 +42,18 @@ import com.android.internal.app.MessageSamplingConfig;
 // frameworks/native/libs/permission/include/binder/IAppOpsService.h must match the order here.
 // Please be careful to respect both these issues when modifying this file.
 interface IAppOpsService {
-    // Deprecated, use checkOperationWithState instead.
+    // These methods are also called by native code, so please be careful that the number in
+    // frameworks/native/libs/permission/include/binder/IAppOpsService.h matches the ordering here.
     int checkOperation(int code, int uid, String packageName);
-    // Deprecated, use noteOperationWithState instead.
     SyncNotedAppOp noteOperation(int code, int uid, String packageName, @nullable String attributionTag,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage);
-    // Deprecated, use startOperationWithState instead.
     SyncNotedAppOp startOperation(IBinder clientId, int code, int uid, String packageName,
             @nullable String attributionTag, boolean startIfModeDefault,
             boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
             int attributionFlags, int attributionChainId);
-    // Deprecated, use finishOperationWithState instead.
     @UnsupportedAppUsage
     void finishOperation(IBinder clientId, int code, int uid, String packageName,
             @nullable String attributionTag);
-    // These methods are also called by native code, so please be careful that the number in
-    // frameworks/native/libs/permission/include/binder/IAppOpsService.h matches the ordering here.
     void startWatchingMode(int op, String packageName, IAppOpsCallback callback);
     void stopWatchingMode(IAppOpsCallback callback);
     int permissionToOpCode(String permission);
@@ -138,33 +134,20 @@ interface IAppOpsService {
     void stopWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback);
     List<AsyncNotedAppOp> extractAsyncOps(String packageName);
 
-    // Deprecated, use checkOperationWithStateRaw instead.
     int checkOperationRaw(int code, int uid, String packageName, @nullable String attributionTag);
     void reloadNonHistoricalState();
 
     void collectNoteOpCallsForValidation(String stackTrace, int op, String packageName, long version);
-    // These methods are also called by native code, so please be careful that the number in
-    // frameworks/native/libs/permission/include/binder/IAppOpsService.h matches the ordering here.
-    int checkOperationWithState(int code, in AttributionSourceState attributionSourceState);
-    int checkOperationWithStateRaw(int code, in AttributionSourceState attributionSourceState);
-    SyncNotedAppOp noteOperationWithState(int code, in AttributionSourceState attributionSourceState,
-            boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage);
-    SyncNotedAppOp startOperationWithState(IBinder clientId, int code,
-            in AttributionSourceState attributionSourceState, boolean startIfModeDefault,
-            boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
-            int attributionFlags, int attributionChainId);
-    void finishOperationWithState(IBinder clientId, int code, in AttributionSourceState attributionSourceState);
-    // End of methods also called by native code (there may be more blocks like this of native
-    // methods later in this file).
+
     SyncNotedAppOp noteProxyOperationWithState(int code,
-            in AttributionSourceState attributionSourceStateState,
-            boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
-            boolean skipProxyOperation);
+                in AttributionSourceState attributionSourceStateState,
+                boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+                boolean skipProxyOperation);
     SyncNotedAppOp startProxyOperationWithState(IBinder clientId, int code,
-            in AttributionSourceState attributionSourceStateState, boolean startIfModeDefault,
-            boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
-            boolean skipProxyOperation, int proxyAttributionFlags, int proxiedAttributionFlags,
-            int attributionChainId);
+                in AttributionSourceState attributionSourceStateState, boolean startIfModeDefault,
+                boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+                boolean skipProxyOperation, int proxyAttributionFlags, int proxiedAttributionFlags,
+                int attributionChainId);
     void finishProxyOperationWithState(IBinder clientId, int code,
-            in AttributionSourceState attributionSourceStateState, boolean skipProxyOperation);
+                in AttributionSourceState attributionSourceStateState, boolean skipProxyOperation);
 }
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 9d88a2341fad2efc309be0832921e5ce786ce916..d85227f1a0a8254c874f97113790c6d66173116b 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -70,9 +70,6 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
         mPackageFilt = new IntentFilter();
         // Settings app sends the broadcast
         mPackageFilt.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
-        // AMS sends the broadcast
-        mPackageFilt.addAction(Intent.ACTION_PACKAGE_RESTARTED);
-        mPackageFilt.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
         mPackageFilt.addDataScheme("package");
         if (isCore) {
             mPackageFilt.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 3795fc8594ae87c2cdbb41b10bda42899ceba1a8..440a33244a62c6fb140517642e4ec7686030a329 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -43,7 +43,10 @@ cc_library_shared_for_libandroid_runtime {
 
         "-Wall",
         "-Werror",
+        "-Wextra",
+        "-Wthread-safety",
         "-Wno-error=deprecated-declarations",
+        "-Wno-unused-parameter",
         "-Wunused",
         "-Wunreachable-code",
 
diff --git a/core/jni/android_content_res_ResourceTimer.cpp b/core/jni/android_content_res_ResourceTimer.cpp
index 91e3c921afe6a5101656a8366c07470b732fa96a..66bda6100b319536983978424bd8e07135d3f0e0 100644
--- a/core/jni/android_content_res_ResourceTimer.cpp
+++ b/core/jni/android_content_res_ResourceTimer.cpp
@@ -44,9 +44,9 @@ static struct {
 
 static int NativeGetTimers(JNIEnv* env, jobject /*clazz*/, jobjectArray timer, jboolean reset) {
   size_t size = ResourceTimer::counterSize;
-  if (jsize st = env->GetArrayLength(timer); st < size) {
-    // Shrink the size to the minimum of the available counters and the available space.
-    size = st;
+  if (size_t st = env->GetArrayLength(timer); st < size) {
+      // Shrink the size to the minimum of the available counters and the available space.
+      size = st;
   }
   for (size_t i = 0; i < size; i++) {
     ResourceTimer::Timer src;
diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp
index 30e546cc290d33deac1ad46a4a8f588d9d813960..82570be8e329d983791a2cb319a1f9985d458bbe 100644
--- a/core/jni/android_hardware_camera2_DngCreator.cpp
+++ b/core/jni/android_hardware_camera2_DngCreator.cpp
@@ -1892,8 +1892,8 @@ static sp<TiffWriter> DngCreator_setup(JNIEnv* env, jobject thiz, uint32_t image
         camera_metadata_entry entry =
             results.find(ANDROID_SENSOR_NOISE_PROFILE);
 
-        const status_t numPlaneColors = isBayer ? 3 : 1;
-        const status_t numCfaChannels = isBayer ? 4 : 1;
+        const unsigned long numPlaneColors = isBayer ? 3 : 1;
+        const unsigned long numCfaChannels = isBayer ? 4 : 1;
 
         uint8_t cfaOut[numCfaChannels];
         if ((err = convertCFA(cfaEnum, /*out*/cfaOut)) != OK) {
diff --git a/core/jni/android_hardware_camera2_impl_CameraExtensionJpegProcessor.cpp b/core/jni/android_hardware_camera2_impl_CameraExtensionJpegProcessor.cpp
index 139075907bf3f4d5bfb503155ff8dc1c71f25563..4bde87f65326ebbb61d523f0180d5483ea3bbb05 100644
--- a/core/jni/android_hardware_camera2_impl_CameraExtensionJpegProcessor.cpp
+++ b/core/jni/android_hardware_camera2_impl_CameraExtensionJpegProcessor.cpp
@@ -599,7 +599,7 @@ static jint CameraExtensionJpegProcessor_compressJpegFromYUV420p(
             quality, cropLeft, cropTop, cropRight, cropBottom, rot90);
 
     size_t finalJpegSize = actualJpegSize + sizeof(CameraBlob);
-    if (finalJpegSize > outBufCapacity) {
+    if (finalJpegSize > static_cast<size_t>(outBufCapacity)) {
         ALOGE("%s: Final jpeg buffer %zu not large enough for the jpeg blob header with "\
                 "capacity %d", __FUNCTION__, finalJpegSize, outBufCapacity);
         return actualJpegSize;
diff --git a/core/jni/android_media_AudioProductStrategies.cpp b/core/jni/android_media_AudioProductStrategies.cpp
index 4b563d7f3bd8a88d7a896e0d1dc150e26ea408b6..7c9dae0ef306a27d1faf7e5c5e307b7c9c9c2122 100644
--- a/core/jni/android_media_AudioProductStrategies.cpp
+++ b/core/jni/android_media_AudioProductStrategies.cpp
@@ -88,12 +88,12 @@ static jint convertAudioProductStrategiesFromNative(
     int attrGroupIndex = 0;
     std::map<int /**attributesGroupIndex*/, std::vector<VolumeGroupAttributes> > groups;
     for (const auto &attr : strategy.getVolumeGroupAttributes()) {
-        int groupId = attr.getGroupId();
+        auto groupId = attr.getGroupId();
         int streamType = attr.getStreamType();
         const auto &iter = std::find_if(begin(groups), end(groups),
                                         [groupId, streamType](const auto &iter) {
             const auto &frontAttr = iter.second.front();
-            return frontAttr.getGroupId() == groupId && frontAttr.getStreamType() == streamType;
+            return (frontAttr.getGroupId() == groupId && frontAttr.getStreamType() == streamType);
         });
         // Same Volume Group Id and same stream type
         if (iter != end(groups)) {
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 6440cc3952f84ed53c68c39577e218ccba1331cc..3413ededb8893d24144fa146edac17e91fa6a010 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2501,7 +2501,7 @@ static std::vector<uid_t> convertJIntArrayToUidVector(JNIEnv *env, jintArray jAr
             int *nativeArray = nullptr;
             nativeArray = env->GetIntArrayElements(jArray, 0);
             if (nativeArray != nullptr) {
-                for (size_t i = 0; i < len; i++) {
+                for (size_t i = 0; i < static_cast<size_t>(len); i++) {
                     nativeVector.push_back(nativeArray[i]);
                 }
                 env->ReleaseIntArrayElements(jArray, nativeArray, 0);
@@ -2565,7 +2565,7 @@ static jint android_media_AudioSystem_setSupportedSystemUsages(JNIEnv *env, jobj
 
     if (nativeSystemUsages != nullptr) {
         jsize len = env->GetArrayLength(systemUsages);
-        for (size_t i = 0; i < len; i++) {
+        for (size_t i = 0; i < static_cast<size_t>(len); i++) {
             audio_usage_t nativeAudioUsage =
                     static_cast<audio_usage_t>(nativeSystemUsages[i]);
             nativeSystemUsagesVector.push_back(nativeAudioUsage);
@@ -2776,7 +2776,7 @@ static jint android_media_AudioSystem_getDevicesForAttributes(JNIEnv *env, jobje
         return jStatus;
     }
 
-    if (devices.size() > maxResultSize) {
+    if (devices.size() > static_cast<size_t>(maxResultSize)) {
         return AUDIO_JAVA_INVALID_OPERATION;
     }
     size_t index = 0;
diff --git a/core/jni/android_net_LocalSocketImpl.cpp b/core/jni/android_net_LocalSocketImpl.cpp
index 9bd07007586c2362fb9789b1f2ad8b938b5882d7..47b4a469aa5bc3f9ee91c70dc413786e11cf0029 100644
--- a/core/jni/android_net_LocalSocketImpl.cpp
+++ b/core/jni/android_net_LocalSocketImpl.cpp
@@ -221,7 +221,7 @@ static int socket_write_all(JNIEnv *env, jobject object, int fd,
 
     ssize_t rc = SendFileDescriptorVector(fd, buffer, len, fds);
 
-    while (rc != len) {
+    while (rc != static_cast<ssize_t>(len)) {
         if (rc == -1) {
             jniThrowIOException(env, errno);
             return -1;
diff --git a/core/jni/android_os_HwBlob.cpp b/core/jni/android_os_HwBlob.cpp
index a9db91be1d5b04c352018abf83209199243ec576..e554b44233b52c3af65b123f0338a28a6e207fbd 100644
--- a/core/jni/android_os_HwBlob.cpp
+++ b/core/jni/android_os_HwBlob.cpp
@@ -265,7 +265,7 @@ jobject JHwBlob::NewObject(JNIEnv *env, size_t size) {
     //   justify passing such a large amount of data over this path. So the
     //   alternative (updating the constructor and other code to accept other
     //   types, should also probably not be taken in this case).
-    CHECK_LE(size, std::numeric_limits<jint>::max());
+    CHECK_LE(size, static_cast<size_t>(std::numeric_limits<jint>::max()));
 
     return env->NewObject(clazz.get(), constructID, static_cast<jint>(size));
 }
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 4d8dac1daaf0eabf3cc230c367b197d2e9cea629..3539476b8ce8e80a570f970dd249ac725006b59f 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -691,7 +691,7 @@ static jboolean android_os_Parcel_hasFileDescriptorsInRange(JNIEnv* env, jclass
 
 // String tries to allocate itself on the stack, within a known size, but will
 // make a heap allocation if not.
-template <size_t StackReserve>
+template <jsize StackReserve>
 class StackString {
 public:
     StackString(JNIEnv* env, jstring str) : mEnv(env), mJStr(str) {
diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp
index 27c4cd4d2cd1ce805789ae72b7e30531ac094a29..95bf49fe501ecaa1406b870750a2c121b1b6667c 100644
--- a/core/jni/android_os_PerformanceHintManager.cpp
+++ b/core/jni/android_os_PerformanceHintManager.cpp
@@ -224,7 +224,7 @@ static jintArray nativeGetThreadIds(JNIEnv* env, jclass clazz, jlong nativeSessi
         return nullptr;
     }
     jint* threadIds = env->GetIntArrayElements(jintArr, 0);
-    for (int i = 0; i < size; ++i) {
+    for (size_t i = 0; i < size; ++i) {
         threadIds[i] = tidsVector[i];
     }
     env->ReleaseIntArrayElements(jintArr, threadIds, 0);
diff --git a/core/jni/android_util_CharsetUtils.cpp b/core/jni/android_util_CharsetUtils.cpp
index 7ab6e8f27fb27f04f79a9f3289b51d85ee297b40..7071cf2336d6725b79019a5c45755710f5f5ac67 100644
--- a/core/jni/android_util_CharsetUtils.cpp
+++ b/core/jni/android_util_CharsetUtils.cpp
@@ -25,7 +25,7 @@ static jint android_util_CharsetUtils_toModifiedUtf8Bytes(JNIEnv *env, jobject c
 
     // Quickly check if destination has plenty of room for worst-case
     // 4-bytes-per-char encoded size
-    const size_t worstLen = (srcLen * 4);
+    const jint worstLen = (srcLen * 4);
     if (destOff >= 0 && destOff + worstLen < destLen) {
         env->GetStringUTFRegion(src, 0, srcLen, destPtr + destOff);
         return strlen(destPtr + destOff + srcLen) + srcLen;
@@ -33,7 +33,7 @@ static jint android_util_CharsetUtils_toModifiedUtf8Bytes(JNIEnv *env, jobject c
 
     // String still might fit in destination, but we need to measure
     // its actual encoded size to be sure
-    const size_t encodedLen = env->GetStringUTFLength(src);
+    const jint encodedLen = env->GetStringUTFLength(src);
     if (destOff >= 0 && destOff + encodedLen < destLen) {
         env->GetStringUTFRegion(src, 0, srcLen, destPtr + destOff);
         return encodedLen;
diff --git a/core/jni/android_util_FileObserver.cpp b/core/jni/android_util_FileObserver.cpp
index c64c21244fe0469512ee16cfe0a8e0cc6e8fe571..8d3da7b81f5bca4d349cb4b73fc76b0156ac195b 100644
--- a/core/jni/android_util_FileObserver.cpp
+++ b/core/jni/android_util_FileObserver.cpp
@@ -114,7 +114,7 @@ static void android_os_fileobserver_startWatching(JNIEnv* env, jobject object, j
     if (fd >= 0)
     {
         size_t count = wfds.size();
-        for (jsize i = 0; i < count; ++i) {
+        for (size_t i = 0; i < count; ++i) {
             jstring pathString = (jstring) env->GetObjectArrayElement(pathStrings, i);
 
             ScopedUtfChars path(env, pathString);
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 41c65aec3144df268392945a9cd37c912addb6db..fef8ad7499f7ac8856da112e664201e1e2b53c79 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -140,7 +140,7 @@ static jobject createJavaVsyncEventData(JNIEnv* env, VsyncEventData vsyncEventDa
         env->ExceptionClear();
         return NULL;
     }
-    for (int i = 0; i < vsyncEventData.frameTimelinesLength; i++) {
+    for (size_t i = 0; i < vsyncEventData.frameTimelinesLength; i++) {
         VsyncEventData::FrameTimeline frameTimeline = vsyncEventData.frameTimelines[i];
         ScopedLocalRef<jobject>
                 frameTimelineObj(env,
@@ -193,7 +193,7 @@ void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDispla
                                                               gDisplayEventReceiverClassInfo
                                                                       .vsyncEventDataClassInfo
                                                                       .frameTimelines)));
-        for (int i = 0; i < vsyncEventData.frameTimelinesLength; i++) {
+        for (size_t i = 0; i < vsyncEventData.frameTimelinesLength; i++) {
             VsyncEventData::FrameTimeline& frameTimeline = vsyncEventData.frameTimelines[i];
             ScopedLocalRef<jobject>
                     frameTimelineObj(env, env->GetObjectArrayElement(frameTimelinesObj.get(), i));
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 98335988459a4f83df786097418fabf414d40ee4..a800e6ea60e4460b2d1579b738539150b9726332 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1950,7 +1950,7 @@ public:
 
         jobjectArray jJankDataArray = env->NewObjectArray(jankData.size(),
                 gJankDataClassInfo.clazz, nullptr);
-        for (int i = 0; i < jankData.size(); i++) {
+        for (size_t i = 0; i < jankData.size(); i++) {
             jobject jJankData = env->NewObject(gJankDataClassInfo.clazz,
                     gJankDataClassInfo.ctor, jankData[i].frameVsyncId, jankData[i].jankType);
             env->SetObjectArrayElement(jJankDataArray, i, jJankData);
diff --git a/core/jni/android_window_WindowInfosListener.cpp b/core/jni/android_window_WindowInfosListener.cpp
index 55995df299b0d901ab1ed9a157bfa968305be6f3..bc69d1e676684635252df16c3c9c2fda9dc0b860 100644
--- a/core/jni/android_window_WindowInfosListener.cpp
+++ b/core/jni/android_window_WindowInfosListener.cpp
@@ -67,7 +67,7 @@ jobject fromDisplayInfo(JNIEnv* env, gui::DisplayInfo displayInfo) {
 static jobjectArray fromWindowInfos(JNIEnv* env, const std::vector<WindowInfo>& windowInfos) {
     jobjectArray jWindowHandlesArray =
             env->NewObjectArray(windowInfos.size(), gInputWindowHandleClass, nullptr);
-    for (int i = 0; i < windowInfos.size(); i++) {
+    for (size_t i = 0; i < windowInfos.size(); i++) {
         ScopedLocalRef<jobject>
                 jWindowHandle(env,
                               android_view_InputWindowHandle_fromWindowInfo(env, windowInfos[i]));
@@ -80,7 +80,7 @@ static jobjectArray fromWindowInfos(JNIEnv* env, const std::vector<WindowInfo>&
 static jobjectArray fromDisplayInfos(JNIEnv* env, const std::vector<DisplayInfo>& displayInfos) {
     jobjectArray jDisplayInfoArray =
             env->NewObjectArray(displayInfos.size(), gDisplayInfoClassInfo.clazz, nullptr);
-    for (int i = 0; i < displayInfos.size(); i++) {
+    for (size_t i = 0; i < displayInfos.size(); i++) {
         ScopedLocalRef<jobject> jDisplayInfo(env, fromDisplayInfo(env, displayInfos[i]));
         env->SetObjectArrayElement(jDisplayInfoArray, i, jDisplayInfo.get());
     }
diff --git a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
index d4f6e1868695d282d8a0cbb4ca65fcdcab8a21b1..0c39a6928391433ec34b3b364dbe1d13e0b3fd74 100644
--- a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
+++ b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
@@ -266,11 +266,11 @@ static void CreateFrroFile(JNIEnv* env, jclass /*clazz*/, jstring jsFrroFilePath
         auto jsResourceName = reinterpret_cast<jstring>(
                 env->GetObjectField(entry, gFabricatedOverlayInternalEntryOffsets.resourceName));
         const ScopedUtfChars resourceName(env, jsResourceName);
-        const auto dataType =
+        const jint dataType =
                 env->GetIntField(entry, gFabricatedOverlayInternalEntryOffsets.dataType);
 
         // In Java, the data type is int but the maximum value of data Type is less than 0xff.
-        if (dataType >= UCHAR_MAX) {
+        if (dataType >= static_cast<jint>(UCHAR_MAX)) {
             jniThrowException(env, IllegalArgumentException, "Unsupported data type");
             return;
         }
diff --git a/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp b/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
index 098a4d8682690a969f181a26dd1f25467415f834..cf70c90af8be48ddd07aeba8e0d1e5873d97ea63 100644
--- a/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
+++ b/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
@@ -55,7 +55,7 @@ static void copy2DVecToArray(JNIEnv *env, jlongArray ar, std::vector<std::vector
 
 static jboolean KernelCpuUidFreqTimeBpfMapReader_removeUidRange(JNIEnv *env, jclass, jint startUid,
                                                                 jint endUid) {
-    for (uint32_t uid = startUid; uid <= endUid; ++uid) {
+    for (jint uid = startUid; uid <= endUid; ++uid) {
         if (!android::bpf::clearUidTimes(uid)) return false;
     }
     return true;
diff --git a/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp b/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
index dfae68429f6d12e18faa36328d1e4f39ee685199..be9013bec08eb25461b8a095857bef9f4ddc7a3e 100644
--- a/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
+++ b/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
@@ -147,7 +147,7 @@ static jboolean startAggregatingThreadCpuTimes(JNIEnv *env, jclass, jintArray se
     std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
             getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
 
-    for (int i = 0; i < selectedThreadIds.size(); i++) {
+    for (size_t i = 0; i < selectedThreadIds.size(); i++) {
         if (!cpuTimeInStateReader->startAggregatingTaskCpuTimes(selectedThreadIds[i],
                                                                 SELECTED_THREAD_AGGREGATION_KEY)) {
             return false;
@@ -312,11 +312,11 @@ MockCpuTimeInStateReader::getAggregatedTaskCpuFreqTimes(
         auto fields = android::base::Split(line.c_str(), ":");
         android::base::ParseUint(fields[0], &aggregationKey);
 
-        for (int j = 1; j < fields.size(); j++) {
+        for (size_t j = 1; j < fields.size(); j++) {
             auto numbers = android::base::Split(fields[j], " ");
 
             std::vector<uint64_t> chunk;
-            for (int k = 0; k < numbers.size(); k++) {
+            for (size_t k = 0; k < numbers.size(); k++) {
                 uint64_t time;
                 android::base::ParseUint(numbers[k], &time);
                 chunk.emplace_back(time);
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index 1f29735b93a4521c1b2d862e24544b2b77f8769f..dab47e9a9e28cb95dc4c8b71a3b1f8596998ba88 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -288,7 +288,7 @@ static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jobject sel
     }
 
     bool nonZero = false;
-    for (int i = 0; i < vector->size(); i++) {
+    for (size_t i = 0; i < vector->size(); i++) {
         jint index = scopedIndexMap[i];
         if (index < 0 || index >= size) {
             jniThrowExceptionFmt(env, "java/lang/IndexOutOfBoundsException",
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 9c1bea779201e637e0f790380a6790513ca4b2e6..7c5885adb220f67b63d6e629b5868495fdadc7bf 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1094,8 +1094,8 @@ static std::string getAppDataDirName(std::string_view parent_path, std::string_v
       }
       struct dirent* ent;
       while ((ent = readdir(dir.get()))) {
-        if (ent->d_ino == ce_data_inode) {
-          return ent->d_name;
+        if (static_cast<long long>(ent->d_ino) == ce_data_inode) {
+            return ent->d_name;
         }
       }
     }
@@ -1765,14 +1765,14 @@ static void ReloadBuildJavaConstant(JNIEnv* env, jclass build_class, const char*
 static void ReloadBuildJavaConstants(JNIEnv* env) {
   jclass build_cls = env->FindClass("android/os/Build");
   size_t arr_size = sizeof(build_constants) / sizeof(build_constants[0]);
-  for (int i = 0; i < arr_size; i++) {
+  for (size_t i = 0; i < arr_size; i++) {
     const char* field_name = build_constants[i].first;
     const char* sysprop_name = build_constants[i].second;
     ReloadBuildJavaConstant(env, build_cls, field_name, "Ljava/lang/String;", sysprop_name);
   }
   jclass build_version_cls = env->FindClass("android/os/Build$VERSION");
   arr_size = sizeof(build_version_constants) / sizeof(build_version_constants[0]);
-  for (int i = 0; i < arr_size; i++) {
+  for (size_t i = 0; i < arr_size; i++) {
     const char* field_name = build_version_constants[i].first;
     const char* sysprop_name = build_version_constants[i].second;
     ReloadBuildJavaConstant(env, build_version_cls, field_name, "Ljava/lang/String;", sysprop_name);
@@ -2901,7 +2901,7 @@ static jint com_android_internal_os_Zygote_nativeParseSigChld(JNIEnv* env, jclas
         return -1;
     }
     ScopedByteArrayRO source(env, in);
-    if (source.size() < length) {
+    if (source.size() < static_cast<size_t>(length)) {
         // Invalid parameter
         jniThrowException(env, "java/lang/IllegalArgumentException", nullptr);
         return -1;
diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
index 2b5b8f7a108e5c6b5e28ffff89b1dcaddf418524..87ab4969040ef2cef461ce5ae27fdc97238837a2 100644
--- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
+++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
@@ -76,7 +76,7 @@ class NativeCommandBuffer {
             return {};
           }
           fail_fn(CREATE_ERROR("session socket read failed: %s", strerror(errno)));
-        } else if (nread == MAX_COMMAND_BYTES - mEnd) {
+        } else if (nread == static_cast<ssize_t>(MAX_COMMAND_BYTES - mEnd)) {
           // This is pessimistic by one character, but close enough.
           fail_fn("ZygoteCommandBuffer overflowed: command too long");
         }
@@ -136,7 +136,7 @@ class NativeCommandBuffer {
     }
     char* countString = line.value().first;  // Newline terminated.
     long nArgs = atol(countString);
-    if (nArgs <= 0 || nArgs >= MAX_COMMAND_BYTES / 2) {
+    if (nArgs <= 0 || nArgs >= static_cast<long>(MAX_COMMAND_BYTES / 2)) {
       fail_fn(CREATE_ERROR("Unreasonable argument count %ld", nArgs));
     }
     mLinesLeft = nArgs;
@@ -153,7 +153,7 @@ class NativeCommandBuffer {
   // As a side effect, this sets mNiceName to a non-empty string, if possible.
   template<class FailFn>
   bool isSimpleForkCommand(int minUid, FailFn fail_fn) {
-    if (mLinesLeft <= 0 || mLinesLeft  >= MAX_COMMAND_BYTES / 2) {
+    if (mLinesLeft <= 0 || mLinesLeft >= static_cast<int32_t>(MAX_COMMAND_BYTES / 2)) {
       return false;
     }
     static const char* RUNTIME_ARGS = "--runtime-args";
@@ -179,14 +179,14 @@ class NativeCommandBuffer {
       if (!read_result.has_value()) {
         return false;
       }
-      auto [arg_start, arg_end] = read_result.value();
-      if (arg_end - arg_start == RA_LENGTH
-          && strncmp(arg_start, RUNTIME_ARGS, RA_LENGTH) == 0) {
+      const auto [arg_start, arg_end] = read_result.value();
+      if (static_cast<size_t>(arg_end - arg_start) == RA_LENGTH &&
+          strncmp(arg_start, RUNTIME_ARGS, RA_LENGTH) == 0) {
         saw_runtime_args = true;
         continue;
       }
-      if (arg_end - arg_start >= NN_LENGTH
-          && strncmp(arg_start, NICE_NAME, NN_LENGTH) == 0) {
+      if (static_cast<size_t>(arg_end - arg_start) >= NN_LENGTH &&
+          strncmp(arg_start, NICE_NAME, NN_LENGTH) == 0) {
         size_t name_len = arg_end - (arg_start + NN_LENGTH);
         size_t copy_len = std::min(name_len, NICE_NAME_BYTES - 1);
         memcpy(mNiceName, arg_start + NN_LENGTH, copy_len);
@@ -196,21 +196,21 @@ class NativeCommandBuffer {
         }
         continue;
       }
-      if (arg_end - arg_start == IW_LENGTH
-          && strncmp(arg_start, INVOKE_WITH, IW_LENGTH) == 0) {
+      if (static_cast<size_t>(arg_end - arg_start) == IW_LENGTH &&
+          strncmp(arg_start, INVOKE_WITH, IW_LENGTH) == 0) {
         // This also removes the need for invoke-with security checks here.
         return false;
       }
-      if (arg_end - arg_start == CZ_LENGTH
-          && strncmp(arg_start, CHILD_ZYGOTE, CZ_LENGTH) == 0) {
+      if (static_cast<size_t>(arg_end - arg_start) == CZ_LENGTH &&
+          strncmp(arg_start, CHILD_ZYGOTE, CZ_LENGTH) == 0) {
         return false;
       }
-      if (arg_end - arg_start >= CA_LENGTH
-          && strncmp(arg_start, CAPABILITIES, CA_LENGTH) == 0) {
+      if (static_cast<size_t>(arg_end - arg_start) >= CA_LENGTH &&
+          strncmp(arg_start, CAPABILITIES, CA_LENGTH) == 0) {
         return false;
       }
-      if (arg_end - arg_start >= SU_LENGTH
-          && strncmp(arg_start, SETUID, SU_LENGTH) == 0) {
+      if (static_cast<size_t>(arg_end - arg_start) >= SU_LENGTH &&
+          strncmp(arg_start, SETUID, SU_LENGTH) == 0) {
         int uid = digitsVal(arg_start + SU_LENGTH, arg_end);
         if (uid < minUid) {
           return false;
@@ -218,8 +218,8 @@ class NativeCommandBuffer {
         saw_setuid = true;
         continue;
       }
-      if (arg_end - arg_start >= SG_LENGTH
-          && strncmp(arg_start, SETGID, SG_LENGTH) == 0) {
+      if (static_cast<size_t>(arg_end - arg_start) >= SG_LENGTH &&
+          strncmp(arg_start, SETGID, SG_LENGTH) == 0) {
         int gid = digitsVal(arg_start + SG_LENGTH, arg_end);
         if (gid == -1) {
           return false;
@@ -422,7 +422,7 @@ jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly(
 
   bool first_time = true;
   do {
-    if (credentials.uid != expected_uid) {
+    if (credentials.uid != static_cast<uid_t>(expected_uid)) {
       return JNI_FALSE;
     }
     n_buffer->readAllLines(first_time ? fail_fn_1 : fail_fn_n);
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 8e619a8bda48287615173ca0ed72e304674321c5..404fa39fb6ed1d609d7122e407b58fd5ce10f58d 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -593,6 +593,7 @@ message InsetsSourceProviderProto {
     optional bool seamless_rotating = 13;
     optional int64 finish_seamless_rotate_frame_number = 14;
     optional bool controllable = 15;
+    optional WindowStateProto source_window_state = 16;
 }
 
 message ImeInsetsSourceProviderProto {
diff --git a/core/proto/android/view/insetssource.proto b/core/proto/android/view/insetssource.proto
index 41b9f432a0ed4a2bd2b0fd2f7ee7a7ce1f47028e..e6c6d59787792842cf51e40490a60196b76fe043 100644
--- a/core/proto/android/view/insetssource.proto
+++ b/core/proto/android/view/insetssource.proto
@@ -30,4 +30,5 @@ message InsetsSourceProto {
     optional .android.graphics.RectProto frame = 2;
     optional .android.graphics.RectProto visible_frame = 3;
     optional bool visible = 4;
+    optional int32 type_number = 5;
 }
\ No newline at end of file
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 862e537aa8c91d77a4feab2a1e2a2c6da1792218..f827d28b26fd19a7293b083d52118ed1d2ad9d23 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -832,6 +832,13 @@
          is set to true. -->
     <bool name="config_remoteInsetsControllerSystemBarsCanBeShownByUserAction">false</bool>
 
+    <!-- To change the default behavior of how the insets get involved when calculating
+         configuration. This will no longer consider status bar and display cutout, and only
+         exclude navigation bars from the screen sizes. This is useful when the display is close to
+         square and the system bars may cause the orientation with non-preferred value.
+         -->
+    <bool name="config_decoupleStatusBarAndDisplayCutoutFromScreenSize">false</bool>
+
     <!-- HDMI behavior -->
 
     <!-- The number of degrees to rotate the display when the device has HDMI connected
@@ -1256,6 +1263,11 @@
     -->
     <bool name="config_shortPressEarlyOnPower">false</bool>
 
+    <!-- Whether a single short press on STEM_PRIMARY should be launched without multi-press delay.
+        This works similarly as config_shortPressEarlyOnPower but for STEM_PRIMARY.
+    -->
+    <bool name="config_shortPressEarlyOnStemPrimary">false</bool>
+
     <!-- Control the behavior of the search key.
             0 - Launch default search activity
             1 - Launch target activity defined by config_searchKeyTargetActivity
@@ -2707,6 +2719,12 @@
          turned off and the screen off animation has been performed. -->
     <bool name="config_dozeAfterScreenOffByDefault">false</bool>
 
+    <!-- If true, bright policy will be applied when we have entered dozing wakefulness but haven't
+         started doze component. This can be used to suppress the temporary dim state before
+         starting a dream service. This is typically used together with
+         config_maximumScreenDimDuration set to 0 to completely suppress dim effect. -->
+    <bool name="config_brightWhenDozing">false</bool>
+
     <!-- Doze: should the TYPE_PICK_UP_GESTURE sensor be used as a pulse signal. -->
     <bool name="config_dozePulsePickup">false</bool>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8748ca1f48a5ed60ef15127aa6110604f3f7e729..2f3b5101269334dd64e244dcbac0f6135516f491 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -473,6 +473,7 @@
   <java-symbol type="integer" name="config_doublePressOnStemPrimaryBehavior" />
   <java-symbol type="integer" name="config_triplePressOnStemPrimaryBehavior" />
   <java-symbol type="bool" name="config_shortPressEarlyOnPower" />
+  <java-symbol type="bool" name="config_shortPressEarlyOnStemPrimary" />
   <java-symbol type="string" name="config_doublePressOnPowerTargetActivity" />
   <java-symbol type="integer" name="config_searchKeyBehavior" />
   <java-symbol type="string" name="config_searchKeyTargetActivity" />
@@ -1732,6 +1733,7 @@
   <java-symbol type="bool" name="config_enableLockScreenRotation" />
   <java-symbol type="bool" name="config_remoteInsetsControllerControlsSystemBars" />
   <java-symbol type="bool" name="config_remoteInsetsControllerSystemBarsCanBeShownByUserAction" />
+  <java-symbol type="bool" name="config_decoupleStatusBarAndDisplayCutoutFromScreenSize" />
   <java-symbol type="bool" name="config_lidControlsScreenLock" />
   <java-symbol type="bool" name="config_lidControlsSleep" />
   <java-symbol type="bool" name="config_lockDayNightMode" />
@@ -1955,6 +1957,7 @@
   <java-symbol type="bool" name="config_enableNightMode" />
   <java-symbol type="bool" name="config_tintNotificationActionButtons" />
   <java-symbol type="bool" name="config_dozeAfterScreenOffByDefault" />
+  <java-symbol type="bool" name="config_brightWhenDozing" />
   <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" />
   <java-symbol type="bool" name="config_defaultAdasGnssLocationEnabled" />
   <java-symbol type="bool" name="config_enableFusedLocationOverlay" />
diff --git a/framework-minus-apex-ravenwood-policies.txt b/framework-minus-apex-ravenwood-policies.txt
index 8e76fd22f99a8be49338628c3f00e3f54ff543fd..48c0a2de8f866d1a112c1d4192db0003b54dd5b5 100644
--- a/framework-minus-apex-ravenwood-policies.txt
+++ b/framework-minus-apex-ravenwood-policies.txt
@@ -71,8 +71,9 @@ class android.util.proto.WireTypeMismatchException stubclass
 # Misc
 class android.util.Dumpable stubclass
 class android.util.DebugUtils stubclass
-class android.util.UtilConfig stubclass
+class android.util.MathUtils stubclass
 class android.util.Patterns stubclass
+class android.util.UtilConfig stubclass
 
 # Internals
 class com.android.internal.util.ArrayUtils stubclass
@@ -89,3 +90,26 @@ class com.android.internal.util.GrowingArrayUtils stubclass
 class com.android.internal.util.LineBreakBufferedWriter stubclass
 class com.android.internal.util.Preconditions stubclass
 class com.android.internal.util.StringPool stubclass
+
+# Parcel
+class android.os.Parcel stubclass
+    method writeException (Ljava/lang/Exception;)V @writeException$ravenwood
+    method writeNoException ()V @writeNoException$ravenwood
+class android.os.Parcel !com.android.hoststubgen.nativesubstitution.Parcel_host
+
+class android.os.Parcelable stubclass
+class android.os.ParcelFormatException stubclass
+class android.os.BadParcelableException stubclass
+class android.os.BadTypeParcelableException stubclass
+
+# Binder: just enough to construct, no further functionality
+class android.os.Binder stub
+    method <init> ()V stub
+    method <init> (Ljava/lang/String;)V stub
+    method isDirectlyHandlingTransaction ()Z stub
+    method isDirectlyHandlingTransactionNative ()Z @isDirectlyHandlingTransactionNative$ravenwood
+    method getNativeBBinderHolder ()J @getNativeBBinderHolder$ravenwood
+
+# Containers
+class android.os.BaseBundle stubclass
+class android.os.Bundle stubclass
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
index 42dc19ce838a81568d1b2ae9d4715161e40d5169..7749394b21d315f5c351b4e44a7a97b913683da9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
@@ -130,7 +130,14 @@ public class BackAnimationBackground {
                     mStartBounds);
             mCustomizer.customizeStatusBarAppearance(region);
         } else {
-            mCustomizer.customizeStatusBarAppearance(null);
+            resetStatusBarCustomization();
         }
     }
+
+    /**
+     * Resets the statusbar customization
+     */
+    public void resetStatusBarCustomization() {
+        mCustomizer.customizeStatusBarAppearance(null);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index a596cef9562a133a6d105d71188abce674f6e80e..f90e3f04ae5e4159538b7126b4430ae60007f1a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -21,6 +21,7 @@ import static android.view.RemoteAnimationTarget.MODE_OPENING;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
 import static com.android.wm.shell.back.BackAnimationConstants.PROGRESS_COMMIT_THRESHOLD;
+import static com.android.wm.shell.back.BackAnimationConstants.UPDATE_SYSUI_FLAGS_THRESHOLD;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
 
 import android.animation.Animator;
@@ -191,6 +192,8 @@ public class CrossActivityAnimation extends ShellBackAnimation {
         // Draw background with task background color.
         mBackground.ensureBackground(mClosingTarget.windowConfiguration.getBounds(),
                 mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
+        setEnteringProgress(0);
+        setLeavingProgress(0);
     }
 
     private void applyTransform(SurfaceControl leash, RectF targetRect, float targetAlpha) {
@@ -272,12 +275,16 @@ public class CrossActivityAnimation extends ShellBackAnimation {
         valueAnimator.addUpdateListener(animation -> {
             float progress = animation.getAnimatedFraction();
             updatePostCommitEnteringAnimation(progress);
+            if (progress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD) {
+                mBackground.resetStatusBarCustomization();
+            }
             mTransaction.apply();
         });
 
         valueAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
+                mBackground.resetStatusBarCustomization();
                 finishAnimation();
             }
         });
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 5a156740e605e21d1e220d2a640250766f6cdd14..80fc3a867d48606859e6df6bc41691e61dd4c71a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -18,8 +18,10 @@ package com.android.wm.shell.back;
 
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.window.BackEvent.EDGE_RIGHT;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_TASK;
+import static com.android.wm.shell.back.BackAnimationConstants.UPDATE_SYSUI_FLAGS_THRESHOLD;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
 
 import android.animation.Animator;
@@ -100,6 +102,7 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
     private RemoteAnimationTarget mClosingTarget;
     private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
     private boolean mBackInProgress = false;
+    private boolean mIsRightEdge;
     private final PointF mTouchPos = new PointF();
     private IRemoteAnimationFinishedCallback mFinishCallback;
     private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
@@ -171,7 +174,12 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
         // Move the window along the Y axis.
         float scaledTop = (height - scaledHeight) * 0.5f + deltaY;
         // Move the window along the X axis.
-        float right = width - (progress * mVerticalMargin);
+        float right;
+        if (mIsRightEdge) {
+            right = (width - scaledWidth) * 0.5f + scaledWidth;
+        } else {
+            right = width - (progress * mVerticalMargin);
+        }
         float left = right - scaledWidth;
 
         mClosingCurrentRect.set(left, scaledTop, right, scaledTop + scaledHeight);
@@ -261,6 +269,7 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
 
     private void onGestureProgress(@NonNull BackEvent backEvent) {
         if (!mBackInProgress) {
+            mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT;
             mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
             mBackInProgress = true;
         }
@@ -287,12 +296,16 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
             float progress = animation.getAnimatedFraction();
             updatePostCommitEnteringAnimation(progress);
             updatePostCommitClosingAnimation(progress);
+            if (progress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD) {
+                mBackground.resetStatusBarCustomization();
+            }
             mTransaction.apply();
         });
 
         valueAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
+                mBackground.resetStatusBarCustomization();
                 finishAnimation();
             }
         });
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index 0890861596a5459cb4d2033fdb20b5855e332898..e63bbc07bc4197c20ab83770707c5a3e3d5a01e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -252,12 +252,7 @@ public class KeyguardTransitionHandler
                 Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e);
             }
             nextFinishCallback.onTransitionFinished(null);
-        } else if (nextInfo.getType() == TRANSIT_SLEEP) {
-            // An empty SLEEP transition comes in as a signal to abort transitions whenever a sleep
-            // token is held. In cases where keyguard is showing, we are running the animation for
-            // the device sleeping/waking, so it's best to ignore this and keep playing anyway.
-            return;
-        } else if (handles(nextInfo)) {
+        } else {
             // In all other cases, fast-forward to let the next queued transition start playing.
             finishAnimationImmediately(currentTransition, playing);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 57cc28d1dde5c04a22c0817a99efffceaacb4682..8eb4a5a1a4c9513030e5142ce891f15a5ebd0590 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -427,10 +427,10 @@ public class PipAnimationController {
                     new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint));
         }
 
-        void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo,
-                int appIconSizePx) {
+        void setAppIconContentOverlay(Context context, Rect appBounds, Rect destinationBounds,
+                ActivityInfo activityInfo, int appIconSizePx) {
             reattachContentOverlay(
-                    new PipContentOverlay.PipAppIconOverlay(context, bounds,
+                    new PipContentOverlay.PipAppIconOverlay(context, appBounds, destinationBounds,
                             new IconProvider(context).getIcon(activityInfo), appIconSizePx));
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index c701b9581ca212e5d0eb7aa9534ca09de1d7cda8..850b06a0fb7df0ae0ee3aa87dcdd85e7f4f2de9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -180,20 +180,34 @@ public abstract class PipContentOverlay {
         private final Context mContext;
         private final int mAppIconSizePx;
         private final Rect mAppBounds;
+        private final int mOverlayHalfSize;
         private final Matrix mTmpTransform = new Matrix();
         private final float[] mTmpFloat9 = new float[9];
 
         private Bitmap mBitmap;
 
-        public PipAppIconOverlay(Context context, Rect appBounds,
+        public PipAppIconOverlay(Context context, Rect appBounds, Rect destinationBounds,
                 Drawable appIcon, int appIconSizePx) {
             mContext = context;
             final int maxAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
                     MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics());
             mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx);
-            mAppBounds = new Rect(appBounds);
-            mBitmap = Bitmap.createBitmap(appBounds.width(), appBounds.height(),
-                    Bitmap.Config.ARGB_8888);
+
+            final int appWidth = appBounds.width();
+            final int appHeight = appBounds.height();
+
+            // In order to have the overlay always cover the pip window during the transition, the
+            // overlay will be drawn with the max size of the start and end bounds in different
+            // rotation.
+            final int overlaySize = Math.max(Math.max(appWidth, appHeight),
+                    Math.max(destinationBounds.width(), destinationBounds.height())) + 1;
+            mOverlayHalfSize = overlaySize >> 1;
+
+            // When the activity is in the secondary split, make sure the scaling center is not
+            // offset.
+            mAppBounds = new Rect(0, 0, appWidth, appHeight);
+
+            mBitmap = Bitmap.createBitmap(overlaySize, overlaySize, Bitmap.Config.ARGB_8888);
             prepareAppIconOverlay(appIcon);
             mLeash = new SurfaceControl.Builder(new SurfaceSession())
                     .setCallsite(TAG)
@@ -215,12 +229,19 @@ public abstract class PipContentOverlay {
         public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
                 Rect currentBounds, float fraction) {
             mTmpTransform.reset();
+            // In order for the overlay to always cover the pip window, the overlay may have a
+            // size larger than the pip window. Make sure that app icon is at the center.
+            final int appBoundsCenterX = mAppBounds.centerX();
+            final int appBoundsCenterY = mAppBounds.centerY();
+            mTmpTransform.setTranslate(
+                    appBoundsCenterX - mOverlayHalfSize,
+                    appBoundsCenterY - mOverlayHalfSize);
             // Scale back the bitmap with the pivot point at center.
             mTmpTransform.postScale(
                     (float) mAppBounds.width() / currentBounds.width(),
                     (float) mAppBounds.height() / currentBounds.height(),
-                    mAppBounds.centerX(),
-                    mAppBounds.centerY());
+                    appBoundsCenterX,
+                    appBoundsCenterY);
             atomicTx.setMatrix(mLeash, mTmpTransform, mTmpFloat9)
                     .setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
         }
@@ -253,10 +274,10 @@ public abstract class PipContentOverlay {
                 ta.recycle();
             }
             final Rect appIconBounds = new Rect(
-                    mAppBounds.centerX() - mAppIconSizePx / 2,
-                    mAppBounds.centerY() - mAppIconSizePx / 2,
-                    mAppBounds.centerX() + mAppIconSizePx / 2,
-                    mAppBounds.centerY() + mAppIconSizePx / 2);
+                    mOverlayHalfSize - mAppIconSizePx / 2,
+                    mOverlayHalfSize - mAppIconSizePx / 2,
+                    mOverlayHalfSize + mAppIconSizePx / 2,
+                    mOverlayHalfSize + mAppIconSizePx / 2);
             appIcon.setBounds(appIconBounds);
             appIcon.draw(canvas);
             mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */);
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 9e8f9c68d43d6def4ba59c41fd104a364a2fb208..083cd08e9a17ef8789812bc80e56c0af61b412ec 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
@@ -1718,7 +1718,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
             sourceHintRect = computeRotatedBounds(rotationDelta, direction, destinationBounds,
                     sourceHintRect);
         }
-        Rect baseBounds = direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
+        final Rect baseBounds = direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
                 ? mPipBoundsState.getBounds() : currentBounds;
         final boolean existingAnimatorRunning = mPipAnimationController.getCurrentAnimator() != null
                 && mPipAnimationController.getCurrentAnimator().isRunning();
@@ -1741,7 +1741,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
                 final boolean hasTopActivityInfo = mTaskInfo.topActivityInfo != null;
                 if (hasTopActivityInfo) {
                     animator.setAppIconContentOverlay(
-                            mContext, currentBounds, mTaskInfo.topActivityInfo,
+                            mContext, currentBounds, destinationBounds, mTaskInfo.topActivityInfo,
                             mPipBoundsState.getLauncherState().getAppIconSizePx());
                 } else {
                     ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
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 018d674e54276b43adedfed7c197ac41bb21d8a8..d5b29e384c092ee3c82fa139975b3d774021c6ed 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
@@ -916,7 +916,7 @@ public class PipTransition extends PipTransitionController {
                 final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null;
                 if (hasTopActivityInfo) {
                     animator.setAppIconContentOverlay(
-                            mContext, currentBounds, taskInfo.topActivityInfo,
+                            mContext, currentBounds, destinationBounds, taskInfo.topActivityInfo,
                             mPipBoundsState.getLauncherState().getAppIconSizePx());
                 } else {
                     ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
index 386983ce6aae8dc43c587a1fac7418fb541cbc58..4d11dfb37c05e4a3fb5ecb5f32b2cf9ad77a6f1c 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
@@ -124,10 +124,6 @@ android_test {
         ":WMShellFlickerTestsPipCommon-src",
     ],
     static_libs: ["WMShellFlickerTestsBase"],
-    test_suites: [
-        "device-tests",
-        "csuite",
-    ],
 }
 
 csuite_test {
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index ad600d0bab9352a095941e70a0368ce9c99c5920..6c3172a267516051b9de76e6528ccea1be8b6797 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -46,7 +46,7 @@ namespace uirenderer {
 
 #ifndef __ANDROID__ // Layoutlib does not compile HWUIProperties.sysprop as it depends on cutils properties
 std::optional<bool> use_vulkan() {
-    return base::GetBoolProperty("ro.hwui.use_vulkan", false);
+    return base::GetBoolProperty("ro.hwui.use_vulkan", true);
 }
 
 std::optional<std::int32_t> render_ahead() {
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 774478669058f78c840dd3d5dfe6b8dd13704e3b..c5ffbb70213ef04ed8ac8ac8678966fb876221ad 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -187,11 +187,12 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
         dumpResourceCacheUsage();
     }
 
-    return {true, IRenderPipeline::DrawResult::kUnknownTime};
+    return {true, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd{}};
 }
 
-bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
-                                     FrameInfo* currentFrameInfo, bool* requireSwap) {
+bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, IRenderPipeline::DrawResult& drawResult,
+                                     const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+                                     bool* requireSwap) {
     GL_CHECKPOINT(LOW);
 
     // Even if we decided to cancel the frame, from the perspective of jank
@@ -202,7 +203,7 @@ bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect
         return false;
     }
 
-    *requireSwap = drew || mEglManager.damageRequiresSwap();
+    *requireSwap = drawResult.success || mEglManager.damageRequiresSwap();
 
     if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) {
         return false;
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index f0461bef170c56f97de92862b8cd2393d9220698..098a74655831ee022c89074459f2438afaa50566 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -44,8 +44,9 @@ public:
             const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler,
             const renderthread::HardwareBufferRenderParams& bufferParams) override;
     GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; }
-    bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
-                     FrameInfo* currentFrameInfo, bool* requireSwap) override;
+    bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
+                     const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+                     bool* requireSwap) override;
     DeferredLayerUpdater* createTextureLayer() override;
     bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override;
     [[nodiscard]] android::base::unique_fd flush() override;
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 86096d5bd01c6fbdcf436af3a18d55fcdd2c1447..12cb69da772b964dfd0d8d468066dda52a1b5d67 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -87,7 +87,7 @@ IRenderPipeline::DrawResult SkiaVulkanPipeline::draw(
     }
 
     if (backBuffer.get() == nullptr) {
-        return {false, -1};
+        return {false, -1, android::base::unique_fd{}};
     }
 
     // update the coordinates of the global light position based on surface rotation
@@ -110,10 +110,10 @@ IRenderPipeline::DrawResult SkiaVulkanPipeline::draw(
         profiler->draw(profileRenderer);
     }
 
-    nsecs_t submissionTime = IRenderPipeline::DrawResult::kUnknownTime;
+    VulkanManager::VkDrawResult drawResult;
     {
         ATRACE_NAME("flush commands");
-        submissionTime = vulkanManager().finishFrame(backBuffer.get());
+        drawResult = vulkanManager().finishFrame(backBuffer.get());
     }
     layerUpdateQueue->clear();
 
@@ -122,11 +122,12 @@ IRenderPipeline::DrawResult SkiaVulkanPipeline::draw(
         dumpResourceCacheUsage();
     }
 
-    return {true, submissionTime};
+    return {true, drawResult.submissionTime, std::move(drawResult.presentFence)};
 }
 
-bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
-                                     FrameInfo* currentFrameInfo, bool* requireSwap) {
+bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, IRenderPipeline::DrawResult& drawResult,
+                                     const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+                                     bool* requireSwap) {
     // Even if we decided to cancel the frame, from the perspective of jank
     // metrics the frame was swapped at this point
     currentFrameInfo->markSwapBuffers();
@@ -135,10 +136,10 @@ bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect
         return false;
     }
 
-    *requireSwap = drew;
+    *requireSwap = drawResult.success;
 
     if (*requireSwap) {
-        vulkanManager().swapBuffers(mVkSurface, screenDirty);
+        vulkanManager().swapBuffers(mVkSurface, screenDirty, std::move(drawResult.presentFence));
     }
 
     return *requireSwap;
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index 284cde537ec08b9657506cd0bf6ab1a818f1cc2f..e2ea57d32cd5483ad6fced8868b1036442bed73a 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -44,8 +44,9 @@ public:
             const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler,
             const renderthread::HardwareBufferRenderParams& bufferParams) override;
     GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; }
-    bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
-                     FrameInfo* currentFrameInfo, bool* requireSwap) override;
+    bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
+                     const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+                     bool* requireSwap) override;
     DeferredLayerUpdater* createTextureLayer() override;
     [[nodiscard]] android::base::unique_fd flush() override;
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 9e3bb79f03f39e585ba142d0460532208033727d..56b52dc2abe763bea5b25511f45b8c7971087d09 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -664,8 +664,8 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) {
     bool didDraw = false;
 
     int error = OK;
-    bool didSwap = mRenderPipeline->swapBuffers(frame, drawResult.success, windowDirty,
-                                                mCurrentFrameInfo, &requireSwap);
+    bool didSwap = mRenderPipeline->swapBuffers(frame, drawResult, windowDirty, mCurrentFrameInfo,
+                                                &requireSwap);
 
     mCurrentFrameInfo->set(FrameInfoIndex::CommandSubmissionCompleted) = std::max(
             drawResult.commandSubmissionTime, mCurrentFrameInfo->get(FrameInfoIndex::SwapBuffers));
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 6c2cb9d71c5534c8b770b1fefd836c50af062393..9c879d5a58d1f4fe7aebba0d819368467121d100 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -61,6 +61,7 @@ public:
         // submission occurred. -1 if this time is unknown.
         static constexpr nsecs_t kUnknownTime = -1;
         nsecs_t commandSubmissionTime = kUnknownTime;
+        android::base::unique_fd presentFence;
     };
     virtual DrawResult draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
                             const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
@@ -68,8 +69,9 @@ public:
                             const std::vector<sp<RenderNode>>& renderNodes,
                             FrameInfoVisualizer* profiler,
                             const HardwareBufferRenderParams& bufferParams) = 0;
-    virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
-                             FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
+    virtual bool swapBuffers(const Frame& frame, IRenderPipeline::DrawResult&,
+                             const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+                             bool* requireSwap) = 0;
     virtual DeferredLayerUpdater* createTextureLayer() = 0;
     [[nodiscard]] virtual android::base::unique_fd flush() = 0;
     virtual void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) = 0;
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 22c58624870585ea64e5e6fc611e68e356a734fa..e706eb083ab66661ab0dc94a770752d2afe019a4 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -531,32 +531,21 @@ struct DestroySemaphoreInfo {
     PFN_vkDestroySemaphore mDestroyFunction;
     VkDevice mDevice;
     VkSemaphore mSemaphore;
-    // We need to make sure we don't delete the VkSemaphore until it is done being used by both Skia
-    // (including by the GPU) and inside the VulkanManager. So we always start with two refs, one
-    // owned by Skia and one owned by the VulkanManager. The refs are decremented each time
-    // destroy_semaphore is called with this object. Skia will call destroy_semaphore once it is
-    // done with the semaphore and the GPU has finished work on the semaphore. The VulkanManager
-    // calls destroy_semaphore after sending the semaphore to Skia and exporting it if need be.
-    int mRefs = 2;
 
     DestroySemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device,
                          VkSemaphore semaphore)
             : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) {}
+
+    ~DestroySemaphoreInfo() { mDestroyFunction(mDevice, mSemaphore, nullptr); }
 };
 
 static void destroy_semaphore(void* context) {
     DestroySemaphoreInfo* info = reinterpret_cast<DestroySemaphoreInfo*>(context);
-    --info->mRefs;
-    if (!info->mRefs) {
-        info->mDestroyFunction(info->mDevice, info->mSemaphore, nullptr);
-        delete info;
-    }
+    delete info;
 }
 
-nsecs_t VulkanManager::finishFrame(SkSurface* surface) {
+VulkanManager::VkDrawResult VulkanManager::finishFrame(SkSurface* surface) {
     ATRACE_NAME("Vulkan finish frame");
-    ALOGE_IF(mSwapSemaphore != VK_NULL_HANDLE || mDestroySemaphoreContext != nullptr,
-             "finishFrame already has an outstanding semaphore");
 
     VkExportSemaphoreCreateInfo exportInfo;
     exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
@@ -576,11 +565,11 @@ nsecs_t VulkanManager::finishFrame(SkSurface* surface) {
 
     GrFlushInfo flushInfo;
     if (err == VK_SUCCESS) {
-        mDestroySemaphoreContext = new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore);
         flushInfo.fNumSemaphores = 1;
         flushInfo.fSignalSemaphores = &backendSemaphore;
         flushInfo.fFinishedProc = destroy_semaphore;
-        flushInfo.fFinishedContext = mDestroySemaphoreContext;
+        flushInfo.fFinishedContext =
+                new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore);
     } else {
         semaphore = VK_NULL_HANDLE;
     }
@@ -589,10 +578,11 @@ nsecs_t VulkanManager::finishFrame(SkSurface* surface) {
     GrSemaphoresSubmitted submitted = context->flush(
             surface, SkSurfaces::BackendSurfaceAccess::kPresent, flushInfo);
     context->submit();
-    const nsecs_t submissionTime = systemTime();
+    VkDrawResult drawResult{
+            .submissionTime = systemTime(),
+    };
     if (semaphore != VK_NULL_HANDLE) {
         if (submitted == GrSemaphoresSubmitted::kYes) {
-            mSwapSemaphore = semaphore;
             if (mFrameBoundaryANDROID) {
                 // retrieve VkImage used as render target
                 VkImage image = VK_NULL_HANDLE;
@@ -611,45 +601,37 @@ nsecs_t VulkanManager::finishFrame(SkSurface* surface) {
                 }
                 // frameBoundaryANDROID needs to know about mSwapSemaphore, but
                 // it won't wait on it.
-                mFrameBoundaryANDROID(mDevice, mSwapSemaphore, image);
+                mFrameBoundaryANDROID(mDevice, semaphore, image);
             }
-        } else {
-            destroy_semaphore(mDestroySemaphoreContext);
-            mDestroySemaphoreContext = nullptr;
         }
-    }
-    skiapipeline::ShaderCache::get().onVkFrameFlushed(context);
-
-    return submissionTime;
-}
-
-void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect) {
-    if (CC_UNLIKELY(Properties::waitForGpuCompletion)) {
-        ATRACE_NAME("Finishing GPU work");
-        mDeviceWaitIdle(mDevice);
-    }
-
-    int fenceFd = -1;
-    if (mSwapSemaphore != VK_NULL_HANDLE) {
         VkSemaphoreGetFdInfoKHR getFdInfo;
         getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
         getFdInfo.pNext = nullptr;
-        getFdInfo.semaphore = mSwapSemaphore;
+        getFdInfo.semaphore = semaphore;
         getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
 
-        VkResult err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
+        int fenceFd = -1;
+        err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
         ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd");
+        drawResult.presentFence.reset(fenceFd);
     } else {
-        ALOGE("VulkanManager::swapBuffers(): Semaphore submission failed");
+        ALOGE("VulkanManager::finishFrame(): Semaphore submission failed");
         mQueueWaitIdle(mGraphicsQueue);
     }
-    if (mDestroySemaphoreContext) {
-        destroy_semaphore(mDestroySemaphoreContext);
+
+    skiapipeline::ShaderCache::get().onVkFrameFlushed(context);
+
+    return drawResult;
+}
+
+void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect,
+                                android::base::unique_fd&& presentFence) {
+    if (CC_UNLIKELY(Properties::waitForGpuCompletion)) {
+        ATRACE_NAME("Finishing GPU work");
+        mDeviceWaitIdle(mDevice);
     }
 
-    surface->presentCurrentBuffer(dirtyRect, fenceFd);
-    mSwapSemaphore = VK_NULL_HANDLE;
-    mDestroySemaphoreContext = nullptr;
+    surface->presentCurrentBuffer(dirtyRect, presentFence.release());
 }
 
 void VulkanManager::destroySurface(VulkanSurface* surface) {
@@ -753,22 +735,17 @@ status_t VulkanManager::createReleaseFence(int* nativeFence, GrDirectContext* gr
     GrBackendSemaphore backendSemaphore;
     backendSemaphore.initVulkan(semaphore);
 
-    DestroySemaphoreInfo* destroyInfo =
-            new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore);
     // Even if Skia fails to submit the semaphore, it will still call the destroy_semaphore callback
-    // which will remove its ref to the semaphore. The VulkanManager must still release its ref,
-    // when it is done with the semaphore.
     GrFlushInfo flushInfo;
     flushInfo.fNumSemaphores = 1;
     flushInfo.fSignalSemaphores = &backendSemaphore;
     flushInfo.fFinishedProc = destroy_semaphore;
-    flushInfo.fFinishedContext = destroyInfo;
+    flushInfo.fFinishedContext = new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore);
     GrSemaphoresSubmitted submitted = grContext->flush(flushInfo);
     grContext->submit();
 
     if (submitted == GrSemaphoresSubmitted::kNo) {
         ALOGE("VulkanManager::createReleaseFence: Failed to submit semaphore");
-        destroy_semaphore(destroyInfo);
         return INVALID_OPERATION;
     }
 
@@ -781,7 +758,6 @@ status_t VulkanManager::createReleaseFence(int* nativeFence, GrDirectContext* gr
     int fenceFd = 0;
 
     err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
-    destroy_semaphore(destroyInfo);
     if (VK_SUCCESS != err) {
         ALOGE("VulkanManager::createReleaseFence: Failed to get semaphore Fd");
         return INVALID_OPERATION;
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index dbef7fbd51b2e4d74bb3007f8f180a8d4fdbf5bd..b92ebb3cdf71d616a27bc6162675eebac8ca7618 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -22,6 +22,7 @@
 #endif
 #include <GrContextOptions.h>
 #include <SkSurface.h>
+#include <android-base/unique_fd.h>
 #include <utils/StrongPointer.h>
 #include <vk/GrVkBackendContext.h>
 #include <vk/GrVkExtensions.h>
@@ -82,10 +83,17 @@ public:
     void destroySurface(VulkanSurface* surface);
 
     Frame dequeueNextBuffer(VulkanSurface* surface);
+
+    struct VkDrawResult {
+        // The estimated start time for intiating GPU work, -1 if unknown.
+        nsecs_t submissionTime;
+        android::base::unique_fd presentFence;
+    };
+
     // Finishes the frame and submits work to the GPU
-    // Returns the estimated start time for intiating GPU work, -1 otherwise.
-    nsecs_t finishFrame(SkSurface* surface);
-    void swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect);
+    VkDrawResult finishFrame(SkSurface* surface);
+    void swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect,
+                     android::base::unique_fd&& presentFence);
 
     // Inserts a wait on fence command into the Vulkan command buffer.
     status_t fenceWait(int fence, GrDirectContext* grContext);
@@ -201,9 +209,6 @@ private:
     GrVkExtensions mExtensions;
     uint32_t mDriverVersion = 0;
 
-    VkSemaphore mSwapSemaphore = VK_NULL_HANDLE;
-    void* mDestroySemaphoreContext = nullptr;
-
     std::once_flag mInitFlag;
     std::atomic_bool mInitialized = false;
 };
diff --git a/media/java/android/media/audiopolicy/AudioVolumeGroup.java b/media/java/android/media/audiopolicy/AudioVolumeGroup.java
index 1a9ebbc56d681dbd7b69b212eba75279e88014e0..d60712674990c59f767e3c9d46a8e89bf016ea69 100644
--- a/media/java/android/media/audiopolicy/AudioVolumeGroup.java
+++ b/media/java/android/media/audiopolicy/AudioVolumeGroup.java
@@ -17,6 +17,7 @@
 package android.media.audiopolicy;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.media.AudioAttributes;
 import android.media.AudioSystem;
@@ -107,7 +108,7 @@ public final class AudioVolumeGroup implements Parcelable {
     }
 
     @Override
-    public boolean equals(@NonNull Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index 08a8d897c96eb44b0f9b7f0d20b8a1caa05282f4..2769dbc430ba94b5185c86965881e02b1a8423f8 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -127,7 +127,7 @@ jobject MediaMetricsJNI::writeMetricsToBundle(
     if (item->getTimestamp() > 0) {
         bh.put(mediametrics::BUNDLE_TIMESTAMP, (int64_t)item->getTimestamp());
     }
-    if (item->getUid() != -1) {
+    if (static_cast<int32_t>(item->getUid()) != -1) {
         bh.put(mediametrics::BUNDLE_UID, (int32_t)item->getUid());
     }
     for (const auto &prop : *item) {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
index e914d5ce5140bc4f9729b8dc7fd4dfeee0c4f7c8..8386bc1b1ba3ddbad8c15318030cfc24ad849225 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
@@ -18,6 +18,9 @@ package com.android.settingslib.spa.gallery.card
 
 import android.os.Bundle
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Error
+import androidx.compose.material.icons.outlined.PowerOff
+import androidx.compose.material.icons.outlined.Shield
 import androidx.compose.material.icons.outlined.WarningAmber
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.res.stringResource
@@ -29,13 +32,15 @@ import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.gallery.R
 import com.android.settingslib.spa.widget.card.CardButton
+import com.android.settingslib.spa.widget.card.CardModel
 import com.android.settingslib.spa.widget.card.SettingsCard
+import com.android.settingslib.spa.widget.card.SettingsCollapsibleCard
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 
 object CardPageProvider : SettingsPageProvider {
-    override val name = "ActionButton"
+    override val name = "CardPage"
 
     override fun getTitle(arguments: Bundle?) = TITLE
 
@@ -44,18 +49,21 @@ object CardPageProvider : SettingsPageProvider {
         RegularScaffold(title = TITLE) {
             SettingsCardWithIcon()
             SettingsCardWithoutIcon()
+            SampleSettingsCollapsibleCard()
         }
     }
 
     @Composable
     private fun SettingsCardWithIcon() {
         SettingsCard(
-            title = stringResource(R.string.sample_title),
-            text = stringResource(R.string.sample_text),
-            imageVector = Icons.Outlined.WarningAmber,
-            buttons = listOf(
-                CardButton(text = "Action") {},
-                CardButton(text = "Action", isMain = true) {},
+            CardModel(
+                title = stringResource(R.string.sample_title),
+                text = stringResource(R.string.sample_text),
+                imageVector = Icons.Outlined.WarningAmber,
+                buttons = listOf(
+                    CardButton(text = "Action") {},
+                    CardButton(text = "Action", isMain = true) {},
+                )
             )
         )
     }
@@ -63,10 +71,39 @@ object CardPageProvider : SettingsPageProvider {
     @Composable
     private fun SettingsCardWithoutIcon() {
         SettingsCard(
-            title = stringResource(R.string.sample_title),
-            text = stringResource(R.string.sample_text),
-            buttons = listOf(
-                CardButton(text = "Action") {},
+            CardModel(
+                title = stringResource(R.string.sample_title),
+                text = stringResource(R.string.sample_text),
+                buttons = listOf(
+                    CardButton(text = "Action") {},
+                ),
+            )
+        )
+    }
+
+    @Composable
+    fun SampleSettingsCollapsibleCard() {
+        SettingsCollapsibleCard(
+            title = "More alerts",
+            imageVector = Icons.Outlined.Error,
+            models = listOf(
+                CardModel(
+                    title = stringResource(R.string.sample_title),
+                    text = stringResource(R.string.sample_text),
+                    imageVector = Icons.Outlined.PowerOff,
+                    buttons = listOf(
+                        CardButton(text = "Action") {},
+                    )
+                ),
+                CardModel(
+                    title = stringResource(R.string.sample_title),
+                    text = stringResource(R.string.sample_text),
+                    imageVector = Icons.Outlined.Shield,
+                    buttons = listOf(
+                        CardButton(text = "Action") {},
+                        CardButton(text = "Main action", isMain = true) {},
+                    )
+                )
             )
         )
     }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/UiModePreviews.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/UiModePreviews.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d48e56464d9406caa394e84440a3dfd96c245743
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/UiModePreviews.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.settingslib.spa.debug
+
+import android.content.res.Configuration
+import androidx.compose.ui.tooling.preview.Preview
+
+@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
+@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
+annotation class UiModePreviews
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 4088ffd43986d6fa775eb63835dd6c689bb3c775..47660bc44cc80283599d9c116307510b4af21d8a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,6 +20,8 @@ import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.ui.unit.dp
 
 object SettingsDimension {
+    val paddingSmall = 4.dp
+
     val itemIconSize = 24.dp
     val itemIconContainerSize = 72.dp
     val itemPaddingStart = 24.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c113f4332fc38d339e98d3bfdf90892276c3795a
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.settingslib.spa.widget.card
+
+import androidx.compose.ui.graphics.vector.ImageVector
+
+data class CardButton(
+    val text: String,
+    val isMain: Boolean = false,
+    val onClick: () -> Unit,
+)
+
+data class CardModel(
+    val title: String,
+    val text: String,
+    val imageVector: ImageVector? = null,
+    val buttons: List<CardButton> = emptyList(),
+)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
index 98873e037df80658a27c6ae2309ba2d93e46aa97..10e26869a76ef3221cd7d8b3f4af2e3355378d1b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
@@ -16,9 +16,9 @@
 
 package com.android.settingslib.spa.widget.card
 
-import android.content.res.Configuration
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
@@ -37,26 +37,15 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.debug.UiModePreviews
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraLarge
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.ui.SettingsBody
 import com.android.settingslib.spa.widget.ui.SettingsTitle
 
-data class CardButton(
-    val text: String,
-    val isMain: Boolean = false,
-    val onClick: () -> Unit,
-)
-
 @Composable
-fun SettingsCard(
-    title: String,
-    text: String,
-    imageVector: ImageVector? = null,
-    buttons: List<CardButton> = emptyList(),
-) {
+fun SettingsCard(content: @Composable ColumnScope.() -> Unit) {
     Card(
         shape = CornerExtraLarge,
         colors = CardDefaults.cardColors(
@@ -68,16 +57,27 @@ fun SettingsCard(
                 horizontal = SettingsDimension.itemPaddingEnd,
                 vertical = SettingsDimension.itemPaddingAround,
             ),
+        content = content,
+    )
+}
+
+@Composable
+fun SettingsCard(model: CardModel) {
+    SettingsCard {
+        SettingsCardImpl(model)
+    }
+}
+
+@Composable
+internal fun SettingsCardImpl(model: CardModel) {
+    Column(
+        modifier = Modifier.padding(SettingsDimension.itemPaddingStart),
+        verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround)
     ) {
-        Column(
-            modifier = Modifier.padding(SettingsDimension.itemPaddingStart),
-            verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround)
-        ) {
-            CardIcon(imageVector)
-            SettingsTitle(title)
-            SettingsBody(text)
-            Buttons(buttons)
-        }
+        CardIcon(model.imageVector)
+        SettingsTitle(model.title)
+        SettingsBody(model.text)
+        Buttons(model.buttons)
     }
 }
 
@@ -136,28 +136,19 @@ private fun Button(button: CardButton) {
     }
 }
 
-@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
-@Composable
-private fun SettingsCardPreviewLight() {
-    SettingsCardPreview()
-}
-
-@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
-@Composable
-private fun SettingsCardPreviewDark() {
-    SettingsCardPreview()
-}
-
+@UiModePreviews
 @Composable
 private fun SettingsCardPreview() {
     SettingsTheme {
         SettingsCard(
-            title = "Lorem ipsum",
-            text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
-            imageVector = Icons.Outlined.WarningAmber,
-            buttons = listOf(
-                CardButton(text = "Action") {},
-                CardButton(text = "Action", isMain = true) {},
+            CardModel(
+                title = "Lorem ipsum",
+                text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
+                imageVector = Icons.Outlined.WarningAmber,
+                buttons = listOf(
+                    CardButton(text = "Action") {},
+                    CardButton(text = "Action", isMain = true) {},
+                )
             )
         )
     }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7d1064512a644d3aef39cf6cd7dba8dd2347a734
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.settingslib.spa.widget.card
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Error
+import androidx.compose.material.icons.outlined.PowerOff
+import androidx.compose.material.icons.outlined.Shield
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import com.android.settingslib.spa.debug.UiModePreviews
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsShape
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.ui.ExpandIcon
+import com.android.settingslib.spa.widget.ui.SettingsDialogItem
+import com.android.settingslib.spa.widget.ui.SettingsTitleSmall
+
+@Composable
+fun SettingsCollapsibleCard(
+    title: String,
+    imageVector: ImageVector,
+    models: List<CardModel>
+) {
+    var expanded by rememberSaveable { mutableStateOf(false) }
+    SettingsCard {
+        Header(title, imageVector, models.size, expanded) { expanded = it }
+        AnimatedVisibility(expanded) {
+            Column {
+                for (model in models) {
+                    HorizontalDivider(
+                        thickness = SettingsDimension.paddingSmall,
+                        color = MaterialTheme.colorScheme.surface,
+                    )
+                    SettingsCardImpl(model)
+                }
+            }
+        }
+    }
+}
+
+@Composable
+private fun Header(
+    title: String,
+    imageVector: ImageVector,
+    cardCount: Int,
+    expanded: Boolean,
+    setExpanded: (Boolean) -> Unit,
+) {
+    Row(
+        modifier = Modifier
+            .fillMaxWidth()
+            .clickable { setExpanded(!expanded) }
+            .padding(
+                horizontal = SettingsDimension.itemPaddingStart,
+                vertical = SettingsDimension.itemPaddingVertical,
+            ),
+        horizontalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingStart),
+        verticalAlignment = Alignment.CenterVertically,
+    ) {
+        Icon(
+            imageVector = imageVector,
+            contentDescription = null,
+            modifier = Modifier.size(SettingsDimension.itemIconSize),
+            tint = MaterialTheme.colorScheme.primary,
+        )
+        Box(modifier = Modifier.weight(1f)) {
+            SettingsTitleSmall(title, useMediumWeight = true)
+        }
+        CardCount(cardCount, expanded)
+    }
+}
+
+@Composable
+private fun CardCount(modelSize: Int, expanded: Boolean) {
+    Surface(
+        shape = SettingsShape.CornerExtraLarge,
+        color = MaterialTheme.colorScheme.secondaryContainer,
+    ) {
+        Row(
+            modifier = Modifier.padding(SettingsDimension.paddingSmall),
+            verticalAlignment = Alignment.CenterVertically,
+        ) {
+            Spacer(modifier = Modifier.padding(SettingsDimension.paddingSmall))
+            SettingsDialogItem(modelSize.toString())
+            ExpandIcon(expanded)
+        }
+    }
+}
+
+@UiModePreviews
+@Composable
+private fun SettingsCollapsibleCardPreview() {
+    SettingsTheme {
+        SettingsCollapsibleCard(
+            title = "More alerts",
+            imageVector = Icons.Outlined.Error,
+            models = listOf(
+                CardModel(
+                    title = "Lorem ipsum",
+                    text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
+                    imageVector = Icons.Outlined.PowerOff,
+                    buttons = listOf(
+                        CardButton(text = "Action") {},
+                    )
+                ),
+                CardModel(
+                    title = "Lorem ipsum",
+                    text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
+                    imageVector = Icons.Outlined.Shield,
+                    buttons = listOf(
+                        CardButton(text = "Action") {},
+                        CardButton(text = "Main action", isMain = true) {},
+                    )
+                )
+            )
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
index 8cbf7cc2ea475bc4386ab7519bc9a6d0a26031e1..a9974dc7d3893d68f0b64e3cc8122477a6fa5de9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -76,13 +76,7 @@ fun Spinner(options: List<SpinnerOption>, selectedId: Int?, setId: (id: Int) ->
             contentPadding = contentPadding,
         ) {
             SpinnerText(options.find { it.id == selectedId })
-            Icon(
-                imageVector = when {
-                    expanded -> Icons.Outlined.ExpandLess
-                    else -> Icons.Outlined.ExpandMore
-                },
-                contentDescription = null,
-            )
+            ExpandIcon(expanded)
         }
         DropdownMenu(
             expanded = expanded,
@@ -109,6 +103,17 @@ fun Spinner(options: List<SpinnerOption>, selectedId: Int?, setId: (id: Int) ->
     }
 }
 
+@Composable
+internal fun ExpandIcon(expanded: Boolean) {
+    Icon(
+        imageVector = when {
+            expanded -> Icons.Outlined.ExpandLess
+            else -> Icons.Outlined.ExpandMore
+        },
+        contentDescription = null,
+    )
+}
+
 @Composable
 private fun SpinnerText(
     option: SpinnerOption?,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
index 7f1acffe7a8a416e1e803b141ac473c604632e28..f4b284362c52d1839697a79efff6599aef2e3477 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
@@ -39,12 +40,16 @@ fun SettingsTitle(title: String, useMediumWeight: Boolean = false) {
     Text(
         text = title,
         color = MaterialTheme.colorScheme.onSurface,
-        style = MaterialTheme.typography.titleMedium.let {
-            when (useMediumWeight) {
-                true -> it.toMediumWeight()
-                else -> it
-            }
-        },
+        style = MaterialTheme.typography.titleMedium.withWeight(useMediumWeight),
+    )
+}
+
+@Composable
+fun SettingsTitleSmall(title: String, useMediumWeight: Boolean = false) {
+    Text(
+        text = title,
+        color = MaterialTheme.colorScheme.onSurface,
+        style = MaterialTheme.typography.titleSmall.withWeight(useMediumWeight),
     )
 }
 
@@ -78,7 +83,9 @@ fun SettingsBody(
 @Composable
 fun PlaceholderTitle(title: String) {
     Box(
-        modifier = Modifier.fillMaxSize().padding(SettingsDimension.itemPadding),
+        modifier = Modifier
+            .fillMaxSize()
+            .padding(SettingsDimension.itemPadding),
         contentAlignment = Alignment.Center,
     ) {
         Text(
@@ -89,6 +96,11 @@ fun PlaceholderTitle(title: String) {
     }
 }
 
+private fun TextStyle.withWeight(useMediumWeight: Boolean = false) = when (useMediumWeight) {
+    true -> toMediumWeight()
+    else -> this
+}
+
 @Preview
 @Composable
 private fun BasePreferencePreview() {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
index 0ec8507cb88bb1193acc6f287f7ee0d8adb23205..fd3ae49d93c850da812be2207763c9dd6525323e 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
@@ -35,8 +35,10 @@ class SettingsCardTest {
     fun settingsCard_titleDisplayed() {
         composeTestRule.setContent {
             SettingsCard(
-                title = TITLE,
-                text = "",
+                CardModel(
+                    title = TITLE,
+                    text = "",
+                )
             )
         }
 
@@ -47,8 +49,10 @@ class SettingsCardTest {
     fun settingsCard_textDisplayed() {
         composeTestRule.setContent {
             SettingsCard(
-                title = "",
-                text = TEXT,
+                CardModel(
+                    title = "",
+                    text = TEXT,
+                )
             )
         }
 
@@ -59,11 +63,13 @@ class SettingsCardTest {
     fun settingsCard_buttonDisplayed() {
         composeTestRule.setContent {
             SettingsCard(
-                title = "",
-                text = "",
-                buttons = listOf(
-                    CardButton(text = TEXT) {}
-                ),
+                CardModel(
+                    title = "",
+                    text = "",
+                    buttons = listOf(
+                        CardButton(text = TEXT) {}
+                    ),
+                )
             )
         }
 
@@ -75,11 +81,13 @@ class SettingsCardTest {
         var buttonClicked = false
         composeTestRule.setContent {
             SettingsCard(
-                title = "",
-                text = "",
-                buttons = listOf(
-                    CardButton(text = TEXT) { buttonClicked = true }
-                ),
+                CardModel(
+                    title = "",
+                    text = "",
+                    buttons = listOf(
+                        CardButton(text = TEXT) { buttonClicked = true }
+                    ),
+                )
             )
         }
 
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCardTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCardTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..efe1c701b1f6d83a67fbc7dd38735e26274f1503
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCardTest.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.settingslib.spa.widget.card
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Error
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsCollapsibleCardTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun settingsCollapsibleCard_titleDisplayed() {
+        setContent()
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun settingsCollapsibleCard_cardCountDisplayed() {
+        setContent()
+
+        composeTestRule.onNodeWithText("1").assertIsDisplayed()
+    }
+
+    @Test
+    fun settingsCollapsibleCard_initial_cardTextNotExists() {
+        setContent()
+
+        composeTestRule.onNodeWithText(CARD_TEXT).assertDoesNotExist()
+    }
+
+    @Test
+    fun settingsCollapsibleCard_afterExpand_cardTextDisplayed() {
+        setContent()
+
+        composeTestRule.onNodeWithText(TITLE).performClick()
+
+        composeTestRule.onNodeWithText(CARD_TEXT).assertIsDisplayed()
+    }
+
+    private fun setContent() {
+        composeTestRule.setContent {
+            SettingsCollapsibleCard(
+                title = TITLE,
+                imageVector = Icons.Outlined.Error,
+                models = listOf(
+                    CardModel(
+                        title = "",
+                        text = CARD_TEXT,
+                    )
+                ),
+            )
+        }
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        const val CARD_TEXT = "Card Text"
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index f0741061a051d858e6dcf1d3cd73f2ee908433a7..f03263b71138e1efb2a1a98ab8c88a2257ff0d3b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -23,6 +23,7 @@ import android.graphics.ColorFilter;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.drawable.Drawable;
+import android.hardware.usb.flags.Flags;
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
@@ -704,12 +705,23 @@ public class Utils {
                 continue;
             }
             for (int complianceWarningType : complianceWarnings) {
-                switch (complianceWarningType) {
-                    case UsbPortStatus.COMPLIANCE_WARNING_OTHER:
-                    case UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY:
-                        return true;
-                    default:
-                        break;
+                if (Flags.enableUsbDataComplianceWarning()
+                        && Flags.enableInputPowerLimitedWarning()) {
+                    switch (complianceWarningType) {
+                        case UsbPortStatus.COMPLIANCE_WARNING_INPUT_POWER_LIMITED:
+                        case UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY:
+                            return true;
+                        default:
+                            break;
+                    }
+                } else {
+                    switch (complianceWarningType) {
+                        case UsbPortStatus.COMPLIANCE_WARNING_OTHER:
+                        case UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY:
+                            return true;
+                        default:
+                            break;
+                    }
                 }
             }
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index a93524f6d87355824d1a33fbdb92abab093f6bb1..51164e8365909a079bb97c968e818cc3dad0dcec 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -126,13 +126,25 @@ public class LeAudioProfile implements LocalBluetoothProfile {
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
+        return getDevicesByStates(new int[] {
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    public List<BluetoothDevice> getConnectableDevices() {
+        return getDevicesByStates(new int[] {
+                BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    private List<BluetoothDevice> getDevicesByStates(int[] states) {
         if (mService == null) {
-            return new ArrayList<BluetoothDevice>(0);
+            return new ArrayList<>(0);
         }
-        return mService.getDevicesMatchingConnectionStates(
-              new int[] {BluetoothProfile.STATE_CONNECTED,
-                         BluetoothProfile.STATE_CONNECTING,
-                         BluetoothProfile.STATE_DISCONNECTING});
+        return mService.getDevicesMatchingConnectionStates(states);
     }
 
     /*
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 29846ac30909a9da5c84bbfe64354a8bc03e5b4e..a88a9c72e4a91012ebe507a61edf6216605d3236 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -28,6 +28,7 @@ import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
+import android.hardware.usb.flags.Flags;
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
@@ -36,6 +37,7 @@ import android.media.AudioManager;
 import android.os.BatteryManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.NetworkRegistrationInfo;
@@ -44,6 +46,7 @@ import android.text.TextUtils;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatcher;
@@ -85,6 +88,8 @@ public class UtilsTest {
     @Mock
     private UsbPortStatus mUsbPortStatus;
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -425,8 +430,38 @@ public class UtilsTest {
     }
 
     @Test
-    public void containsIncompatibleChargers_complianeWarningOther_returnTrue() {
+    public void containsIncompatibleChargers_complianeWarningOther_returnTrue_flagDisabled() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING);
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_INPUT_POWER_LIMITED_WARNING);
         setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_OTHER);
+
+        assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isTrue();
+    }
+
+    @Test
+    public void containsIncompatibleChargers_complianeWarningPower_returnFalse_flagDisabled() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING);
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_INPUT_POWER_LIMITED_WARNING);
+        setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_INPUT_POWER_LIMITED);
+
+        assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
+    }
+
+    @Test
+    public void containsIncompatibleChargers_complianeWarningOther_returnFalse_flagEnabled() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING);
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_INPUT_POWER_LIMITED_WARNING);
+        setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_OTHER);
+
+        assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
+    }
+
+    @Test
+    public void containsIncompatibleChargers_complianeWarningPower_returnTrue_flagEnabled() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING);
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_INPUT_POWER_LIMITED_WARNING);
+        setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_INPUT_POWER_LIMITED);
+
         assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isTrue();
     }
 
@@ -446,7 +481,6 @@ public class UtilsTest {
     public void containsIncompatibleChargers_emptyComplianceWarnings_returnFalse() {
         setupIncompatibleCharging();
         when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[1]);
-
         assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
     }
 
@@ -476,7 +510,7 @@ public class UtilsTest {
     }
 
     private void setupIncompatibleCharging() {
-        setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_OTHER);
+        setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY);
     }
 
     private void setupIncompatibleCharging(int complianceWarningType) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index 6bc27160c0a867e3def9f8880c589a45618fbc70..6ff36d43f91f3b32caddaa208c871ddd5e3bed08 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -29,6 +29,7 @@ import android.app.ActivityManager;
 import android.content.AttributionSource;
 import android.content.IContentProvider;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
@@ -219,14 +220,33 @@ public final class DeviceConfigService extends Binder {
         return lines;
       }
 
+      private static void log(String msg) {
+        if (Build.IS_DEBUGGABLE) {
+            Slog.wtf(TAG, msg);
+        } else {
+            Slog.e(TAG, msg);
+        }
+      }
+
       public static List<String> listAllAconfigFlags(IContentProvider provider) {
         HashMap<String, String> allFlags = getAllFlags(provider);
         HashSet<String> aconfigFlagNames = getAconfigFlagNamesInDeviceConfig();
         final ArrayList<String> lines = new ArrayList<>();
-        for (String key : aconfigFlagNames) {
-          String val = allFlags.get(key);
+        for (String aconfigFlag : aconfigFlagNames) {
+          String val = allFlags.get(aconfigFlag);
           if (val != null) {
-            lines.add(key + "=" + val);
+            // aconfigFlag is in the form of [namespace]/[package].[flag_name]
+            int idx = aconfigFlag.indexOf("/");
+            if (idx == -1 || idx == aconfigFlag.length() - 1 || idx == 0) {
+              log("invalid flag entry in device config: " + aconfigFlag);
+              continue;
+            }
+
+            // we intend to print out [package].[flag_name] [namespace]=val
+            String aconfigFlagNameByPackage = aconfigFlag.substring(idx + 1);
+            String namespace = aconfigFlag.substring(0, idx);
+            lines.add("flag:" + aconfigFlagNameByPackage + " namespace:" + namespace +
+                " value:" + val);
           }
         }
         Collections.sort(lines);
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index d668c691ee0dfb719299e30d19d3da57a1070009..defaa20a84c79fe6374836191b517ed45962c5aa 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -25,7 +25,9 @@ import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.animation.core.snap
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.Image
+import androidx.compose.foundation.combinedClickable
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
@@ -68,7 +70,6 @@ import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.DpOffset
 import androidx.compose.ui.unit.LayoutDirection
@@ -77,7 +78,9 @@ import androidx.compose.ui.unit.times
 import com.android.compose.PlatformButton
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.thenIf
 import com.android.compose.windowsizeclass.LocalWindowSizeClass
+import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
 import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
@@ -195,6 +198,7 @@ private fun Bouncer(
     val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
     val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
     var dialog: Dialog? by remember { mutableStateOf(null) }
+    val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState()
 
     Column(
         horizontalAlignment = Alignment.CenterHorizontally,
@@ -221,21 +225,7 @@ private fun Bouncer(
             )
         }
 
-        if (viewModel.isEmergencyButtonVisible) {
-            Button(
-                onClick = viewModel::onEmergencyServicesButtonClicked,
-                colors =
-                    ButtonDefaults.buttonColors(
-                        containerColor = MaterialTheme.colorScheme.tertiaryContainer,
-                        contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
-                    ),
-            ) {
-                Text(
-                    text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
-                    style = MaterialTheme.typography.bodyMedium,
-                )
-            }
-        }
+        actionButton?.let { BouncerActionButton(viewModel = it) }
 
         if (dialogMessage != null) {
             if (dialog == null) {
@@ -320,6 +310,37 @@ private fun UserInputArea(
     }
 }
 
+/**
+ * Renders the action button on the bouncer, which triggers either Return to Call or Emergency Call.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+private fun BouncerActionButton(
+    viewModel: BouncerActionButtonModel,
+    modifier: Modifier = Modifier,
+) {
+    Button(
+        onClick = viewModel.onClick,
+        modifier =
+            modifier.thenIf(viewModel.onLongClick != null) {
+                Modifier.combinedClickable(
+                    onClick = viewModel.onClick,
+                    onLongClick = viewModel.onLongClick,
+                )
+            },
+        colors =
+            ButtonDefaults.buttonColors(
+                containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+                contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+            ),
+    ) {
+        Text(
+            text = viewModel.label,
+            style = MaterialTheme.typography.bodyMedium,
+        )
+    }
+}
+
 /** Renders the UI of the user switcher that's displayed on large screens next to the bouncer UI. */
 @Composable
 private fun UserSwitcher(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 17726ab06e0f921f9d3543a13d0c73f14952bf9c..142978284cfbbb3478aa30a0c58e830becf79145 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -16,15 +16,17 @@
 
 package com.android.systemui.communal.ui.compose
 
-import android.appwidget.AppWidgetHostView
 import android.os.Bundle
 import android.util.SizeF
+import android.widget.FrameLayout
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.GridItemSpan
 import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
@@ -44,9 +46,11 @@ import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
-import com.android.systemui.communal.ui.model.CommunalContentUiModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.MediaHostState
 import com.android.systemui.res.R
 
 @Composable
@@ -54,8 +58,7 @@ fun CommunalHub(
     modifier: Modifier = Modifier,
     viewModel: CommunalViewModel,
 ) {
-    val showTutorial by viewModel.showTutorialContent.collectAsState(initial = false)
-    val widgetContent by viewModel.widgetContent.collectAsState(initial = emptyList())
+    val communalContent by viewModel.communalContent.collectAsState(initial = emptyList())
     Box(
         modifier = modifier.fillMaxSize().background(Color.White),
     ) {
@@ -65,31 +68,22 @@ fun CommunalHub(
             horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
             verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
         ) {
-            if (showTutorial) {
-                items(
-                    count = tutorialContentSizes.size,
-                    // TODO(b/308148193): a more scalable solution for unique ids.
-                    key = { index -> "tutorial_$index" },
-                    span = { index -> GridItemSpan(tutorialContentSizes[index].span) },
-                ) { index ->
-                    TutorialCard(
-                        modifier =
-                            Modifier.size(Dimensions.CardWidth, tutorialContentSizes[index].dp()),
-                    )
-                }
-            } else {
-                items(
-                    count = widgetContent.size,
-                    key = { index -> widgetContent[index].id },
-                    span = { index -> GridItemSpan(widgetContent[index].size.span) },
-                ) { index ->
-                    val widget = widgetContent[index]
-                    ContentCard(
-                        modifier = Modifier.size(Dimensions.CardWidth, widget.size.dp()),
-                        model = widget,
-                        deleteOnClick = viewModel::onDeleteWidget
-                    )
-                }
+            items(
+                count = communalContent.size,
+                key = { index -> communalContent[index].key },
+                span = { index -> GridItemSpan(communalContent[index].size.span) },
+            ) { index ->
+                CommunalContent(
+                    modifier = Modifier.fillMaxHeight().width(Dimensions.CardWidth),
+                    model = communalContent[index],
+                    viewModel = viewModel,
+                    deleteOnClick = viewModel::onDeleteWidget,
+                    size =
+                        SizeF(
+                            Dimensions.CardWidth.value,
+                            communalContent[index].size.dp().value,
+                        ),
+                )
             }
         }
         IconButton(onClick = viewModel::onOpenWidgetPicker) {
@@ -101,15 +95,26 @@ fun CommunalHub(
     }
 }
 
-// A placeholder for tutorial content.
 @Composable
-private fun TutorialCard(modifier: Modifier = Modifier) {
-    Card(modifier = modifier, content = {})
+private fun CommunalContent(
+    model: CommunalContentModel,
+    viewModel: CommunalViewModel,
+    size: SizeF,
+    deleteOnClick: (id: Int) -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    when (model) {
+        is CommunalContentModel.Widget -> WidgetContent(model, size, deleteOnClick, modifier)
+        is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier)
+        is CommunalContentModel.Tutorial -> TutorialContent(modifier)
+        is CommunalContentModel.Umo -> Umo(viewModel, modifier)
+    }
 }
 
 @Composable
-private fun ContentCard(
-    model: CommunalContentUiModel,
+private fun WidgetContent(
+    model: CommunalContentModel.Widget,
+    size: SizeF,
     deleteOnClick: (id: Int) -> Unit,
     modifier: Modifier = Modifier,
 ) {
@@ -117,31 +122,70 @@ private fun ContentCard(
     Box(
         modifier = modifier.fillMaxSize().background(Color.White),
     ) {
-        // TODO(b/308148193): this will be cleaned up soon once the change to convert to
-        // CommunalContentUiModel interface is merged
-        val widgetId = getWidgetId(model.id)
-        widgetId?.let {
-            IconButton(onClick = { deleteOnClick(it) }) {
-                Icon(
-                    Icons.Default.Close,
-                    LocalContext.current.getString(R.string.button_to_remove_widget)
-                )
-            }
+        IconButton(onClick = { deleteOnClick(model.appWidgetId) }) {
+            Icon(
+                Icons.Default.Close,
+                LocalContext.current.getString(R.string.button_to_remove_widget)
+            )
         }
         AndroidView(
             modifier = modifier,
-            factory = {
-                model.view.apply {
-                    if (this is AppWidgetHostView) {
-                        val size = SizeF(Dimensions.CardWidth.value, model.size.dp().value)
-                        updateAppWidgetSize(Bundle.EMPTY, listOf(size))
-                    }
-                }
+            factory = { context ->
+                model.appWidgetHost
+                    .createView(context, model.appWidgetId, model.providerInfo)
+                    .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
             },
+            // For reusing composition in lazy lists.
+            onReset = {}
         )
     }
 }
 
+@Composable
+private fun SmartspaceContent(
+    model: CommunalContentModel.Smartspace,
+    modifier: Modifier = Modifier,
+) {
+    AndroidView(
+        modifier = modifier,
+        factory = { context ->
+            FrameLayout(context).apply { addView(model.remoteViews.apply(context, this)) }
+        },
+        // For reusing composition in lazy lists.
+        onReset = {}
+    )
+}
+
+@Composable
+private fun TutorialContent(modifier: Modifier = Modifier) {
+    Card(modifier = modifier, content = {})
+}
+
+@Composable
+private fun Umo(viewModel: CommunalViewModel, modifier: Modifier = Modifier) {
+    AndroidView(
+        modifier =
+            modifier
+                .width(Dimensions.CardWidth)
+                .height(Dimensions.CardHeightThird)
+                .padding(Dimensions.Spacing),
+        factory = {
+            viewModel.mediaHost.expansion = MediaHostState.EXPANDED
+            viewModel.mediaHost.showsOnlyActiveMedia = false
+            viewModel.mediaHost.falsingProtectionNeeded = false
+            viewModel.mediaHost.init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
+            viewModel.mediaHost.hostView.layoutParams =
+                FrameLayout.LayoutParams(
+                    FrameLayout.LayoutParams.MATCH_PARENT,
+                    FrameLayout.LayoutParams.MATCH_PARENT
+                )
+            viewModel.mediaHost.hostView
+        },
+        // For reusing composition in lazy lists.
+        onReset = {},
+    )
+}
+
 private fun CommunalContentSize.dp(): Dp {
     return when (this) {
         CommunalContentSize.FULL -> Dimensions.CardHeightFull
@@ -150,23 +194,6 @@ private fun CommunalContentSize.dp(): Dp {
     }
 }
 
-private fun getWidgetId(id: String): Int? {
-    return if (id.startsWith("widget_")) id.substring("widget_".length).toInt() else null
-}
-
-// Sizes for the tutorial placeholders.
-private val tutorialContentSizes =
-    listOf(
-        CommunalContentSize.FULL,
-        CommunalContentSize.THIRD,
-        CommunalContentSize.THIRD,
-        CommunalContentSize.THIRD,
-        CommunalContentSize.HALF,
-        CommunalContentSize.HALF,
-        CommunalContentSize.HALF,
-        CommunalContentSize.HALF,
-    )
-
 private object Dimensions {
     val CardWidth = 464.dp
     val CardHeightFull = 630.dp
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c2117ae5bda40814b6f8814a1bc3d48091db3748
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 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.bouncer.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.SceneTestUtils
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class EmergencyServicesRepositoryImplTest : SysuiTestCase() {
+
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
+
+    private lateinit var underTest: EmergencyServicesRepository
+
+    @Before
+    fun setUp() {
+        overrideResource(
+            R.bool.config_enable_emergency_call_while_sim_locked,
+            ENABLE_EMERGENCY_CALL_WHILE_SIM_LOCKED
+        )
+
+        underTest =
+            EmergencyServicesRepository(
+                resources = context.resources,
+                applicationScope = testScope.backgroundScope,
+                configurationRepository = utils.configurationRepository,
+            )
+    }
+
+    @Test
+    fun enableEmergencyCallWhileSimLocked() =
+        testScope.runTest {
+            val enableEmergencyCallWhileSimLocked by
+                collectLastValue(underTest.enableEmergencyCallWhileSimLocked)
+
+            setEmergencyCallWhileSimLocked(isEnabled = false)
+            assertThat(enableEmergencyCallWhileSimLocked).isFalse()
+
+            setEmergencyCallWhileSimLocked(isEnabled = true)
+            assertThat(enableEmergencyCallWhileSimLocked).isTrue()
+        }
+
+    private fun TestScope.setEmergencyCallWhileSimLocked(isEnabled: Boolean) {
+        overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, isEnabled)
+        utils.configurationRepository.onConfigurationChange()
+        runCurrent()
+    }
+
+    companion object {
+        private const val ENABLE_EMERGENCY_CALL_WHILE_SIM_LOCKED = true
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..fde3ad723d718b3bbc7e8500025788afcd4c7c03
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright 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.bouncer.domain.interactor
+
+import android.app.ActivityTaskManager
+import android.telecom.TelecomManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.internal.logging.nano.MetricsProto
+import com.android.internal.logging.testing.FakeMetricsLogger
+import com.android.internal.util.EmergencyAffordanceManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.eq
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BouncerActionButtonInteractorTest : SysuiTestCase() {
+
+    @Mock private lateinit var activityTaskManager: ActivityTaskManager
+    @Mock private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager
+    @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var tableLogger: TableLogBuffer
+    @Mock private lateinit var telecomManager: TelecomManager
+
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
+    private val metricsLogger = FakeMetricsLogger()
+    private var currentUserId: Int = 0
+    private var needsEmergencyAffordance = true
+
+    private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
+
+    private lateinit var underTest: BouncerActionButtonInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        overrideResource(R.string.lockscreen_emergency_call, MESSAGE_EMERGENCY_CALL)
+        overrideResource(R.string.lockscreen_return_to_call, MESSAGE_RETURN_TO_CALL)
+        overrideResource(
+            R.bool.config_enable_emergency_call_while_sim_locked,
+            ENABLE_EMERGENCY_CALL_WHILE_SIM_LOCKED
+        )
+        whenever(selectedUserInteractor.getSelectedUserId()).thenReturn(currentUserId)
+        whenever(emergencyAffordanceManager.needsEmergencyAffordance())
+            .thenReturn(needsEmergencyAffordance)
+        whenever(telecomManager.isInCall).thenReturn(false)
+
+        utils.featureFlags.set(REFACTOR_GETCURRENTUSER, true)
+
+        mobileConnectionsRepository =
+            FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger)
+
+        utils.telephonyRepository.setHasTelephonyRadio(true)
+
+        underTest =
+            utils.bouncerActionButtonInteractor(
+                mobileConnectionsRepository = mobileConnectionsRepository,
+                activityTaskManager = activityTaskManager,
+                telecomManager = telecomManager,
+                emergencyAffordanceManager = emergencyAffordanceManager,
+                metricsLogger = metricsLogger,
+            )
+    }
+
+    @Test
+    fun noTelephonyRadio_noButton() =
+        testScope.runTest {
+            utils.telephonyRepository.setHasTelephonyRadio(false)
+            underTest =
+                utils.bouncerActionButtonInteractor(
+                    mobileConnectionsRepository = mobileConnectionsRepository,
+                    activityTaskManager = activityTaskManager,
+                    telecomManager = telecomManager,
+                )
+
+            val actionButton by collectLastValue(underTest.actionButton)
+            assertThat(actionButton).isNull()
+        }
+
+    @Test
+    fun noTelecomManager_noButton() =
+        testScope.runTest {
+            underTest =
+                utils.bouncerActionButtonInteractor(
+                    mobileConnectionsRepository = mobileConnectionsRepository,
+                    activityTaskManager = activityTaskManager,
+                    telecomManager = null,
+                )
+            val actionButton by collectLastValue(underTest.actionButton)
+            assertThat(actionButton).isNull()
+        }
+
+    @Test
+    fun duringCall_returnToCallButton() =
+        testScope.runTest {
+            val actionButton by collectLastValue(underTest.actionButton)
+            utils.telephonyRepository.setIsInCall(true)
+
+            assertThat(actionButton).isNotNull()
+            assertThat(actionButton?.label).isEqualTo(MESSAGE_RETURN_TO_CALL)
+            assertThat(actionButton?.onClick).isNotNull()
+            assertThat(actionButton?.onLongClick).isNull()
+
+            actionButton?.onClick?.invoke()
+
+            assertThat(metricsLogger.logs.size).isEqualTo(1)
+            assertThat(metricsLogger.logs.element().category)
+                .isEqualTo(MetricsProto.MetricsEvent.ACTION_EMERGENCY_CALL)
+            verify(activityTaskManager).stopSystemLockTaskMode()
+            verify(telecomManager).showInCallScreen(eq(false))
+        }
+
+    @Test
+    fun noCall_secureAuthMethod_emergencyCallButton() =
+        testScope.runTest {
+            val actionButton by collectLastValue(underTest.actionButton)
+            mobileConnectionsRepository.isAnySimSecure.value = false
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.telephonyRepository.setIsInCall(false)
+
+            assertThat(actionButton).isNotNull()
+            assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL)
+            assertThat(actionButton?.onClick).isNotNull()
+            assertThat(actionButton?.onLongClick).isNotNull()
+
+            actionButton?.onClick?.invoke()
+
+            assertThat(metricsLogger.logs.size).isEqualTo(1)
+            assertThat(metricsLogger.logs.element().category)
+                .isEqualTo(MetricsProto.MetricsEvent.ACTION_EMERGENCY_CALL)
+            verify(activityTaskManager).stopSystemLockTaskMode()
+
+            // TODO(b/25189994): Test the activity has been started once we switch to the
+            //  ActivityStarter interface here.
+            verify(emergencyAffordanceManager, never()).performEmergencyCall()
+
+            actionButton?.onLongClick?.invoke()
+            verify(emergencyAffordanceManager).performEmergencyCall()
+        }
+
+    @Test
+    fun noCall_insecureAuthMethodButSecureSim_emergencyCallButton() =
+        testScope.runTest {
+            val actionButton by collectLastValue(underTest.actionButton)
+            mobileConnectionsRepository.isAnySimSecure.value = true
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.telephonyRepository.setIsInCall(false)
+
+            assertThat(actionButton).isNotNull()
+            assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL)
+            assertThat(actionButton?.onClick).isNotNull()
+            assertThat(actionButton?.onLongClick).isNotNull()
+        }
+
+    @Test
+    fun noCall_insecure_noButton() =
+        testScope.runTest {
+            val actionButton by collectLastValue(underTest.actionButton)
+            mobileConnectionsRepository.isAnySimSecure.value = false
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.telephonyRepository.setIsInCall(false)
+
+            assertThat(actionButton).isNull()
+        }
+
+    @Test
+    fun noCall_simSecureButEmergencyNotSupported_noButton() =
+        testScope.runTest {
+            val actionButton by collectLastValue(underTest.actionButton)
+            mobileConnectionsRepository.isAnySimSecure.value = true
+            overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, false)
+            utils.configurationRepository.onConfigurationChange()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.telephonyRepository.setIsInCall(false)
+            runCurrent()
+
+            assertThat(actionButton).isNull()
+        }
+
+    companion object {
+        private const val MESSAGE_EMERGENCY_CALL = "Emergency"
+        private const val MESSAGE_RETURN_TO_CALL = "Return to call"
+        private const val ENABLE_EMERGENCY_CALL_WHILE_SIM_LOCKED = true
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7196de6608cb0de4fef8b0293fe311f18c39ee73
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.communal.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.data.repository.SceneContainerRepository
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalRepositoryImplTest : SysuiTestCase() {
+    private lateinit var underTest: CommunalRepositoryImpl
+
+    private lateinit var testScope: TestScope
+
+    private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
+    private lateinit var sceneContainerFlags: FakeSceneContainerFlags
+    private lateinit var sceneContainerRepository: SceneContainerRepository
+
+    @Before
+    fun setUp() {
+        testScope = TestScope()
+
+        val sceneTestUtils = SceneTestUtils(this)
+        sceneContainerFlags = FakeSceneContainerFlags(enabled = false)
+        sceneContainerRepository = sceneTestUtils.fakeSceneContainerRepository()
+        featureFlagsClassic = FakeFeatureFlagsClassic()
+
+        featureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
+
+        underTest =
+            CommunalRepositoryImpl(
+                featureFlagsClassic,
+                sceneContainerFlags,
+                sceneContainerRepository,
+            )
+    }
+
+    @Test
+    fun isCommunalShowing_sceneContainerDisabled_onCommunalScene_true() =
+        testScope.runTest {
+            underTest.setDesiredScene(CommunalSceneKey.Communal)
+
+            val isCommunalHubShowing by collectLastValue(underTest.isCommunalHubShowing)
+            assertThat(isCommunalHubShowing).isTrue()
+        }
+
+    @Test
+    fun isCommunalShowing_sceneContainerDisabled_onBlankScene_false() =
+        testScope.runTest {
+            underTest.setDesiredScene(CommunalSceneKey.Blank)
+
+            val isCommunalHubShowing by collectLastValue(underTest.isCommunalHubShowing)
+            assertThat(isCommunalHubShowing).isFalse()
+        }
+
+    @Test
+    fun isCommunalShowing_sceneContainerEnabled_onCommunalScene_true() =
+        testScope.runTest {
+            sceneContainerFlags = FakeSceneContainerFlags(enabled = true)
+            underTest =
+                CommunalRepositoryImpl(
+                    featureFlagsClassic,
+                    sceneContainerFlags,
+                    sceneContainerRepository,
+                )
+
+            sceneContainerRepository.setDesiredScene(SceneModel(key = SceneKey.Communal))
+
+            val isCommunalHubShowing by collectLastValue(underTest.isCommunalHubShowing)
+            assertThat(isCommunalHubShowing).isTrue()
+        }
+
+    @Test
+    fun isCommunalShowing_sceneContainerEnabled_onLockscreenScene_false() =
+        testScope.runTest {
+            sceneContainerFlags = FakeSceneContainerFlags(enabled = true)
+            underTest =
+                CommunalRepositoryImpl(
+                    featureFlagsClassic,
+                    sceneContainerFlags,
+                    sceneContainerRepository,
+                )
+
+            sceneContainerRepository.setDesiredScene(SceneModel(key = SceneKey.Lockscreen))
+
+            val isCommunalHubShowing by collectLastValue(underTest.isCommunalHubShowing)
+            assertThat(isCommunalHubShowing).isFalse()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..9a3129f2e962aaea37ae13829337b1a4248a3bfe
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -0,0 +1,184 @@
+/*
+ * 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.communal.domain.interactor
+
+import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
+import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
+import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_STARTED
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+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.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalTutorialInteractorTest : SysuiTestCase() {
+
+    @Mock private lateinit var userTracker: UserTracker
+
+    private lateinit var testScope: TestScope
+    private lateinit var underTest: CommunalTutorialInteractor
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var communalTutorialRepository: FakeCommunalTutorialRepository
+    private lateinit var communalRepository: FakeCommunalRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        testScope = TestScope()
+
+        val withDeps = CommunalTutorialInteractorFactory.create(testScope)
+        keyguardRepository = withDeps.keyguardRepository
+        communalTutorialRepository = withDeps.communalTutorialRepository
+        communalRepository = withDeps.communalRepository
+
+        underTest = withDeps.communalTutorialInteractor
+
+        whenever(userTracker.userHandle).thenReturn(mock())
+    }
+
+    @Test
+    fun tutorialUnavailable_whenKeyguardNotVisible() =
+        testScope.runTest {
+            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
+            keyguardRepository.setKeyguardShowing(false)
+            assertThat(isTutorialAvailable).isFalse()
+        }
+
+    @Test
+    fun tutorialUnavailable_whenTutorialIsCompleted() =
+        testScope.runTest {
+            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            communalRepository.setIsCommunalHubShowing(false)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+            assertThat(isTutorialAvailable).isFalse()
+        }
+
+    @Test
+    fun tutorialAvailable_whenTutorialNotStarted() =
+        testScope.runTest {
+            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            communalRepository.setIsCommunalHubShowing(false)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
+            assertThat(isTutorialAvailable).isTrue()
+        }
+
+    @Test
+    fun tutorialAvailable_whenTutorialIsStarted() =
+        testScope.runTest {
+            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            communalRepository.setIsCommunalHubShowing(true)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
+            assertThat(isTutorialAvailable).isTrue()
+        }
+
+    @Test
+    fun tutorialState_notStartedAndCommunalSceneShowing_tutorialStarted() =
+        testScope.runTest {
+            val tutorialSettingState by
+                collectLastValue(communalTutorialRepository.tutorialSettingState)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
+
+            communalRepository.setIsCommunalHubShowing(true)
+
+            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED)
+        }
+
+    @Test
+    fun tutorialState_startedAndCommunalSceneShowing_stateWillNotUpdate() =
+        testScope.runTest {
+            val tutorialSettingState by
+                collectLastValue(communalTutorialRepository.tutorialSettingState)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
+
+            communalRepository.setIsCommunalHubShowing(true)
+
+            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED)
+        }
+
+    @Test
+    fun tutorialState_completedAndCommunalSceneShowing_stateWillNotUpdate() =
+        testScope.runTest {
+            val tutorialSettingState by
+                collectLastValue(communalTutorialRepository.tutorialSettingState)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+            communalRepository.setIsCommunalHubShowing(true)
+
+            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
+        }
+
+    @Test
+    fun tutorialState_notStartedAndCommunalSceneNotShowing_stateWillNotUpdate() =
+        testScope.runTest {
+            val tutorialSettingState by
+                collectLastValue(communalTutorialRepository.tutorialSettingState)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
+
+            communalRepository.setIsCommunalHubShowing(false)
+
+            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED)
+        }
+
+    @Test
+    fun tutorialState_startedAndCommunalSceneNotShowing_tutorialCompleted() =
+        testScope.runTest {
+            val tutorialSettingState by
+                collectLastValue(communalTutorialRepository.tutorialSettingState)
+            communalRepository.setIsCommunalHubShowing(true)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
+
+            communalRepository.setIsCommunalHubShowing(false)
+
+            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
+        }
+
+    @Test
+    fun tutorialState_completedAndCommunalSceneNotShowing_stateWillNotUpdate() =
+        testScope.runTest {
+            val tutorialSettingState by
+                collectLastValue(communalTutorialRepository.tutorialSettingState)
+            communalRepository.setIsCommunalHubShowing(true)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+            communalRepository.setIsCommunalHubShowing(false)
+
+            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
similarity index 54%
rename from packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
index 020903057eb3acc64045da80c4fd0a3a314e0751..262795fe0eb6de07dc0f4a5fd449fc9c0f04df3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
@@ -17,48 +17,62 @@
 
 package com.android.systemui.telephony.data.repository
 
+import android.telecom.TelecomManager
 import android.telephony.TelephonyCallback
+import android.telephony.TelephonyManager
+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.scene.SceneTestUtils
 import com.android.systemui.telephony.TelephonyListenerManager
 import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class TelephonyRepositoryImplTest : SysuiTestCase() {
 
     @Mock private lateinit var manager: TelephonyListenerManager
+    @Mock private lateinit var telecomManager: TelecomManager
+
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
 
     private lateinit var underTest: TelephonyRepositoryImpl
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        whenever(telecomManager.isInCall).thenReturn(false)
 
         underTest =
             TelephonyRepositoryImpl(
+                applicationScope = testScope.backgroundScope,
                 applicationContext = context,
+                backgroundDispatcher = utils.testDispatcher,
                 manager = manager,
+                telecomManager = telecomManager,
             )
     }
 
     @Test
     fun callState() =
-        runBlocking(IMMEDIATE) {
-            var callState: Int? = null
-            val job = underTest.callState.onEach { callState = it }.launchIn(this)
+        testScope.runTest {
+            val callState by collectLastValue(underTest.callState)
+            runCurrent()
+
             val listenerCaptor = kotlinArgumentCaptor<TelephonyCallback.CallStateListener>()
             verify(manager).addCallStateListener(listenerCaptor.capture())
             val listener = listenerCaptor.value
@@ -71,13 +85,25 @@ class TelephonyRepositoryImplTest : SysuiTestCase() {
 
             listener.onCallStateChanged(2)
             assertThat(callState).isEqualTo(2)
+        }
 
-            job.cancel()
+    @Test
+    fun isInCall() =
+        testScope.runTest {
+            val isInCall by collectLastValue(underTest.isInCall)
+            runCurrent()
 
-            verify(manager).removeCallStateListener(listener)
-        }
+            val listenerCaptor = kotlinArgumentCaptor<TelephonyCallback.CallStateListener>()
+            verify(manager).addCallStateListener(listenerCaptor.capture())
+            val listener = listenerCaptor.value
+            whenever(telecomManager.isInCall).thenReturn(true)
+            listener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK)
 
-    companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-    }
+            assertThat(isInCall).isTrue()
+
+            whenever(telecomManager.isInCall).thenReturn(false)
+            listener.onCallStateChanged(TelephonyManager.CALL_STATE_IDLE)
+
+            assertThat(isInCall).isFalse()
+        }
 }
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9a3c6d5b322f28b91acc5178ebcaadc9d7d23515..8780f58743cdce5e2b026aae8f6a50a389bd54c3 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3266,9 +3266,4 @@
     <string name="privacy_dialog_active_app_usage_2">In use by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g> \u2022 <xliff:g id="proxy_label" example="Speech services">%3$s</xliff:g>)</string>
     <!-- Label for recent app usage of a phone sensor with sub-attribution and proxy label in the privacy dialog [CHAR LIMIT=NONE] -->
     <string name="privacy_dialog_recent_app_usage_2">Recently used by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g> \u2022 <xliff:g id="proxy_label" example="Speech services">%3$s</xliff:g>)</string>
-
-    <!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] -->
-    <string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
-    <!-- Content description for keyboard backlight brightness value [CHAR LIMIT=NONE] -->
-    <string name="keyboard_backlight_value">Level %1$d of %2$d</string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index 5de370f22eb8d98a5865344562a24f914eeb0927..77338410642c469fbc630ccb981ea5b776aa25ec 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -28,7 +28,6 @@ import android.os.PowerManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.telecom.TelecomManager;
-import android.telephony.TelephonyManager;
 import android.util.Log;
 
 import androidx.annotation.Nullable;
@@ -54,21 +53,20 @@ import javax.inject.Inject;
 /** View Controller for {@link com.android.keyguard.EmergencyButton}. */
 @KeyguardBouncerScope
 public class EmergencyButtonController extends ViewController<EmergencyButton> {
-    static final String LOG_TAG = "EmergencyButton";
+    private static final String TAG = "EmergencyButton";
     private final ConfigurationController mConfigurationController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final TelephonyManager mTelephonyManager;
     private final PowerManager mPowerManager;
     private final ActivityTaskManager mActivityTaskManager;
-    private ShadeController mShadeController;
+    private final ShadeController mShadeController;
     private final TelecomManager mTelecomManager;
     private final MetricsLogger mMetricsLogger;
 
     private EmergencyButtonCallback mEmergencyButtonCallback;
-    private LockPatternUtils mLockPatternUtils;
-    private Executor mMainExecutor;
-    private Executor mBackgroundExecutor;
-    private SelectedUserInteractor mSelectedUserInteractor;
+    private final LockPatternUtils mLockPatternUtils;
+    private final Executor mMainExecutor;
+    private final Executor mBackgroundExecutor;
+    private final SelectedUserInteractor mSelectedUserInteractor;
 
     private final KeyguardUpdateMonitorCallback mInfoCallback =
             new KeyguardUpdateMonitorCallback() {
@@ -93,17 +91,18 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> {
     @VisibleForTesting
     public EmergencyButtonController(@Nullable EmergencyButton view,
             ConfigurationController configurationController,
-            KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
-            PowerManager powerManager, ActivityTaskManager activityTaskManager,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            PowerManager powerManager,
+            ActivityTaskManager activityTaskManager,
             ShadeController shadeController,
-            @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
+            @Nullable TelecomManager telecomManager,
+            MetricsLogger metricsLogger,
             LockPatternUtils lockPatternUtils,
             Executor mainExecutor, Executor backgroundExecutor,
             SelectedUserInteractor selectedUserInteractor) {
         super(view);
         mConfigurationController = configurationController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mTelephonyManager = telephonyManager;
         mPowerManager = powerManager;
         mActivityTaskManager = activityTaskManager;
         mShadeController = shadeController;
@@ -183,7 +182,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> {
                 } else {
                     mKeyguardUpdateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
                     if (mTelecomManager == null) {
-                        Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
+                        Log.wtf(TAG, "TelecomManager was null, cannot launch emergency dialer");
                         return;
                     }
                     Intent emergencyDialIntent =
@@ -212,10 +211,9 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> {
     public static class Factory {
         private final ConfigurationController mConfigurationController;
         private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-        private final TelephonyManager mTelephonyManager;
         private final PowerManager mPowerManager;
         private final ActivityTaskManager mActivityTaskManager;
-        private ShadeController mShadeController;
+        private final ShadeController mShadeController;
         @Nullable
         private final TelecomManager mTelecomManager;
         private final MetricsLogger mMetricsLogger;
@@ -226,10 +224,12 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> {
 
         @Inject
         public Factory(ConfigurationController configurationController,
-                KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
-                PowerManager powerManager, ActivityTaskManager activityTaskManager,
+                KeyguardUpdateMonitor keyguardUpdateMonitor,
+                PowerManager powerManager,
+                ActivityTaskManager activityTaskManager,
                 ShadeController shadeController,
-                @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
+                @Nullable TelecomManager telecomManager,
+                MetricsLogger metricsLogger,
                 LockPatternUtils lockPatternUtils,
                 @Main Executor mainExecutor,
                 @Background Executor backgroundExecutor,
@@ -237,7 +237,6 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> {
 
             mConfigurationController = configurationController;
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-            mTelephonyManager = telephonyManager;
             mPowerManager = powerManager;
             mActivityTaskManager = activityTaskManager;
             mShadeController = shadeController;
@@ -252,9 +251,9 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> {
         /** Construct an {@link com.android.keyguard.EmergencyButtonController}. */
         public EmergencyButtonController create(EmergencyButton view) {
             return new EmergencyButtonController(view, mConfigurationController,
-                    mKeyguardUpdateMonitor, mTelephonyManager, mPowerManager, mActivityTaskManager,
-                    mShadeController, mTelecomManager, mMetricsLogger, mLockPatternUtils,
-                    mMainExecutor, mBackgroundExecutor, mSelectedUserInteractor);
+                    mKeyguardUpdateMonitor, mPowerManager, mActivityTaskManager, mShadeController,
+                    mTelecomManager, mMetricsLogger, mLockPatternUtils, mMainExecutor,
+                    mBackgroundExecutor, mSelectedUserInteractor);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt
new file mode 100644
index 0000000000000000000000000000000000000000..bba00506df85533a3c8ac84a5e62d0b07a65ab49
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.bouncer.data.repository
+
+import android.content.res.Resources
+import com.android.internal.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.dagger.qualifiers.Main
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Encapsulates Emergency Services related state. */
+@SysUISingleton
+class EmergencyServicesRepository
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    @Main private val resources: Resources,
+    configurationRepository: ConfigurationRepository,
+) {
+    /**
+     * Whether to enable emergency services calls while the SIM card is locked. This is disabled in
+     * certain countries that don't support this.
+     */
+    val enableEmergencyCallWhileSimLocked: StateFlow<Boolean> =
+        configurationRepository.onConfigurationChange
+            .map { getEnableEmergencyCallWhileSimLocked() }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = getEnableEmergencyCallWhileSimLocked()
+            )
+
+    private fun getEnableEmergencyCallWhileSimLocked(): Boolean {
+        return resources.getBoolean(R.bool.config_enable_emergency_call_while_sim_locked)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f36ef6630a4802aaf4fadc8c40054b5f7e3e391e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt
@@ -0,0 +1,181 @@
+/*
+ * 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.bouncer.domain.interactor
+
+import android.annotation.SuppressLint
+import android.app.ActivityOptions
+import android.app.ActivityTaskManager
+import android.content.Context
+import android.content.Intent
+import android.os.UserHandle
+import android.telecom.TelecomManager
+import com.android.internal.R
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent
+import com.android.internal.util.EmergencyAffordanceManager
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.bouncer.data.repository.EmergencyServicesRepository
+import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.doze.DozeLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.EmergencyDialerConstants
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.withContext
+
+/**
+ * Encapsulates business logic and application state for the bouncer action button. The action
+ * button can support multiple different actions, depending on device state.
+ */
+@SysUISingleton
+class BouncerActionButtonInteractor
+@Inject
+constructor(
+    @Application private val applicationContext: Context,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val repository: EmergencyServicesRepository,
+    // TODO(b/307977401): Replace with `MobileConnectionsInteractor` when available.
+    private val mobileConnectionsRepository: MobileConnectionsRepository,
+    private val telephonyInteractor: TelephonyInteractor,
+    private val authenticationInteractor: AuthenticationInteractor,
+    private val selectedUserInteractor: SelectedUserInteractor,
+    private val activityTaskManager: ActivityTaskManager,
+    private val telecomManager: TelecomManager?,
+    private val emergencyAffordanceManager: EmergencyAffordanceManager,
+    private val emergencyDialerIntentFactory: EmergencyDialerIntentFactory,
+    private val metricsLogger: MetricsLogger,
+    private val dozeLogger: DozeLogger,
+) {
+    /** The bouncer action button. If `null`, the button should not be shown. */
+    val actionButton: Flow<BouncerActionButtonModel?> =
+        if (telecomManager == null || !telephonyInteractor.hasTelephonyRadio) {
+            flowOf(null)
+        } else {
+            merge(
+                    telephonyInteractor.isInCall.asUnitFlow,
+                    mobileConnectionsRepository.isAnySimSecure.asUnitFlow,
+                    authenticationInteractor.authenticationMethod.asUnitFlow,
+                    repository.enableEmergencyCallWhileSimLocked.asUnitFlow,
+                )
+                .map {
+                    when {
+                        isReturnToCallButton() -> returnToCallButtonModel
+                        isEmergencyCallButton() -> emergencyCallButtonModel
+                        else -> null // Do not show the button.
+                    }
+                }
+                .distinctUntilChanged()
+        }
+
+    private val returnToCallButtonModel: BouncerActionButtonModel by lazy {
+        BouncerActionButtonModel(
+            label = applicationContext.getString(R.string.lockscreen_return_to_call),
+            onClick = {
+                prepareToPerformAction()
+                returnToCall()
+            },
+            onLongClick = null
+        )
+    }
+
+    private val emergencyCallButtonModel: BouncerActionButtonModel by lazy {
+        BouncerActionButtonModel(
+            label = applicationContext.getString(R.string.lockscreen_emergency_call),
+            onClick = {
+                prepareToPerformAction()
+                dozeLogger.logEmergencyCall()
+                startEmergencyDialerActivity()
+            },
+            // TODO(b/308001302): The long click detector doesn't work properly, investigate.
+            onLongClick = {
+                if (emergencyAffordanceManager.needsEmergencyAffordance()) {
+                    prepareToPerformAction()
+
+                    // TODO(b/298026988): Check that !longPressWasDragged before invoking.
+                    emergencyAffordanceManager.performEmergencyCall()
+                }
+            }
+        )
+    }
+
+    private fun startEmergencyDialerActivity() {
+        emergencyDialerIntentFactory()?.apply {
+            flags =
+                Intent.FLAG_ACTIVITY_NEW_TASK or
+                    Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or
+                    Intent.FLAG_ACTIVITY_CLEAR_TOP
+
+            putExtra(
+                EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
+                EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON,
+            )
+
+            // TODO(b/25189994): Use the ActivityStarter interface instead.
+            applicationContext.startActivityAsUser(
+                this,
+                ActivityOptions.makeCustomAnimation(applicationContext, 0, 0).toBundle(),
+                UserHandle(selectedUserInteractor.getSelectedUserId())
+            )
+        }
+    }
+
+    private fun isReturnToCallButton() = telephonyInteractor.isInCall.value
+
+    private suspend fun isEmergencyCallButton(): Boolean {
+        return if (mobileConnectionsRepository.getIsAnySimSecure()) {
+            // Some countries can't handle emergency calls while SIM is locked.
+            repository.enableEmergencyCallWhileSimLocked.value
+        } else {
+            // Only show if there is a secure screen (password/pin/pattern/SIM pin/SIM puk).
+            withContext(backgroundDispatcher) {
+                authenticationInteractor.getAuthenticationMethod().isSecure
+            }
+        }
+    }
+
+    private fun prepareToPerformAction() {
+        // TODO(b/308001302): Trigger occlusion and resetting bouncer state.
+        metricsLogger.action(MetricsEvent.ACTION_EMERGENCY_CALL)
+        activityTaskManager.stopSystemLockTaskMode()
+    }
+
+    @SuppressLint("MissingPermission")
+    private fun returnToCall() {
+        telecomManager?.showInCallScreen(/* showDialpad = */ false)
+    }
+
+    private val <T> Flow<T>.asUnitFlow: Flow<Unit>
+        get() = map {}
+}
+
+/**
+ * Creates an intent to launch the Emergency Services dialer. If no [TelecomManager] is present,
+ * returns `null`.
+ */
+interface EmergencyDialerIntentFactory {
+    operator fun invoke(): Intent?
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e398c930e86e52f815176e2c62396e04ef0dcac7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 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.bouncer.domain.interactor
+
+import android.content.Context
+import android.content.Intent
+import android.telecom.TelecomManager
+import com.android.internal.util.EmergencyAffordanceManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import dagger.Module
+import dagger.Provides
+
+/** Module for providing interactor-related objects for the bouncer. */
+@Module
+object BouncerInteractorModule {
+
+    @Provides
+    fun emergencyDialerIntentFactory(
+        telecomManager: TelecomManager?
+    ): EmergencyDialerIntentFactory {
+        return object : EmergencyDialerIntentFactory {
+            override fun invoke(): Intent? {
+                return telecomManager?.createLaunchEmergencyDialerIntent(/* number = */ null)
+            }
+        }
+    }
+
+    @Provides
+    @SysUISingleton
+    fun emergencyAffordanceManager(
+        @Application applicationContext: Context,
+    ): EmergencyAffordanceManager {
+        return EmergencyAffordanceManager(applicationContext)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerActionButtonModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerActionButtonModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7f1730cb2abdd5b18ba86abb2865f06cbedd6798
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerActionButtonModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 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.bouncer.shared.model
+
+/** Models the action button on the bouncer. */
+data class BouncerActionButtonModel(
+    /** The text to be shown on the button. */
+    val label: String,
+
+    /** The action to perform when the user clicks on the button. */
+    val onClick: () -> Unit,
+
+    /**
+     * The action to perform when the user long-clicks on the button. When not provided, long-clicks
+     * will be treated as regular clicks.
+     */
+    val onLongClick: (() -> Unit)? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index ef0609a99e0576c0f6d54382ab3e0f860168b89b..0f7772454b782eda29797a933f1a706fdc4b49c7 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -21,14 +21,15 @@ import android.graphics.Bitmap
 import androidx.core.graphics.drawable.toBitmap
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
-import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
 import com.android.systemui.user.ui.viewmodel.UserActionViewModel
 import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
 import com.android.systemui.user.ui.viewmodel.UserViewModel
@@ -59,10 +60,10 @@ class BouncerViewModel(
     private val bouncerInteractor: BouncerInteractor,
     authenticationInteractor: AuthenticationInteractor,
     flags: SceneContainerFlags,
-    private val telephonyInteractor: TelephonyInteractor,
     selectedUser: Flow<UserViewModel>,
     users: Flow<List<UserViewModel>>,
     userSwitcherMenu: Flow<List<UserActionViewModel>>,
+    actionButtonInteractor: BouncerActionButtonInteractor,
 ) {
     val selectedUserImage: StateFlow<Bitmap?> =
         selectedUser
@@ -150,8 +151,16 @@ class BouncerViewModel(
                     ),
             )
 
-    val isEmergencyButtonVisible: Boolean
-        get() = telephonyInteractor.hasTelephonyRadio
+    /**
+     * The bouncer action button (Return to Call / Emergency Call). If `null`, the button should not
+     * be shown.
+     */
+    val actionButton: StateFlow<BouncerActionButtonModel?> =
+        actionButtonInteractor.actionButton.stateIn(
+            scope = applicationScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = null
+        )
 
     init {
         if (flags.isEnabled()) {
@@ -176,11 +185,6 @@ class BouncerViewModel(
         }
     }
 
-    /** Notifies that the emergency services button was clicked. */
-    fun onEmergencyServicesButtonClicked() {
-        // TODO(b/280877228): implement this
-    }
-
     /** Notifies that a throttling dialog has been dismissed by the user. */
     fun onThrottlingDialogDismissed() {
         _throttlingDialogMessage.value = null
@@ -271,8 +275,8 @@ object BouncerViewModelModule {
         bouncerInteractor: BouncerInteractor,
         authenticationInteractor: AuthenticationInteractor,
         flags: SceneContainerFlags,
-        telephonyInteractor: TelephonyInteractor,
         userSwitcherViewModel: UserSwitcherViewModel,
+        actionButtonInteractor: BouncerActionButtonInteractor,
     ): BouncerViewModel {
         return BouncerViewModel(
             applicationContext = applicationContext,
@@ -281,10 +285,10 @@ object BouncerViewModelModule {
             bouncerInteractor = bouncerInteractor,
             authenticationInteractor = authenticationInteractor,
             flags = flags,
-            telephonyInteractor = telephonyInteractor,
             selectedUser = userSwitcherViewModel.selectedUser,
             users = userSwitcherViewModel.users,
             userSwitcherMenu = userSwitcherViewModel.menu,
+            actionButtonInteractor = actionButtonInteractor,
         )
     }
 }
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
index e4492743271961482362ce6381e416dc859d9fee..7fa762a6614fa5c9b0a04a5b394ecad12016c52f 100644
--- 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
@@ -44,11 +44,15 @@ import kotlinx.coroutines.flow.stateIn
 interface ConfigurationRepository {
     /** Called whenever ui mode, theme or configuration has changed. */
     val onAnyConfigurationChange: Flow<Unit>
+
+    /** Called whenever the configuration has changed. */
+    val onConfigurationChange: Flow<Unit>
+
     val scaleForResolution: Flow<Float>
 
     fun getResolutionScale(): Float
 
-    /** Convience to context.resources.getDimensionPixelSize() */
+    /** Convenience to context.resources.getDimensionPixelSize() */
     fun getDimensionPixelSize(id: Int): Int
 }
 
@@ -87,7 +91,7 @@ constructor(
             awaitClose { configurationController.removeCallback(callback) }
         }
 
-    private val configurationChange: Flow<Unit> =
+    override val onConfigurationChange: Flow<Unit> =
         ConflatedCallbackFlow.conflatedCallbackFlow {
             val callback =
                 object : ConfigurationController.ConfigurationListener {
@@ -100,7 +104,7 @@ constructor(
         }
 
     override val scaleForResolution: StateFlow<Float> =
-        configurationChange
+        onConfigurationChange
             .mapLatest { getResolutionScale() }
             .distinctUntilChanged()
             .stateIn(scope, SharingStarted.WhileSubscribed(), getResolutionScale())
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index c3421de8f6637f3c6e2914896e16ce3ee8a53e35..273adcf83271ff7f951a07024a105181391f82ed 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.dagger
 
 import com.android.systemui.communal.data.db.CommunalDatabaseModule
+import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
@@ -26,6 +27,7 @@ import dagger.Module
     includes =
         [
             CommunalRepositoryModule::class,
+            CommunalMediaRepositoryModule::class,
             CommunalTutorialRepositoryModule::class,
             CommunalWidgetRepositoryModule::class,
             CommunalDatabaseModule::class,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e41c32261c11cffd54bf3d32bfd0310c8581fefc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.communal.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.pipeline.MediaDataManager
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.onCompletion
+import kotlinx.coroutines.flow.onStart
+
+/** Encapsulates the state of smartspace in communal. */
+interface CommunalMediaRepository {
+    val mediaPlaying: Flow<Boolean>
+}
+
+@SysUISingleton
+class CommunalMediaRepositoryImpl
+@Inject
+constructor(
+    private val mediaDataManager: MediaDataManager,
+) : CommunalMediaRepository {
+
+    private val mediaDataListener =
+        object : MediaDataManager.Listener {
+            override fun onMediaDataLoaded(
+                key: String,
+                oldKey: String?,
+                data: MediaData,
+                immediately: Boolean,
+                receivedSmartspaceCardLatency: Int,
+                isSsReactivated: Boolean
+            ) {
+                if (!mediaDataManager.hasAnyMediaOrRecommendation()) {
+                    return
+                }
+                _mediaPlaying.value = true
+            }
+
+            override fun onMediaDataRemoved(key: String) {
+                if (mediaDataManager.hasAnyMediaOrRecommendation()) {
+                    return
+                }
+                _mediaPlaying.value = false
+            }
+        }
+
+    private val _mediaPlaying: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    override val mediaPlaying: Flow<Boolean> =
+        _mediaPlaying
+            .onStart {
+                mediaDataManager.addListener(mediaDataListener)
+                _mediaPlaying.value = mediaDataManager.hasAnyMediaOrRecommendation()
+            }
+            .onCompletion { mediaDataManager.removeListener(mediaDataListener) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2c6d9e4c39c3cfb66d2a2638ad7a685430dbd258
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryModule.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.communal.data.repository
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface CommunalMediaRepositoryModule {
+    @Binds fun communalMediaRepository(impl: CommunalMediaRepositoryImpl): CommunalMediaRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index b40570bccc55c08495092c445b5e4bedc053b00c..3119b9e98bac5ae3e872b61bc4c412f51fe2a86d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -21,16 +21,24 @@ import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.scene.data.repository.SceneContainerRepository
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.model.SceneKey
 import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
 
 /** Encapsulates the state of communal mode. */
 interface CommunalRepository {
     /** Whether communal features are enabled. */
     val isCommunalEnabled: Boolean
 
+    /** Whether the communal hub is showing. */
+    val isCommunalHubShowing: Flow<Boolean>
+
     /**
      * Target scene as requested by the underlying [SceneTransitionLayout] or through
      * [setDesiredScene].
@@ -46,6 +54,8 @@ class CommunalRepositoryImpl
 @Inject
 constructor(
     private val featureFlagsClassic: FeatureFlagsClassic,
+    sceneContainerFlags: SceneContainerFlags,
+    sceneContainerRepository: SceneContainerRepository,
 ) : CommunalRepository {
     override val isCommunalEnabled: Boolean
         get() = featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
@@ -57,4 +67,11 @@ constructor(
     override fun setDesiredScene(desiredScene: CommunalSceneKey) {
         _desiredScene.value = desiredScene
     }
+
+    override val isCommunalHubShowing: Flow<Boolean> =
+        if (sceneContainerFlags.isEnabled()) {
+            sceneContainerRepository.desiredScene.map { scene -> scene.key == SceneKey.Communal }
+        } else {
+            desiredScene.map { sceneKey -> sceneKey == CommunalSceneKey.Communal }
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 2c683ee828aab06b468975460b01bb42d24efeb0..eb36b19972f98d0f2f717e8a169fe1c17a9ffc6c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -16,25 +16,39 @@
 
 package com.android.systemui.communal.domain.interactor
 
+import android.app.smartspace.SmartspaceTarget
+import android.appwidget.AppWidgetHost
 import android.content.ComponentName
+import com.android.systemui.communal.data.repository.CommunalMediaRepository
 import com.android.systemui.communal.data.repository.CommunalRepository
 import com.android.systemui.communal.data.repository.CommunalWidgetRepository
+import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.shared.model.CommunalSceneKey
-import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.smartspace.data.repository.SmartspaceRepository
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 
 /** Encapsulates business-logic related to communal mode. */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class CommunalInteractor
 @Inject
 constructor(
     private val communalRepository: CommunalRepository,
     private val widgetRepository: CommunalWidgetRepository,
+    mediaRepository: CommunalMediaRepository,
+    smartspaceRepository: SmartspaceRepository,
+    tutorialInteractor: CommunalTutorialInteractor,
+    private val appWidgetHost: AppWidgetHost,
 ) {
 
     /** Whether communal features are enabled. */
@@ -44,14 +58,6 @@ constructor(
     /** A flow of info about the widget to be displayed, or null if widget is unavailable. */
     val appWidgetInfo: Flow<CommunalAppWidgetInfo?> = widgetRepository.stopwatchAppWidgetInfo
 
-    /**
-     * A flow of information about widgets to be shown in communal hub.
-     *
-     * Currently only showing persistent widgets that have been bound to the app widget service
-     * (have an allocated id).
-     */
-    val widgetContent: Flow<List<CommunalWidgetContentModel>> = widgetRepository.communalWidgets
-
     /**
      * Target scene as requested by the underlying [SceneTransitionLayout] or through
      * [onSceneChanged].
@@ -76,4 +82,72 @@ constructor(
 
     /** Delete a widget by id. */
     fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id)
+
+    /** A list of all the communal content to be displayed in the communal hub. */
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val communalContent: Flow<List<CommunalContentModel>> =
+        tutorialInteractor.isTutorialAvailable.flatMapLatest { isTutorialMode ->
+            if (isTutorialMode) {
+                return@flatMapLatest flowOf(tutorialContent)
+            }
+            combine(smartspaceContent, umoContent, widgetContent) { smartspace, umo, widgets ->
+                smartspace + umo + widgets
+            }
+        }
+
+    /** A list of widget content to be displayed in the communal hub. */
+    private val widgetContent: Flow<List<CommunalContentModel.Widget>> =
+        widgetRepository.communalWidgets.map { widgets ->
+            widgets.map Widget@{ widget ->
+                return@Widget CommunalContentModel.Widget(
+                    appWidgetId = widget.appWidgetId,
+                    providerInfo = widget.providerInfo,
+                    appWidgetHost = appWidgetHost,
+                )
+            }
+        }
+
+    /** A flow of available smartspace content. Currently only showing timer targets. */
+    private val smartspaceContent: Flow<List<CommunalContentModel.Smartspace>> =
+        if (!smartspaceRepository.isSmartspaceRemoteViewsEnabled) {
+            flowOf(emptyList())
+        } else {
+            smartspaceRepository.lockscreenSmartspaceTargets.map { targets ->
+                targets
+                    .filter { target ->
+                        target.featureType == SmartspaceTarget.FEATURE_TIMER &&
+                            target.remoteViews != null
+                    }
+                    .map Target@{ target ->
+                        return@Target CommunalContentModel.Smartspace(
+                            smartspaceTargetId = target.smartspaceTargetId,
+                            remoteViews = target.remoteViews!!,
+                            // Smartspace always as HALF for now.
+                            size = CommunalContentSize.HALF,
+                        )
+                    }
+            }
+        }
+
+    /** A list of tutorial content to be displayed in the communal hub in tutorial mode. */
+    private val tutorialContent: List<CommunalContentModel.Tutorial> =
+        listOf(
+            CommunalContentModel.Tutorial(id = 0, CommunalContentSize.FULL),
+            CommunalContentModel.Tutorial(id = 1, CommunalContentSize.THIRD),
+            CommunalContentModel.Tutorial(id = 2, CommunalContentSize.THIRD),
+            CommunalContentModel.Tutorial(id = 3, CommunalContentSize.THIRD),
+            CommunalContentModel.Tutorial(id = 4, CommunalContentSize.HALF),
+            CommunalContentModel.Tutorial(id = 5, CommunalContentSize.HALF),
+            CommunalContentModel.Tutorial(id = 6, CommunalContentSize.HALF),
+            CommunalContentModel.Tutorial(id = 7, CommunalContentSize.HALF),
+        )
+
+    private val umoContent: Flow<List<CommunalContentModel.Umo>> =
+        mediaRepository.mediaPlaying.flatMapLatest { mediaPlaying ->
+            if (mediaPlaying) {
+                flowOf(listOf(CommunalContentModel.Umo(CommunalContentSize.THIRD)))
+            } else {
+                flowOf(emptyList())
+            }
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
index 7f43eb5682d653ca69bc832c7f31a87dbf00969a..5ca89f28f1fc249ab0d37e242ae7cbc365d0a7b2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
@@ -17,13 +17,11 @@
 package com.android.systemui.communal.domain.interactor
 
 import android.provider.Settings
+import com.android.systemui.communal.data.repository.CommunalRepository
 import com.android.systemui.communal.data.repository.CommunalTutorialRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
-import com.android.systemui.scene.shared.model.SceneKey
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -46,9 +44,7 @@ constructor(
     @Application private val scope: CoroutineScope,
     private val communalTutorialRepository: CommunalTutorialRepository,
     keyguardInteractor: KeyguardInteractor,
-    private val communalInteractor: CommunalInteractor,
-    private val sceneContainerFlags: SceneContainerFlags,
-    private val sceneInteractor: SceneInteractor,
+    private val communalRepository: CommunalRepository,
 ) {
     /** An observable for whether the tutorial is available. */
     val isTutorialAvailable: Flow<Boolean> =
@@ -74,17 +70,11 @@ constructor(
                 if (tutorialSettingState == Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) {
                     return@flatMapLatest flowOf(null)
                 }
-                if (sceneContainerFlags.isEnabled()) {
-                    sceneInteractor.desiredScene.map { sceneModel ->
-                        nextStateAfterTransition(
-                            tutorialSettingState,
-                            sceneModel.key == SceneKey.Communal
-                        )
-                    }
-                } else {
-                    communalInteractor.isCommunalShowing.map {
-                        nextStateAfterTransition(tutorialSettingState, it)
-                    }
+                communalRepository.isCommunalHubShowing.map { isCommunalShowing ->
+                    nextStateAfterTransition(
+                        tutorialSettingState,
+                        isCommunalShowing,
+                    )
                 }
             }
             .filterNotNull()
@@ -102,7 +92,7 @@ constructor(
 
     private var job: Job? = null
     private fun listenForTransitionToUpdateTutorialState() {
-        if (!communalInteractor.isCommunalEnabled) {
+        if (!communalRepository.isCommunalEnabled) {
             return
         }
         job =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..bb9b4b5f522f468f485096472e26c3beb8e197e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.communal.domain.model
+
+import android.appwidget.AppWidgetHost
+import android.appwidget.AppWidgetProviderInfo
+import android.widget.RemoteViews
+import com.android.systemui.communal.shared.model.CommunalContentSize
+
+/** Encapsulates data for a communal content. */
+sealed interface CommunalContentModel {
+    /** Unique key across all types of content models. */
+    val key: String
+
+    /** Size to be rendered in the grid. */
+    val size: CommunalContentSize
+
+    class Widget(
+        val appWidgetId: Int,
+        val providerInfo: AppWidgetProviderInfo,
+        val appWidgetHost: AppWidgetHost,
+    ) : CommunalContentModel {
+        override val key = "widget_$appWidgetId"
+        // Widget size is always half.
+        override val size = CommunalContentSize.HALF
+    }
+
+    class Tutorial(
+        id: Int,
+        override val size: CommunalContentSize,
+    ) : CommunalContentModel {
+        override val key = "tutorial_$id"
+    }
+
+    class Smartspace(
+        smartspaceTargetId: String,
+        val remoteViews: RemoteViews,
+        override val size: CommunalContentSize,
+    ) : CommunalContentModel {
+        override val key = "smartspace_$smartspaceTargetId"
+    }
+
+    class Umo(
+        override val size: CommunalContentSize,
+    ) : CommunalContentModel {
+        override val key = UMO_KEY
+    }
+
+    companion object {
+        /** Key for the [Umo] in CommunalContentModel. There should only ever be one UMO. */
+        const val UMO_KEY = "umo"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
deleted file mode 100644
index b60dc2a21699bc4f20496cda1a76bce565b7c436..0000000000000000000000000000000000000000
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.android.systemui.communal.ui.model
-
-import android.view.View
-import com.android.systemui.communal.shared.model.CommunalContentSize
-
-/**
- * Encapsulates data for a communal content that holds a view.
- *
- * This model stays in the UI layer.
- */
-data class CommunalContentUiModel(
-    val id: String,
-    val view: View,
-    val size: CommunalContentSize = CommunalContentSize.HALF,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
index d8d1dc0c11ef757c220af776d7ed01413a0da86e..702554ab4943345a927d92bb0a7f1307ea2747b0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
@@ -33,8 +33,8 @@ constructor(
     defaultCommunalWidgetSection: DefaultCommunalWidgetSection,
 ) : KeyguardBlueprint {
     override val id: String = COMMUNAL
-    override val sections: Set<KeyguardSection> =
-        setOf(
+    override val sections: List<KeyguardSection> =
+        listOf(
             defaultCommunalHubSection,
             defaultCommunalWidgetSection,
         )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 197dece47cc5cd45052ca3d5aac81cb3aa0bd7ad..5efe6ceeb65acf7a2d118e32e100c88e184dc8f2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -16,53 +16,33 @@
 
 package com.android.systemui.communal.ui.viewmodel
 
-import android.appwidget.AppWidgetHost
 import android.content.ComponentName
-import android.content.Context
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
+import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalSceneKey
-import com.android.systemui.communal.ui.model.CommunalContentUiModel
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.dagger.MediaModule
 import javax.inject.Inject
+import javax.inject.Named
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
 
 @SysUISingleton
 class CommunalViewModel
 @Inject
 constructor(
-    @Application private val context: Context,
-    private val appWidgetHost: AppWidgetHost,
     private val communalInteractor: CommunalInteractor,
-    tutorialInteractor: CommunalTutorialInteractor,
+    @Named(MediaModule.COMMUNAL_HUB) val mediaHost: MediaHost,
 ) {
-    /** Whether communal hub should show tutorial content. */
-    val showTutorialContent: Flow<Boolean> = tutorialInteractor.isTutorialAvailable
-
-    /** List of widgets to be displayed in the communal hub. */
-    val widgetContent: Flow<List<CommunalContentUiModel>> =
-        communalInteractor.widgetContent.map { widgets ->
-            widgets.map Widget@{ widget ->
-                // TODO(b/306406256): As adding and removing widgets functionalities are
-                // supported, cache the host views so they're not recreated each time.
-                val hostView =
-                    appWidgetHost.createView(context, widget.appWidgetId, widget.providerInfo)
-                return@Widget CommunalContentUiModel(
-                    // TODO(b/308148193): a more scalable solution for unique ids.
-                    id = "widget_${widget.appWidgetId}",
-                    view = hostView,
-                )
-            }
-        }
-
     val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
     fun onSceneChanged(scene: CommunalSceneKey) {
         communalInteractor.onSceneChanged(scene)
     }
 
+    /** A list of all the communal content to be displayed in the communal hub. */
+    val communalContent: Flow<List<CommunalContentModel>> = communalInteractor.communalContent
+
     /** Delete a widget by id. */
     fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
 
diff --git a/packages/SystemUI/src/com/android/systemui/complication/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/complication/SmartSpaceComplication.java
index b98794ef8026ea0c47cddd14f85c9f3ba8603e4d..9bb23d8564e42b282137b8e466ac0041e080c973 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/SmartSpaceComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/SmartSpaceComplication.java
@@ -71,6 +71,18 @@ public class SmartSpaceComplication implements Complication {
         private final DreamOverlayStateController mDreamOverlayStateController;
         private final SmartSpaceComplication mComplication;
         private final FeatureFlags mFeatureFlags;
+        private final DreamOverlayStateController.Callback mStateControllerCallback =
+                new DreamOverlayStateController.Callback() {
+                    @Override
+                    public void onStateChanged() {
+                        if (mDreamOverlayStateController.isOverlayActive()) {
+                            mSmartSpaceController.addListener(mSmartspaceListener);
+                        } else {
+                            mSmartSpaceController.removeListener(mSmartspaceListener);
+                            mDreamOverlayStateController.removeComplication(mComplication);
+                        }
+                    }
+                };
 
         private final BcSmartspaceDataPlugin.SmartspaceTargetListener mSmartspaceListener =
                 new BcSmartspaceDataPlugin.SmartspaceTargetListener() {
@@ -103,17 +115,7 @@ public class SmartSpaceComplication implements Complication {
                 return;
             }
 
-            mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() {
-                @Override
-                public void onStateChanged() {
-                    if (mDreamOverlayStateController.isOverlayActive()) {
-                        mSmartSpaceController.addListener(mSmartspaceListener);
-                    } else {
-                        mSmartSpaceController.removeListener(mSmartspaceListener);
-                        mDreamOverlayStateController.removeComplication(mComplication);
-                    }
-                }
-            });
+            mDreamOverlayStateController.addCallback(mStateControllerCallback);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 58dcf06916874ebb963786984ab09eda6551e92d..b34b4599cf70b54a3cefcbbe19f304689e322f3c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -37,6 +37,7 @@ import com.android.systemui.biometrics.FingerprintReEnrollNotification;
 import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
 import com.android.systemui.biometrics.dagger.BiometricsModule;
 import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule;
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractorModule;
 import com.android.systemui.bouncer.ui.BouncerViewModule;
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
@@ -129,6 +130,7 @@ import com.android.systemui.user.domain.UserDomainLayerModule;
 import com.android.systemui.util.concurrency.SysUIConcurrencyModule;
 import com.android.systemui.util.dagger.UtilModule;
 import com.android.systemui.util.kotlin.CoroutinesModule;
+import com.android.systemui.util.reference.ReferenceModule;
 import com.android.systemui.util.sensors.SensorModule;
 import com.android.systemui.util.settings.SettingsUtilModule;
 import com.android.systemui.util.time.SystemClock;
@@ -166,6 +168,7 @@ import javax.inject.Named;
         AuthenticationModule.class,
         BiometricsModule.class,
         BiometricsDomainLayerModule.class,
+        BouncerInteractorModule.class,
         BouncerViewModule.class,
         ClipboardOverlayModule.class,
         ClockRegistryModule.class,
@@ -199,6 +202,7 @@ import javax.inject.Named;
         PrivacyModule.class,
         QRCodeScannerModule.class,
         QSFragmentStartableModule.class,
+        ReferenceModule.class,
         RetailModeModule.class,
         ScreenshotModule.class,
         SensorModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
index cf868856c4198d4de08ae77549e7b43370c09b53..20a9e5d572c9e0a59ea6eb599e4fa9f897e63ec3 100644
--- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
@@ -20,15 +20,18 @@ import android.companion.virtual.VirtualDeviceManager
 import android.companion.virtual.flags.Flags
 import android.view.Display
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.display.data.repository.DisplayRepository
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 
 /** Provides information about an external connected display. */
@@ -81,6 +84,7 @@ constructor(
     private val virtualDeviceManager: VirtualDeviceManager,
     keyguardRepository: KeyguardRepository,
     displayRepository: DisplayRepository,
+    @Background backgroundCoroutineDispatcher: CoroutineDispatcher,
 ) : ConnectedDisplayInteractor {
 
     override val connectedDisplayState: Flow<State> =
@@ -101,6 +105,7 @@ constructor(
                     State.CONNECTED
                 }
             }
+            .flowOn(backgroundCoroutineDispatcher)
             .distinctUntilChanged()
 
     override val connectedDisplayAddition: Flow<Unit> =
@@ -108,6 +113,7 @@ constructor(
             .filter {
                 it != null && (isExternalDisplay(it) || isVirtualDeviceOwnedMirrorDisplay(it))
             }
+            .flowOn(backgroundCoroutineDispatcher)
             .map {} // map to Unit
 
     // Provides the pending display only if the lockscreen is unlocked
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index c9748f9546700c2b57d13a291db80d82e0622a2e..0e333f21dd14aece3c23a223103b3e4dcddcf005 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -31,11 +31,15 @@ import com.android.systemui.flags.Flags;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.dagger.DreamLog;
 import com.android.systemui.statusbar.policy.CallbackController;
+import com.android.systemui.util.annotations.WeaklyReferencedCallback;
+import com.android.systemui.util.reference.WeakReferenceFactory;
 
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -68,7 +72,10 @@ public class DreamOverlayStateController implements
 
     /**
      * Callback for dream overlay events.
+     * NOTE: Caller should maintain a strong reference to this themselves so the callback does
+     * not get garbage collected.
      */
+    @WeaklyReferencedCallback
     public interface Callback {
         /**
          * Called when the composition of complications changes.
@@ -97,7 +104,7 @@ public class DreamOverlayStateController implements
 
     private final Executor mExecutor;
     private final boolean mOverlayEnabled;
-    private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+    private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
 
     @Complication.ComplicationType
     private int mAvailableComplicationTypes = Complication.COMPLICATION_TYPE_NONE;
@@ -107,6 +114,7 @@ public class DreamOverlayStateController implements
     private final Collection<Complication> mComplications = new HashSet();
 
     private final FeatureFlags mFeatureFlags;
+    private final WeakReferenceFactory mWeakReferenceFactory;
 
     private final int mSupportedTypes;
 
@@ -117,11 +125,13 @@ public class DreamOverlayStateController implements
     public DreamOverlayStateController(@Main Executor executor,
             @Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled,
             FeatureFlags featureFlags,
-            @DreamLog LogBuffer logBuffer) {
+            @DreamLog LogBuffer logBuffer,
+            WeakReferenceFactory weakReferenceFactory) {
         mExecutor = executor;
         mOverlayEnabled = overlayEnabled;
         mLogger = new DreamLogger(logBuffer, TAG);
         mFeatureFlags = featureFlags;
+        mWeakReferenceFactory = weakReferenceFactory;
         if (mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)) {
             mSupportedTypes = Complication.COMPLICATION_TYPE_NONE
                     | Complication.COMPLICATION_TYPE_HOME_CONTROLS;
@@ -143,7 +153,7 @@ public class DreamOverlayStateController implements
         mExecutor.execute(() -> {
             if (mComplications.add(complication)) {
                 mLogger.logAddComplication(complication.toString());
-                mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
+                notifyCallbacksLocked(Callback::onComplicationsChanged);
             }
         });
     }
@@ -160,7 +170,7 @@ public class DreamOverlayStateController implements
         mExecutor.execute(() -> {
             if (mComplications.remove(complication)) {
                 mLogger.logRemoveComplication(complication.toString());
-                mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
+                notifyCallbacksLocked(Callback::onComplicationsChanged);
             }
         });
     }
@@ -199,22 +209,33 @@ public class DreamOverlayStateController implements
     }
 
     private void notifyCallbacks(Consumer<Callback> callbackConsumer) {
-        mExecutor.execute(() -> {
-            for (Callback callback : mCallbacks) {
+        mExecutor.execute(() -> notifyCallbacksLocked(callbackConsumer));
+    }
+
+    private void notifyCallbacksLocked(Consumer<Callback> callbackConsumer) {
+        final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
+        while (iterator.hasNext()) {
+            final Callback callback = iterator.next().get();
+            // Remove any callbacks which have been GC'd
+            if (callback == null) {
+                iterator.remove();
+            } else {
                 callbackConsumer.accept(callback);
             }
-        });
+        }
     }
 
     @Override
     public void addCallback(@NonNull Callback callback) {
         mExecutor.execute(() -> {
             Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
-            if (mCallbacks.contains(callback)) {
+            final boolean containsCallback = mCallbacks.stream()
+                    .anyMatch(reference -> reference.get() == callback);
+            if (containsCallback) {
                 return;
             }
 
-            mCallbacks.add(callback);
+            mCallbacks.add(mWeakReferenceFactory.create(callback));
 
             if (mComplications.isEmpty()) {
                 return;
@@ -228,7 +249,13 @@ public class DreamOverlayStateController implements
     public void removeCallback(@NonNull Callback callback) {
         mExecutor.execute(() -> {
             Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
-            mCallbacks.remove(callback);
+            final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
+            while (iterator.hasNext()) {
+                final Callback cb = iterator.next().get();
+                if (cb == null || cb == callback) {
+                    iterator.remove();
+                }
+            }
         });
     }
 
@@ -318,7 +345,7 @@ public class DreamOverlayStateController implements
 
         if (isLowLightActive() && !active) {
             // Notify that we're exiting low light only on the transition from active to not active.
-            mCallbacks.forEach(Callback::onExitLowLight);
+            notifyCallbacks(Callback::onExitLowLight);
         }
         modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_LOW_LIGHT_ACTIVE);
     }
@@ -375,7 +402,7 @@ public class DreamOverlayStateController implements
         mExecutor.execute(() -> {
             mLogger.logAvailableComplicationTypes(types);
             mAvailableComplicationTypes = types;
-            mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
+            notifyCallbacksLocked(Callback::onAvailableComplicationTypesChanged);
         });
     }
 
@@ -393,7 +420,7 @@ public class DreamOverlayStateController implements
         mExecutor.execute(() -> {
             mLogger.logShouldShowComplications(shouldShowComplications);
             mShouldShowComplications = shouldShowComplications;
-            mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
+            notifyCallbacksLocked(Callback::onAvailableComplicationTypesChanged);
         });
     }
 }
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 1e9be09bc3f3677785587f9eaaf53db1b96f29b2..e16bb0bb848214c0f4e4c4c60d2621232f07b30e 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
@@ -30,7 +30,6 @@ import android.view.View
 import android.view.ViewGroup.MarginLayoutParams
 import android.view.Window
 import android.view.WindowManager
-import android.view.accessibility.AccessibilityEvent
 import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
@@ -79,29 +78,23 @@ class KeyboardBacklightDialog(
     private lateinit var stepProperties: StepViewProperties
 
     @ColorInt
-    private val filledRectangleColor =
-        getColorFromStyle(com.android.internal.R.attr.materialColorPrimary)
+    var filledRectangleColor = getColorFromStyle(com.android.internal.R.attr.materialColorPrimary)
     @ColorInt
-    private val emptyRectangleColor =
+    var emptyRectangleColor =
         getColorFromStyle(com.android.internal.R.attr.materialColorOutlineVariant)
     @ColorInt
-    private val backgroundColor =
-        getColorFromStyle(com.android.internal.R.attr.materialColorSurfaceBright)
+    var backgroundColor = getColorFromStyle(com.android.internal.R.attr.materialColorSurfaceBright)
     @ColorInt
-    private val defaultIconColor =
-        getColorFromStyle(com.android.internal.R.attr.materialColorOnPrimary)
+    var defaultIconColor = getColorFromStyle(com.android.internal.R.attr.materialColorOnPrimary)
     @ColorInt
-    private val defaultIconBackgroundColor =
+    var defaultIconBackgroundColor =
         getColorFromStyle(com.android.internal.R.attr.materialColorPrimary)
     @ColorInt
-    private val dimmedIconColor =
-        getColorFromStyle(com.android.internal.R.attr.materialColorOnSurface)
+    var dimmedIconColor = getColorFromStyle(com.android.internal.R.attr.materialColorOnSurface)
     @ColorInt
-    private val dimmedIconBackgroundColor =
+    var dimmedIconBackgroundColor =
         getColorFromStyle(com.android.internal.R.attr.materialColorSurfaceDim)
 
-    private val levelContentDescription = context.getString(R.string.keyboard_backlight_value)
-
     init {
         currentLevel = initialCurrentLevel
         maxLevel = initialMaxLevel
@@ -110,8 +103,6 @@ class KeyboardBacklightDialog(
     override fun onCreate(savedInstanceState: Bundle?) {
         setUpWindowProperties(this)
         setWindowPosition()
-        // title is used for a11y announcement
-        window?.setTitle(context.getString(R.string.keyboard_backlight_dialog_title))
         updateResources()
         rootView = buildRootView()
         setContentView(rootView)
@@ -168,12 +159,6 @@ class KeyboardBacklightDialog(
         currentLevel = current
         updateIconTile()
         updateStepColors()
-        updateAccessibilityInfo()
-    }
-
-    private fun updateAccessibilityInfo() {
-        rootView.contentDescription = String.format(levelContentDescription, currentLevel, maxLevel)
-        rootView.sendAccessibilityEvent(AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION)
     }
 
     private fun updateIconTile() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index 654f2d10620669875e268969be28ef2cc955356e..f5f5571dbb1b9d06217e84090bbf810c3dc68870 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -66,7 +66,7 @@ import kotlinx.coroutines.flow.transformLatest
 
 /**
  * Acts as source of truth for biometric authentication related settings like enrollments, device
- * policy, etc.
+ * policy specifically for device entry usage.
  *
  * Abstracts-away data sources and their schemas so the rest of the app doesn't need to worry about
  * upstream changes.
@@ -74,7 +74,8 @@ import kotlinx.coroutines.flow.transformLatest
 interface BiometricSettingsRepository {
     /**
      * If the current user can enter the device using fingerprint. This is true if user has enrolled
-     * fingerprints and fingerprint auth is not disabled through settings/device policy
+     * fingerprints and fingerprint auth is not disabled for device entry through settings and
+     * device policy
      */
     val isFingerprintEnrolledAndEnabled: StateFlow<Boolean>
 
@@ -247,9 +248,11 @@ constructor(
             }
         }
 
-    private val isFaceEnabledByBiometricsManagerForCurrentUser: Flow<Boolean> =
+    private val areBiometricsEnabledForCurrentUser: Flow<Boolean> =
         userRepository.selectedUserInfo.flatMapLatest { userInfo ->
-            isFaceEnabledByBiometricsManager.map { biometricsEnabledForUser[userInfo.id] ?: false }
+            areBiometricsEnabledForDeviceEntryFromUserSetting.map {
+                biometricsEnabledForUser[userInfo.id] ?: false
+            }
         }
 
     private val isFaceEnabledByDevicePolicy: Flow<Boolean> =
@@ -263,13 +266,13 @@ constructor(
             .distinctUntilChanged()
 
     private val isFaceAuthenticationEnabled: Flow<Boolean> =
-        combine(isFaceEnabledByBiometricsManagerForCurrentUser, isFaceEnabledByDevicePolicy) {
+        combine(areBiometricsEnabledForCurrentUser, isFaceEnabledByDevicePolicy) {
             biometricsManagerSetting,
             devicePolicySetting ->
             biometricsManagerSetting && devicePolicySetting
         }
 
-    private val isFaceEnabledByBiometricsManager: Flow<Pair<Int, Boolean>> =
+    private val areBiometricsEnabledForDeviceEntryFromUserSetting: Flow<Pair<Int, Boolean>> =
         conflatedCallbackFlow {
                 val callback =
                     object : IBiometricEnabledOnKeyguardCallback.Stub() {
@@ -340,6 +343,7 @@ constructor(
 
     override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean> =
         isFingerprintEnrolled
+            .and(areBiometricsEnabledForCurrentUser)
             .and(isFingerprintEnabledByDevicePolicy)
             .stateIn(scope, SharingStarted.Eagerly, false)
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
index f5ef27daecdd7aea2487961efdaee26e54545d6d..fbd62cef7dfbad641544a784825ad57823205fb7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
@@ -17,33 +17,29 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import android.util.Log
 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.keyguard.shared.model.KeyguardBlueprint
 import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
 import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule
 import java.io.PrintWriter
 import java.util.TreeMap
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.launch
 
 /**
  * Manages blueprint changes for the lockscreen.
  *
  * To add a blueprint, create a class that implements LockscreenBlueprint and bind it to the map in
- * the dagger module:
+ * the dagger module: [KeyguardBlueprintModule]
  *
  * A Blueprint determines how the layout should be constrained on a high level.
  *
  * A Section is a modular piece of code that implements the constraints. The blueprint uses the
  * sections to define the constraints.
- *
- * @see KeyguardBlueprintModule
  */
 @SysUISingleton
 class KeyguardBlueprintRepository
@@ -51,18 +47,16 @@ class KeyguardBlueprintRepository
 constructor(
     configurationRepository: ConfigurationRepository,
     blueprints: Set<@JvmSuppressWildcards KeyguardBlueprint>,
-    @Application private val applicationScope: CoroutineScope,
 ) {
     private val blueprintIdMap: TreeMap<String, KeyguardBlueprint> = TreeMap()
     private val _blueprint: MutableSharedFlow<KeyguardBlueprint> = MutableSharedFlow(replay = 1)
     val blueprint: Flow<KeyguardBlueprint> = _blueprint.asSharedFlow()
 
+    val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
+
     init {
         blueprintIdMap.putAll(blueprints.associateBy { it.id })
         applyBlueprint(blueprintIdMap[DEFAULT]!!)
-        applicationScope.launch {
-            configurationRepository.onAnyConfigurationChange.collect { refreshBlueprint() }
-        }
     }
 
     /**
@@ -86,9 +80,18 @@ constructor(
      * @return whether the transition has succeeded.
      */
     fun applyBlueprint(blueprintId: String?): Boolean {
-        val blueprint = blueprintIdMap[blueprintId] ?: return false
-        applyBlueprint(blueprint)
-        return true
+        val blueprint = blueprintIdMap[blueprintId]
+        return if (blueprint != null) {
+            applyBlueprint(blueprint)
+            true
+        } else {
+            Log.e(
+                TAG,
+                "Could not find blueprint with id: $blueprintId. " +
+                    "Perhaps it was not added to KeyguardBlueprintModule?"
+            )
+            false
+        }
     }
 
     /** Emits the blueprint value to the collectors. */
@@ -107,4 +110,8 @@ constructor(
     fun printBlueprints(pw: PrintWriter) {
         blueprintIdMap.onEachIndexed { index, entry -> pw.println("$index: ${entry.key}") }
     }
+
+    companion object {
+        private const val TAG = "KeyguardBlueprintRepository"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index 6ce91854ea56dd8fb5d4b55d853cd68e5cf9513e..7dab84dc7da33f248feb48249c7d2a318dade22c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -17,16 +17,56 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
+import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
+import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
+import com.android.systemui.statusbar.policy.SplitShadeStateController
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
 
 @SysUISingleton
 class KeyguardBlueprintInteractor
 @Inject
-constructor(private val keyguardBlueprintRepository: KeyguardBlueprintRepository) {
+constructor(
+    private val keyguardBlueprintRepository: KeyguardBlueprintRepository,
+    @Application private val applicationScope: CoroutineScope,
+    private val context: Context,
+    private val splitShadeStateController: SplitShadeStateController,
+) {
+
     val blueprint = keyguardBlueprintRepository.blueprint
 
+    init {
+        applicationScope.launch {
+            keyguardBlueprintRepository.configurationChange
+                .onStart { emit(Unit) }
+                .collect { updateBlueprint() }
+        }
+    }
+
+    /**
+     * Detects when a new blueprint should be applied and calls [transitionToBlueprint]. This may
+     * end up reapplying the same blueprint, which is fine as configuration may have changed.
+     */
+    private fun updateBlueprint() {
+        val useSplitShade =
+            splitShadeStateController.shouldUseSplitNotificationShade(context.resources)
+
+        val blueprintId =
+            if (useSplitShade) {
+                SplitShadeKeyguardBlueprint.ID
+            } else {
+                DefaultKeyguardBlueprint.DEFAULT
+            }
+
+        transitionToBlueprint(blueprintId)
+    }
+
     /**
      * Transitions to a blueprint.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt
index 7fc1911a3477887f827a411cfd7a159ae1ae8f78..344044019c0dde7b445fd7212f28aa0084389f5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt
@@ -22,7 +22,7 @@ import androidx.constraintlayout.widget.ConstraintSet
 /** Determines the constraints for the ConstraintSet in the lockscreen root view. */
 interface KeyguardBlueprint {
     val id: String
-    val sections: Set<KeyguardSection>
+    val sections: List<KeyguardSection>
 
     /**
      * Removes views of old blueprint and add views of new blueprint.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index f1ea4469a2ae9f48988ca728005247f626cca6f8..2e64c41bace817d3d44adde61a676ca60f055902 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -32,7 +32,6 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSec
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
-import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
 import javax.inject.Inject
 
 /**
@@ -53,7 +52,6 @@ constructor(
     defaultStatusViewSection: DefaultStatusViewSection,
     defaultStatusBarSection: DefaultStatusBarSection,
     defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
-    splitShadeGuidelines: SplitShadeGuidelines,
     aodNotificationIconsSection: AodNotificationIconsSection,
     aodBurnInSection: AodBurnInSection,
     communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
@@ -63,7 +61,7 @@ constructor(
     override val id: String = DEFAULT
 
     override val sections =
-        setOf(
+        listOf(
             defaultIndicationAreaSection,
             defaultDeviceEntryIconSection,
             defaultShortcutsSection,
@@ -72,7 +70,6 @@ constructor(
             defaultStatusViewSection,
             defaultStatusBarSection,
             defaultNotificationStackScrollLayoutSection,
-            splitShadeGuidelines,
             aodNotificationIconsSection,
             aodBurnInSection,
             communalTutorialIndicatorSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
index fda4c3d5376a6188ac00c652868054d8af1f23f4..4f1a754adbd59983228bdbb8236e5096a6435e3f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
@@ -31,6 +31,12 @@ abstract class KeyguardBlueprintModule {
         defaultLockscreenBlueprint: DefaultKeyguardBlueprint
     ): KeyguardBlueprint
 
+    @Binds
+    @IntoSet
+    abstract fun bindSplitShadeBlueprint(
+        splitShadeBlueprint: SplitShadeKeyguardBlueprint
+    ): KeyguardBlueprint
+
     @Binds
     @IntoSet
     abstract fun bindShortcutsBesideUdfpsLockscreenBlueprint(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index f8dd7c1a58c7e3ee7ddaa89039e4a45236b305f6..d8b368b4a0d38bd59cfa3a05926dfffaaf511469 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -52,7 +52,7 @@ constructor(
     override val id: String = SHORTCUTS_BESIDE_UDFPS
 
     override val sections =
-        setOf(
+        listOf(
             defaultIndicationAreaSection,
             defaultDeviceEntryIconSection,
             defaultAmbientIndicationAreaSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
new file mode 100644
index 0000000000000000000000000000000000000000..35679b84771b4dad07933fe4ed70d2c98b0565e1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.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.systemui.keyguard.ui.view.layout.blueprints
+
+import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
+import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
+import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection
+import javax.inject.Inject
+
+/**
+ * Split-shade layout, mostly used for larger devices like foldables and tablets when in landscape
+ * orientation.
+ */
+@SysUISingleton
+@JvmSuppressWildcards
+class SplitShadeKeyguardBlueprint
+@Inject
+constructor(
+    defaultIndicationAreaSection: DefaultIndicationAreaSection,
+    defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection,
+    defaultShortcutsSection: DefaultShortcutsSection,
+    defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
+    defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
+    defaultStatusViewSection: DefaultStatusViewSection,
+    defaultStatusBarSection: DefaultStatusBarSection,
+    splitShadeNotificationStackScrollLayoutSection: SplitShadeNotificationStackScrollLayoutSection,
+    splitShadeGuidelines: SplitShadeGuidelines,
+    aodNotificationIconsSection: AodNotificationIconsSection,
+    aodBurnInSection: AodBurnInSection,
+    communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
+) : KeyguardBlueprint {
+    override val id: String = ID
+
+    override val sections =
+        listOf(
+            defaultIndicationAreaSection,
+            defaultDeviceEntryIconSection,
+            defaultShortcutsSection,
+            defaultAmbientIndicationAreaSection,
+            defaultSettingsPopupMenuSection,
+            defaultStatusViewSection,
+            defaultStatusBarSection,
+            splitShadeNotificationStackScrollLayoutSection,
+            splitShadeGuidelines,
+            aodNotificationIconsSection,
+            aodBurnInSection,
+            communalTutorialIndicatorSection,
+        )
+
+    companion object {
+        const val ID = "split-shade"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index 199566129eb3f2924a8187284e2c1f76b7032aa2..078fefff394a561fec3f45ddbd95cb7814589831 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -18,9 +18,6 @@
 package com.android.systemui.keyguard.ui.view.layout.sections
 
 import android.content.Context
-import android.view.View
-import android.view.ViewGroup
-import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
 import androidx.constraintlayout.widget.ConstraintSet.END
@@ -29,55 +26,34 @@ import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
-import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
 import javax.inject.Inject
 
+/** Single column format for notifications (default for phones) */
 class DefaultNotificationStackScrollLayoutSection
 @Inject
 constructor(
-    private val context: Context,
-    private val featureFlags: FeatureFlags,
-    private val notificationPanelView: NotificationPanelView,
-    private val sharedNotificationContainer: SharedNotificationContainer,
-    private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
-    private val controller: NotificationStackScrollLayoutController,
-    private val smartspaceViewModel: KeyguardSmartspaceViewModel
-) : KeyguardSection() {
-    private val placeHolderId = R.id.nssl_placeholder
-
-    override fun addViews(constraintLayout: ConstraintLayout) {
-        if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
-            return
-        }
-        // This moves the existing NSSL view to a different parent, as the controller is a
-        // singleton and recreating it has other bad side effects
-        notificationPanelView.findViewById<View?>(R.id.notification_stack_scroller)?.let {
-            (it.parent as ViewGroup).removeView(it)
-            sharedNotificationContainer.addNotificationStackScrollLayout(it)
-        }
-
-        val view = View(context, null).apply { id = placeHolderId }
-        constraintLayout.addView(view)
-    }
-
-    override fun bindData(constraintLayout: ConstraintLayout) {
-        if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
-            return
-        }
-        SharedNotificationContainerBinder.bind(
-            sharedNotificationContainer,
-            sharedNotificationContainerViewModel,
-            controller,
-        )
-    }
-
+    context: Context,
+    featureFlags: FeatureFlags,
+    notificationPanelView: NotificationPanelView,
+    sharedNotificationContainer: SharedNotificationContainer,
+    sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
+    controller: NotificationStackScrollLayoutController,
+    private val smartspaceViewModel: KeyguardSmartspaceViewModel,
+) :
+    NotificationStackScrollLayoutSection(
+        context,
+        featureFlags,
+        notificationPanelView,
+        sharedNotificationContainer,
+        sharedNotificationContainerViewModel,
+        controller,
+    ) {
     override fun applyConstraints(constraintSet: ConstraintSet) {
         if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
             return
@@ -85,41 +61,29 @@ constructor(
         constraintSet.apply {
             val bottomMargin =
                 context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin)
-            val useSplitShade =
-                context.resources.getBoolean(R.bool.config_use_split_notification_shade)
 
-            val topAlignment =
-                if (useSplitShade) {
-                    TOP
-                } else {
-                    BOTTOM
-                }
             if (featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) {
                 connect(
                     R.id.nssl_placeholder,
                     TOP,
                     smartspaceViewModel.smartspaceViewId,
-                    topAlignment,
+                    BOTTOM,
                     bottomMargin
                 )
                 setGoneMargin(R.id.nssl_placeholder, TOP, bottomMargin)
             } else {
-                connect(
-                    R.id.nssl_placeholder,
-                    TOP,
-                    R.id.keyguard_status_view,
-                    topAlignment,
-                    bottomMargin
-                )
+                connect(R.id.nssl_placeholder, TOP, R.id.keyguard_status_view, BOTTOM, bottomMargin)
             }
-
             connect(R.id.nssl_placeholder, START, PARENT_ID, START)
             connect(R.id.nssl_placeholder, END, PARENT_ID, END)
-            connect(R.id.nssl_placeholder, BOTTOM, R.id.lock_icon_view, TOP)
-        }
-    }
 
-    override fun removeViews(constraintLayout: ConstraintLayout) {
-        constraintLayout.removeView(placeHolderId)
+            val lockId =
+                if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
+                    R.id.device_entry_icon_view
+                } else {
+                    R.id.lock_icon_view
+                }
+            connect(R.id.nssl_placeholder, BOTTOM, lockId, TOP)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
new file mode 100644
index 0000000000000000000000000000000000000000..00966f235a5789dd783ca9667f8edb0b811896b8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.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.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.res.R
+import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
+import kotlinx.coroutines.DisposableHandle
+
+abstract class NotificationStackScrollLayoutSection
+constructor(
+    protected val context: Context,
+    protected val featureFlags: FeatureFlags,
+    private val notificationPanelView: NotificationPanelView,
+    private val sharedNotificationContainer: SharedNotificationContainer,
+    private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
+    private val controller: NotificationStackScrollLayoutController,
+) : KeyguardSection() {
+    private val placeHolderId = R.id.nssl_placeholder
+    private var disposableHandle: DisposableHandle? = null
+
+    override fun addViews(constraintLayout: ConstraintLayout) {
+        if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+            return
+        }
+        // This moves the existing NSSL view to a different parent, as the controller is a
+        // singleton and recreating it has other bad side effects
+        notificationPanelView.findViewById<View?>(R.id.notification_stack_scroller)?.let {
+            (it.parent as ViewGroup).removeView(it)
+            sharedNotificationContainer.addNotificationStackScrollLayout(it)
+        }
+
+        val view = View(context, null).apply { id = placeHolderId }
+        constraintLayout.addView(view)
+    }
+
+    override fun bindData(constraintLayout: ConstraintLayout) {
+        if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+            return
+        }
+        disposableHandle?.dispose()
+        disposableHandle =
+            SharedNotificationContainerBinder.bind(
+                sharedNotificationContainer,
+                sharedNotificationContainerViewModel,
+                controller,
+            )
+    }
+
+    override fun removeViews(constraintLayout: ConstraintLayout) {
+        disposableHandle?.dispose()
+        constraintLayout.removeView(placeHolderId)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
new file mode 100644
index 0000000000000000000000000000000000000000..bf95c77229e9660109e46b642f6f2bbef2d6a41a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
+import com.android.systemui.res.R
+import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
+import javax.inject.Inject
+
+/** Large-screen format for notifications, shown as two columns on the device */
+class SplitShadeNotificationStackScrollLayoutSection
+@Inject
+constructor(
+    context: Context,
+    featureFlags: FeatureFlags,
+    notificationPanelView: NotificationPanelView,
+    sharedNotificationContainer: SharedNotificationContainer,
+    sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
+    controller: NotificationStackScrollLayoutController,
+    private val smartspaceViewModel: KeyguardSmartspaceViewModel,
+) :
+    NotificationStackScrollLayoutSection(
+        context,
+        featureFlags,
+        notificationPanelView,
+        sharedNotificationContainer,
+        sharedNotificationContainerViewModel,
+        controller,
+    ) {
+    override fun applyConstraints(constraintSet: ConstraintSet) {
+        if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+            return
+        }
+        constraintSet.apply {
+            val bottomMargin =
+                context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin)
+
+            if (featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) {
+                connect(
+                    R.id.nssl_placeholder,
+                    TOP,
+                    smartspaceViewModel.smartspaceViewId,
+                    TOP,
+                    bottomMargin
+                )
+                setGoneMargin(R.id.nssl_placeholder, TOP, bottomMargin)
+            } else {
+                connect(R.id.nssl_placeholder, TOP, R.id.keyguard_status_view, TOP, bottomMargin)
+            }
+            connect(R.id.nssl_placeholder, START, PARENT_ID, START)
+            connect(R.id.nssl_placeholder, END, PARENT_ID, END)
+
+            val lockId =
+                if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
+                    R.id.device_entry_icon_view
+                } else {
+                    R.id.lock_icon_view
+                }
+            connect(R.id.nssl_placeholder, BOTTOM, lockId, TOP)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
index 83a6e58cc44479e335bfcaaf5214924e478a221e..773c292befcf36a0d99d5a0e2d2b6fafd3d7a508 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
@@ -23,11 +23,14 @@ import android.net.Uri
 import android.os.Handler
 import android.os.UserHandle
 import android.provider.Settings
+import android.util.Log
 import android.view.View
 import android.view.ViewGroup
 import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.media.dagger.MediaModule.KEYGUARD
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
@@ -36,7 +39,11 @@ import com.android.systemui.statusbar.notification.stack.MediaContainerView
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.SplitShadeStateController
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.println
 import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.withIncreasedIndent
+import java.io.PrintWriter
 import javax.inject.Inject
 import javax.inject.Named
 
@@ -55,13 +62,18 @@ constructor(
     private val secureSettings: SecureSettings,
     @Main private val handler: Handler,
     configurationController: ConfigurationController,
-    private val splitShadeStateController: SplitShadeStateController
-) {
+    private val splitShadeStateController: SplitShadeStateController,
+    dumpManager: DumpManager,
+) : Dumpable {
+    /** It's added for debugging purpose to directly see last received StatusBarState. */
+    private var lastReceivedStatusBarState = -1
 
     init {
+        dumpManager.registerDumpable(this)
         statusBarStateController.addCallback(
             object : StatusBarStateController.StateListener {
                 override fun onStateChanged(newState: Int) {
+                    lastReceivedStatusBarState = newState
                     refreshMediaPosition()
                 }
 
@@ -206,7 +218,17 @@ constructor(
     }
 
     fun refreshMediaPosition() {
-        val keyguardOrUserSwitcher = (statusBarStateController.state == StatusBarState.KEYGUARD)
+        val currentState = statusBarStateController.state
+        if (lastReceivedStatusBarState != -1 && currentState != lastReceivedStatusBarState) {
+            Log.wtfStack(
+                TAG,
+                "currentState[${StatusBarState.toString(currentState)}] is " +
+                    "different from the last " +
+                    "received one[${StatusBarState.toString(lastReceivedStatusBarState)}]."
+            )
+        }
+
+        val keyguardOrUserSwitcher = (currentState == StatusBarState.KEYGUARD)
         // mediaHost.visible required for proper animations handling
         visible =
             mediaHost.visible &&
@@ -263,4 +285,34 @@ constructor(
             visibilityChangedListener?.invoke(newVisibility == View.VISIBLE)
         }
     }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.asIndenting().run {
+            println("KeyguardMediaController")
+            withIncreasedIndent {
+                println("Self", this@KeyguardMediaController)
+                println("visible", visible)
+                println("useSplitShade", useSplitShade)
+                println("allowMediaPlayerOnLockScreen", allowMediaPlayerOnLockScreen)
+                println("bypassController.bypassEnabled", bypassController.bypassEnabled)
+                println("isDozeWakeUpAnimationWaiting", isDozeWakeUpAnimationWaiting)
+                println("singlePaneContainer", singlePaneContainer)
+                println("splitShadeContainer", splitShadeContainer)
+                if (lastReceivedStatusBarState != -1) {
+                    println(
+                        "lastReceivedStatusBarState",
+                        StatusBarState.toString(lastReceivedStatusBarState)
+                    )
+                }
+                println(
+                    "statusBarStateController.state",
+                    StatusBarState.toString(statusBarStateController.state)
+                )
+            }
+        }
+    }
+
+    private companion object {
+        private const val TAG = "KeyguardMediaController"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
similarity index 75%
rename from packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
index eea369f15a16fd646b6da41fafd314919e5e54bc..654fffe8947126d1dcce3dcee0db36d86b513147 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.mediaprojection.permission
 
+import android.app.AlertDialog
 import android.content.Context
 import android.os.Bundle
 import android.view.Gravity
@@ -28,36 +29,36 @@ import android.widget.ArrayAdapter
 import android.widget.ImageView
 import android.widget.Spinner
 import android.widget.TextView
+import androidx.annotation.CallSuper
 import androidx.annotation.ColorRes
 import androidx.annotation.DrawableRes
 import androidx.annotation.LayoutRes
 import androidx.annotation.StringRes
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.DialogDelegate
 
 /** Base permission dialog for screen share and recording */
-open class BaseScreenSharePermissionDialog(
-    context: Context,
+abstract class BaseMediaProjectionPermissionDialogDelegate<T : AlertDialog>(
     private val screenShareOptions: List<ScreenShareOption>,
     private val appName: String?,
     private val hostUid: Int,
     private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
     @DrawableRes private val dialogIconDrawable: Int? = null,
     @ColorRes private val dialogIconTint: Int? = null,
-) : SystemUIDialog(context), AdapterView.OnItemSelectedListener {
+) : DialogDelegate<T>, AdapterView.OnItemSelectedListener {
     private lateinit var dialogTitle: TextView
     private lateinit var startButton: TextView
     private lateinit var cancelButton: TextView
     private lateinit var warning: TextView
     private lateinit var screenShareModeSpinner: Spinner
     private var hasCancelBeenLogged: Boolean = false
+    protected lateinit var dialog: AlertDialog
     var selectedScreenShareOption: ScreenShareOption = screenShareOptions.first()
 
-    override fun dismiss() {
-        super.dismiss()
-
-        // Dismiss can be called multiple times and we only want to log once.
+    @CallSuper
+    override fun onStop(dialog: T) {
+        // onStop can be called multiple times and we only want to log once.
         if (hasCancelBeenLogged) {
             return
         }
@@ -66,42 +67,43 @@ open class BaseScreenSharePermissionDialog(
         hasCancelBeenLogged = true
     }
 
-    public override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
-        window?.setGravity(Gravity.CENTER)
-        setContentView(R.layout.screen_share_dialog)
-        dialogTitle = requireViewById(R.id.screen_share_dialog_title)
-        warning = requireViewById(R.id.text_warning)
-        startButton = requireViewById(android.R.id.button1)
-        cancelButton = requireViewById(android.R.id.button2)
+    @CallSuper
+    override fun onCreate(dialog: T, savedInstanceState: Bundle?) {
+        this.dialog = dialog
+        dialog.window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+        dialog.window?.setGravity(Gravity.CENTER)
+        dialog.setContentView(R.layout.screen_share_dialog)
+        dialogTitle = dialog.requireViewById(R.id.screen_share_dialog_title)
+        warning = dialog.requireViewById(R.id.text_warning)
+        startButton = dialog.requireViewById(android.R.id.button1)
+        cancelButton = dialog.requireViewById(android.R.id.button2)
         updateIcon()
         initScreenShareOptions()
         createOptionsView(getOptionsViewLayoutId())
     }
 
     private fun updateIcon() {
-        val icon = requireViewById<ImageView>(R.id.screen_share_dialog_icon)
+        val icon = dialog.requireViewById<ImageView>(R.id.screen_share_dialog_icon)
         if (dialogIconTint != null) {
-            icon.setColorFilter(context.getColor(dialogIconTint))
+            icon.setColorFilter(dialog.context.getColor(dialogIconTint))
         }
         if (dialogIconDrawable != null) {
-            icon.setImageDrawable(context.getDrawable(dialogIconDrawable))
+            icon.setImageDrawable(dialog.context.getDrawable(dialogIconDrawable))
         }
     }
 
-    protected fun initScreenShareOptions() {
+    private fun initScreenShareOptions() {
         selectedScreenShareOption = screenShareOptions.first()
         warning.text = warningText
         initScreenShareSpinner()
     }
 
     private val warningText: String
-        get() = context.getString(selectedScreenShareOption.warningText, appName)
+        get() = dialog.context.getString(selectedScreenShareOption.warningText, appName)
 
     private fun initScreenShareSpinner() {
-        val adapter = OptionsAdapter(context.applicationContext, screenShareOptions)
-        screenShareModeSpinner = requireViewById(R.id.screen_share_mode_spinner)
+        val adapter = OptionsAdapter(dialog.context.applicationContext, screenShareOptions)
+        screenShareModeSpinner = dialog.requireViewById(R.id.screen_share_mode_spinner)
         screenShareModeSpinner.adapter = adapter
         screenShareModeSpinner.onItemSelectedListener = this
     }
@@ -115,7 +117,7 @@ open class BaseScreenSharePermissionDialog(
 
     /** Protected methods for the text updates & functionality */
     protected fun setDialogTitle(@StringRes stringId: Int) {
-        val title = context.getString(stringId, appName)
+        val title = dialog.context.getString(stringId, appName)
         dialogTitle.text = title
     }
 
@@ -137,7 +139,7 @@ open class BaseScreenSharePermissionDialog(
 
     private fun createOptionsView(@LayoutRes layoutId: Int?) {
         if (layoutId == null) return
-        val stub = requireViewById<View>(R.id.options_stub) as ViewStub
+        val stub = dialog.requireViewById<View>(R.id.options_stub) as ViewStub
         stub.layoutResource = layoutId
         stub.inflate()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index eacfa578ac44d8d9635d1c5f76f2723805916edb..039372d878358ce3ad3e5bea291f3b90a7425f37 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -60,6 +60,7 @@ import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelect
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
 import com.android.systemui.res.R;
+import com.android.systemui.statusbar.phone.AlertDialogWithDelegate;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.Utils;
 
@@ -222,27 +223,30 @@ public class MediaProjectionPermissionActivity extends Activity
         // the correct screen width when in split screen.
         Context dialogContext = getApplicationContext();
         if (isPartialScreenSharingEnabled()) {
-            mDialog = new MediaProjectionPermissionDialog(
-                    dialogContext,
-                    getMediaProjectionConfig(),
-                    () -> {
-                        MediaProjectionPermissionDialog dialog =
-                                (MediaProjectionPermissionDialog) mDialog;
-                        ScreenShareOption selectedOption = dialog.getSelectedScreenShareOption();
-                        grantMediaProjectionPermission(selectedOption.getMode());
-                    },
-                    () -> finish(RECORD_CANCEL, /* projection= */ null),
-                    appName,
-                    mUid,
-                    mMediaProjectionMetricsLogger);
+            MediaProjectionPermissionDialogDelegate delegate =
+                    new MediaProjectionPermissionDialogDelegate(
+                            dialogContext,
+                            getMediaProjectionConfig(),
+                            dialog -> {
+                                ScreenShareOption selectedOption =
+                                        dialog.getSelectedScreenShareOption();
+                                grantMediaProjectionPermission(selectedOption.getMode());
+                            },
+                            () -> finish(RECORD_CANCEL, /* projection= */ null),
+                            appName,
+                            mUid,
+                            mMediaProjectionMetricsLogger);
+            mDialog =
+                    new AlertDialogWithDelegate(
+                            dialogContext, R.style.Theme_SystemUI_Dialog, delegate);
         } else {
-            AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(dialogContext,
-                    R.style.Theme_SystemUI_Dialog)
-                    .setTitle(dialogTitle)
-                    .setIcon(R.drawable.ic_media_projection_permission)
-                    .setMessage(dialogText)
-                    .setPositiveButton(R.string.media_projection_action_text, this)
-                    .setNeutralButton(android.R.string.cancel, this);
+            AlertDialog.Builder dialogBuilder =
+                    new AlertDialog.Builder(dialogContext, R.style.Theme_SystemUI_Dialog)
+                            .setTitle(dialogTitle)
+                            .setIcon(R.drawable.ic_media_projection_permission)
+                            .setMessage(dialogText)
+                            .setPositiveButton(R.string.media_projection_action_text, this)
+                            .setNeutralButton(android.R.string.cancel, this);
             mDialog = dialogBuilder.create();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
index cff22b0dc019c6301f159e909b0111833f8531b0..8453af19d26fb1506ac3da87732922254b0c9b40 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
@@ -15,31 +15,32 @@
  */
 package com.android.systemui.mediaprojection.permission
 
+import android.app.AlertDialog
 import android.content.Context
 import android.media.projection.MediaProjectionConfig
 import android.os.Bundle
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.res.R
+import java.util.function.Consumer
 
 /** Dialog to select screen recording options */
-class MediaProjectionPermissionDialog(
+class MediaProjectionPermissionDialogDelegate(
     context: Context,
     mediaProjectionConfig: MediaProjectionConfig?,
-    private val onStartRecordingClicked: Runnable,
+    private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
     private val onCancelClicked: Runnable,
     private val appName: String?,
     hostUid: Int,
     mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
 ) :
-    BaseScreenSharePermissionDialog(
-        context,
+    BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
         createOptionList(context, appName, mediaProjectionConfig),
         appName,
         hostUid,
         mediaProjectionMetricsLogger
     ) {
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
+    override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) {
+        super.onCreate(dialog, savedInstanceState)
         // TODO(b/270018943): Handle the case of System sharing (not recording nor casting)
         if (appName == null) {
             setDialogTitle(R.string.media_projection_entry_cast_permission_dialog_title)
@@ -51,12 +52,12 @@ class MediaProjectionPermissionDialog(
         setStartButtonOnClickListener {
             // Note that it is important to run this callback before dismissing, so that the
             // callback can disable the dialog exit animation if it wants to.
-            onStartRecordingClicked.run()
-            dismiss()
+            onStartRecordingClicked.accept(this)
+            dialog.dismiss()
         }
         setCancelButtonOnClickListener {
             onCancelClicked.run()
-            dismiss()
+            dialog.dismiss()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index c77f3f49a77c98ae2587aca02fec555bb2a45d16..fa03dc245745e17cb774aa1727b1f70455da09e4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -22,7 +22,6 @@ import android.graphics.Paint
 import android.graphics.Point
 import android.os.Handler
 import android.os.SystemClock
-import android.os.VibrationEffect
 import android.util.Log
 import android.util.MathUtils
 import android.view.Gravity
@@ -37,8 +36,6 @@ import androidx.core.view.isVisible
 import androidx.dynamicanimation.animation.DynamicAnimation
 import com.android.internal.util.LatencyTracker
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.plugins.NavigationEdgeBackPlugin
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -78,12 +75,6 @@ private const val POP_ON_ENTRY_TO_ACTIVE_VELOCITY = 4.5f
 private const val POP_ON_INACTIVE_TO_ACTIVE_VELOCITY = 4.7f
 private const val POP_ON_INACTIVE_VELOCITY = -1.5f
 
-internal val VIBRATE_ACTIVATED_EFFECT =
-    VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
-
-internal val VIBRATE_DEACTIVATED_EFFECT =
-    VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)
-
 private const val DEBUG = false
 
 class BackPanelController
@@ -95,7 +86,6 @@ internal constructor(
     private val vibratorHelper: VibratorHelper,
     private val configurationController: ConfigurationController,
     private val latencyTracker: LatencyTracker,
-    private val featureFlags: FeatureFlags
 ) : ViewController<BackPanel>(BackPanel(context, latencyTracker)), NavigationEdgeBackPlugin {
 
     /**
@@ -113,7 +103,6 @@ internal constructor(
         private val vibratorHelper: VibratorHelper,
         private val configurationController: ConfigurationController,
         private val latencyTracker: LatencyTracker,
-        private val featureFlags: FeatureFlags
     ) {
         /** Construct a [BackPanelController]. */
         fun create(context: Context): BackPanelController {
@@ -126,7 +115,6 @@ internal constructor(
                     vibratorHelper,
                     configurationController,
                     latencyTracker,
-                    featureFlags
                 )
             backPanelController.init()
             return backPanelController
@@ -992,35 +980,22 @@ internal constructor(
                 val springForceOnCancelled =
                     params.cancelledIndicator.arrowDimens.alphaSpring?.get(0f)?.value
                 mView.popArrowAlpha(0f, springForceOnCancelled)
-                if (!featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION))
-                    mainHandler.postDelayed(10L) { vibratorHelper.cancel() }
             }
         }
     }
 
     private fun performDeactivatedHapticFeedback() {
-        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-            vibratorHelper.performHapticFeedback(
-                    mView,
-                    HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE
-            )
-        } else {
-            vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT)
-        }
+        vibratorHelper.performHapticFeedback(
+                mView,
+                HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE
+        )
     }
 
     private fun performActivatedHapticFeedback() {
-        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-            vibratorHelper.performHapticFeedback(
-                    mView,
-                    HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
-            )
-        } else {
-            vibratorHelper.cancel()
-            mainHandler.postDelayed(10L) {
-                vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
-            }
-        }
+        vibratorHelper.performHapticFeedback(
+                mView,
+                HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
+        )
     }
 
     private fun convertVelocityToAnimationFactor(
diff --git a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
index 934f31040ce1dd895f152421c6b0b247e5507368..82420875e0de77d10b0d4d1ea1ae388fd8f688a1 100644
--- a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
@@ -32,12 +32,12 @@ import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakefulnessModel
 import com.android.systemui.power.shared.model.WakefulnessState
 import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import javax.inject.Inject
 
 /** Defines interface for classes that act as source of truth for power-related data. */
 interface PowerRepository {
@@ -67,15 +67,22 @@ interface PowerRepository {
     /** Wakes up the device. */
     fun wakeUp(why: String, @PowerManager.WakeReason wakeReason: Int)
 
-    /** Notifies the power repository that a user touch happened. */
-    fun userTouch()
+    /**
+     * Notifies the power repository that a user touch happened.
+     *
+     * @param noChangeLights If true, does not cause the keyboard backlight to turn on because of
+     *   this event. This is set when the power key is pressed. We want the device to stay on while
+     *   the button is down, but we're about to turn off the screen so we don't want the keyboard
+     *   backlight to turn on again. Otherwise the lights flash on and then off and it looks weird.
+     */
+    fun userTouch(noChangeLights: Boolean = false)
 
     /** Updates the wakefulness state, keeping previous values by default. */
     fun updateWakefulness(
-            rawState: WakefulnessState = wakefulness.value.internalWakefulnessState,
-            lastWakeReason: WakeSleepReason = wakefulness.value.lastWakeReason,
-            lastSleepReason: WakeSleepReason = wakefulness.value.lastSleepReason,
-            powerButtonLaunchGestureTriggered: Boolean =
+        rawState: WakefulnessState = wakefulness.value.internalWakefulnessState,
+        lastWakeReason: WakeSleepReason = wakefulness.value.lastWakeReason,
+        lastSleepReason: WakeSleepReason = wakefulness.value.lastSleepReason,
+        powerButtonLaunchGestureTriggered: Boolean =
             wakefulness.value.powerButtonLaunchGestureTriggered,
     )
 
@@ -121,10 +128,10 @@ constructor(
     override val wakefulness = _wakefulness.asStateFlow()
 
     override fun updateWakefulness(
-            rawState: WakefulnessState,
-            lastWakeReason: WakeSleepReason,
-            lastSleepReason: WakeSleepReason,
-            powerButtonLaunchGestureTriggered: Boolean,
+        rawState: WakefulnessState,
+        lastWakeReason: WakeSleepReason,
+        lastSleepReason: WakeSleepReason,
+        powerButtonLaunchGestureTriggered: Boolean,
     ) {
         _wakefulness.value =
             WakefulnessModel(
@@ -150,11 +157,11 @@ constructor(
         )
     }
 
-    override fun userTouch() {
+    override fun userTouch(noChangeLights: Boolean) {
         manager.userActivity(
             systemClock.uptimeMillis(),
             PowerManager.USER_ACTIVITY_EVENT_TOUCH,
-            /* flags= */ 0,
+            if (noChangeLights) PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS else 0,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 05f125f6f531cff2c30939ff383174765e0d1cf2..bff0b93dccb05960c3841ae67fd23423472182d7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -46,6 +46,7 @@ import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDi
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.CallbackController;
 
 import dagger.Lazy;
@@ -75,6 +76,7 @@ public class RecordingController
     private final UserContextProvider mUserContextProvider;
     private final UserTracker mUserTracker;
     private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+    private final SystemUIDialog.Factory mDialogFactory;
 
     protected static final String INTENT_UPDATE_STATE =
             "com.android.systemui.screenrecord.UPDATE_STATE";
@@ -120,7 +122,8 @@ public class RecordingController
             UserContextProvider userContextProvider,
             Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver,
             UserTracker userTracker,
-            MediaProjectionMetricsLogger mediaProjectionMetricsLogger) {
+            MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
+            SystemUIDialog.Factory dialogFactory) {
         mMainExecutor = mainExecutor;
         mContext = context;
         mFlags = flags;
@@ -129,6 +132,7 @@ public class RecordingController
         mUserContextProvider = userContextProvider;
         mUserTracker = userTracker;
         mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
+        mDialogFactory = dialogFactory;
 
         BroadcastOptions options = BroadcastOptions.makeBasic();
         options.setInteractive(true);
@@ -166,15 +170,14 @@ public class RecordingController
                 getHostUid(), SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
 
         return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
-                ? new ScreenRecordPermissionDialog(
-                        context,
-                        getHostUserHandle(),
-                        getHostUid(),
-                        /* controller= */ this,
-                        activityStarter,
-                        mUserContextProvider,
-                        onStartRecordingClicked,
-                        mMediaProjectionMetricsLogger)
+                ? mDialogFactory.create(new ScreenRecordPermissionDialogDelegate(
+                getHostUserHandle(),
+                getHostUid(),
+                /* controller= */ this,
+                activityStarter,
+                mUserContextProvider,
+                onStartRecordingClicked,
+                mMediaProjectionMetricsLogger))
                 : new ScreenRecordDialog(
                         context,
                         /* controller= */ this,
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
rename to packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
index f74234bc2e21998d9bcab7cfcfa95dfd8c24b236..e57a0fddc936459c8c04bf7c50a61331c5644134 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
@@ -17,7 +17,6 @@ package com.android.systemui.screenrecord
 
 import android.app.Activity
 import android.app.PendingIntent
-import android.content.Context
 import android.content.Intent
 import android.os.Bundle
 import android.os.Handler
@@ -35,17 +34,17 @@ import androidx.annotation.LayoutRes
 import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity
-import com.android.systemui.mediaprojection.permission.BaseScreenSharePermissionDialog
+import com.android.systemui.mediaprojection.permission.BaseMediaProjectionPermissionDialogDelegate
 import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
 import com.android.systemui.mediaprojection.permission.SINGLE_APP
 import com.android.systemui.mediaprojection.permission.ScreenShareOption
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.statusbar.phone.SystemUIDialog
 
 /** Dialog to select screen recording options */
-class ScreenRecordPermissionDialog(
-    context: Context,
+class ScreenRecordPermissionDialogDelegate(
     private val hostUserHandle: UserHandle,
     private val hostUid: Int,
     private val controller: RecordingController,
@@ -54,8 +53,7 @@ class ScreenRecordPermissionDialog(
     private val onStartRecordingClicked: Runnable?,
     mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
 ) :
-    BaseScreenSharePermissionDialog(
-        context,
+    BaseMediaProjectionPermissionDialogDelegate<SystemUIDialog>(
         createOptionList(),
         appName = null,
         hostUid = hostUid,
@@ -68,10 +66,10 @@ class ScreenRecordPermissionDialog(
     private lateinit var audioSwitch: Switch
     private lateinit var options: Spinner
 
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
+    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+        super.onCreate(dialog, savedInstanceState)
         setDialogTitle(R.string.screenrecord_permission_dialog_title)
-        setTitle(R.string.screenrecord_title)
+        dialog.setTitle(R.string.screenrecord_title)
         setStartButtonText(R.string.screenrecord_permission_dialog_continue)
         setStartButtonOnClickListener { v: View? ->
             onStartRecordingClicked?.run()
@@ -79,7 +77,7 @@ class ScreenRecordPermissionDialog(
                 requestScreenCapture(/* captureTarget= */ null)
             }
             if (selectedScreenShareOption.mode == SINGLE_APP) {
-                val intent = Intent(context, MediaProjectionAppSelectorActivity::class.java)
+                val intent = Intent(dialog.context, MediaProjectionAppSelectorActivity::class.java)
                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
 
                 // We can't start activity for result here so we use result receiver to get
@@ -96,22 +94,26 @@ class ScreenRecordPermissionDialog(
                 intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID, hostUid)
                 activityStarter.startActivity(intent, /* dismissShade= */ true)
             }
-            dismiss()
+            dialog.dismiss()
         }
-        setCancelButtonOnClickListener { dismiss() }
+        setCancelButtonOnClickListener { dialog.dismiss() }
         initRecordOptionsView()
     }
 
     @LayoutRes override fun getOptionsViewLayoutId(): Int = R.layout.screen_record_options
 
     private fun initRecordOptionsView() {
-        audioSwitch = requireViewById(R.id.screenrecord_audio_switch)
-        tapsSwitch = requireViewById(R.id.screenrecord_taps_switch)
-        tapsView = requireViewById(R.id.show_taps)
+        audioSwitch = dialog.requireViewById(R.id.screenrecord_audio_switch)
+        tapsSwitch = dialog.requireViewById(R.id.screenrecord_taps_switch)
+        tapsView = dialog.requireViewById(R.id.show_taps)
         updateTapsViewVisibility()
-        options = requireViewById(R.id.screen_recording_options)
+        options = dialog.requireViewById(R.id.screen_recording_options)
         val a: ArrayAdapter<*> =
-            ScreenRecordingAdapter(context, android.R.layout.simple_spinner_dropdown_item, MODES)
+            ScreenRecordingAdapter(
+                dialog.context,
+                android.R.layout.simple_spinner_dropdown_item,
+                MODES
+            )
         a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
         options.adapter = a
         options.setOnItemClickListenerInt { _: AdapterView<*>?, _: View?, _: Int, _: Long ->
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
index 03be88fc31d9a53dfd1ce1944cb7142176623f22..c59ef2632f159fd40575774ffcc6cdb22ee22761 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
@@ -18,13 +18,15 @@ package com.android.systemui.smartspace.dagger
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.smartspace.SmartspacePrecondition
 import com.android.systemui.smartspace.SmartspaceTargetFilter
+import com.android.systemui.smartspace.data.repository.SmartspaceRepositoryModule
 import com.android.systemui.smartspace.preconditions.LockscreenPrecondition
 import dagger.Binds
 import dagger.BindsOptionalOf
 import dagger.Module
 import javax.inject.Named
 
-@Module(subcomponents = [SmartspaceViewComponent::class])
+@Module(subcomponents = [SmartspaceViewComponent::class],
+    includes = [SmartspaceRepositoryModule::class])
 abstract class SmartspaceModule {
     @Module
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2fc0ec290a90452a53bc7fe42403e6d316436e51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.smartspace.data.repository
+
+import android.app.smartspace.SmartspaceTarget
+import android.os.Parcelable
+import android.widget.RemoteViews
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.BcSmartspaceDataPlugin
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.onCompletion
+import kotlinx.coroutines.flow.onStart
+
+interface SmartspaceRepository {
+    /** Whether [RemoteViews] are passed through smartspace targets. */
+    val isSmartspaceRemoteViewsEnabled: Boolean
+
+    /** Smartspace targets for the lockscreen surface. */
+    val lockscreenSmartspaceTargets: Flow<List<SmartspaceTarget>>
+}
+
+@SysUISingleton
+class SmartspaceRepositoryImpl
+@Inject
+constructor(
+    private val lockscreenSmartspaceController: LockscreenSmartspaceController,
+) : SmartspaceRepository, BcSmartspaceDataPlugin.SmartspaceTargetListener {
+
+    override val isSmartspaceRemoteViewsEnabled: Boolean
+        get() = android.app.smartspace.flags.Flags.remoteViews()
+
+    private val _lockscreenSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> =
+        MutableStateFlow(emptyList())
+    override val lockscreenSmartspaceTargets: Flow<List<SmartspaceTarget>> =
+        _lockscreenSmartspaceTargets
+            .onStart {
+                lockscreenSmartspaceController.addListener(listener = this@SmartspaceRepositoryImpl)
+            }
+            .onCompletion {
+                lockscreenSmartspaceController.removeListener(
+                    listener = this@SmartspaceRepositoryImpl
+                )
+            }
+
+    override fun onSmartspaceTargetsUpdated(targetsNullable: MutableList<out Parcelable>?) {
+        targetsNullable?.let { targets ->
+            _lockscreenSmartspaceTargets.value = targets.filterIsInstance<SmartspaceTarget>()
+        }
+            ?: run { _lockscreenSmartspaceTargets.value = emptyList() }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c77bcc50b69a8455cc9daec3a80c01815b18e891
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryModule.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.smartspace.data.repository
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface SmartspaceRepositoryModule {
+    @Binds fun smartspaceRepository(impl: SmartspaceRepositoryImpl): SmartspaceRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index 65b798a14e0b25121cefd19f4e3af38e548a9c88..62c3e9e486b43e94828b3f5968d3ec5225b6a807 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.icon.ui.viewbinder
 import android.graphics.Color
 import android.graphics.Rect
 import android.view.View
-import android.view.ViewGroup
 import android.widget.FrameLayout
 import androidx.annotation.ColorInt
 import androidx.collection.ArrayMap
@@ -32,7 +31,6 @@ import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.notification.NotificationUtils
 import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColorLookup
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColors
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
@@ -52,6 +50,7 @@ import com.android.systemui.util.ui.stopAnimating
 import com.android.systemui.util.ui.value
 import javax.inject.Inject
 import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
@@ -88,14 +87,17 @@ object NotificationIconContainerViewBinder {
         return view.repeatWhenAttached {
             lifecycleScope.run {
                 launch {
+                    val iconColors =
+                        viewModel.iconColors.mapNotNull { it.iconColors(view.viewBounds) }
                     viewModel.icons.bindIcons(
                         view,
                         configuration,
                         configurationController,
-                        viewStore
-                    )
+                        viewStore,
+                    ) { _, sbiv ->
+                        iconColors.collect { sbiv.updateTintForIcon(it, contrastColorUtil) }
+                    }
                 }
-                launch { viewModel.iconColors.bindIconColors(view, contrastColorUtil) }
                 launch { viewModel.bindIsolatedIcon(view, viewStore) }
                 launch { viewModel.animationsEnabled.bindAnimationsEnabled(view) }
             }
@@ -119,15 +121,17 @@ object NotificationIconContainerViewBinder {
                         configuration,
                         configurationController,
                         viewStore,
-                    )
+                    ) { _, sbiv ->
+                        configuration
+                            .getColorAttr(R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR)
+                            .collect { tint ->
+                                sbiv.staticDrawableColor = tint
+                                sbiv.setDecorColor(tint)
+                            }
+                    }
                 }
                 launch { viewModel.animationsEnabled.bindAnimationsEnabled(view) }
                 launch { viewModel.isDozing.bindIsDozing(view, dozeParameters) }
-                launch {
-                    configuration
-                        .getColorAttr(R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR)
-                        .bindIconColors(view)
-                }
             }
         }
     }
@@ -137,31 +141,6 @@ object NotificationIconContainerViewBinder {
         collect(view::setAnimationsEnabled)
     }
 
-    /**
-     * Binds to the [StatusBarIconView.setStaticDrawableColor] and [StatusBarIconView.setDecorColor]
-     * of the [children] of an [NotificationIconContainer].
-     */
-    private suspend fun Flow<NotificationIconColorLookup>.bindIconColors(
-        view: NotificationIconContainer,
-        contrastColorUtil: ContrastColorUtil,
-    ) {
-        mapNotNull { lookup -> lookup.iconColors(view.viewBounds) }
-            .collect { iconLookup -> view.applyTint(iconLookup, contrastColorUtil) }
-    }
-
-    /**
-     * Binds to the [StatusBarIconView.setStaticDrawableColor] and [StatusBarIconView.setDecorColor]
-     * of the [children] of an [NotificationIconContainer].
-     */
-    private suspend fun Flow<Int>.bindIconColors(view: NotificationIconContainer) {
-        collect { tint ->
-            view.children.filterIsInstance<StatusBarIconView>().forEach { icon ->
-                icon.staticDrawableColor = tint
-                icon.setDecorColor(tint)
-            }
-        }
-    }
-
     private suspend fun Flow<AnimatedValue<Boolean>>.bindIsDozing(
         view: NotificationIconContainer,
         dozeParameters: DozeParameters,
@@ -208,12 +187,19 @@ object NotificationIconContainerViewBinder {
         }
     }
 
-    /** Binds [NotificationIconsViewData] to a [NotificationIconContainer]'s [children]. */
+    /**
+     * Binds [NotificationIconsViewData] to a [NotificationIconContainer]'s [children].
+     *
+     * [bindIcon] will be invoked to bind a child [StatusBarIconView] to an icon associated with the
+     * given `iconKey`. The parent [Job] of this coroutine will be cancelled automatically when the
+     * view is to be unbound.
+     */
     private suspend fun Flow<NotificationIconsViewData>.bindIcons(
         view: NotificationIconContainer,
         configuration: ConfigurationState,
         configurationController: ConfigurationController,
         viewStore: IconViewStore,
+        bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit = { _, _ -> },
     ): Unit = coroutineScope {
         val iconSizeFlow: Flow<Int> =
             configuration.getDimensionPixelSize(
@@ -242,6 +228,7 @@ object NotificationIconContainerViewBinder {
             }
         }
 
+        val iconBindings = mutableMapOf<String, Job>()
         var prevIcons = NotificationIconsViewData()
         sample(layoutParams, ::Pair).collect {
             (iconsData: NotificationIconsViewData, layoutParams: FrameLayout.LayoutParams),
@@ -261,15 +248,20 @@ object NotificationIconContainerViewBinder {
                 }
 
             iconsDiff.removed
-                .mapNotNull { key -> childrenByNotifKey[key] }
-                .forEach { child -> view.removeView(child) }
+                .mapNotNull { key -> childrenByNotifKey[key]?.let { key to it } }
+                .forEach { (key, child) ->
+                    view.removeView(child)
+                    iconBindings.remove(key)?.cancel()
+                }
 
-            val toAdd = iconsDiff.added.map { viewStore.iconView(it.notifKey) }
-            for ((i, sbiv) in toAdd.withIndex()) {
+            val toAdd = iconsDiff.added.map { it.notifKey to viewStore.iconView(it.notifKey) }
+            for ((i, keyAndView) in toAdd.withIndex()) {
+                val (key, sbiv) = keyAndView
                 // The view might still be transiently added if it was just removed
                 // and added again
                 view.removeTransientView(sbiv)
                 view.addView(sbiv, i, layoutParams)
+                iconBindings[key] = launch { bindIcon(key, sbiv) }
             }
 
             view.setChangingViewPositions(true)
@@ -292,16 +284,6 @@ object NotificationIconContainerViewBinder {
 
     // TODO(b/305739416): Once StatusBarIconView has its own Recommended Architecture stack, this
     //  can be moved there and cleaned up.
-    private fun ViewGroup.applyTint(
-        iconColors: NotificationIconColors,
-        contrastColorUtil: ContrastColorUtil,
-    ) {
-        children
-            .filterIsInstance<StatusBarIconView>()
-            .filter { it.width != 0 }
-            .forEach { iv -> iv.updateTintForIcon(iconColors, contrastColorUtil) }
-    }
-
     private fun StatusBarIconView.updateTintForIcon(
         iconColors: NotificationIconColors,
         contrastColorUtil: ContrastColorUtil,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 6785da4bf4f15710c4d430e287606195dda87903..a1a0ccac35001623ff4480a8dd582901a307a240 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -22,6 +22,7 @@ import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
+import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.launch
 
 /** Binds the shared notification container to its view-model. */
@@ -32,39 +33,46 @@ object SharedNotificationContainerBinder {
         view: SharedNotificationContainer,
         viewModel: SharedNotificationContainerViewModel,
         controller: NotificationStackScrollLayoutController,
-    ) {
-        view.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.CREATED) {
-                launch {
-                    viewModel.configurationBasedDimensions.collect {
-                        view.updateConstraints(
-                            useSplitShade = it.useSplitShade,
-                            marginStart = it.marginStart,
-                            marginTop = it.marginTop,
-                            marginEnd = it.marginEnd,
-                            marginBottom = it.marginBottom,
-                        )
+    ): DisposableHandle {
+        val disposableHandle =
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    launch {
+                        viewModel.configurationBasedDimensions.collect {
+                            view.updateConstraints(
+                                useSplitShade = it.useSplitShade,
+                                marginStart = it.marginStart,
+                                marginTop = it.marginTop,
+                                marginEnd = it.marginEnd,
+                                marginBottom = it.marginBottom,
+                            )
 
-                        controller.setOverExpansion(0f)
-                        controller.setOverScrollAmount(0)
-                        controller.updateFooter()
+                            controller.setOverExpansion(0f)
+                            controller.setOverScrollAmount(0)
+                            controller.updateFooter()
+                        }
                     }
-                }
 
-                launch {
-                    viewModel.maxNotifications.collect {
-                        controller.setMaxDisplayedNotifications(it)
+                    launch {
+                        viewModel.maxNotifications.collect {
+                            controller.setMaxDisplayedNotifications(it)
+                        }
                     }
-                }
 
-                launch {
-                    viewModel.position.collect {
-                        val animate = it.animate || controller.isAddOrRemoveAnimationPending()
-                        controller.updateTopPadding(it.top, animate)
+                    launch {
+                        viewModel.position.collect {
+                            val animate = it.animate || controller.isAddOrRemoveAnimationPending()
+                            controller.updateTopPadding(it.top, animate)
+                        }
                     }
+
+                    launch { viewModel.translationY.collect { controller.setTranslationY(it) } }
                 }
+            }
 
-                launch { viewModel.translationY.collect { controller.setTranslationY(it) } }
+        return object : DisposableHandle {
+            override fun dispose() {
+                disposableHandle.dispose()
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index 13c99647f00bfc058f8e4fd596863d369d91cc2e..9471574fc755cc702d9dc9b803315bb7834151fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -99,6 +99,17 @@ interface MobileConnectionsRepository {
      */
     val isAnySimSecure: Flow<Boolean>
 
+    /**
+     * Returns whether any active SIM on the device is in
+     * [android.telephony.TelephonyManager.SIM_STATE_PIN_REQUIRED] or
+     * [android.telephony.TelephonyManager.SIM_STATE_PUK_REQUIRED] or
+     * [android.telephony.TelephonyManager.SIM_STATE_PERM_DISABLED].
+     *
+     * Note: Unfortunately, we cannot name this [isAnySimSecure] due to a conflict with the flow
+     * name above (Java code-gen is having issues with it).
+     */
+    fun getIsAnySimSecure(): Boolean
+
     /**
      * Checks if any subscription has [android.telephony.TelephonyManager.getEmergencyCallbackMode]
      * == true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index 87dd17ec486534bcff87c9c687e3eb95a95d2124..8a8e33efbcef94e1918deb2d68fe32401ad64164 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -152,6 +152,7 @@ constructor(
         activeRepo.flatMapLatest { it.defaultMobileIconGroup }
 
     override val isAnySimSecure: Flow<Boolean> = activeRepo.flatMapLatest { it.isAnySimSecure }
+    override fun getIsAnySimSecure(): Boolean = activeRepo.value.getIsAnySimSecure()
 
     override val defaultDataSubId: StateFlow<Int> =
         activeRepo
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 2ecd435177ee5332d053ed7c01af50c4a4c0a10f..2b3c6326032c35d455740d2bc35e13ad54991614 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -136,7 +136,8 @@ constructor(
 
     override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G)
 
-    override val isAnySimSecure: Flow<Boolean> = flowOf(false)
+    override val isAnySimSecure: Flow<Boolean> = flowOf(getIsAnySimSecure())
+    override fun getIsAnySimSecure(): Boolean = false
 
     override val defaultMobileIconMapping = MutableStateFlow(TelephonyIcons.ICON_NAME_TO_ICON)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index cf7bf86504fb65d1af24045669d518c583669362..2a510e41ec9c9e6a1b13c640d72414a5d1e19883 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -262,7 +262,7 @@ constructor(
                     object : KeyguardUpdateMonitorCallback() {
                         override fun onSimStateChanged(subId: Int, slotId: Int, simState: Int) {
                             logger.logOnSimStateChanged()
-                            trySend(keyguardUpdateMonitor.isSimPinSecure)
+                            trySend(getIsAnySimSecure())
                         }
                     }
                 keyguardUpdateMonitor.registerCallback(callback)
@@ -277,6 +277,8 @@ constructor(
             )
             .distinctUntilChanged()
 
+    override fun getIsAnySimSecure() = keyguardUpdateMonitor.isSimPinSecure
+
     override fun getRepoForSubId(subId: Int): FullMobileConnectionRepository =
         getOrCreateRepoForSubId(subId)
 
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
index 3b300249aac20b70a0cafa5f30fd16382c7cf1ec..b1b6014bfbdebad8ec1122044016db2049477e1a 100644
--- a/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
@@ -17,23 +17,40 @@
 
 package com.android.systemui.telephony.data.repository
 
+import android.annotation.SuppressLint
 import android.content.Context
 import android.content.pm.PackageManager
+import android.telecom.TelecomManager
 import android.telephony.Annotation
 import android.telephony.TelephonyCallback
 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.telephony.TelephonyListenerManager
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+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.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
 
 /** Defines interface for classes that encapsulate _some_ telephony-related state. */
 interface TelephonyRepository {
     /** The state of the current call. */
     @Annotation.CallState val callState: Flow<Int>
 
+    /**
+     * Whether there is an ongoing phone call (can be in dialing, ringing, active or holding states)
+     * originating from either a manager or self-managed {@link ConnectionService}.
+     */
+    val isInCall: StateFlow<Boolean>
+
     /** Whether the device has a radio that can be used for telephony. */
     val hasTelephonyRadio: Boolean
 }
@@ -49,18 +66,35 @@ interface TelephonyRepository {
 class TelephonyRepositoryImpl
 @Inject
 constructor(
+    @Application private val applicationScope: CoroutineScope,
     @Application private val applicationContext: Context,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val manager: TelephonyListenerManager,
+    private val telecomManager: TelecomManager?,
 ) : TelephonyRepository {
+
     @Annotation.CallState
     override val callState: Flow<Int> = conflatedCallbackFlow {
-        val listener = TelephonyCallback.CallStateListener { state -> trySend(state) }
+        val listener = TelephonyCallback.CallStateListener(::trySend)
 
         manager.addCallStateListener(listener)
 
         awaitClose { manager.removeCallStateListener(listener) }
     }
 
-    override val hasTelephonyRadio: Boolean
-        get() = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+    @SuppressLint("MissingPermission")
+    override val isInCall: StateFlow<Boolean> =
+        if (telecomManager == null) {
+                flowOf(false)
+            } else {
+                callState.map { withContext(backgroundDispatcher) { telecomManager.isInCall } }
+            }
+            .stateIn(
+                applicationScope,
+                SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    override val hasTelephonyRadio =
+        applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
index 4642f552615a07cd92ede5de3afd9c7c6ae112a7..4b0e5d188ffaf1de71270de976b3127fe920c1d3 100644
--- a/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
@@ -22,17 +22,18 @@ import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.telephony.data.repository.TelephonyRepository
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 
 /** Hosts business logic related to telephony. */
 @SysUISingleton
 class TelephonyInteractor
 @Inject
 constructor(
-    private val repository: TelephonyRepository,
+    repository: TelephonyRepository,
 ) {
     @Annotation.CallState val callState: Flow<Int> = repository.callState
 
-    /** Whether the device has a radio that can be used for telephony. */
-    val hasTelephonyRadio: Boolean
-        get() = repository.hasTelephonyRadio
+    val isInCall: StateFlow<Boolean> = repository.isInCall
+
+    val hasTelephonyRadio: Boolean = repository.hasTelephonyRadio
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
index 78fb7a491534948540506c3a32b8afe49ab421c7..3ed05aac3e10d7f9838e85cab7b4f534bd0f93b6 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
@@ -34,10 +34,10 @@ constructor(
     @UserIdInt
     @JvmOverloads
     fun getSelectedUserId(bypassFlag: Boolean = false): Int {
-        if (bypassFlag || flags.isEnabled(REFACTOR_GETCURRENTUSER)) {
-            return repository.getSelectedUserInfo().id
+        return if (bypassFlag || flags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+            repository.getSelectedUserInfo().id
         } else {
-            return KeyguardUpdateMonitor.getCurrentUser()
+            KeyguardUpdateMonitor.getCurrentUser()
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/reference/ReferenceModule.kt b/packages/SystemUI/src/com/android/systemui/util/reference/ReferenceModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e7a91e505e47cf8c52077b400594704eba3872a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/reference/ReferenceModule.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.reference
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+abstract class ReferenceModule {
+    @Binds
+    abstract fun bindWeakReferenceFactory(impl: WeakReferenceFactoryImpl): WeakReferenceFactory
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/reference/WeakReferenceFactory.kt b/packages/SystemUI/src/com/android/systemui/util/reference/WeakReferenceFactory.kt
new file mode 100644
index 0000000000000000000000000000000000000000..658f0404aa84c441d38b5165845a2b3f7ade6fcb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/reference/WeakReferenceFactory.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.reference
+
+import java.lang.ref.WeakReference
+import javax.inject.Inject
+
+interface WeakReferenceFactory {
+    fun <T> create(referent: T): WeakReference<T>
+}
+
+class WeakReferenceFactoryImpl @Inject constructor() : WeakReferenceFactory {
+    override fun <T> create(referent: T): WeakReference<T> {
+        return WeakReference(referent)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
index c61b11ae73363566bc5c40c157d0b67c66dfc4bb..9a95b17fc7d12b632391e46467f0d74d1fb28b41 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
@@ -48,7 +48,6 @@ import org.mockito.MockitoAnnotations
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class EmergencyButtonControllerTest : SysuiTestCase() {
-    lateinit var underTest: EmergencyButtonController
     @Mock lateinit var emergencyButton: EmergencyButton
     @Mock lateinit var configurationController: ConfigurationController
     @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@@ -61,10 +60,13 @@ class EmergencyButtonControllerTest : SysuiTestCase() {
     @Mock lateinit var lockPatternUtils: LockPatternUtils
     @Mock lateinit var packageManager: PackageManager
     @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
+
     val fakeSystemClock = FakeSystemClock()
     val mainExecutor = FakeExecutor(fakeSystemClock)
     val backgroundExecutor = FakeExecutor(fakeSystemClock)
 
+    lateinit var underTest: EmergencyButtonController
+
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
@@ -73,7 +75,6 @@ class EmergencyButtonControllerTest : SysuiTestCase() {
                 emergencyButton,
                 configurationController,
                 keyguardUpdateMonitor,
-                telephonyManager,
                 powerManager,
                 activityTaskManager,
                 shadeController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
index a0491918535063de968570f4c3786c868da390eb..44c57f34fa1b6a62037bc1b49663e8e7a699d668 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -2,15 +2,13 @@ package com.android.systemui.bouncer.data.repository
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.keyguard.ViewMediatorCallback
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -18,32 +16,34 @@ import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardBouncerRepositoryTest : SysuiTestCase() {
 
     @Mock private lateinit var systemClock: SystemClock
-    @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
     @Mock private lateinit var bouncerLogger: TableLogBuffer
+
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
+
     lateinit var underTest: KeyguardBouncerRepository
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        val testCoroutineScope = TestCoroutineScope()
         underTest =
             KeyguardBouncerRepositoryImpl(
                 systemClock,
-                testCoroutineScope,
+                testScope.backgroundScope,
                 bouncerLogger,
             )
     }
 
     @Test
-    fun changingFlowValueTriggersLogging() = runBlocking {
-        underTest.setPrimaryShow(true)
-        Mockito.verify(bouncerLogger)
-            .logChange(eq(""), eq("PrimaryBouncerShow"), value = eq(false), any())
-    }
+    fun changingFlowValueTriggersLogging() =
+        testScope.runTest {
+            underTest.setPrimaryShow(true)
+            Mockito.verify(bouncerLogger)
+                .logChange(eq(""), eq("PrimaryBouncerShow"), value = eq(false), any())
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 2c97809bf3672985777977d80541a76086d7aee0..c159b66c10a0c914b5f16de945bbf5a921d044de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -45,6 +45,7 @@ class BouncerViewModelTest : SysuiTestCase() {
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
     private val authenticationInteractor = utils.authenticationInteractor()
+    private val actionButtonInteractor = utils.bouncerActionButtonInteractor()
     private val deviceEntryInteractor =
         utils.deviceEntryInteractor(
             authenticationInteractor = authenticationInteractor,
@@ -60,6 +61,7 @@ class BouncerViewModelTest : SysuiTestCase() {
         utils.bouncerViewModel(
             bouncerInteractor = bouncerInteractor,
             authenticationInteractor = authenticationInteractor,
+            actionButtonInteractor = actionButtonInteractor,
         )
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index b75f3e0876ff69655d9899bbf7be80385d3d92c9..2cc8f0a479550444d47005a62abeca6eebfb162d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -52,8 +52,7 @@ import org.mockito.MockitoAnnotations
 @RunWith(AndroidJUnit4::class)
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 class KeyguardBouncerViewModelTest : SysuiTestCase() {
-    lateinit var underTest: KeyguardBouncerViewModel
-    lateinit var bouncerInteractor: PrimaryBouncerInteractor
+
     @Mock lateinit var bouncerView: BouncerView
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
@@ -62,9 +61,13 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() {
     @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
     @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+    lateinit var bouncerInteractor: PrimaryBouncerInteractor
     private val mainHandler = FakeHandler(Looper.getMainLooper())
     val repository = FakeKeyguardBouncerRepository()
 
+    lateinit var underTest: KeyguardBouncerViewModel
+
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index ba8dcef683c74905d706079f1578c9b6bea051d9..390742031381f7ecb56165536ae0ee66da61bddb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -61,7 +61,9 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
         utils.bouncerViewModel(
             bouncerInteractor = bouncerInteractor,
             authenticationInteractor = authenticationInteractor,
+            actionButtonInteractor = utils.bouncerActionButtonInteractor(),
         )
+
     private val underTest =
         PasswordBouncerViewModel(
             viewModelScope = testScope.backgroundScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index bfaa6edefdca57bdddc3f59c0e5380f67d78ab4d..47db4f8faeca041e0521571cda62515386a723fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -64,6 +64,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
         utils.bouncerViewModel(
             bouncerInteractor = bouncerInteractor,
             authenticationInteractor = authenticationInteractor,
+            actionButtonInteractor = utils.bouncerActionButtonInteractor(),
         )
     private val underTest =
         PatternBouncerViewModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 7873899f2d357582f59b9c1fda8340bab4ee2369..3ddac7e1ad1dfbc97edcb9ff187dbc7a1e1fbe0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -63,6 +63,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
         utils.bouncerViewModel(
             bouncerInteractor = bouncerInteractor,
             authenticationInteractor = authenticationInteractor,
+            actionButtonInteractor = utils.bouncerActionButtonInteractor(),
         )
     private val underTest =
         PinBouncerViewModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..455f9865edf348c7ce308525145dce0e5b4a2856
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.communal.data.repository
+
+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.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+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.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalMediaRepositoryImplTest : SysuiTestCase() {
+    @Mock private lateinit var mediaDataManager: MediaDataManager
+    @Mock private lateinit var mediaData: MediaData
+
+    private val mediaDataListenerCaptor: KotlinArgumentCaptor<MediaDataManager.Listener> by lazy {
+        KotlinArgumentCaptor(MediaDataManager.Listener::class.java)
+    }
+
+    private lateinit var mediaRepository: CommunalMediaRepository
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun mediaPlaying_defaultsToFalse() =
+        testScope.runTest {
+            mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
+
+            val isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+            runCurrent()
+            assertThat(isMediaPlaying()).isFalse()
+        }
+
+    @Test
+    fun mediaPlaying_emitsInitialValue() =
+        testScope.runTest {
+            // Start with media available.
+            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+
+            mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
+
+            val isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+            runCurrent()
+            assertThat(isMediaPlaying()).isTrue()
+        }
+
+    @Test
+    fun mediaPlaying_updatesWhenMediaDataLoaded() =
+        testScope.runTest {
+            mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
+
+            // Initial value is false.
+            var isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+            runCurrent()
+            assertThat(isMediaPlaying()).isFalse()
+
+            // Listener is added
+            verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
+
+            // Change to media available and notify the listener.
+            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+            mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
+
+            // mediaPlaying now returns true.
+            isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+            runCurrent()
+            assertThat(isMediaPlaying()).isTrue()
+        }
+
+    @Test
+    fun mediaPlaying_updatesWhenMediaDataRemoved() =
+        testScope.runTest {
+            // Start with media available.
+            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+
+            mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
+
+            // Initial value is true.
+            var isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+            runCurrent()
+            assertThat(isMediaPlaying()).isTrue()
+
+            // Listener is added.
+            verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
+
+            // Change to media unavailable and notify the listener.
+            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false)
+            mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
+
+            // mediaPlaying now returns false.
+            isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+            runCurrent()
+            assertThat(isMediaPlaying()).isFalse()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 2f17b6fa35edd2330366538e85554668ecc78a5d..08d54c001d11e23b973299b7716e07421a56fa78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -17,14 +17,26 @@
 
 package com.android.systemui.communal.domain.interactor
 
+import android.app.smartspace.SmartspaceTarget
+import android.provider.Settings
+import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
+import android.widget.RemoteViews
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
 import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -34,6 +46,7 @@ import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.mock
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -44,24 +57,37 @@ class CommunalInteractorTest : SysuiTestCase() {
 
     private lateinit var testScope: TestScope
 
+    private lateinit var tutorialRepository: FakeCommunalTutorialRepository
     private lateinit var communalRepository: FakeCommunalRepository
+    private lateinit var mediaRepository: FakeCommunalMediaRepository
     private lateinit var widgetRepository: FakeCommunalWidgetRepository
-    private lateinit var interactor: CommunalInteractor
+    private lateinit var smartspaceRepository: FakeSmartspaceRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+
+    private lateinit var underTest: CommunalInteractor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
         testScope = TestScope()
-        communalRepository = FakeCommunalRepository()
-        widgetRepository = FakeCommunalWidgetRepository()
-        interactor = CommunalInteractor(communalRepository, widgetRepository)
+
+        val withDeps = CommunalInteractorFactory.create()
+
+        tutorialRepository = withDeps.tutorialRepository
+        communalRepository = withDeps.communalRepository
+        mediaRepository = withDeps.mediaRepository
+        widgetRepository = withDeps.widgetRepository
+        smartspaceRepository = withDeps.smartspaceRepository
+        keyguardRepository = withDeps.keyguardRepository
+
+        underTest = withDeps.communalInteractor
     }
 
     @Test
     fun appWidgetInfoFlow() =
         testScope.runTest {
-            val lastAppWidgetInfo = collectLastValue(interactor.appWidgetInfo)
+            val lastAppWidgetInfo = collectLastValue(underTest.appWidgetInfo)
             runCurrent()
             assertThat(lastAppWidgetInfo()).isNull()
 
@@ -74,31 +100,215 @@ class CommunalInteractorTest : SysuiTestCase() {
     fun communalEnabled() =
         testScope.runTest {
             communalRepository.setIsCommunalEnabled(true)
-
-            val interactor = CommunalInteractor(communalRepository, widgetRepository)
-            assertThat(interactor.isCommunalEnabled).isTrue()
+            assertThat(underTest.isCommunalEnabled).isTrue()
         }
 
     @Test
     fun communalDisabled() =
         testScope.runTest {
             communalRepository.setIsCommunalEnabled(false)
+            assertThat(underTest.isCommunalEnabled).isFalse()
+        }
+
+    @Test
+    fun tutorial_tutorialNotCompletedAndKeyguardVisible_showTutorialContent() =
+        testScope.runTest {
+            // Keyguard showing, and tutorial not started.
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            tutorialRepository.setTutorialSettingState(
+                Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
+            )
+
+            val communalContent by collectLastValue(underTest.communalContent)
+
+            assertThat(communalContent!!).isNotEmpty()
+            communalContent!!.forEach { model ->
+                assertThat(model is CommunalContentModel.Tutorial).isTrue()
+            }
+        }
+
+    @Test
+    fun widget_tutorialCompletedAndWidgetsAvailable_showWidgetContent() =
+        testScope.runTest {
+            // Keyguard showing, and tutorial completed.
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+            // Widgets are available.
+            val widgets =
+                listOf(
+                    CommunalWidgetContentModel(
+                        appWidgetId = 0,
+                        priority = 30,
+                        providerInfo = mock(),
+                    ),
+                    CommunalWidgetContentModel(
+                        appWidgetId = 1,
+                        priority = 20,
+                        providerInfo = mock(),
+                    ),
+                    CommunalWidgetContentModel(
+                        appWidgetId = 2,
+                        priority = 10,
+                        providerInfo = mock(),
+                    ),
+                )
+            widgetRepository.setCommunalWidgets(widgets)
+
+            val communalContent by collectLastValue(underTest.communalContent)
+
+            assertThat(communalContent!!).isNotEmpty()
+            communalContent!!.forEachIndexed { index, model ->
+                assertThat((model as CommunalContentModel.Widget).appWidgetId)
+                    .isEqualTo(widgets[index].appWidgetId)
+            }
+        }
+
+    @Test
+    fun smartspace_onlyShowTimersWithRemoteViews() =
+        testScope.runTest {
+            // Keyguard showing, and tutorial completed.
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+            // Not a timer
+            val target1 = mock(SmartspaceTarget::class.java)
+            whenever(target1.smartspaceTargetId).thenReturn("target1")
+            whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_WEATHER)
+            whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java))
+
+            // Does not have RemoteViews
+            val target2 = mock(SmartspaceTarget::class.java)
+            whenever(target1.smartspaceTargetId).thenReturn("target2")
+            whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+            whenever(target1.remoteViews).thenReturn(null)
+
+            // Timer and has RemoteViews
+            val target3 = mock(SmartspaceTarget::class.java)
+            whenever(target1.smartspaceTargetId).thenReturn("target3")
+            whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+            whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java))
+
+            val targets = listOf(target1, target2, target3)
+            smartspaceRepository.setLockscreenSmartspaceTargets(targets)
 
-            val interactor = CommunalInteractor(communalRepository, widgetRepository)
-            assertThat(interactor.isCommunalEnabled).isFalse()
+            val communalContent by collectLastValue(underTest.communalContent)
+            assertThat(communalContent?.size).isEqualTo(1)
+            assertThat(communalContent?.get(0)?.key).isEqualTo("smartspace_target3")
+        }
+
+    @Test
+    fun smartspace_smartspaceAndWidgetsAvailable_showSmartspaceAndWidgetContent() =
+        testScope.runTest {
+            // Keyguard showing, and tutorial completed.
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+            // Widgets available.
+            val widgets =
+                listOf(
+                    CommunalWidgetContentModel(
+                        appWidgetId = 0,
+                        priority = 30,
+                        providerInfo = mock(),
+                    ),
+                    CommunalWidgetContentModel(
+                        appWidgetId = 1,
+                        priority = 20,
+                        providerInfo = mock(),
+                    ),
+                )
+            widgetRepository.setCommunalWidgets(widgets)
+
+            // Smartspace available.
+            val target = mock(SmartspaceTarget::class.java)
+            whenever(target.smartspaceTargetId).thenReturn("target")
+            whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+            whenever(target.remoteViews).thenReturn(mock(RemoteViews::class.java))
+            smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target))
+
+            val communalContent by collectLastValue(underTest.communalContent)
+
+            assertThat(communalContent?.size).isEqualTo(3)
+            assertThat(communalContent?.get(0)?.key).isEqualTo("smartspace_target")
+            assertThat(communalContent?.get(1)?.key).isEqualTo("widget_0")
+            assertThat(communalContent?.get(2)?.key).isEqualTo("widget_1")
+        }
+
+    @Test
+    fun umo_mediaPlaying_showsUmo() =
+        testScope.runTest {
+            // Tutorial completed.
+            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+            // Media is playing.
+            mediaRepository.mediaPlaying.value = true
+
+            val communalContent by collectLastValue(underTest.communalContent)
+
+            assertThat(communalContent?.size).isEqualTo(1)
+            assertThat(communalContent?.get(0)).isInstanceOf(CommunalContentModel.Umo::class.java)
+            assertThat(communalContent?.get(0)?.key).isEqualTo(CommunalContentModel.UMO_KEY)
+        }
+
+    @Test
+    fun contentOrdering() =
+        testScope.runTest {
+            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+            // Widgets available.
+            val widgets =
+                listOf(
+                    CommunalWidgetContentModel(
+                        appWidgetId = 0,
+                        priority = 30,
+                        providerInfo = mock(),
+                    ),
+                    CommunalWidgetContentModel(
+                        appWidgetId = 1,
+                        priority = 20,
+                        providerInfo = mock(),
+                    ),
+                )
+            widgetRepository.setCommunalWidgets(widgets)
+
+            // Smartspace available.
+            val target = mock(SmartspaceTarget::class.java)
+            whenever(target.smartspaceTargetId).thenReturn("target")
+            whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+            whenever(target.remoteViews).thenReturn(mock(RemoteViews::class.java))
+            smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target))
+
+            // Media playing.
+            mediaRepository.mediaPlaying.value = true
+
+            val communalContent by collectLastValue(underTest.communalContent)
+
+            // Order is smart space, then UMO, then widget content.
+            assertThat(communalContent?.size).isEqualTo(4)
+            assertThat(communalContent?.get(0))
+                .isInstanceOf(CommunalContentModel.Smartspace::class.java)
+            assertThat(communalContent?.get(1)).isInstanceOf(CommunalContentModel.Umo::class.java)
+            assertThat(communalContent?.get(2))
+                .isInstanceOf(CommunalContentModel.Widget::class.java)
+            assertThat(communalContent?.get(3))
+                .isInstanceOf(CommunalContentModel.Widget::class.java)
         }
 
     @Test
     fun listensToSceneChange() =
         testScope.runTest {
-            val interactor = CommunalInteractor(communalRepository, widgetRepository)
-            var desiredScene = collectLastValue(interactor.desiredScene)
+            var desiredScene = collectLastValue(underTest.desiredScene)
             runCurrent()
             assertThat(desiredScene()).isEqualTo(CommunalSceneKey.Blank)
 
             val targetScene = CommunalSceneKey.Communal
             communalRepository.setDesiredScene(targetScene)
-            desiredScene = collectLastValue(interactor.desiredScene)
+            desiredScene = collectLastValue(underTest.desiredScene)
             runCurrent()
             assertThat(desiredScene()).isEqualTo(targetScene)
         }
@@ -106,10 +316,9 @@ class CommunalInteractorTest : SysuiTestCase() {
     @Test
     fun updatesScene() =
         testScope.runTest {
-            val interactor = CommunalInteractor(communalRepository, widgetRepository)
             val targetScene = CommunalSceneKey.Communal
 
-            interactor.onSceneChanged(targetScene)
+            underTest.onSceneChanged(targetScene)
 
             val desiredScene = collectLastValue(communalRepository.desiredScene)
             runCurrent()
@@ -119,15 +328,13 @@ class CommunalInteractorTest : SysuiTestCase() {
     @Test
     fun isCommunalShowing() =
         testScope.runTest {
-            val interactor = CommunalInteractor(communalRepository, widgetRepository)
-
-            var isCommunalShowing = collectLastValue(interactor.isCommunalShowing)
+            var isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
             runCurrent()
             assertThat(isCommunalShowing()).isEqualTo(false)
 
-            interactor.onSceneChanged(CommunalSceneKey.Communal)
+            underTest.onSceneChanged(CommunalSceneKey.Communal)
 
-            isCommunalShowing = collectLastValue(interactor.isCommunalShowing)
+            isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
             runCurrent()
             assertThat(isCommunalShowing()).isEqualTo(true)
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
deleted file mode 100644
index 61d1502f307e0403a8d5e9fcae757c1e29a99837..0000000000000000000000000000000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ /dev/null
@@ -1,317 +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.systemui.communal.domain.interactor
-
-import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
-import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
-import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_STARTED
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
-import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
-import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.shared.model.CommunalSceneKey
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-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.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class CommunalTutorialInteractorTest : SysuiTestCase() {
-
-    @Mock private lateinit var userTracker: UserTracker
-
-    private lateinit var testScope: TestScope
-    private lateinit var underTest: CommunalTutorialInteractor
-    private lateinit var keyguardRepository: FakeKeyguardRepository
-    private lateinit var keyguardInteractor: KeyguardInteractor
-    private lateinit var communalTutorialRepository: FakeCommunalTutorialRepository
-    private lateinit var sceneContainerFlags: FakeSceneContainerFlags
-    private lateinit var communalInteractor: CommunalInteractor
-    private lateinit var communalRepository: FakeCommunalRepository
-
-    private val utils = SceneTestUtils(this)
-    private lateinit var sceneInteractor: SceneInteractor
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        sceneInteractor = utils.sceneInteractor()
-        testScope = utils.testScope
-        sceneContainerFlags = utils.sceneContainerFlags.apply { enabled = false }
-        communalRepository = FakeCommunalRepository(isCommunalEnabled = true)
-        communalInteractor = CommunalInteractor(communalRepository, FakeCommunalWidgetRepository())
-
-        val withDeps = KeyguardInteractorFactory.create()
-        keyguardInteractor = withDeps.keyguardInteractor
-        keyguardRepository = withDeps.repository
-        communalTutorialRepository = FakeCommunalTutorialRepository()
-
-        underTest =
-            CommunalTutorialInteractor(
-                scope = testScope.backgroundScope,
-                communalTutorialRepository = communalTutorialRepository,
-                keyguardInteractor = keyguardInteractor,
-                communalInteractor = communalInteractor,
-                sceneContainerFlags = sceneContainerFlags,
-                sceneInteractor = sceneInteractor,
-            )
-
-        whenever(userTracker.userHandle).thenReturn(mock())
-    }
-
-    @Test
-    fun tutorialUnavailable_whenKeyguardNotVisible() =
-        testScope.runTest {
-            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
-            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
-            keyguardRepository.setKeyguardShowing(false)
-            assertThat(isTutorialAvailable).isFalse()
-        }
-
-    @Test
-    fun tutorialUnavailable_whenTutorialIsCompleted() =
-        testScope.runTest {
-            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
-            keyguardRepository.setKeyguardShowing(true)
-            keyguardRepository.setKeyguardOccluded(false)
-            communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
-            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
-            assertThat(isTutorialAvailable).isFalse()
-        }
-
-    @Test
-    fun tutorialAvailable_whenTutorialNotStarted() =
-        testScope.runTest {
-            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
-            keyguardRepository.setKeyguardShowing(true)
-            keyguardRepository.setKeyguardOccluded(false)
-            communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
-            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
-            assertThat(isTutorialAvailable).isTrue()
-        }
-
-    @Test
-    fun tutorialAvailable_whenTutorialIsStarted() =
-        testScope.runTest {
-            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
-            keyguardRepository.setKeyguardShowing(true)
-            keyguardRepository.setKeyguardOccluded(false)
-            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
-            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
-            assertThat(isTutorialAvailable).isTrue()
-        }
-
-    /* Testing tutorial states with transitions when flexiglass off */
-    @Test
-    fun tutorialState_notStartedAndCommunalSceneShowing_tutorialStarted() =
-        testScope.runTest {
-            val tutorialSettingState by
-                collectLastValue(communalTutorialRepository.tutorialSettingState)
-            val currentScene by collectLastValue(communalInteractor.desiredScene)
-            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
-
-            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
-
-            assertThat(currentScene).isEqualTo(CommunalSceneKey.Communal)
-            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED)
-        }
-
-    @Test
-    fun tutorialState_startedAndCommunalSceneShowing_stateWillNotUpdate() =
-        testScope.runTest {
-            val tutorialSettingState by
-                collectLastValue(communalTutorialRepository.tutorialSettingState)
-            val currentScene by collectLastValue(communalInteractor.desiredScene)
-            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
-
-            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
-
-            assertThat(currentScene).isEqualTo(CommunalSceneKey.Communal)
-            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED)
-        }
-
-    @Test
-    fun tutorialState_completedAndCommunalSceneShowing_stateWillNotUpdate() =
-        testScope.runTest {
-            val tutorialSettingState by
-                collectLastValue(communalTutorialRepository.tutorialSettingState)
-            val currentScene by collectLastValue(communalInteractor.desiredScene)
-            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
-
-            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
-
-            assertThat(currentScene).isEqualTo(CommunalSceneKey.Communal)
-            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
-        }
-
-    @Test
-    fun tutorialState_notStartedAndCommunalSceneNotShowing_stateWillNotUpdate() =
-        testScope.runTest {
-            val tutorialSettingState by
-                collectLastValue(communalTutorialRepository.tutorialSettingState)
-            val currentScene by collectLastValue(communalInteractor.desiredScene)
-            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
-
-            communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
-
-            assertThat(currentScene).isEqualTo(CommunalSceneKey.Blank)
-            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED)
-        }
-
-    @Test
-    fun tutorialState_startedAndCommunalSceneNotShowing_tutorialCompleted() =
-        testScope.runTest {
-            val tutorialSettingState by
-                collectLastValue(communalTutorialRepository.tutorialSettingState)
-            val currentScene by collectLastValue(communalInteractor.desiredScene)
-            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
-            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
-
-            communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
-
-            assertThat(currentScene).isEqualTo(CommunalSceneKey.Blank)
-            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
-        }
-
-    @Test
-    fun tutorialState_completedAndCommunalSceneNotShowing_stateWillNotUpdate() =
-        testScope.runTest {
-            val tutorialSettingState by
-                collectLastValue(communalTutorialRepository.tutorialSettingState)
-            val currentScene by collectLastValue(communalInteractor.desiredScene)
-            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
-            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
-
-            communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
-
-            assertThat(currentScene).isEqualTo(CommunalSceneKey.Blank)
-            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
-        }
-
-    /* Testing tutorial states with transitions when flexiglass on */
-    @Test
-    fun tutorialState_notStartedCommunalSceneShowingAndFlexiglassOn_tutorialStarted() =
-        testScope.runTest {
-            sceneContainerFlags.enabled = true
-            val tutorialSettingState by
-                collectLastValue(communalTutorialRepository.tutorialSettingState)
-            val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
-
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Communal), "reason")
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Communal))
-            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED)
-        }
-
-    @Test
-    fun tutorialState_startedCommunalSceneShowingAndFlexiglassOn_stateWillNotUpdate() =
-        testScope.runTest {
-            sceneContainerFlags.enabled = true
-            val tutorialSettingState by
-                collectLastValue(communalTutorialRepository.tutorialSettingState)
-            val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
-
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Communal), "reason")
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Communal))
-            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED)
-        }
-
-    @Test
-    fun tutorialState_completedCommunalSceneShowingAndFlexiglassOn_stateWillNotUpdate() =
-        testScope.runTest {
-            sceneContainerFlags.enabled = true
-            val tutorialSettingState by
-                collectLastValue(communalTutorialRepository.tutorialSettingState)
-            val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
-
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Communal), "reason")
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Communal))
-            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
-        }
-
-    @Test
-    fun tutorialState_notStartedCommunalSceneNotShowingAndFlexiglassOn_stateWillNotUpdate() =
-        testScope.runTest {
-            sceneContainerFlags.enabled = true
-            val tutorialSettingState by
-                collectLastValue(communalTutorialRepository.tutorialSettingState)
-            val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
-
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED)
-        }
-
-    @Test
-    fun tutorialState_startedCommunalSceneNotShowingAndFlexiglassOn_tutorialCompleted() =
-        testScope.runTest {
-            sceneContainerFlags.enabled = true
-            val tutorialSettingState by
-                collectLastValue(communalTutorialRepository.tutorialSettingState)
-            val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Communal), "reason")
-            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
-
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
-        }
-
-    @Test
-    fun tutorialState_completedCommunalSceneNotShowingAndFlexiglassOn_stateWillNotUpdate() =
-        testScope.runTest {
-            sceneContainerFlags.enabled = true
-            val tutorialSettingState by
-                collectLastValue(communalTutorialRepository.tutorialSettingState)
-            val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Communal), "reason")
-            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
-
-            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
index 40f0ed3626db8717db34f64c86bfee28e0deedce..288f3b651a3cff2739a9928b64c3e4fc817e9218 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
@@ -33,6 +33,7 @@ import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.log.core.FakeLogBuffer;
 import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.reference.FakeWeakReferenceFactory;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
@@ -68,7 +69,8 @@ public class ComplicationCollectionLiveDataTest extends SysuiTestCase {
                 mExecutor,
                 /* overlayEnabled= */ true,
                 mFeatureFlags,
-                FakeLogBuffer.Factory.Companion.create());
+                FakeLogBuffer.Factory.Companion.create(),
+                new FakeWeakReferenceFactory());
         mLiveData = new ComplicationCollectionLiveData(mStateController);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
index 0db3de2ce0dd30fe133a646c481726013461a799..1f18705edfdb0bd4643aa400f6188e16a8c6e5c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
@@ -63,7 +63,8 @@ class ConnectedDisplayInteractorTest : SysuiTestCase() {
         ConnectedDisplayInteractorImpl(
             virtualDeviceManager,
             fakeKeyguardRepository,
-            fakeDisplayRepository
+            fakeDisplayRepository,
+            UnconfinedTestDispatcher(),
         )
     private val testScope = TestScope(UnconfinedTestDispatcher())
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 365f67b5e56607ad3e99e59733d1075ea7793571..6d5cd49b8af63c5d4e25a2a66e29ef1ce9a41178 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -36,6 +36,7 @@ import com.android.systemui.flags.Flags;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.core.FakeLogBuffer;
 import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.reference.FakeWeakReferenceFactory;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
@@ -63,6 +64,8 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
 
     final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
+    final FakeWeakReferenceFactory mWeakReferenceFactory = new FakeWeakReferenceFactory();
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -407,12 +410,36 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
         assertThat(stateController.getComplications()).contains(homeControlsComplication);
     }
 
+    @Test
+    public void testCallbacksIgnoredWhenWeakReferenceCleared() {
+        final DreamOverlayStateController.Callback callback1 = Mockito.mock(
+                DreamOverlayStateController.Callback.class);
+        final DreamOverlayStateController.Callback callback2 = Mockito.mock(
+                DreamOverlayStateController.Callback.class);
+
+        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
+        stateController.addCallback(callback1);
+        stateController.addCallback(callback2);
+        mExecutor.runAllReady();
+
+        // Simulate callback1 getting GC'd by clearing the reference
+        mWeakReferenceFactory.clear(callback1);
+        stateController.setOverlayActive(true);
+        mExecutor.runAllReady();
+
+        // Callback2 should still be called, but never callback1
+        verify(callback1, never()).onStateChanged();
+        verify(callback2).onStateChanged();
+        assertThat(stateController.isOverlayActive()).isTrue();
+    }
+
     private DreamOverlayStateController getDreamOverlayStateController(boolean overlayEnabled) {
         return new DreamOverlayStateController(
                 mExecutor,
                 overlayEnabled,
                 mFeatureFlags,
-                mLogBuffer
+                mLogBuffer,
+                mWeakReferenceFactory
         );
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialogTest.kt
deleted file mode 100644
index 8b572eb3d906d99e8064095dbcb33f126b599916..0000000000000000000000000000000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialogTest.kt
+++ /dev/null
@@ -1,84 +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.systemui.keyboard.backlight.ui.view
-
-import android.testing.TestableLooper.RunWithLooper
-import android.view.View
-import android.view.accessibility.AccessibilityEvent
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.res.R
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWithLooper
-@SmallTest
-@RunWith(JUnit4::class)
-class KeyboardBacklightDialogTest : SysuiTestCase() {
-
-    private lateinit var dialog: KeyboardBacklightDialog
-    private lateinit var rootView: View
-    private val descriptionString = context.getString(R.string.keyboard_backlight_value)
-
-    @Before
-    fun setUp() {
-        dialog =
-            KeyboardBacklightDialog(context, initialCurrentLevel = 0, initialMaxLevel = MAX_LEVEL)
-        dialog.show()
-        rootView = dialog.requireViewById(R.id.keyboard_backlight_dialog_container)
-    }
-
-    @Test
-    fun rootViewContentDescription_containsInitialLevel() {
-        assertThat(rootView.contentDescription).isEqualTo(contentDescriptionForLevel(INITIAL_LEVEL))
-    }
-
-    @Test
-    fun contentDescriptionUpdated_afterEveryLevelUpdate() {
-        val events = startCollectingAccessibilityEvents(rootView)
-
-        dialog.updateState(current = 1, max = MAX_LEVEL)
-
-        assertThat(rootView.contentDescription).isEqualTo(contentDescriptionForLevel(1))
-        assertThat(events).contains(AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION)
-    }
-
-    private fun contentDescriptionForLevel(level: Int): String {
-        return String.format(descriptionString, level, MAX_LEVEL)
-    }
-
-    private fun startCollectingAccessibilityEvents(rootView: View): MutableList<Int> {
-        val events = mutableListOf<Int>()
-        rootView.accessibilityDelegate =
-            object : View.AccessibilityDelegate() {
-                override fun sendAccessibilityEvent(host: View, eventType: Int) {
-                    super.sendAccessibilityEvent(host, eventType)
-                    events.add(eventType)
-                }
-            }
-        return events
-    }
-
-    companion object {
-        private const val MAX_LEVEL = 5
-        private const val INITIAL_LEVEL = 0
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index 90fd6523ec126b4707cdf9bf1bd95b6a31849036..4587ea6dbdc8fc321f53fa338cbde91ccc0be7f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -154,6 +154,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
     fun fingerprintEnrollmentChange() =
         testScope.runTest {
             createBiometricSettingsRepository()
+            biometricsAreEnabledBySettings()
             val fingerprintAllowed = collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
             runCurrent()
 
@@ -169,12 +170,35 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
             assertThat(fingerprintAllowed()).isFalse()
         }
 
+    @Test
+    fun fingerprintEnabledStateChange() =
+        testScope.runTest {
+            createBiometricSettingsRepository()
+            biometricsAreEnabledBySettings()
+            val fingerprintAllowed = collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
+            runCurrent()
+
+            // start state
+            whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(true)
+            enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
+            assertThat(fingerprintAllowed()).isTrue()
+
+            // when biometrics are not enabled by settings
+            biometricsAreNotEnabledBySettings()
+            assertThat(fingerprintAllowed()).isFalse()
+
+            // when biometrics are enabled by settings
+            biometricsAreEnabledBySettings()
+            assertThat(fingerprintAllowed()).isTrue()
+        }
+
     @Test
     fun strongBiometricAllowedChange() =
         testScope.runTest {
             fingerprintIsEnrolled()
             doNotDisableKeyguardAuthFeatures()
             createBiometricSettingsRepository()
+            biometricsAreEnabledBySettings()
 
             val strongBiometricAllowed by
                 collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
@@ -197,7 +221,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
             createBiometricSettingsRepository()
             val convenienceFaceAuthAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
             doNotDisableKeyguardAuthFeatures()
-            faceAuthIsEnabledByBiometricManager()
+            biometricsAreEnabledBySettings()
 
             onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
             onNonStrongAuthChanged(true, PRIMARY_USER_ID)
@@ -238,6 +262,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
             faceAuthIsNonStrongBiometric()
             faceAuthIsEnrolled()
             doNotDisableKeyguardAuthFeatures()
+            biometricsAreEnabledBySettings()
 
             val convenienceBiometricAllowed = collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
             runCurrent()
@@ -258,7 +283,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
             faceAuthIsEnrolled()
             createBiometricSettingsRepository()
             doNotDisableKeyguardAuthFeatures()
-            faceAuthIsEnabledByBiometricManager()
+            biometricsAreEnabledBySettings()
             runCurrent()
 
             val convenienceBiometricAllowed by
@@ -291,6 +316,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
         testScope.runTest {
             fingerprintIsEnrolled(PRIMARY_USER_ID)
             createBiometricSettingsRepository()
+            biometricsAreEnabledBySettings()
 
             val fingerprintEnabledByDevicePolicy =
                 collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
@@ -316,7 +342,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
             createBiometricSettingsRepository()
             val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
 
-            faceAuthIsEnabledByBiometricManager()
+            biometricsAreEnabledBySettings()
 
             doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)
 
@@ -351,12 +377,18 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
             assertThat(faceAuthAllowed()).isTrue()
         }
 
-    private fun faceAuthIsEnabledByBiometricManager(userId: Int = PRIMARY_USER_ID) {
+    private fun biometricsAreEnabledBySettings(userId: Int = PRIMARY_USER_ID) {
         verify(biometricManager, atLeastOnce())
             .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
         biometricManagerCallback.value.onChanged(true, userId)
     }
 
+    private fun biometricsAreNotEnabledBySettings(userId: Int = PRIMARY_USER_ID) {
+        verify(biometricManager, atLeastOnce())
+            .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
+        biometricManagerCallback.value.onChanged(false, userId)
+    }
+
     @Test
     fun faceEnrollmentStatusOfNewUserUponUserSwitch() =
         testScope.runTest {
@@ -427,7 +459,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
             faceAuthIsEnrolled()
             createBiometricSettingsRepository()
 
-            faceAuthIsEnabledByBiometricManager()
+            biometricsAreEnabledBySettings()
             doNotDisableKeyguardAuthFeatures()
             mobileConnectionsRepository.isAnySimSecure.value = false
             runCurrent()
@@ -454,7 +486,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
             deviceIsInPostureThatSupportsFaceAuth()
             doNotDisableKeyguardAuthFeatures()
             faceAuthIsStrongBiometric()
-            faceAuthIsEnabledByBiometricManager()
+            biometricsAreEnabledBySettings()
             mobileConnectionsRepository.isAnySimSecure.value = false
 
             onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
@@ -636,7 +668,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
             deviceIsInPostureThatSupportsFaceAuth()
             doNotDisableKeyguardAuthFeatures()
             faceAuthIsStrongBiometric()
-            faceAuthIsEnabledByBiometricManager()
+            biometricsAreEnabledBySettings()
 
             onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
             onNonStrongAuthChanged(false, PRIMARY_USER_ID)
@@ -660,7 +692,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
             deviceIsInPostureThatSupportsFaceAuth()
             doNotDisableKeyguardAuthFeatures()
             faceAuthIsNonStrongBiometric()
-            faceAuthIsEnabledByBiometricManager()
+            biometricsAreEnabledBySettings()
 
             onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
             onNonStrongAuthChanged(false, PRIMARY_USER_ID)
@@ -682,6 +714,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
     fun fpAuthCurrentlyAllowed_dependsOnNonStrongAuthBiometricSetting_ifFpIsNotStrong() =
         testScope.runTest {
             createBiometricSettingsRepository()
+            biometricsAreEnabledBySettings()
             val isFingerprintCurrentlyAllowed by
                 collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
 
@@ -723,6 +756,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
     fun fpAuthCurrentlyAllowed_dependsOnStrongAuthBiometricSetting_ifFpIsStrong() =
         testScope.runTest {
             createBiometricSettingsRepository()
+            biometricsAreEnabledBySettings()
             val isFingerprintCurrentlyAllowed by
                 collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
index 66ead14d3d4c4a0a63fac3af64a1213f47e652f7..5852bdb5c35165e068a8f479ae7961ac140c512e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
@@ -25,10 +25,8 @@ import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBl
 import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.MutableSharedFlow
 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
@@ -43,20 +41,16 @@ class KeyguardBlueprintRepositoryTest : SysuiTestCase() {
     private lateinit var underTest: KeyguardBlueprintRepository
     @Mock lateinit var configurationRepository: ConfigurationRepository
     @Mock lateinit var defaultLockscreenBlueprint: DefaultKeyguardBlueprint
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
-    private val configurationFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+    private val testScope = TestScope(StandardTestDispatcher())
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
         whenever(defaultLockscreenBlueprint.id).thenReturn(DEFAULT)
-        whenever(configurationRepository.onAnyConfigurationChange).thenReturn(configurationFlow)
         underTest =
             KeyguardBlueprintRepository(
                 configurationRepository,
                 setOf(defaultLockscreenBlueprint),
-                testScope.backgroundScope,
             )
     }
 
@@ -64,20 +58,7 @@ class KeyguardBlueprintRepositoryTest : SysuiTestCase() {
     fun testApplyBlueprint_DefaultLayout() {
         testScope.runTest {
             val blueprint by collectLastValue(underTest.blueprint)
-            runCurrent()
             underTest.applyBlueprint(defaultLockscreenBlueprint)
-            runCurrent()
-            assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint)
-        }
-    }
-
-    @Test
-    fun testConfigurationChange() {
-        testScope.runTest {
-            val blueprint by collectLastValue(underTest.blueprint)
-            runCurrent()
-            configurationFlow.tryEmit(Unit)
-            runCurrent()
             assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint)
         }
     }
@@ -86,9 +67,7 @@ class KeyguardBlueprintRepositoryTest : SysuiTestCase() {
     fun testRefreshBlueprint() {
         testScope.runTest {
             val blueprint by collectLastValue(underTest.blueprint)
-            runCurrent()
             underTest.refreshBlueprint()
-            runCurrent()
             assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint)
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
index b8a2e9d4afc7551b7fb19d5ed8356acd85a953cb..9fe40d73fa1d580a561064498b03bea77ade4441 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
@@ -21,24 +21,77 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
+import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
+import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
+import com.android.systemui.statusbar.policy.SplitShadeStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.flow.MutableSharedFlow
+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.mockito.Mock
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardBlueprintInteractorTest : SysuiTestCase() {
+    private val configurationFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
     private lateinit var underTest: KeyguardBlueprintInteractor
+    private lateinit var testScope: TestScope
 
+    @Mock private lateinit var splitShadeStateController: SplitShadeStateController
     @Mock private lateinit var keyguardBlueprintRepository: KeyguardBlueprintRepository
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        underTest = KeyguardBlueprintInteractor(keyguardBlueprintRepository)
+        testScope = TestScope(StandardTestDispatcher())
+        whenever(keyguardBlueprintRepository.configurationChange).thenReturn(configurationFlow)
+
+        underTest =
+            KeyguardBlueprintInteractor(
+                keyguardBlueprintRepository,
+                testScope.backgroundScope,
+                mContext,
+                splitShadeStateController,
+            )
+    }
+
+    @Test
+    fun testAppliesDefaultBlueprint() {
+        testScope.runTest {
+            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
+                .thenReturn(false)
+
+            reset(keyguardBlueprintRepository)
+            configurationFlow.tryEmit(Unit)
+            runCurrent()
+
+            verify(keyguardBlueprintRepository)
+                .applyBlueprint(DefaultKeyguardBlueprint.Companion.DEFAULT)
+        }
+    }
+
+    @Test
+    fun testAppliesSplitShadeBlueprint() {
+        testScope.runTest {
+            whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
+                .thenReturn(true)
+
+            reset(keyguardBlueprintRepository)
+            configurationFlow.tryEmit(Unit)
+            runCurrent()
+
+            verify(keyguardBlueprintRepository)
+                .applyBlueprint(SplitShadeKeyguardBlueprint.Companion.ID)
+        }
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 2831053184ccd65b147496d98450ed03035132e8..43d70adf26b0f73936897e5d83f2e9c9ae40ce06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -86,7 +86,6 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() {
                 defaultStatusViewSection,
                 defaultStatusBarViewSection,
                 defaultNSSLSection,
-                splitShadeGuidelines,
                 aodNotificationIconsSection,
                 aodBurnInSection,
                 communalTutorialIndicatorSection,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
index 7ad2ce8ae110a0aa42e5ce08973bf902cda09caf..f4293f035cd1f79f522ea9093da5b2cbafeada8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
@@ -24,6 +24,7 @@ import android.view.View.GONE
 import android.view.View.VISIBLE
 import android.widget.FrameLayout
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -32,6 +33,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.util.animation.UniqueObjectHostView
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.utils.os.FakeHandler
@@ -91,7 +93,8 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
                 settings,
                 fakeHandler,
                 configurationController,
-                ResourcesSplitShadeStateController()
+                ResourcesSplitShadeStateController(),
+                mock<DumpManager>()
             )
         keyguardMediaController.attachSinglePaneContainer(mediaContainerView)
         keyguardMediaController.useSplitShade = false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 5bfe56931bb42706e209db93296d44409e216eaf..a2eb5ef9e4639348c267b63d9af272123f15dacc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -26,8 +26,7 @@ import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardViewController
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
-import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
 import com.android.systemui.dreams.DreamOverlayStateController
@@ -106,7 +105,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
     private val configurationController = FakeConfigurationController()
     private val communalRepository = FakeCommunalRepository(isCommunalEnabled = true)
     private val communalInteractor =
-        CommunalInteractor(communalRepository, FakeCommunalWidgetRepository())
+        CommunalInteractorFactory.create(communalRepository = communalRepository).communalInteractor
     private val notifPanelEvents = ShadeExpansionStateManager()
     private val settings = FakeSettings()
     private lateinit var testableLooper: TestableLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index 2d3dc585ac70d21611ce675b38ab7bc59c2c7312..f93d52b2c35ca33f30e7928076cf4a07d584af46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -30,8 +30,6 @@ import android.view.WindowManager
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.plugins.NavigationEdgeBackPlugin
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -63,7 +61,6 @@ class BackPanelControllerTest : SysuiTestCase() {
     @Mock private lateinit var latencyTracker: LatencyTracker
     @Mock private lateinit var layoutParams: WindowManager.LayoutParams
     @Mock private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback
-    private val featureFlags = FakeFeatureFlags()
 
     @Before
     fun setup() {
@@ -77,7 +74,6 @@ class BackPanelControllerTest : SysuiTestCase() {
                 vibratorHelper,
                 configurationController,
                 latencyTracker,
-                featureFlags
             )
         mBackPanelController.setLayoutParams(layoutParams)
         mBackPanelController.setBackCallback(backCallback)
@@ -106,32 +102,6 @@ class BackPanelControllerTest : SysuiTestCase() {
 
     @Test
     fun handlesBackCommitted() {
-        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
-        startTouch()
-        // Move once to cross the touch slop
-        continueTouch(START_X + touchSlop.toFloat() + 1)
-        // Move again to cross the back trigger threshold
-        continueTouch(START_X + touchSlop + triggerThreshold + 1)
-        // Wait threshold duration and hold touch past trigger threshold
-        Thread.sleep((MAX_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION + 1).toLong())
-        continueTouch(START_X + touchSlop + triggerThreshold + 1)
-
-        assertThat(mBackPanelController.currentState)
-            .isEqualTo(BackPanelController.GestureState.ACTIVE)
-        verify(backCallback).setTriggerBack(true)
-        testableLooper.moveTimeForward(100)
-        testableLooper.processAllMessages()
-        verify(vibratorHelper).vibrate(VIBRATE_ACTIVATED_EFFECT)
-
-        finishTouchActionUp(START_X + touchSlop + triggerThreshold + 1)
-        assertThat(mBackPanelController.currentState)
-            .isEqualTo(BackPanelController.GestureState.COMMITTED)
-        verify(backCallback).triggerBack()
-    }
-
-    @Test
-    fun handlesBackCommitted_withOneWayHapticsAPI() {
-        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
         startTouch()
         // Move once to cross the touch slop
         continueTouch(START_X + touchSlop.toFloat() + 1)
@@ -148,7 +118,6 @@ class BackPanelControllerTest : SysuiTestCase() {
         testableLooper.processAllMessages()
         verify(vibratorHelper)
             .performHapticFeedback(any(), eq(HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE))
-
         finishTouchActionUp(START_X + touchSlop + triggerThreshold + 1)
         assertThat(mBackPanelController.currentState)
             .isEqualTo(BackPanelController.GestureState.COMMITTED)
@@ -157,38 +126,6 @@ class BackPanelControllerTest : SysuiTestCase() {
 
     @Test
     fun handlesBackCancelled() {
-        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
-        startTouch()
-        // Move once to cross the touch slop
-        continueTouch(START_X + touchSlop.toFloat() + 1)
-        // Move again to cross the back trigger threshold
-        continueTouch(
-            START_X + touchSlop + triggerThreshold -
-                mBackPanelController.params.deactivationTriggerThreshold
-        )
-        // Wait threshold duration and hold touch before trigger threshold
-        Thread.sleep((MAX_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION + 1).toLong())
-        continueTouch(
-            START_X + touchSlop + triggerThreshold -
-                mBackPanelController.params.deactivationTriggerThreshold
-        )
-        clearInvocations(backCallback)
-        Thread.sleep(MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION)
-        // Move in the opposite direction to cross the deactivation threshold and cancel back
-        continueTouch(START_X)
-
-        assertThat(mBackPanelController.currentState)
-            .isEqualTo(BackPanelController.GestureState.INACTIVE)
-        verify(backCallback).setTriggerBack(false)
-        verify(vibratorHelper).vibrate(VIBRATE_DEACTIVATED_EFFECT)
-
-        finishTouchActionUp(START_X)
-        verify(backCallback).cancelBack()
-    }
-
-    @Test
-    fun handlesBackCancelled_withOneWayHapticsAPI() {
-        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
         startTouch()
         // Move once to cross the touch slop
         continueTouch(START_X + touchSlop.toFloat() + 1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
index f566efe6f176a8093b295e8686963033ef794e77..f3b114d662b5dbf1f68fd8b2a14a7fd1ef901154 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
@@ -210,6 +210,22 @@ class PowerRepositoryImplTest : SysuiTestCase() {
         assertThat(flagsCaptor.value).isNotEqualTo(PowerManager.USER_ACTIVITY_FLAG_INDIRECT)
     }
 
+    @Test
+    fun userActivity_notifiesPowerManager_noChangeLightsTrue() {
+        systemClock.setUptimeMillis(345000)
+
+        underTest.userTouch(noChangeLights = true)
+
+        val flagsCaptor = argumentCaptor<Int>()
+        verify(manager)
+            .userActivity(
+                eq(345000L),
+                eq(PowerManager.USER_ACTIVITY_EVENT_TOUCH),
+                capture(flagsCaptor)
+            )
+        assertThat(flagsCaptor.value).isEqualTo(PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS)
+    }
+
     private fun verifyRegistered() {
         // We must verify with all arguments, even those that are optional because they have default
         // values because Mockito is forcing us to. Once we can use mockito-kotlin, we should be
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 3feb5bfd088a52d7bb8e11e69071690a17af9a44..e84d274b4763895054b14df78fd984183499d78c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -18,17 +18,23 @@
 
 package com.android.systemui.scene
 
+import android.telecom.TelecomManager
+import android.telephony.TelephonyManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.internal.util.EmergencyAffordanceManager
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.model.SysUiState
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
@@ -49,7 +55,9 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobi
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -63,6 +71,10 @@ import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
 
 /**
  * Integration test cases for the Scene Framework.
@@ -87,6 +99,10 @@ import org.junit.runner.RunWith
 @RunWith(AndroidJUnit4::class)
 class SceneFrameworkIntegrationTest : SysuiTestCase() {
 
+    @Mock private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager
+    @Mock private lateinit var tableLogger: TableLogBuffer
+    @Mock private lateinit var telecomManager: TelecomManager
+
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
     private val sceneContainerConfig = utils.fakeSceneContainerConfig()
@@ -123,11 +139,10 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
             authenticationInteractor = authenticationInteractor,
             sceneInteractor = sceneInteractor,
         )
-    private val bouncerViewModel =
-        utils.bouncerViewModel(
-            bouncerInteractor = bouncerInteractor,
-            authenticationInteractor = authenticationInteractor,
-        )
+
+    private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
+    private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor
+    private lateinit var bouncerViewModel: BouncerViewModel
 
     private val lockscreenSceneViewModel =
         LockscreenSceneViewModel(
@@ -141,7 +156,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
         )
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
-    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     private var mobileIconsViewModel: MobileIconsViewModel =
         MobileIconsViewModel(
@@ -155,7 +169,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
                     FakeMobileConnectionsRepository(),
                 ),
             constants = mock(),
-            flags,
+            utils.featureFlags,
             scope = testScope.backgroundScope,
         )
 
@@ -173,6 +187,39 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
 
     @Before
     fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, true)
+        whenever(telecomManager.isInCall).thenReturn(false)
+        whenever(emergencyAffordanceManager.needsEmergencyAffordance()).thenReturn(true)
+
+        utils.featureFlags.apply {
+            set(Flags.NEW_NETWORK_SLICE_UI, false)
+            set(Flags.REFACTOR_GETCURRENTUSER, true)
+        }
+
+        mobileConnectionsRepository =
+            FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger)
+        mobileConnectionsRepository.isAnySimSecure.value = true
+
+        utils.telephonyRepository.apply {
+            setHasTelephonyRadio(true)
+            setCallState(TelephonyManager.CALL_STATE_IDLE)
+            setIsInCall(false)
+        }
+
+        bouncerActionButtonInteractor =
+            utils.bouncerActionButtonInteractor(
+                mobileConnectionsRepository = mobileConnectionsRepository,
+                telecomManager = telecomManager,
+                emergencyAffordanceManager = emergencyAffordanceManager,
+            )
+        bouncerViewModel =
+            utils.bouncerViewModel(
+                bouncerInteractor = bouncerInteractor,
+                authenticationInteractor = authenticationInteractor,
+                actionButtonInteractor = bouncerActionButtonInteractor,
+            )
+
         shadeHeaderViewModel =
             ShadeHeaderViewModel(
                 applicationScope = testScope.backgroundScope,
@@ -395,6 +442,45 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
             emulateUiSceneTransition()
         }
 
+    @Test
+    fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
+        testScope.runTest {
+            setAuthMethod(DomainLayerAuthenticationMethodModel.Password)
+            val upDestinationSceneKey by
+                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer)
+            emulateUserDrivenTransition(to = upDestinationSceneKey)
+
+            val bouncerActionButton by collectLastValue(bouncerViewModel.actionButton)
+            assertWithMessage("Bouncer action button not visible")
+                .that(bouncerActionButton)
+                .isNotNull()
+            bouncerActionButton?.onClick?.invoke()
+            runCurrent()
+
+            // TODO(b/298026988): Assert that an activity was started once we use ActivityStarter.
+        }
+
+    @Test
+    fun bouncerActionButtonClick_duringCall_returnsToCall() =
+        testScope.runTest {
+            setAuthMethod(DomainLayerAuthenticationMethodModel.Password)
+            startPhoneCall()
+            val upDestinationSceneKey by
+                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer)
+            emulateUserDrivenTransition(to = upDestinationSceneKey)
+
+            val bouncerActionButton by collectLastValue(bouncerViewModel.actionButton)
+            assertWithMessage("Bouncer action button not visible during call")
+                .that(bouncerActionButton)
+                .isNotNull()
+            bouncerActionButton?.onClick?.invoke()
+            runCurrent()
+
+            verify(telecomManager).showInCallScreen(any())
+        }
+
     /**
      * Asserts that the current scene in the view-model matches what's expected.
      *
@@ -438,6 +524,17 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
         runCurrent()
     }
 
+    /** Emulates a phone call in progress. */
+    private fun TestScope.startPhoneCall() {
+        whenever(telecomManager.isInCall).thenReturn(true)
+        utils.telephonyRepository.apply {
+            setHasTelephonyRadio(true)
+            setIsInCall(true)
+            setCallState(TelephonyManager.CALL_STATE_OFFHOOK)
+        }
+        runCurrent()
+    }
+
     /**
      * Emulates a complete transition in the UI from whatever the current scene is in the UI to
      * whatever the current scene should be, based on the value in
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 49049bc51460b4a4c9deaa65d83cfb5ea9fe11ce..1b4ba6433ded413e75a116ab0ecbc77dc4fdd421 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -36,20 +36,27 @@ import android.content.Intent;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
 import com.android.systemui.mediaprojection.SessionCreationSource;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
+import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.phone.DialogDelegate;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -92,6 +99,7 @@ public class RecordingControllerTest extends SysuiTestCase {
 
     private FakeFeatureFlags mFeatureFlags;
     private RecordingController mController;
+    private TestSystemUIDialogFactory mDialogFactory;
 
     private static final int USER_ID = 10;
 
@@ -103,6 +111,15 @@ public class RecordingControllerTest extends SysuiTestCase {
 
         when(mUserContextProvider.getUserContext()).thenReturn(spiedContext);
 
+        mDialogFactory = new TestSystemUIDialogFactory(
+                mContext,
+                mFeatureFlags,
+                Dependency.get(SystemUIDialogManager.class),
+                Dependency.get(SysUiState.class),
+                Dependency.get(BroadcastDispatcher.class),
+                Dependency.get(DialogLaunchAnimator.class)
+        );
+
         mFeatureFlags = new FakeFeatureFlags();
         mController = new RecordingController(
                 mMainExecutor,
@@ -112,7 +129,8 @@ public class RecordingControllerTest extends SysuiTestCase {
                 mUserContextProvider,
                 () -> mDevicePolicyResolver,
                 mUserTracker,
-                mMediaProjectionMetricsLogger);
+                mMediaProjectionMetricsLogger,
+                mDialogFactory);
         mController.addCallback(mCallback);
     }
 
@@ -218,10 +236,17 @@ public class RecordingControllerTest extends SysuiTestCase {
         mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
         when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
 
-        Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
-                mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
-
-        assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class);
+        Dialog dialog =
+                mController.createScreenRecordDialog(
+                        mContext,
+                        mFeatureFlags,
+                        mDialogLaunchAnimator,
+                        mActivityStarter,
+                        /* onStartRecordingClicked= */ null);
+
+        assertThat(dialog).isSameInstanceAs(mDialogFactory.mLastCreatedDialog);
+        assertThat(mDialogFactory.mLastDelegate)
+                .isInstanceOf(ScreenRecordPermissionDialogDelegate.class);
     }
 
     @Test
@@ -253,10 +278,17 @@ public class RecordingControllerTest extends SysuiTestCase {
         mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
         when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
 
-        Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
-                mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
-
-        assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class);
+        Dialog dialog =
+                mController.createScreenRecordDialog(
+                        mContext,
+                        mFeatureFlags,
+                        mDialogLaunchAnimator,
+                        mActivityStarter,
+                        /* onStartRecordingClicked= */ null);
+
+        assertThat(dialog).isSameInstanceAs(mDialogFactory.mLastCreatedDialog);
+        assertThat(mDialogFactory.mLastDelegate)
+                .isInstanceOf(ScreenRecordPermissionDialogDelegate.class);
     }
 
     @Test
@@ -273,4 +305,34 @@ public class RecordingControllerTest extends SysuiTestCase {
                         /* hostUid= */ myUid(),
                         SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
     }
+
+    private static class TestSystemUIDialogFactory extends SystemUIDialog.Factory {
+
+        @Nullable private DialogDelegate<SystemUIDialog> mLastDelegate;
+        @Nullable private SystemUIDialog mLastCreatedDialog;
+
+        TestSystemUIDialogFactory(
+                Context context,
+                FeatureFlags featureFlags,
+                SystemUIDialogManager systemUIDialogManager,
+                SysUiState sysUiState,
+                BroadcastDispatcher broadcastDispatcher,
+                DialogLaunchAnimator dialogLaunchAnimator) {
+            super(
+                    context,
+                    featureFlags,
+                    systemUIDialogManager,
+                    sysUiState,
+                    broadcastDispatcher,
+                    dialogLaunchAnimator);
+        }
+
+        @Override
+        public SystemUIDialog create(DialogDelegate<SystemUIDialog> delegate) {
+            SystemUIDialog dialog = super.create(delegate);
+            mLastDelegate = delegate;
+            mLastCreatedDialog = dialog;
+            return dialog;
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index fd381392c3e93d6f3f2a70df575f2afb737b7758..c848287aa53e5c56e290f52f9651d84b34c0aa01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -23,16 +23,22 @@ import android.testing.TestableLooper
 import android.view.View
 import android.widget.Spinner
 import androidx.test.filters.SmallTest
+import com.android.systemui.Dependency
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity
 import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
 import com.android.systemui.mediaprojection.permission.SINGLE_APP
+import com.android.systemui.model.SysUiState
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
@@ -50,7 +56,7 @@ import org.mockito.MockitoAnnotations
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-class ScreenRecordPermissionDialogTest : SysuiTestCase() {
+class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() {
 
     @Mock private lateinit var starter: ActivityStarter
     @Mock private lateinit var controller: RecordingController
@@ -59,15 +65,23 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() {
     @Mock private lateinit var onStartRecordingClicked: Runnable
     @Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger
 
-    private lateinit var dialog: ScreenRecordPermissionDialog
+    private lateinit var dialog: SystemUIDialog
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        dialog =
-            ScreenRecordPermissionDialog(
+        val systemUIDialogFactory =
+            SystemUIDialog.Factory(
                 context,
+                Dependency.get(FeatureFlags::class.java),
+                Dependency.get(SystemUIDialogManager::class.java),
+                Dependency.get(SysUiState::class.java),
+                Dependency.get(BroadcastDispatcher::class.java),
+                Dependency.get(DialogLaunchAnimator::class.java),
+            )
+        val delegate =
+            ScreenRecordPermissionDialogDelegate(
                 UserHandle.of(0),
                 TEST_HOST_UID,
                 controller,
@@ -76,20 +90,21 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() {
                 onStartRecordingClicked,
                 mediaProjectionMetricsLogger,
             )
-        dialog.onCreate(null)
+        dialog = systemUIDialogFactory.create(delegate)
+        delegate.onCreate(dialog, savedInstanceState = null)
         whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
     }
 
     @After
     fun teardown() {
         if (::dialog.isInitialized) {
-            dialog.dismiss()
+            dismissDialog()
         }
     }
 
     @Test
     fun testShowDialog_partialScreenSharingEnabled_optionsSpinnerIsVisible() {
-        dialog.show()
+        showDialog()
 
         val visibility = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner).visibility
         assertThat(visibility).isEqualTo(View.VISIBLE)
@@ -97,7 +112,7 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() {
 
     @Test
     fun testShowDialog_singleAppSelected_showTapsIsGone() {
-        dialog.show()
+        showDialog()
         onSpinnerItemSelected(SINGLE_APP)
 
         val visibility = dialog.requireViewById<View>(R.id.show_taps).visibility
@@ -106,7 +121,7 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() {
 
     @Test
     fun testShowDialog_entireScreenSelected_showTapsIsVisible() {
-        dialog.show()
+        showDialog()
         onSpinnerItemSelected(ENTIRE_SCREEN)
 
         val visibility = dialog.requireViewById<View>(R.id.show_taps).visibility
@@ -115,7 +130,7 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() {
 
     @Test
     fun startClicked_singleAppSelected_passesHostUidToAppSelector() {
-        dialog.show()
+        showDialog()
         onSpinnerItemSelected(SINGLE_APP)
 
         clickOnStart()
@@ -128,14 +143,14 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() {
 
     @Test
     fun showDialog_dialogIsShowing() {
-        dialog.show()
+        showDialog()
 
         assertThat(dialog.isShowing).isTrue()
     }
 
     @Test
     fun showDialog_singleAppIsDefault() {
-        dialog.show()
+        showDialog()
 
         val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
         val singleApp = context.getString(R.string.screen_share_permission_dialog_option_single_app)
@@ -144,7 +159,7 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() {
 
     @Test
     fun showDialog_cancelClicked_dialogIsDismissed() {
-        dialog.show()
+        showDialog()
 
         clickOnCancel()
 
@@ -153,7 +168,7 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() {
 
     @Test
     fun showDialog_cancelClickedMultipleTimes_projectionRequestCancelledIsLoggedOnce() {
-        dialog.show()
+        showDialog()
 
         clickOnCancel()
         clickOnCancel()
@@ -163,16 +178,22 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() {
 
     @Test
     fun dismissDialog_dismissCalledMultipleTimes_projectionRequestCancelledIsLoggedOnce() {
-        dialog.show()
+        showDialog()
 
-        TestableLooper.get(this).runWithLooper {
-            dialog.dismiss()
-            dialog.dismiss()
-        }
+        dismissDialog()
+        dismissDialog()
 
         verify(mediaProjectionMetricsLogger).notifyProjectionRequestCancelled(TEST_HOST_UID)
     }
 
+    private fun showDialog() {
+        dialog.show()
+    }
+
+    private fun dismissDialog() {
+        dialog.dismiss()
+    }
+
     private fun clickOnCancel() {
         dialog.requireViewById<View>(android.R.id.button2).performClick()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 74b2723e870e01b4bdc925624c73d2f4a97c863e..cce038f4ffc187b11cb1511d63f907dbe388dd80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -94,6 +94,7 @@ class FakeMobileConnectionsRepository(
     override val defaultMobileIconGroup = _defaultMobileIconGroup
 
     override val isAnySimSecure = MutableStateFlow(false)
+    override fun getIsAnySimSecure(): Boolean = isAnySimSecure.value
 
     private var isInEcmMode: Boolean = false
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index ac75d4f189cc9201537e242d12896f866a083884..03f300542a6f0585da5749827aa5ef5c681e5c3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -92,7 +92,6 @@ import org.mockito.MockitoAnnotations
 // to run the callback and this makes the looper place nicely with TestScope etc.
 @TestableLooper.RunWithLooper
 class MobileConnectionsRepositoryTest : SysuiTestCase() {
-    private lateinit var underTest: MobileConnectionsRepositoryImpl
 
     private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
     private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
@@ -101,6 +100,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
     private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
     private lateinit var wifiRepository: WifiRepository
     private lateinit var carrierConfigRepository: CarrierConfigRepository
+
     @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var subscriptionManager: SubscriptionManager
     @Mock private lateinit var telephonyManager: TelephonyManager
@@ -115,6 +115,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
     private val dispatcher = StandardTestDispatcher()
     private val testScope = TestScope(dispatcher)
 
+    private lateinit var underTest: MobileConnectionsRepositoryImpl
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -1178,6 +1180,16 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
             assertThat(latest).isFalse()
         }
 
+    @Test
+    fun getIsAnySimSecure_delegatesCallToKeyguardUpdateMonitor() =
+        testScope.runTest {
+            assertThat(underTest.getIsAnySimSecure()).isFalse()
+
+            whenever(updateMonitor.isSimPinSecure).thenReturn(true)
+
+            assertThat(underTest.getIsAnySimSecure()).isTrue()
+        }
+
     @Test
     fun noSubscriptionsInEcmMode_notInEcmMode() =
         testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 58d93c98f015e028c2932cd1f555417c0830ad29..2f79955c4f5b1bd86b8759bbf5bc3738df4cc67f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -294,6 +294,6 @@ public class BatteryControllerTest extends SysuiTestCase {
         when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
         when(mUsbPortStatus.isConnected()).thenReturn(true);
         when(mUsbPortStatus.getComplianceWarnings())
-                .thenReturn(new int[]{UsbPortStatus.COMPLIANCE_WARNING_OTHER});
+                .thenReturn(new int[]{UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY});
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index 1968d75a7964c72a9c36f8efb00a77d38730c723..017eefe38f2a49a6e1d3b9612634775ef73002da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -38,16 +38,14 @@ import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController
 import com.android.systemui.res.R
+import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
-import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.source.UserRecord
@@ -65,9 +63,6 @@ import junit.framework.Assert.assertNotNull
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -102,18 +97,16 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
-    private lateinit var underTest: UserSwitcherInteractor
-
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private lateinit var spyContext: Context
-    private lateinit var testScope: TestScope
     private lateinit var userRepository: FakeUserRepository
     private lateinit var keyguardReply: KeyguardInteractorFactory.WithDependencies
     private lateinit var keyguardRepository: FakeKeyguardRepository
-    private lateinit var telephonyRepository: FakeTelephonyRepository
-    private lateinit var testDispatcher: TestDispatcher
-    private lateinit var featureFlags: FakeFeatureFlags
     private lateinit var refreshUsersScheduler: RefreshUsersScheduler
 
+    private lateinit var underTest: UserSwitcherInteractor
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -127,22 +120,17 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
             SUPERVISED_USER_CREATION_APP_PACKAGE,
         )
 
-        featureFlags =
-            FakeFeatureFlags().apply {
-                set(Flags.FULL_SCREEN_USER_SWITCHER, false)
-                set(Flags.FACE_AUTH_REFACTOR, true)
-            }
+        utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+        utils.featureFlags.set(Flags.FACE_AUTH_REFACTOR, true)
+
         spyContext = spy(context)
-        keyguardReply = KeyguardInteractorFactory.create(featureFlags = featureFlags)
+        keyguardReply = KeyguardInteractorFactory.create(featureFlags = utils.featureFlags)
         keyguardRepository = keyguardReply.repository
         userRepository = FakeUserRepository()
-        telephonyRepository = FakeTelephonyRepository()
-        testDispatcher = StandardTestDispatcher()
-        testScope = TestScope(testDispatcher)
         refreshUsersScheduler =
             RefreshUsersScheduler(
                 applicationScope = testScope.backgroundScope,
-                mainDispatcher = testDispatcher,
+                mainDispatcher = utils.testDispatcher,
                 repository = userRepository,
             )
     }
@@ -372,7 +360,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
     fun actions_deviceUnlocked_fullScreen() {
         createUserInteractor()
         testScope.runTest {
-            featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 2, includeGuest = false)
 
             userRepository.setUserInfos(userInfos)
@@ -456,7 +444,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
     fun actions_deviceLockedAddFromLockscreenSet_fullList_fullScreen() {
         createUserInteractor()
         testScope.runTest {
-            featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
             userRepository.setSelectedUserInfo(userInfos[0])
@@ -649,7 +637,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
 
             val refreshUsersCallCount = userRepository.refreshUsersCallCount
 
-            telephonyRepository.setCallState(1)
+            utils.telephonyRepository.setCallState(1)
             runCurrent()
 
             assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
@@ -801,7 +789,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
     fun userRecordsFullScreen() {
         createUserInteractor()
         testScope.runTest {
-            featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 3, includeGuest = false)
             userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
             userRepository.setUserInfos(userInfos)
@@ -910,7 +898,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
     fun showUserSwitcher_fullScreenEnabled_launchesFullScreenDialog() {
         createUserInteractor()
         testScope.runTest {
-            featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
 
             val expandable = mock<Expandable>()
             underTest.showUserSwitcher(expandable)
@@ -1125,21 +1113,18 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
                 manager = manager,
                 headlessSystemUserMode = headlessSystemUserMode,
                 applicationScope = testScope.backgroundScope,
-                telephonyInteractor =
-                    TelephonyInteractor(
-                        repository = telephonyRepository,
-                    ),
+                telephonyInteractor = utils.telephonyInteractor(),
                 broadcastDispatcher = fakeBroadcastDispatcher,
                 keyguardUpdateMonitor = keyguardUpdateMonitor,
-                backgroundDispatcher = testDispatcher,
+                backgroundDispatcher = utils.testDispatcher,
                 activityManager = activityManager,
                 refreshUsersScheduler = refreshUsersScheduler,
                 guestUserInteractor =
                     GuestUserInteractor(
                         applicationContext = spyContext,
                         applicationScope = testScope.backgroundScope,
-                        mainDispatcher = testDispatcher,
-                        backgroundDispatcher = testDispatcher,
+                        mainDispatcher = utils.testDispatcher,
+                        backgroundDispatcher = utils.testDispatcher,
                         manager = manager,
                         repository = userRepository,
                         deviceProvisionedController = deviceProvisionedController,
@@ -1150,7 +1135,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
                         resetOrExitSessionReceiver = resetOrExitSessionReceiver,
                     ),
                 uiEventLogger = uiEventLogger,
-                featureFlags = featureFlags,
+                featureFlags = utils.featureFlags,
                 userRestrictionChecker = mock(),
             )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 8353cf78d983d1ee72cff1477d9c6696a7bd08ab..d0fa27e3b79a10d9e8a0cae96565edb70c179b03 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -20,6 +20,7 @@ import com.android.systemui.dagger.SysUISingleton
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
+import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -31,16 +32,23 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor
     private val _onAnyConfigurationChange = MutableSharedFlow<Unit>()
     override val onAnyConfigurationChange: Flow<Unit> = _onAnyConfigurationChange.asSharedFlow()
 
+    private val _onConfigurationChange =
+        MutableSharedFlow<Unit>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+    override val onConfigurationChange: Flow<Unit> = _onConfigurationChange.asSharedFlow()
+
     private val _scaleForResolution = MutableStateFlow(1f)
     override val scaleForResolution: Flow<Float> = _scaleForResolution.asStateFlow()
 
     private val pixelSizes = mutableMapOf<Int, MutableStateFlow<Int>>()
-    private val colors = mutableMapOf<Int, MutableStateFlow<Int>>()
 
     fun onAnyConfigurationChange() {
         _onAnyConfigurationChange.tryEmit(Unit)
     }
 
+    fun onConfigurationChange() {
+        _onConfigurationChange.tryEmit(Unit)
+    }
+
     fun setScaleForResolution(scale: Float) {
         _scaleForResolution.value = scale
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3ab1b6c11e021bd65b3538759e24e2973914a2c2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.communal.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeCommunalMediaRepository(
+    override val mediaPlaying: MutableStateFlow<Boolean> = MutableStateFlow(false)
+) : CommunalMediaRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index 799bb403a8ff251c97b2f61bb185e657c5dc8ae5..2cb17b5badc41e09066f091bf21d4825396cf90f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -1,6 +1,7 @@
 package com.android.systemui.communal.data.repository
 
 import com.android.systemui.communal.shared.model.CommunalSceneKey
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
 /** Fake implementation of [CommunalRepository]. */
@@ -16,4 +17,11 @@ class FakeCommunalRepository(
     fun setIsCommunalEnabled(value: Boolean) {
         isCommunalEnabled = value
     }
+
+    private val _isCommunalHubShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val isCommunalHubShowing: Flow<Boolean> = _isCommunalHubShowing
+
+    fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) {
+        _isCommunalHubShowing.value = isCommunalHubShowing
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0c821eab65e053123e935e1bf61710899372da4c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.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.communal.domain.interactor
+
+import android.appwidget.AppWidgetHost
+import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
+import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.test.TestScope
+
+object CommunalInteractorFactory {
+
+    @JvmOverloads
+    @JvmStatic
+    fun create(
+        testScope: TestScope = TestScope(),
+        communalRepository: FakeCommunalRepository = FakeCommunalRepository(),
+        widgetRepository: FakeCommunalWidgetRepository = FakeCommunalWidgetRepository(),
+        mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(),
+        smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(),
+        tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(),
+        appWidgetHost: AppWidgetHost = mock(),
+    ): WithDependencies {
+        val withDeps =
+            CommunalTutorialInteractorFactory.create(
+                testScope = testScope,
+                communalTutorialRepository = tutorialRepository,
+                communalRepository = communalRepository,
+            )
+        return WithDependencies(
+            communalRepository,
+            widgetRepository,
+            mediaRepository,
+            smartspaceRepository,
+            tutorialRepository,
+            withDeps.keyguardRepository,
+            withDeps.keyguardInteractor,
+            withDeps.communalTutorialInteractor,
+            appWidgetHost,
+            CommunalInteractor(
+                communalRepository,
+                widgetRepository,
+                mediaRepository,
+                smartspaceRepository,
+                withDeps.communalTutorialInteractor,
+                appWidgetHost,
+            ),
+        )
+    }
+
+    data class WithDependencies(
+        val communalRepository: FakeCommunalRepository,
+        val widgetRepository: FakeCommunalWidgetRepository,
+        val mediaRepository: FakeCommunalMediaRepository,
+        val smartspaceRepository: FakeSmartspaceRepository,
+        val tutorialRepository: FakeCommunalTutorialRepository,
+        val keyguardRepository: FakeKeyguardRepository,
+        val keyguardInteractor: KeyguardInteractor,
+        val tutorialInteractor: CommunalTutorialInteractor,
+        val appWidgetHost: AppWidgetHost,
+        val communalInteractor: CommunalInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e5cadabfc1c722f415d229b922803a9e06944123
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.communal.domain.interactor
+
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import kotlinx.coroutines.test.TestScope
+
+object CommunalTutorialInteractorFactory {
+
+    @JvmOverloads
+    @JvmStatic
+    fun create(
+        testScope: TestScope,
+        communalTutorialRepository: FakeCommunalTutorialRepository =
+            FakeCommunalTutorialRepository(),
+        communalRepository: FakeCommunalRepository =
+            FakeCommunalRepository(isCommunalEnabled = true),
+        keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(),
+        keyguardInteractor: KeyguardInteractor =
+            KeyguardInteractorFactory.create(
+                    repository = keyguardRepository,
+                )
+                .keyguardInteractor
+    ): WithDependencies {
+        return WithDependencies(
+            testScope = testScope,
+            communalRepository = communalRepository,
+            communalTutorialRepository = communalTutorialRepository,
+            keyguardRepository = keyguardRepository,
+            keyguardInteractor = keyguardInteractor,
+            communalTutorialInteractor =
+                CommunalTutorialInteractor(
+                    testScope.backgroundScope,
+                    communalTutorialRepository,
+                    keyguardInteractor,
+                    communalRepository,
+                )
+        )
+    }
+
+    data class WithDependencies(
+        val testScope: TestScope,
+        val communalRepository: FakeCommunalRepository,
+        val communalTutorialRepository: FakeCommunalTutorialRepository,
+        val keyguardRepository: FakeKeyguardRepository,
+        val keyguardInteractor: KeyguardInteractor,
+        val communalTutorialInteractor: CommunalTutorialInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
index 957fbbd8680f5bff2b5bed56209728794154a344..ace650020a9d462585d9f2982b3b57fcc75c1ca5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
@@ -18,11 +18,11 @@
 package com.android.systemui.power.data.repository
 
 import android.os.PowerManager
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.power.shared.model.ScreenPowerState
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakefulnessModel
 import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.dagger.SysUISingleton
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
@@ -55,15 +55,15 @@ class FakePowerRepository @Inject constructor() : PowerRepository {
         lastWakeReason = wakeReason
     }
 
-    override fun userTouch() {
+    override fun userTouch(noChangeLights: Boolean) {
         userTouchRegistered = true
     }
 
     override fun updateWakefulness(
-            rawState: WakefulnessState,
-            lastWakeReason: WakeSleepReason,
-            lastSleepReason: WakeSleepReason,
-            powerButtonLaunchGestureTriggered: Boolean
+        rawState: WakefulnessState,
+        lastWakeReason: WakeSleepReason,
+        lastSleepReason: WakeSleepReason,
+        powerButtonLaunchGestureTriggered: Boolean
     ) {
         _wakefulness.value =
             WakefulnessModel(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index f97d6b3d262f28b7c000d0031323c759508a0104..36ec18fc4de4049ec8fa11103fc8a6b5d88087a3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -16,10 +16,15 @@
 
 package com.android.systemui.scene
 
+import android.app.ActivityTaskManager
 import android.content.Context
+import android.content.Intent
 import android.content.pm.UserInfo
 import android.graphics.Bitmap
 import android.graphics.drawable.BitmapDrawable
+import android.telecom.TelecomManager
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.util.EmergencyAffordanceManager
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.AuthenticationRepository
@@ -27,8 +32,11 @@ import com.android.systemui.authentication.data.repository.FakeAuthenticationRep
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
 import com.android.systemui.bouncer.data.repository.BouncerRepository
+import com.android.systemui.bouncer.data.repository.EmergencyServicesRepository
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.EmergencyDialerIntentFactory
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
@@ -36,11 +44,12 @@ import com.android.systemui.classifier.domain.interactor.FalsingInteractor
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
-import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
 import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.doze.DozeLogger
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
@@ -62,10 +71,13 @@ import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.data.repository.TelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.user.ui.viewmodel.UserActionViewModel
 import com.android.systemui.user.ui.viewmodel.UserViewModel
 import com.android.systemui.util.mockito.mock
@@ -102,15 +114,23 @@ class SceneTestUtils(
             currentTime = { testScope.currentTime },
         )
     }
+    val configurationRepository: FakeConfigurationRepository by lazy {
+        FakeConfigurationRepository()
+    }
+    private val emergencyServicesRepository: EmergencyServicesRepository by lazy {
+        EmergencyServicesRepository(
+            applicationScope = applicationScope(),
+            resources = context.resources,
+            configurationRepository = configurationRepository,
+        )
+    }
+    val telephonyRepository: FakeTelephonyRepository by lazy { FakeTelephonyRepository() }
 
     val communalRepository: FakeCommunalRepository by lazy { FakeCommunalRepository() }
-    private val communalWidgetRepository: FakeCommunalWidgetRepository by lazy {
-        FakeCommunalWidgetRepository()
-    }
     val keyguardRepository: FakeKeyguardRepository by lazy { FakeKeyguardRepository() }
     val powerRepository: FakePowerRepository by lazy { FakePowerRepository() }
 
-    private val userRepository: UserRepository by lazy {
+    val userRepository: UserRepository by lazy {
         FakeUserRepository().apply {
             val users = listOf(UserInfo(/* id=  */ 0, "name", /* flags= */ 0))
             setUserInfos(users)
@@ -186,7 +206,7 @@ class SceneTestUtils(
             featureFlags = featureFlags,
             sceneContainerFlags = sceneContainerFlags,
             bouncerRepository = FakeKeyguardBouncerRepository(),
-            configurationRepository = FakeConfigurationRepository(),
+            configurationRepository = configurationRepository,
             shadeRepository = FakeShadeRepository(),
             sceneInteractorProvider = { sceneInteractor() },
             powerInteractor = PowerInteractorFactory.create().powerInteractor,
@@ -194,10 +214,10 @@ class SceneTestUtils(
     }
 
     fun communalInteractor(): CommunalInteractor {
-        return CommunalInteractor(
-            communalRepository = communalRepository,
-            widgetRepository = communalWidgetRepository,
-        )
+        return CommunalInteractorFactory.create(
+                communalRepository = communalRepository,
+            )
+            .communalInteractor
     }
 
     fun bouncerInteractor(
@@ -220,6 +240,7 @@ class SceneTestUtils(
     fun bouncerViewModel(
         bouncerInteractor: BouncerInteractor,
         authenticationInteractor: AuthenticationInteractor,
+        actionButtonInteractor: BouncerActionButtonInteractor,
         users: List<UserViewModel> = createUsers(),
     ): BouncerViewModel {
         return BouncerViewModel(
@@ -232,13 +253,16 @@ class SceneTestUtils(
             selectedUser = flowOf(users.first { it.isSelectionMarkerVisible }),
             users = flowOf(users),
             userSwitcherMenu = flowOf(createMenuActions()),
-            telephonyInteractor =
-                TelephonyInteractor(
-                    repository = FakeTelephonyRepository(),
-                ),
+            actionButtonInteractor = actionButtonInteractor,
         )
     }
 
+    fun telephonyInteractor(
+        repository: TelephonyRepository = telephonyRepository,
+    ): TelephonyInteractor {
+        return TelephonyInteractor(repository = repository)
+    }
+
     fun falsingInteractor(collector: FalsingCollector = falsingCollector()): FalsingInteractor {
         return falsingInteractor ?: FalsingInteractor(collector).also { falsingInteractor = it }
     }
@@ -288,6 +312,40 @@ class SceneTestUtils(
         }
     }
 
+    fun selectedUserInteractor(): SelectedUserInteractor {
+        return SelectedUserInteractor(userRepository, featureFlags)
+    }
+
+    fun bouncerActionButtonInteractor(
+        mobileConnectionsRepository: MobileConnectionsRepository = mock(),
+        activityTaskManager: ActivityTaskManager = mock(),
+        telecomManager: TelecomManager? = null,
+        emergencyAffordanceManager: EmergencyAffordanceManager =
+            EmergencyAffordanceManager(context),
+        emergencyDialerIntentFactory: EmergencyDialerIntentFactory =
+            object : EmergencyDialerIntentFactory {
+                override fun invoke(): Intent = Intent()
+            },
+        metricsLogger: MetricsLogger = mock(),
+        dozeLogger: DozeLogger = mock(),
+    ): BouncerActionButtonInteractor {
+        return BouncerActionButtonInteractor(
+            applicationContext = context,
+            backgroundDispatcher = testDispatcher,
+            repository = emergencyServicesRepository,
+            mobileConnectionsRepository = mobileConnectionsRepository,
+            telephonyInteractor = telephonyInteractor(),
+            authenticationInteractor = authenticationInteractor(),
+            selectedUserInteractor = selectedUserInteractor(),
+            activityTaskManager = activityTaskManager,
+            telecomManager = telecomManager,
+            emergencyAffordanceManager = emergencyAffordanceManager,
+            emergencyDialerIntentFactory = emergencyDialerIntentFactory,
+            metricsLogger = metricsLogger,
+            dozeLogger = dozeLogger,
+        )
+    }
+
     companion object {
         fun DomainLayerAuthenticationMethodModel.toDataLayer(): DataLayerAuthenticationMethodModel {
             return when (this) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c8013ef96fa7081caed0695f75be7bbe70267f53
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt
@@ -0,0 +1,21 @@
+package com.android.systemui.smartspace.data.repository
+
+import android.app.smartspace.SmartspaceTarget
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeSmartspaceRepository(
+    smartspaceRemoteViewsEnabled: Boolean = true,
+) : SmartspaceRepository {
+
+    override val isSmartspaceRemoteViewsEnabled = smartspaceRemoteViewsEnabled
+
+    private val _lockscreenSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> =
+        MutableStateFlow(emptyList())
+    override val lockscreenSmartspaceTargets: Flow<List<SmartspaceTarget>> =
+        _lockscreenSmartspaceTargets
+
+    fun setLockscreenSmartspaceTargets(targets: List<SmartspaceTarget>) {
+        _lockscreenSmartspaceTargets.value = targets
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt
index 992ac62fe3c448e9fda8271db5a7f1033f9fa7ea..5cde3868199b47f7fce635b31bbe6eca13756ce3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt
@@ -23,6 +23,7 @@ import dagger.Module
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
 @SysUISingleton
@@ -31,6 +32,9 @@ class FakeTelephonyRepository @Inject constructor() : TelephonyRepository {
     private val _callState = MutableStateFlow(0)
     override val callState: Flow<Int> = _callState.asStateFlow()
 
+    private val _isInCall = MutableStateFlow(false)
+    override val isInCall: StateFlow<Boolean> = _isInCall.asStateFlow()
+
     override var hasTelephonyRadio: Boolean = true
         private set
 
@@ -38,8 +42,12 @@ class FakeTelephonyRepository @Inject constructor() : TelephonyRepository {
         _callState.value = value
     }
 
-    fun setHasRadio(hasRadio: Boolean) {
-        this.hasTelephonyRadio = hasRadio
+    fun setIsInCall(isInCall: Boolean) {
+        _isInCall.value = isInCall
+    }
+
+    fun setHasTelephonyRadio(hasTelephonyRadio: Boolean) {
+        this.hasTelephonyRadio = hasTelephonyRadio
     }
 }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/reference/FakeWeakReferenceFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/reference/FakeWeakReferenceFactory.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f0a8fd0abf3d85cbe84fa573133433d4e142972f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/reference/FakeWeakReferenceFactory.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.util.reference
+
+import java.lang.ref.WeakReference
+
+class FakeWeakReferenceFactory : WeakReferenceFactory {
+    private val managedReferents = mutableListOf<WeakReference<*>>()
+
+    override fun <T> create(referent: T): WeakReference<T> {
+        val weakRef = WeakReference(referent)
+        managedReferents.add(weakRef)
+        return weakRef
+    }
+
+    /**
+     * Clears any [WeakReference] objects pointing to this object. If argument is null, clears all
+     * references.
+     */
+    fun <T> clear(referent: T) {
+        managedReferents.filter { it.get() == referent }.forEach { it.clear() }
+    }
+}
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index d0e442e7058d78c96a6051e43ea1e9ef93f57324..5c9bf182834784d3399f58f48d08940364c65331 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -32,3 +32,14 @@ java_library {
     host_supported: true,
     visibility: ["//visibility:public"],
 }
+
+java_library {
+    name: "ravenwood-junit",
+    srcs: ["junit-src/**/*.java"],
+    libs: [
+        "junit",
+    ],
+    sdk_version: "core_current",
+    host_supported: true,
+    visibility: ["//visibility:public"],
+}
diff --git a/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java
new file mode 100644
index 0000000000000000000000000000000000000000..0aac084dd4ced9c8a5d2b250afa1a6b3dcf38e8e
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java
@@ -0,0 +1,33 @@
+/*
+ * 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.platform.test.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * @hide
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface IgnoreUnderRavenwood {
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
new file mode 100644
index 0000000000000000000000000000000000000000..a6b3f668efa6b073a398ca029520e75d4a777ef7
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -0,0 +1,50 @@
+/*
+ * 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.platform.test.ravenwood;
+
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+
+import org.junit.Assume;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * THIS RULE IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * @hide
+ */
+public class RavenwoodRule implements TestRule {
+    public boolean isUnderRavenwood() {
+        // TODO: give ourselves a better environment signal
+        return System.getProperty("java.class.path").contains("ravenwood");
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
+                    Assume.assumeFalse(isUnderRavenwood());
+                }
+                base.evaluate();
+            }
+        };
+    }
+}
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index b37bbd6ea27f5df37f800458be17dd981453d32e..ab678d93c665c201a044b480f71304e2ff94cc3b 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -20,3 +20,10 @@ flag {
   description: "Mitigation for relayout issue"
   bug: "294330426"
 }
+
+flag {
+  name: "ignore_view_state_reset_to_empty"
+  namespace: "autofill"
+  description: "Mitigation for view state reset to empty causing no save dialog to show issue"
+  bug: "297976948"
+}
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 4f322203192facf10a4c0dbce90ecc98f69ca848..065a447a7cd1848162cbe6bb98b3095936902b25 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1421,7 +1421,7 @@ public abstract class PackageManagerInternal {
             @UserIdInt int userId);
 
     /**
-     * Sends the PACKAGE_RESTARTED broadcast on the package manager handler thread.
+     * Sends the PACKAGE_RESTARTED broadcast.
      */
     public abstract void sendPackageRestartedBroadcast(@NonNull String packageName,
             int uid, @Intent.Flags int flags);
@@ -1431,4 +1431,10 @@ public abstract class PackageManagerInternal {
      */
     public abstract ParceledListSlice<PackageInstaller.SessionInfo> getHistoricalSessions(
             int userId);
+
+    /**
+     * Sends the ACTION_PACKAGE_DATA_CLEARED broadcast.
+     */
+    public abstract void sendPackageDataClearedBroadcast(@NonNull String packageName,
+            int uid, int userId, boolean isRestore, boolean isInstantApp);
 }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 02235096ac15a724907d8d1878c423cf9ec62bad..15fc2dc15d029e67103aa45549125dbd9acf915b 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -61,7 +61,6 @@ import android.app.KeyguardManager;
 import android.app.PendingIntent;
 import android.app.admin.SecurityLog;
 import android.app.usage.StorageStatsManager;
-import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -2139,13 +2138,8 @@ class StorageManagerService extends IStorageManager.Stub
                         | MATCH_DIRECT_BOOT_UNAWARE | MATCH_UNINSTALLED_PACKAGES | MATCH_ANY_USER,
                         userId, Process.myUid())) {
             try {
-                final AttributionSource attributionSource = new AttributionSource.Builder(ai.uid)
-                        .setPackageName(ai.packageName)
-                        .build();
-                boolean hasLegacy =
-                        mIAppOpsService.checkOperationWithState(
-                                        OP_LEGACY_STORAGE, attributionSource.asState())
-                                == MODE_ALLOWED;
+                boolean hasLegacy = mIAppOpsService.checkOperation(OP_LEGACY_STORAGE, ai.uid,
+                        ai.packageName) == MODE_ALLOWED;
                 updateLegacyStorageApps(ai.packageName, ai.uid, hasLegacy);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to check legacy op for package " + ai.packageName, e);
@@ -4546,11 +4540,8 @@ class StorageManagerService extends IStorageManager.Stub
             // sharing the uid and allow same level of storage access for all packages even if
             // one of the packages has the appop granted.
             for (String uidPackageName : packagesForUid) {
-                final AttributionSource attributionSource =
-                        new AttributionSource.Builder(uid).setPackageName(uidPackageName).build();
-                if (mIAppOpsService.checkOperationWithState(
-                                OP_REQUEST_INSTALL_PACKAGES, attributionSource.asState())
-                        == MODE_ALLOWED) {
+                if (mIAppOpsService.checkOperation(
+                        OP_REQUEST_INSTALL_PACKAGES, uid, uidPackageName) == MODE_ALLOWED) {
                     hasInstallOp = true;
                     break;
                 }
@@ -4847,11 +4838,8 @@ class StorageManagerService extends IStorageManager.Stub
         @Override
         public boolean hasExternalStorageAccess(int uid, String packageName) {
             try {
-                final AttributionSource attributionSource =
-                        new AttributionSource.Builder(uid).setPackageName(packageName).build();
-                final int opMode =
-                        mIAppOpsService.checkOperationWithState(
-                                OP_MANAGE_EXTERNAL_STORAGE, attributionSource.asState());
+                final int opMode = mIAppOpsService.checkOperation(
+                        OP_MANAGE_EXTERNAL_STORAGE, uid, packageName);
                 if (opMode == AppOpsManager.MODE_DEFAULT) {
                     return mIPackageManager.checkUidPermission(
                             MANAGE_EXTERNAL_STORAGE, uid) == PERMISSION_GRANTED;
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index afc0dd188c3cde176cd432f735763b09e649026e..cd9c53bb92b9e51ea91c4652ad40d2274bf49fac 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -83,6 +83,9 @@
                 }
             ],
             "file_patterns": ["VpnManagerService\\.java"]
+        },
+        {
+            "name": "FrameworksNetTests"
         }
     ],
     "presubmit-large": [
@@ -124,9 +127,6 @@
         },
         {
             "name": "CtsSuspendAppsTestCases"
-        },
-        {
-            "name": "FrameworksNetTests"
         }
     ]
 }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4bdb4da971441a9276405df4699492bd8cc642dd..5f1a7e7e81230ce550ce06f19a1445bc60261eba 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -167,7 +167,6 @@ import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.Overridable;
-import android.content.AttributionSource;
 import android.content.ComponentName;
 import android.content.ComponentName.WithComponentName;
 import android.content.Context;
@@ -1101,12 +1100,8 @@ public final class ActiveServices {
                             SystemClock.uptimeMillis()); // Use current time, not lastActivity.
                 }
             }
-            final AttributionSource attributionSource = new AttributionSource.Builder(r.appInfo.uid)
-                    .setPackageName(r.packageName)
-                    .build();
-            mAm.mAppOpsService.startOperationWithState(AppOpsManager.getToken(mAm.mAppOpsService),
-                    AppOpsManager.OP_START_FOREGROUND,
-                    attributionSource.asState(),
+            mAm.mAppOpsService.startOperation(AppOpsManager.getToken(mAm.mAppOpsService),
+                    AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null,
                     true, false, null, false, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
                     AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
         }
@@ -2456,15 +2451,10 @@ public final class ActiveServices {
                             stopProcStatsOp = false;
                         }
 
-                        final AttributionSource attributionSource = new AttributionSource
-                                .Builder(r.appInfo.uid)
-                                .setPackageName(r.packageName)
-                                .build();
-                        mAm.mAppOpsService.startOperationWithState(
+                        mAm.mAppOpsService.startOperation(
                                 AppOpsManager.getToken(mAm.mAppOpsService),
-                                AppOpsManager.OP_START_FOREGROUND, attributionSource.asState(),
-                                true, false, "", false,
-                                AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+                                AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName,
+                                null, true, false, "", false, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
                                 AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
                         registerAppOpCallbackLocked(r);
                         mAm.updateForegroundServiceUsageStats(r.name, r.userId, true);
@@ -2524,13 +2514,10 @@ public final class ActiveServices {
                 if (alreadyStartedOp) {
                     // If we had previously done a start op for direct foreground start,
                     // we have cleared the flag so can now drop it.
-                    final AttributionSource attributionSource = new AttributionSource
-                            .Builder(r.appInfo.uid)
-                            .setPackageName(r.packageName)
-                            .build();
-                    mAm.mAppOpsService.finishOperationWithState(
+                    mAm.mAppOpsService.finishOperation(
                             AppOpsManager.getToken(mAm.mAppOpsService),
-                            AppOpsManager.OP_START_FOREGROUND, attributionSource.asState());
+                            AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName,
+                            null);
                 }
             }
         } else {
@@ -2573,13 +2560,9 @@ public final class ActiveServices {
                                 SystemClock.uptimeMillis());
                     }
                 }
-                final AttributionSource attributionSource =
-                        new AttributionSource.Builder(r.appInfo.uid)
-                                .setPackageName(r.packageName)
-                                .build();
-                mAm.mAppOpsService.finishOperationWithState(
+                mAm.mAppOpsService.finishOperation(
                         AppOpsManager.getToken(mAm.mAppOpsService),
-                        AppOpsManager.OP_START_FOREGROUND, attributionSource.asState());
+                        AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
                 unregisterAppOpCallbackLocked(r);
                 logFGSStateChangeLocked(r,
                         FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
@@ -5721,12 +5704,8 @@ public final class ActiveServices {
                             SystemClock.uptimeMillis());
                 }
             }
-            final AttributionSource attributionSource = new AttributionSource
-                    .Builder(r.appInfo.uid)
-                    .setPackageName(r.packageName)
-                    .build();
-            mAm.mAppOpsService.finishOperationWithState(AppOpsManager.getToken(mAm.mAppOpsService),
-                    AppOpsManager.OP_START_FOREGROUND, attributionSource.asState());
+            mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
+                    AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
             mServiceFGAnrTimer.cancel(r);
             if (r.app != null) {
                 Message msg = mAm.mHandler.obtainMessage(
@@ -5791,13 +5770,9 @@ public final class ActiveServices {
                             SystemClock.uptimeMillis());
                 }
             }
-            final AttributionSource attributionSource = new AttributionSource
-                    .Builder(r.appInfo.uid)
-                    .setPackageName(r.packageName)
-                    .build();
-            mAm.mAppOpsService.finishOperationWithState(
+            mAm.mAppOpsService.finishOperation(
                     AppOpsManager.getToken(mAm.mAppOpsService),
-                    AppOpsManager.OP_START_FOREGROUND, attributionSource.asState());
+                    AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
             unregisterAppOpCallbackLocked(r);
             r.mFgsExitTime = SystemClock.uptimeMillis();
             logFGSStateChangeLocked(r,
@@ -8516,11 +8491,8 @@ public final class ActiveServices {
 
         mAm.mBatteryStatsService.noteServiceStartRunning(callingUid, callingPackage,
                 cn.getClassName());
-        final AttributionSource attributionSource = new AttributionSource.Builder(r.appInfo.uid)
-                .setPackageName(r.packageName)
-                .build();
-        mAm.mAppOpsService.startOperationWithState(AppOpsManager.getToken(mAm.mAppOpsService),
-                AppOpsManager.OP_START_FOREGROUND, attributionSource.asState(),
+        mAm.mAppOpsService.startOperation(AppOpsManager.getToken(mAm.mAppOpsService),
+                AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null,
                 true, false, null, false,
                 AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
         registerAppOpCallbackLocked(r);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 514be1586e424d0785e702a52f48a278e3f8f732..b99a98fe6e8b64e522d9250a9c6c7eb991b306a9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -429,11 +429,10 @@ import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.MemInfoReader;
 import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.HeptFunction;
 import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.NonaFunction;
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.QuintFunction;
-import com.android.internal.util.function.TriFunction;
 import com.android.internal.util.function.UndecFunction;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.BootReceiver;
@@ -3152,11 +3151,8 @@ public class ActivityManagerService extends IActivityManager.Stub
     }
 
     private boolean hasUsageStatsPermission(String callingPackage, int callingUid, int callingPid) {
-        final AttributionSource attributionSource = new AttributionSource.Builder(callingUid)
-                .setPackageName(callingPackage)
-                .build();
-        final int mode = mAppOpsService.noteOperationWithState(AppOpsManager.OP_GET_USAGE_STATS,
-                attributionSource.asState(), false, "", false).getOpMode();
+        final int mode = mAppOpsService.noteOperation(AppOpsManager.OP_GET_USAGE_STATS,
+                callingUid, callingPackage, null, false, "", false).getOpMode();
         if (mode == AppOpsManager.MODE_DEFAULT) {
             return checkPermission(Manifest.permission.PACKAGE_USAGE_STATS, callingPid, callingUid)
                     == PackageManager.PERMISSION_GRANTED;
@@ -3634,30 +3630,10 @@ public class ActivityManagerService extends IActivityManager.Stub
                     }
 
                     if (succeeded) {
-                        final Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED,
-                                Uri.fromParts("package", packageName, null /* fragment */));
-                        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
-                                | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-                        intent.putExtra(Intent.EXTRA_UID,
-                                (appInfo != null) ? appInfo.uid : INVALID_UID);
-                        intent.putExtra(Intent.EXTRA_USER_HANDLE, resolvedUserId);
-                        if (isRestore) {
-                            intent.putExtra(Intent.EXTRA_IS_RESTORE, true);
-                        }
-                        if (isInstantApp) {
-                            intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
-                        }
-                        final int[] visibilityAllowList = mPackageManagerInt.getVisibilityAllowList(
-                                packageName, resolvedUserId);
-
-                        broadcastIntentInPackage("android", null /* featureId */,
-                                SYSTEM_UID, uid, pid, intent, null /* resolvedType */,
-                                null /* resultToApp */, null /* resultTo */, 0 /* resultCode */,
-                                null /* resultData */, null /* resultExtras */,
-                                isInstantApp ? permission.ACCESS_INSTANT_APPS : null,
-                                null /* bOptions */, false /* serialized */, false /* sticky */,
-                                resolvedUserId, BackgroundStartPrivileges.NONE,
-                                visibilityAllowList);
+
+                        mPackageManagerInt.sendPackageDataClearedBroadcast(packageName,
+                                ((appInfo != null) ? appInfo.uid : INVALID_UID), resolvedUserId,
+                                isRestore, isInstantApp);
                     }
 
                     if (observer != null) {
@@ -4178,29 +4154,7 @@ public class ActivityManagerService extends IActivityManager.Stub
             flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
                     | Intent.FLAG_RECEIVER_FOREGROUND;
         }
-        if (android.content.pm.Flags.stayStopped()) {
-            // Sent async using the PM handler, to maintain ordering with PACKAGE_UNSTOPPED
-            mPackageManagerInt.sendPackageRestartedBroadcast(packageName,
-                    uid, flags);
-        } else {
-            Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED,
-                    Uri.fromParts("package", packageName, null));
-            intent.addFlags(flags);
-            final int userId = UserHandle.getUserId(uid);
-            final int[] broadcastAllowList =
-                    getPackageManagerInternal().getVisibilityAllowList(packageName, userId);
-            intent.putExtra(Intent.EXTRA_UID, uid);
-            intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-            broadcastIntentLocked(null /* callerApp */, null /* callerPackage */,
-                    null /* callerFeatureId */, intent, null /* resolvedType */,
-                    null /* resultToApp */, null /* resultTo */,
-                    0 /* resultCode */, null /* resultData */, null /* resultExtras */,
-                    null /* requiredPermissions */, null /* excludedPermissions */,
-                    null /* excludedPackages */, OP_NONE, null /* bOptions */, false /* ordered */,
-                    false /* sticky */, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
-                    Binder.getCallingPid(), userId, BackgroundStartPrivileges.NONE,
-                    broadcastAllowList, null /* filterExtrasForReceiver */);
-        }
+        mPackageManagerInt.sendPackageRestartedBroadcast(packageName, uid, flags);
     }
 
     private void cleanupDisabledPackageComponentsLocked(
@@ -5938,18 +5892,9 @@ public class ActivityManagerService extends IActivityManager.Stub
         @Override
         public int noteOp(String op, int uid, String packageName) {
             // TODO moltmann: Allow to specify featureId
-            final AttributionSource attributionSource = new AttributionSource.Builder(uid)
-                    .setPackageName(packageName)
-                    .build();
-            return mActivityManagerService
-                    .mAppOpsService
-                    .noteOperationWithState(
-                            AppOpsManager.strOpToOp(op),
-                            attributionSource.asState(),
-                            false,
-                            "",
-                            false)
-                    .getOpMode();
+            return mActivityManagerService.mAppOpsService
+                    .noteOperation(AppOpsManager.strOpToOp(op), uid, packageName, null,
+                            false, "", false).getOpMode();
         }
 
         @Override
@@ -20163,26 +20108,20 @@ public class ActivityManagerService extends IActivityManager.Stub
         }
 
         @Override
-        public int checkOperation(int code, AttributionSource attributionSource, boolean raw,
-                TriFunction<Integer, AttributionSource, Boolean, Integer> superImpl) {
-            final int uid = attributionSource.getUid();
-
+        public int checkOperation(int code, int uid, String packageName,
+                String attributionTag, boolean raw,
+                QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl) {
             if (uid == mTargetUid && isTargetOp(code)) {
                 final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
                         Process.SHELL_UID);
-                final AttributionSource shellAttributionSource =
-                        new AttributionSource.Builder(shellUid)
-                                .setPackageName("com.android.shell")
-                                .build();
-
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    return superImpl.apply(code, shellAttributionSource, raw);
+                    return superImpl.apply(code, shellUid, "com.android.shell", null, raw);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
-            return superImpl.apply(code, attributionSource, raw);
+            return superImpl.apply(code, uid, packageName, attributionTag, raw);
         }
 
         @Override
@@ -20202,30 +20141,23 @@ public class ActivityManagerService extends IActivityManager.Stub
         }
 
         @Override
-        public SyncNotedAppOp noteOperation(int code, AttributionSource attributionSource,
-                boolean shouldCollectAsyncNotedOp,
+        public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
+                @Nullable String featureId, boolean shouldCollectAsyncNotedOp,
                 @Nullable String message, boolean shouldCollectMessage,
-                @NonNull QuintFunction<Integer, AttributionSource, Boolean, String, Boolean,
+                @NonNull HeptFunction<Integer, Integer, String, String, Boolean, String, Boolean,
                         SyncNotedAppOp> superImpl) {
-            final int uid = attributionSource.getUid();
-            final String attributionTag = attributionSource.getAttributionTag();
             if (uid == mTargetUid && isTargetOp(code)) {
                 final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
                         Process.SHELL_UID);
                 final long identity = Binder.clearCallingIdentity();
-                final AttributionSource shellAttributionSource =
-                        new AttributionSource.Builder(shellUid)
-                                .setPackageName("com.android.shell")
-                                .setAttributionTag(attributionTag)
-                                .build();
                 try {
-                    return superImpl.apply(code, shellAttributionSource,
+                    return superImpl.apply(code, shellUid, "com.android.shell", featureId,
                             shouldCollectAsyncNotedOp, message, shouldCollectMessage);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
-            return superImpl.apply(code, attributionSource, shouldCollectAsyncNotedOp,
+            return superImpl.apply(code, uid, packageName, featureId, shouldCollectAsyncNotedOp,
                     message, shouldCollectMessage);
         }
 
@@ -20256,37 +20188,28 @@ public class ActivityManagerService extends IActivityManager.Stub
         }
 
         @Override
-        public SyncNotedAppOp startOperation(IBinder token, int code,
-                AttributionSource attributionSource,
+        public SyncNotedAppOp startOperation(IBinder token, int code, int uid,
+                @Nullable String packageName, @Nullable String attributionTag,
                 boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
                 @Nullable String message, boolean shouldCollectMessage,
                 @AttributionFlags int attributionFlags, int attributionChainId,
-                @NonNull NonaFunction<IBinder, Integer, AttributionSource, Boolean,
+                @NonNull UndecFunction<IBinder, Integer, Integer, String, String, Boolean,
                         Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl) {
-            final int uid = attributionSource.getUid();
-            final String attributionTag = attributionSource.getAttributionTag();
-
             if (uid == mTargetUid && isTargetOp(code)) {
                 final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
                         Process.SHELL_UID);
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    final AttributionSource shellAttributionSource =
-                            new AttributionSource.Builder(shellUid)
-                                    .setPackageName("com.android.shell")
-                                    .setAttributionTag(attributionTag)
-                                    .build();
-
-                    return superImpl.apply(token, code, shellAttributionSource,
-                            startIfModeDefault, shouldCollectAsyncNotedOp, message,
+                    return superImpl.apply(token, code, shellUid, "com.android.shell",
+                            attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message,
                             shouldCollectMessage, attributionFlags, attributionChainId);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
-            return superImpl.apply(token, code, attributionSource, startIfModeDefault,
-                    shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
-                    attributionChainId);
+            return superImpl.apply(token, code, uid, packageName, attributionTag,
+                    startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+                    attributionFlags, attributionChainId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/AppPermissionTracker.java b/services/core/java/com/android/server/am/AppPermissionTracker.java
index 947fcd38f5a0ac4fe4b9629bc85338d68d8313ab..18a91535a34cf9149a6d020ebb0fa4c9ead341a6 100644
--- a/services/core/java/com/android/server/am/AppPermissionTracker.java
+++ b/services/core/java/com/android/server/am/AppPermissionTracker.java
@@ -37,7 +37,6 @@ import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_PERMISSION;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
-import android.content.AttributionSource;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager.OnPermissionsChangedListener;
@@ -193,11 +192,7 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy
         if (DEBUG_PERMISSION_TRACKER) {
             final IAppOpsService appOpsService = mInjector.getIAppOpsService();
             try {
-                final AttributionSource attributionSource = new AttributionSource.Builder(uid)
-                        .setPackageName(packageName)
-                        .build();
-                final int mode =
-                        appOpsService.checkOperationWithState(op, attributionSource.asState());
+                final int mode = appOpsService.checkOperation(op, uid, packageName);
                 Slog.i(TAG, "onOpChanged: " + opToPublicName(op)
                         + " " + UserHandle.formatUid(uid)
                         + " " + packageName + " " + mode);
@@ -312,11 +307,7 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy
                 final IAppOpsService appOpsService = mInjector.getIAppOpsService();
                 for (String pkg : packages) {
                     try {
-                        final AttributionSource attributionSource =
-                                new AttributionSource.Builder(mUid).setPackageName(pkg).build();
-                        final int mode =
-                                appOpsService.checkOperationWithState(
-                                        mAppOp, attributionSource.asState());
+                        final int mode = appOpsService.checkOperation(mAppOp, mUid, pkg);
                         if (mode == AppOpsManager.MODE_ALLOWED) {
                             mAppOpAllowed = true;
                             return;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index d6997daaa12bf0d7ee2a75c04329ab2655bba007..052b0c2078d1c8d8050de6f4553f2410c3fc7f9d 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1143,32 +1143,22 @@ public class AppOpsService extends IAppOpsService.Stub {
             }
         }, RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS);
 
-        getPackageManagerInternal()
-                .setExternalSourcesPolicy(
-                        new PackageManagerInternal.ExternalSourcesPolicy() {
-                            @Override
-                            public int getPackageTrustedToInstallApps(String packageName, int uid) {
-                                final AttributionSource attributionSource =
-                                        new AttributionSource.Builder(uid)
-                                                .setPackageName(packageName)
-                                                .build();
-                                int appOpMode =
-                                        checkOperationWithState(
-                                                AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
-                                                attributionSource.asState());
-                                switch (appOpMode) {
-                                    case AppOpsManager.MODE_ALLOWED:
-                                        return PackageManagerInternal.ExternalSourcesPolicy
-                                                .USER_TRUSTED;
-                                    case AppOpsManager.MODE_ERRORED:
-                                        return PackageManagerInternal.ExternalSourcesPolicy
-                                                .USER_BLOCKED;
-                                    default:
-                                        return PackageManagerInternal.ExternalSourcesPolicy
-                                                .USER_DEFAULT;
-                                }
-                            }
-                        });
+        getPackageManagerInternal().setExternalSourcesPolicy(
+                new PackageManagerInternal.ExternalSourcesPolicy() {
+                    @Override
+                    public int getPackageTrustedToInstallApps(String packageName, int uid) {
+                        int appOpMode = checkOperation(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
+                                uid, packageName);
+                        switch (appOpMode) {
+                            case AppOpsManager.MODE_ALLOWED:
+                                return PackageManagerInternal.ExternalSourcesPolicy.USER_TRUSTED;
+                            case AppOpsManager.MODE_ERRORED:
+                                return PackageManagerInternal.ExternalSourcesPolicy.USER_BLOCKED;
+                            default:
+                                return PackageManagerInternal.ExternalSourcesPolicy.USER_DEFAULT;
+                        }
+                    }
+                });
     }
 
     @VisibleForTesting
@@ -2544,41 +2534,22 @@ public class AppOpsService extends IAppOpsService.Stub {
         }
     }
 
-    /** @deprecated Use {@link #checkOperationWithStateRaw} instead. */
     @Override
     public int checkOperationRaw(int code, int uid, String packageName,
-             @Nullable String attributionTag) {
-        final AttributionSource attributionSource = new AttributionSource.Builder(uid)
-                .setPackageName(packageName).setAttributionTag(attributionTag).build();
-        return mCheckOpsDelegateDispatcher.checkOperation(code, attributionSource, true /*raw*/);
-    }
-
-    @Override
-    public int checkOperationWithStateRaw(int code, AttributionSourceState attributionSourceState) {
-        final AttributionSource attributionSource = new AttributionSource(attributionSourceState);
-        return mCheckOpsDelegateDispatcher.checkOperation(code, attributionSource, true /*raw*/);
+            @Nullable String attributionTag) {
+        return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag,
+                true /*raw*/);
     }
 
-    /** @deprecated Use {@link #checkOperationWithState} instead. */
     @Override
     public int checkOperation(int code, int uid, String packageName) {
-        final AttributionSource attributionSource = new AttributionSource.Builder(uid)
-                .setPackageName(packageName)
-                .build();
-        return mCheckOpsDelegateDispatcher.checkOperation(code, attributionSource, false /*raw*/);
+        return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null,
+                false /*raw*/);
     }
 
-    @Override
-    public int checkOperationWithState(int code, AttributionSourceState attributionSourceState) {
-        final AttributionSource attributionSource = new AttributionSource(attributionSourceState);
-        return mCheckOpsDelegateDispatcher.checkOperation(code, attributionSource, false /*raw*/);
-    }
-
-    private int checkOperationImpl(int code, AttributionSource attributionSource, boolean raw) {
+    private int checkOperationImpl(int code, int uid, String packageName,
+            @Nullable String attributionTag, boolean raw) {
         verifyIncomingOp(code);
-        final String packageName = attributionSource.getPackageName();
-        final int uid = attributionSource.getUid();
-        final String attributionTag = attributionSource.getAttributionTag();
         if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
             return AppOpsManager.opToDefaultMode(code);
         }
@@ -2643,10 +2614,7 @@ public class AppOpsService extends IAppOpsService.Stub {
         if (mode != AppOpsManager.MODE_ALLOWED) {
             return mode;
         }
-        final AttributionSource attributionSource = new AttributionSource.Builder(uid)
-                .setPackageName(packageName)
-                .build();
-        return checkOperationWithState(code, attributionSource.asState());
+        return checkOperation(code, uid, packageName);
     }
 
     @Override
@@ -2790,38 +2758,17 @@ public class AppOpsService extends IAppOpsService.Stub {
                 proxiedFlags, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
     }
 
-    /** @deprecated Use {@link #noteOperationWithState} instead. */
     @Override
     public SyncNotedAppOp noteOperation(int code, int uid, String packageName,
             String attributionTag, boolean shouldCollectAsyncNotedOp, String message,
             boolean shouldCollectMessage) {
-        final AttributionSource attributionSource = new AttributionSource.Builder(uid)
-                .setPackageName(packageName)
-                .setAttributionTag(attributionTag)
-                .build();
-        return mCheckOpsDelegateDispatcher.noteOperation(code, attributionSource,
-                shouldCollectAsyncNotedOp, message, shouldCollectMessage);
-    }
-
-    @Override
-    public SyncNotedAppOp noteOperationWithState(
-            int code,
-            AttributionSourceState attributionSourceState,
-            boolean shouldCollectAsyncNotedOp,
-            String message,
-            boolean shouldCollectMessage) {
-        final AttributionSource attributionSource = new AttributionSource(attributionSourceState);
-        return mCheckOpsDelegateDispatcher.noteOperation(
-                code, attributionSource, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+        return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
+                attributionTag, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
     }
 
-    private SyncNotedAppOp noteOperationImpl(int code, AttributionSource attributionSource,
-            boolean shouldCollectAsyncNotedOp,
+    private SyncNotedAppOp noteOperationImpl(int code, int uid, @Nullable String packageName,
+            @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp,
             @Nullable String message, boolean shouldCollectMessage) {
-        final int uid = attributionSource.getUid();
-        final String packageName = attributionSource.getPackageName();
-        final String attributionTag = attributionSource.getAttributionTag();
-
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
         if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
@@ -3216,42 +3163,22 @@ public class AppOpsService extends IAppOpsService.Stub {
         }
     }
 
-    /** @deprecated Use {@link #startOperationWithState} instead. */
     @Override
     public SyncNotedAppOp startOperation(IBinder token, int code, int uid,
-             @Nullable String packageName, @Nullable String attributionTag,
-             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
-             String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
-             int attributionChainId) {
-        final AttributionSource attributionSource = new AttributionSource.Builder(uid)
-                .setPackageName(packageName)
-                .setAttributionTag(attributionTag)
-                .build();
-        return mCheckOpsDelegateDispatcher.startOperation(token, code, attributionSource,
-                startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                attributionFlags, attributionChainId);
-    }
-
-    @Override
-    public SyncNotedAppOp startOperationWithState(IBinder token, int code,
-            AttributionSourceState attributionSourceState,
+            @Nullable String packageName, @Nullable String attributionTag,
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
             String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
             int attributionChainId) {
-        final AttributionSource attributionSource = new AttributionSource(attributionSourceState);
-        return mCheckOpsDelegateDispatcher.startOperation(token, code, attributionSource,
-                startIfModeDefault, shouldCollectAsyncNotedOp, message,
+        return mCheckOpsDelegateDispatcher.startOperation(token, code, uid, packageName,
+                attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message,
                 shouldCollectMessage, attributionFlags, attributionChainId);
     }
 
-    private SyncNotedAppOp startOperationImpl(@NonNull IBinder clientId, int code,
-            AttributionSource attributionSource, boolean startIfModeDefault,
-            boolean shouldCollectAsyncNotedOp, @NonNull String message,
+    private SyncNotedAppOp startOperationImpl(@NonNull IBinder clientId, int code, int uid,
+            @Nullable String packageName, @Nullable String attributionTag,
+            boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @NonNull String message,
             boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
             int attributionChainId) {
-        final String packageName = attributionSource.getPackageName();
-        final int uid = attributionSource.getUid();
-        final String attributionTag = attributionSource.getAttributionTag();
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
         if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
@@ -3273,7 +3200,7 @@ public class AppOpsService extends IAppOpsService.Stub {
         int result = MODE_DEFAULT;
         if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO
                 || code == OP_RECORD_AUDIO_SANDBOXED) {
-            result = checkOperationWithState(OP_RECORD_AUDIO, attributionSource.asState());
+            result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
             // Check result
             if (result != AppOpsManager.MODE_ALLOWED) {
                 return new SyncNotedAppOp(result, code, attributionTag, packageName);
@@ -3281,7 +3208,7 @@ public class AppOpsService extends IAppOpsService.Stub {
         }
         // As a special case for OP_CAMERA_SANDBOXED.
         if (code == OP_CAMERA_SANDBOXED) {
-            result = checkOperationWithState(OP_CAMERA, attributionSource.asState());
+            result = checkOperation(OP_CAMERA, uid, packageName);
             // Check result
             if (result != AppOpsManager.MODE_ALLOWED) {
                 return new SyncNotedAppOp(result, code, attributionTag, packageName);
@@ -3585,29 +3512,15 @@ public class AppOpsService extends IAppOpsService.Stub {
                 packageName);
     }
 
-    /** @deprecated Use {@link #finishOperationWithState} instead. */
     @Override
     public void finishOperation(IBinder clientId, int code, int uid, String packageName,
             String attributionTag) {
-        final AttributionSource attributionSource = new AttributionSource.Builder(uid)
-                .setPackageName(packageName)
-                .setAttributionTag(attributionTag)
-                .build();
-        mCheckOpsDelegateDispatcher.finishOperation(clientId, code, attributionSource);
-    }
-
-    @Override
-    public void finishOperationWithState(IBinder clientId, int code,
-            AttributionSourceState attributionSourceState) {
-        final AttributionSource attributionSource = new AttributionSource(attributionSourceState);
-        mCheckOpsDelegateDispatcher.finishOperation(clientId, code, attributionSource);
+        mCheckOpsDelegateDispatcher.finishOperation(clientId, code, uid, packageName,
+                attributionTag);
     }
 
-    private void finishOperationImpl(IBinder clientId, int code,
-            AttributionSource attributionSource) {
-        final String packageName = attributionSource.getPackageName();
-        final int uid = attributionSource.getUid();
-        final String attributionTag = attributionSource.getAttributionTag();
+    private void finishOperationImpl(IBinder clientId, int code, int uid, String packageName,
+            String attributionTag) {
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
         if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
@@ -5190,13 +5103,8 @@ public class AppOpsService extends IAppOpsService.Stub {
                     }
 
                     if (shell.packageName != null) {
-                        final AttributionSource shellAttributionSource =
-                                new AttributionSource.Builder(shell.packageUid)
-                                        .setPackageName(shell.packageName)
-                                        .setAttributionTag(shell.attributionTag)
-                                        .build();
-                        shell.mInterface.startOperationWithState(shell.mToken, shell.op,
-                                shellAttributionSource.asState(), true, true,
+                        shell.mInterface.startOperation(shell.mToken, shell.op, shell.packageUid,
+                                shell.packageName, shell.attributionTag, true, true,
                                 "appops start shell command", true,
                                 AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR, ATTRIBUTION_CHAIN_ID_NONE);
                     } else {
@@ -5211,13 +5119,8 @@ public class AppOpsService extends IAppOpsService.Stub {
                     }
 
                     if (shell.packageName != null) {
-                        final AttributionSource shellAttributionSource =
-                                new AttributionSource.Builder(shell.packageUid)
-                                        .setPackageName(shell.packageName)
-                                        .setAttributionTag(shell.attributionTag)
-                                        .build();
-                        shell.mInterface.finishOperationWithState(shell.mToken, shell.op,
-                                shellAttributionSource.asState());
+                        shell.mInterface.finishOperation(shell.mToken, shell.op, shell.packageUid,
+                                shell.packageName, shell.attributionTag);
                     } else {
                         return -1;
                     }
@@ -6763,24 +6666,25 @@ public class AppOpsService extends IAppOpsService.Stub {
             return mCheckOpsDelegate;
         }
 
-        public int checkOperation(int code, AttributionSource attributionSource, boolean raw) {
+        public int checkOperation(int code, int uid, String packageName,
+                @Nullable String attributionTag, boolean raw) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
-                    return mPolicy.checkOperation(code, attributionSource, raw,
+                    return mPolicy.checkOperation(code, uid, packageName, attributionTag, raw,
                             this::checkDelegateOperationImpl);
                 } else {
-                    return mPolicy.checkOperation(code, attributionSource, raw,
+                    return mPolicy.checkOperation(code, uid, packageName, attributionTag, raw,
                             AppOpsService.this::checkOperationImpl);
                 }
             } else if (mCheckOpsDelegate != null) {
-                return checkDelegateOperationImpl(code, attributionSource, raw);
+                return checkDelegateOperationImpl(code, uid, packageName, attributionTag, raw);
             }
-            return checkOperationImpl(code, attributionSource, raw);
+            return checkOperationImpl(code, uid, packageName, attributionTag, raw);
         }
 
-        private int checkDelegateOperationImpl(int code, AttributionSource attributionSource,
-                boolean raw) {
-            return mCheckOpsDelegate.checkOperation(code, attributionSource, raw,
+        private int checkDelegateOperationImpl(int code, int uid, String packageName,
+                @Nullable String attributionTag, boolean raw) {
+            return mCheckOpsDelegate.checkOperation(code, uid, packageName, attributionTag, raw,
                     AppOpsService.this::checkOperationImpl);
         }
 
@@ -6805,32 +6709,32 @@ public class AppOpsService extends IAppOpsService.Stub {
                     AppOpsService.this::checkAudioOperationImpl);
         }
 
-        public SyncNotedAppOp noteOperation(int code, AttributionSource attributionSource,
-                boolean shouldCollectAsyncNotedOp, String message,
+        public SyncNotedAppOp noteOperation(int code, int uid, String packageName,
+                String attributionTag, boolean shouldCollectAsyncNotedOp, String message,
                 boolean shouldCollectMessage) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
-                    return mPolicy.noteOperation(code, attributionSource,
+                    return mPolicy.noteOperation(code, uid, packageName, attributionTag,
                             shouldCollectAsyncNotedOp, message, shouldCollectMessage,
                             this::noteDelegateOperationImpl);
                 } else {
-                    return mPolicy.noteOperation(code, attributionSource,
+                    return mPolicy.noteOperation(code, uid, packageName, attributionTag,
                             shouldCollectAsyncNotedOp, message, shouldCollectMessage,
                             AppOpsService.this::noteOperationImpl);
                 }
             } else if (mCheckOpsDelegate != null) {
-                return noteDelegateOperationImpl(code, attributionSource, shouldCollectAsyncNotedOp,
-                        message, shouldCollectMessage);
+                return noteDelegateOperationImpl(code, uid, packageName,
+                        attributionTag, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
             }
-            return noteOperationImpl(code, attributionSource,
+            return noteOperationImpl(code, uid, packageName, attributionTag,
                     shouldCollectAsyncNotedOp, message, shouldCollectMessage);
         }
 
-        private SyncNotedAppOp noteDelegateOperationImpl(int code,
-                AttributionSource attributionSource,
+        private SyncNotedAppOp noteDelegateOperationImpl(int code, int uid,
+                @Nullable String packageName, @Nullable String featureId,
                 boolean shouldCollectAsyncNotedOp, @Nullable String message,
                 boolean shouldCollectMessage) {
-            return mCheckOpsDelegate.noteOperation(code, attributionSource,
+            return mCheckOpsDelegate.noteOperation(code, uid, packageName, featureId,
                     shouldCollectAsyncNotedOp, message, shouldCollectMessage,
                     AppOpsService.this::noteOperationImpl);
         }
@@ -6866,38 +6770,39 @@ public class AppOpsService extends IAppOpsService.Stub {
                     AppOpsService.this::noteProxyOperationImpl);
         }
 
-        public SyncNotedAppOp startOperation(IBinder token, int code,
-                AttributionSource attributionSource, boolean startIfModeDefault,
-                boolean shouldCollectAsyncNotedOp, @Nullable String message,
-                boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
-                int attributionChainId) {
+        public SyncNotedAppOp startOperation(IBinder token, int code, int uid,
+                @Nullable String packageName, @NonNull String attributionTag,
+                boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
+                @Nullable String message, boolean shouldCollectMessage,
+                @AttributionFlags int attributionFlags, int attributionChainId) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
-                    return mPolicy.startOperation(token, code, attributionSource,
-                            startIfModeDefault, shouldCollectAsyncNotedOp, message,
+                    return mPolicy.startOperation(token, code, uid, packageName,
+                            attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message,
                             shouldCollectMessage, attributionFlags, attributionChainId,
                             this::startDelegateOperationImpl);
                 } else {
-                    return mPolicy.startOperation(token, code, attributionSource,
+                    return mPolicy.startOperation(token, code, uid, packageName, attributionTag,
                             startIfModeDefault, shouldCollectAsyncNotedOp, message,
                             shouldCollectMessage, attributionFlags, attributionChainId,
                             AppOpsService.this::startOperationImpl);
                 }
             } else if (mCheckOpsDelegate != null) {
-                return startDelegateOperationImpl(token, code, attributionSource,
+                return startDelegateOperationImpl(token, code, uid, packageName, attributionTag,
                         startIfModeDefault, shouldCollectAsyncNotedOp, message,
                         shouldCollectMessage, attributionFlags, attributionChainId);
             }
-            return startOperationImpl(token, code, attributionSource,
+            return startOperationImpl(token, code, uid, packageName, attributionTag,
                     startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
                     attributionFlags, attributionChainId);
         }
 
-        private SyncNotedAppOp startDelegateOperationImpl(IBinder token, int code,
-                AttributionSource attributionSource, boolean startIfModeDefault,
-                boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
-                @AttributionFlags int attributionFlags, int attributionChainId) {
-            return mCheckOpsDelegate.startOperation(token, code, attributionSource,
+        private SyncNotedAppOp startDelegateOperationImpl(IBinder token, int code, int uid,
+                @Nullable String packageName, @Nullable String attributionTag,
+                boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
+                boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+                int attributionChainId) {
+            return mCheckOpsDelegate.startOperation(token, code, uid, packageName, attributionTag,
                     startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
                     attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl);
         }
@@ -6943,26 +6848,26 @@ public class AppOpsService extends IAppOpsService.Stub {
                     attributionChainId, AppOpsService.this::startProxyOperationImpl);
         }
 
-        public void finishOperation(IBinder clientId, int code,
-                AttributionSource attributionSource) {
+        public void finishOperation(IBinder clientId, int code, int uid, String packageName,
+                String attributionTag) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
-                    mPolicy.finishOperation(clientId, code, attributionSource,
+                    mPolicy.finishOperation(clientId, code, uid, packageName, attributionTag,
                             this::finishDelegateOperationImpl);
                 } else {
-                    mPolicy.finishOperation(clientId, code, attributionSource,
+                    mPolicy.finishOperation(clientId, code, uid, packageName, attributionTag,
                             AppOpsService.this::finishOperationImpl);
                 }
             } else if (mCheckOpsDelegate != null) {
-                finishDelegateOperationImpl(clientId, code, attributionSource);
+                finishDelegateOperationImpl(clientId, code, uid, packageName, attributionTag);
             } else {
-                finishOperationImpl(clientId, code, attributionSource);
+                finishOperationImpl(clientId, code, uid, packageName, attributionTag);
             }
         }
 
-        private void finishDelegateOperationImpl(IBinder clientId, int code,
-                AttributionSource attributionSource) {
-            mCheckOpsDelegate.finishOperation(clientId, code, attributionSource,
+        private void finishDelegateOperationImpl(IBinder clientId, int code, int uid,
+                String packageName, String attributionTag) {
+            mCheckOpsDelegate.finishOperation(clientId, code, uid, packageName, attributionTag,
                     AppOpsService.this::finishOperationImpl);
         }
 
diff --git a/services/core/java/com/android/server/media/BluetoothProfileMonitor.java b/services/core/java/com/android/server/media/BluetoothProfileMonitor.java
index b129dd0f74c651bcf06886655d080b0b17ea1ef1..d61e7fb6591ed61f1f08f42255d7eb84f46d48f8 100644
--- a/services/core/java/com/android/server/media/BluetoothProfileMonitor.java
+++ b/services/core/java/com/android/server/media/BluetoothProfileMonitor.java
@@ -47,21 +47,10 @@ import java.util.Objects;
     @Nullable
     private BluetoothLeAudio mLeAudioProfile;
 
-    @Nullable
-    private OnProfileChangedListener mOnProfileChangedListener;
-
     BluetoothProfileMonitor(@NonNull Context context,
             @NonNull BluetoothAdapter bluetoothAdapter) {
-        Objects.requireNonNull(context);
-        Objects.requireNonNull(bluetoothAdapter);
-
-        mContext = context;
-        mBluetoothAdapter = bluetoothAdapter;
-    }
-
-    /* package */ synchronized void setOnProfileChangedListener(
-            @NonNull OnProfileChangedListener listener) {
-        mOnProfileChangedListener = listener;
+        mContext = Objects.requireNonNull(context);
+        mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter);
     }
 
     /* package */ void start() {
@@ -115,15 +104,9 @@ import java.util.Objects;
         }
     }
 
-    /* package */ interface OnProfileChangedListener {
-        void onProfileChange(int profile);
-    }
-
     private final class ProfileListener implements BluetoothProfile.ServiceListener {
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            OnProfileChangedListener listener;
-
             synchronized (BluetoothProfileMonitor.this) {
                 switch (profile) {
                     case BluetoothProfile.A2DP:
@@ -135,22 +118,12 @@ import java.util.Objects;
                     case BluetoothProfile.LE_AUDIO:
                         mLeAudioProfile = (BluetoothLeAudio) proxy;
                         break;
-                    default:
-                        return;
                 }
-
-                listener = mOnProfileChangedListener;
-            }
-
-            if (listener != null) {
-                listener.onProfileChange(profile);
             }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
-            OnProfileChangedListener listener;
-
             synchronized (BluetoothProfileMonitor.this) {
                 switch (profile) {
                     case BluetoothProfile.A2DP:
@@ -162,17 +135,8 @@ import java.util.Objects;
                     case BluetoothProfile.LE_AUDIO:
                         mLeAudioProfile = null;
                         break;
-                    default:
-                        return;
                 }
-
-                listener = mOnProfileChangedListener;
-            }
-
-            if (listener != null) {
-                listener.onProfileChange(profile);
             }
         }
     }
-
 }
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index b006ac8625339718ea7553bc07d92985f07b2094..a8cba532435d841fade7a74ac42c5f6a9cca59ce 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -35,11 +35,12 @@ import android.util.Slog;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
-import com.android.server.pm.UserManagerInternal;
 import com.android.server.LocalServices;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
+import com.android.server.pm.UserManagerInternal;
 
 import libcore.io.IoUtils;
 
@@ -53,6 +54,9 @@ import java.io.PrintWriter;
 import java.io.RandomAccessFile;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
@@ -114,21 +118,26 @@ public class PersistentDataBlockService extends SystemService {
     private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
 
     private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
-    private static final int HEADER_SIZE = 8;
+    @VisibleForTesting
+    static final int HEADER_SIZE = 8;
     // Magic number to mark block device as adhering to the format consumed by this service
     private static final int PARTITION_TYPE_MARKER = 0x19901873;
     /** Size of the block reserved for FRP credential, including 4 bytes for the size header. */
     private static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000;
     /** Maximum size of the FRP credential handle that can be stored. */
-    private static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4;
+    @VisibleForTesting
+    static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4;
     /**
      * Size of the block reserved for Test Harness Mode data, including 4 bytes for the size header.
      */
     private static final int TEST_MODE_RESERVED_SIZE = 10000;
     /** Maximum size of the Test Harness Mode data that can be stored. */
-    private static final int MAX_TEST_MODE_DATA_SIZE = TEST_MODE_RESERVED_SIZE - 4;
+    @VisibleForTesting
+    static final int MAX_TEST_MODE_DATA_SIZE = TEST_MODE_RESERVED_SIZE - 4;
+
     // Limit to 100k as blocks larger than this might cause strain on Binder.
-    private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
+    @VisibleForTesting
+    static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
 
     public static final int DIGEST_SIZE_BYTES = 32;
     private static final String OEM_UNLOCK_PROP = "sys.oem_unlock_allowed";
@@ -138,12 +147,12 @@ public class PersistentDataBlockService extends SystemService {
 
     private final Context mContext;
     private final String mDataBlockFile;
-    private final boolean mIsRunningDSU;
+    private final boolean mIsFileBacked;
     private final Object mLock = new Object();
     private final CountDownLatch mInitDoneSignal = new CountDownLatch(1);
 
     private int mAllowedUid = -1;
-    private long mBlockDeviceSize;
+    private long mBlockDeviceSize = -1; // Load lazily
 
     @GuardedBy("mLock")
     private boolean mIsWritable = true;
@@ -151,13 +160,23 @@ public class PersistentDataBlockService extends SystemService {
     public PersistentDataBlockService(Context context) {
         super(context);
         mContext = context;
-        mIsRunningDSU = SystemProperties.getBoolean(GSI_RUNNING_PROP, false);
-        if (mIsRunningDSU) {
+        if (SystemProperties.getBoolean(GSI_RUNNING_PROP, false)) {
+            mIsFileBacked = true;
             mDataBlockFile = GSI_SANDBOX;
         } else {
+            mIsFileBacked = false;
             mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
         }
-        mBlockDeviceSize = -1; // Load lazily
+    }
+
+    @VisibleForTesting
+    PersistentDataBlockService(Context context, boolean isFileBacked, String dataBlockFile,
+            long blockDeviceSize) {
+        super(context);
+        mContext = context;
+        mIsFileBacked = isFileBacked;
+        mDataBlockFile = dataBlockFile;
+        mBlockDeviceSize = blockDeviceSize;
     }
 
     private int getAllowedUid() {
@@ -212,6 +231,11 @@ public class PersistentDataBlockService extends SystemService {
         super.onBootPhase(phase);
     }
 
+    @VisibleForTesting
+    void setAllowedUid(int uid) {
+        mAllowedUid = uid;
+    }
+
     private void formatIfOemUnlockEnabled() {
         boolean enabled = doGetOemUnlockEnabled();
         if (enabled) {
@@ -220,7 +244,7 @@ public class PersistentDataBlockService extends SystemService {
             }
         }
 
-        SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
+        setProperty(OEM_UNLOCK_PROP, enabled ? "1" : "0");
     }
 
     private void enforceOemUnlockReadPermission() {
@@ -278,7 +302,7 @@ public class PersistentDataBlockService extends SystemService {
     private long getBlockDeviceSize() {
         synchronized (mLock) {
             if (mBlockDeviceSize == -1) {
-                if (mIsRunningDSU) {
+                if (mIsFileBacked) {
                     mBlockDeviceSize = MAX_DATA_BLOCK_SIZE;
                 } else {
                     mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
@@ -289,14 +313,26 @@ public class PersistentDataBlockService extends SystemService {
         return mBlockDeviceSize;
     }
 
-    private long getFrpCredentialDataOffset() {
-        return getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE;
+    @VisibleForTesting
+    int getMaximumFrpDataSize() {
+        return (int) (getTestHarnessModeDataOffset() - DIGEST_SIZE_BYTES - HEADER_SIZE);
+    }
+
+    @VisibleForTesting
+    long getFrpCredentialDataOffset() {
+        return getOemUnlockDataOffset() - FRP_CREDENTIAL_RESERVED_SIZE;
     }
 
-    private long getTestHarnessModeDataOffset() {
+    @VisibleForTesting
+    long getTestHarnessModeDataOffset() {
         return getFrpCredentialDataOffset() - TEST_MODE_RESERVED_SIZE;
     }
 
+    @VisibleForTesting
+    long getOemUnlockDataOffset() {
+        return getBlockDeviceSize() - 1;
+    }
+
     private boolean enforceChecksumValidity() {
         byte[] storedDigest = new byte[DIGEST_SIZE_BYTES];
 
@@ -385,7 +421,8 @@ public class PersistentDataBlockService extends SystemService {
         return md.digest();
     }
 
-    private void formatPartitionLocked(boolean setOemUnlockEnabled) {
+    @VisibleForTesting
+    void formatPartitionLocked(boolean setOemUnlockEnabled) {
 
         try {
             FileChannel channel = getBlockOutputChannel();
@@ -448,10 +485,15 @@ public class PersistentDataBlockService extends SystemService {
             Slog.e(TAG, "unable to access persistent partition", e);
             return;
         } finally {
-            SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
+            setProperty(OEM_UNLOCK_PROP, enabled ? "1" : "0");
         }
     }
 
+    @VisibleForTesting
+    void setProperty(String name, String value) {
+        SystemProperties.set(name, value);
+    }
+
     private boolean doGetOemUnlockEnabled() {
         DataInputStream inputStream;
         try {
@@ -483,6 +525,16 @@ public class PersistentDataBlockService extends SystemService {
     private native long nativeGetBlockDeviceSize(String path);
     private native int nativeWipe(String path);
 
+    @VisibleForTesting
+    IPersistentDataBlockService getInterfaceForTesting() {
+        return IPersistentDataBlockService.Stub.asInterface(mService);
+    }
+
+    @VisibleForTesting
+    PersistentDataBlockManagerInternal getInternalInterfaceForTesting() {
+        return mInternalService;
+    }
+
     private final IBinder mService = new IPersistentDataBlockService.Stub() {
 
         /**
@@ -588,7 +640,18 @@ public class PersistentDataBlockService extends SystemService {
             enforceOemUnlockWritePermission();
 
             synchronized (mLock) {
-                int ret = nativeWipe(mDataBlockFile);
+                int ret;
+                if (mIsFileBacked) {
+                    try {
+                        Files.write(Paths.get(mDataBlockFile), new byte[MAX_DATA_BLOCK_SIZE],
+                                StandardOpenOption.TRUNCATE_EXISTING);
+                        ret = 0;
+                    } catch (IOException e) {
+                        ret = -1;
+                    }
+                } else {
+                    ret = nativeWipe(mDataBlockFile);
+                }
 
                 if (ret < 0) {
                     Slog.e(TAG, "failed to wipe persistent partition");
@@ -699,7 +762,7 @@ public class PersistentDataBlockService extends SystemService {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
             pw.println("mDataBlockFile: " + mDataBlockFile);
-            pw.println("mIsRunningDSU: " + mIsRunningDSU);
+            pw.println("mIsFileBacked: " + mIsFileBacked);
             pw.println("mInitDoneSignal: " + mInitDoneSignal);
             pw.println("mAllowedUid: " + mAllowedUid);
             pw.println("mBlockDeviceSize: " + mBlockDeviceSize);
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 26e70c0279e3fe42cdcc53b3960d26bc28a583dc..21284a05ddadec174c18e64d0e4d791645a6b08e 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -410,7 +410,7 @@ public abstract class ApexManager {
          * Map of all apex system services to the jar files they are contained in.
          */
         @GuardedBy("mLock")
-        private List<ApexSystemServiceInfo> mApexSystemServices = new ArrayList<>();
+        private final List<ApexSystemServiceInfo> mApexSystemServices = new ArrayList<>();
 
         /**
          * Contains the list of {@code packageName}s of apks-in-apex for given
@@ -418,14 +418,14 @@ public abstract class ApexManager {
          * difference between {@code packageName} and {@code apexModuleName}.
          */
         @GuardedBy("mLock")
-        private ArrayMap<String, List<String>> mApksInApex = new ArrayMap<>();
+        private final ArrayMap<String, List<String>> mApksInApex = new ArrayMap<>();
 
         /**
          * Contains the list of {@code Exception}s that were raised when installing apk-in-apex
          * inside {@code apexModuleName}.
          */
         @GuardedBy("mLock")
-        private Map<String, String> mErrorWithApkInApex = new ArrayMap<>();
+        private final Map<String, String> mErrorWithApkInApex = new ArrayMap<>();
 
         /**
          * An APEX is a file format that delivers the apex-payload wrapped in an apk container. The
diff --git a/services/core/java/com/android/server/pm/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java
index 5b32a94c4eac4a6ea8c7005c26a8f4d7e0c5eed1..a5bc2c36a5a8f9ca07ef8ebf4f944d3a4910e320 100644
--- a/services/core/java/com/android/server/pm/AppsFilterBase.java
+++ b/services/core/java/com/android/server/pm/AppsFilterBase.java
@@ -201,7 +201,7 @@ public abstract class AppsFilterBase implements AppsFilterSnapshot {
 
     protected static final boolean CACHE_VALID = true;
     protected static final boolean CACHE_INVALID = false;
-    protected AtomicBoolean mCacheValid = new AtomicBoolean(CACHE_INVALID);
+    protected final AtomicBoolean mCacheValid = new AtomicBoolean(CACHE_INVALID);
 
     protected boolean isForceQueryable(int callingAppId) {
         return mForceQueryable.contains(callingAppId);
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 9bca9f0192b2a572ab8fc24cd6f0e317045849cb..36677df07ca3e6d2142fc5ec337e0252548b58bb 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -89,7 +89,7 @@ public final class BackgroundDexOptService {
 
     private static final long CANCELLATION_WAIT_CHECK_INTERVAL_MS = 200;
 
-    private static ComponentName sDexoptServiceName =
+    private static final ComponentName sDexoptServiceName =
             new ComponentName("android", BackgroundDexOptJobService.class.getName());
 
     // Possible return codes of individual optimization steps.
@@ -179,7 +179,7 @@ public final class BackgroundDexOptService {
 
     private final long mDowngradeUnusedAppsThresholdInMillis;
 
-    private List<PackagesUpdatedListener> mPackagesUpdatedListeners = new ArrayList<>();
+    private final List<PackagesUpdatedListener> mPackagesUpdatedListeners = new ArrayList<>();
 
     private int mThermalStatusCutoff = THERMAL_CUTOFF_DEFAULT;
 
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index e367609e89b6c3c1e40fc568f506e6bc913da206..7b35589ae6824f5fd56405597376a16eae2506e0 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -100,6 +100,22 @@ public final class BroadcastHelper {
         mAppsFilter = injector.getAppsFilter();
     }
 
+    /**
+     * Sends a broadcast to registered clients on userId for the given Intent.
+     */
+    void sendPackageBroadcastWithIntent(Intent intent, int userId, boolean isInstantApp,
+            @Intent.Flags int flags,
+            int[] visibilityAllowList,
+            final IIntentReceiver finishedReceiver,
+            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+            @Nullable Bundle bOptions) {
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | flags);
+        SparseArray<int[]> broadcastAllowList = new SparseArray<>();
+        broadcastAllowList.put(userId, visibilityAllowList);
+        broadcastIntent(intent, finishedReceiver, isInstantApp, userId, broadcastAllowList,
+                filterExtrasForReceiver, bOptions);
+    }
+
     void sendPackageBroadcast(final String action, final String pkg, final Bundle extras,
             final int flags, final String targetPkg, final IIntentReceiver finishedReceiver,
             final int[] userIds, int[] instantUserIds,
@@ -152,8 +168,6 @@ public final class BroadcastHelper {
         for (int userId : userIds) {
             final Intent intent = new Intent(action,
                     pkg != null ? Uri.fromParts(PACKAGE_SCHEME, pkg, null) : null);
-            final String[] requiredPermissions =
-                    isInstantApp ? INSTANT_APP_BROADCAST_PERMISSION : null;
             if (extras != null) {
                 intent.putExtras(extras);
             }
@@ -172,30 +186,41 @@ public final class BroadcastHelper {
             }
             intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | flags);
-            if (DEBUG_BROADCASTS) {
-                RuntimeException here = new RuntimeException("here");
-                here.fillInStackTrace();
-                Slog.d(TAG, "Sending to user " + userId + ": "
-                        + intent.toShortString(false, true, false, false)
-                        + " " + intent.getExtras(), here);
-            }
-            final boolean ordered;
-            if (mAmInternal.isModernQueueEnabled()) {
-                // When the modern broadcast stack is enabled, deliver all our
-                // broadcasts as unordered, since the modern stack has better
-                // support for sequencing cold-starts, and it supports
-                // delivering resultTo for non-ordered broadcasts
-                ordered = false;
-            } else {
-                ordered = (finishedReceiver != null);
-            }
-            mAmInternal.broadcastIntent(
-                    intent, finishedReceiver, requiredPermissions, ordered, userId,
-                    broadcastAllowList == null ? null : broadcastAllowList.get(userId),
+            broadcastIntent(intent, finishedReceiver, isInstantApp, userId, broadcastAllowList,
                     filterExtrasForReceiver, bOptions);
         }
     }
 
+
+    private void broadcastIntent(Intent intent, IIntentReceiver finishedReceiver,
+            boolean isInstantApp, int userId, @Nullable SparseArray<int[]> broadcastAllowList,
+            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+            @Nullable Bundle bOptions) {
+        final String[] requiredPermissions =
+                isInstantApp ? INSTANT_APP_BROADCAST_PERMISSION : null;
+        if (DEBUG_BROADCASTS) {
+            RuntimeException here = new RuntimeException("here");
+            here.fillInStackTrace();
+            Slog.d(TAG, "Sending to user " + userId + ": "
+                    + intent.toShortString(false, true, false, false)
+                    + " " + intent.getExtras(), here);
+        }
+        final boolean ordered;
+        if (mAmInternal.isModernQueueEnabled()) {
+            // When the modern broadcast stack is enabled, deliver all our
+            // broadcasts as unordered, since the modern stack has better
+            // support for sequencing cold-starts, and it supports
+            // delivering resultTo for non-ordered broadcasts
+            ordered = false;
+        } else {
+            ordered = (finishedReceiver != null);
+        }
+        mAmInternal.broadcastIntent(
+                intent, finishedReceiver, requiredPermissions, ordered, userId,
+                broadcastAllowList == null ? null : broadcastAllowList.get(userId),
+                filterExtrasForReceiver, bOptions);
+    }
+
     void sendResourcesChangedBroadcast(@NonNull Computer snapshot,
                                        boolean mediaStatus,
                                        boolean replacing,
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsService.java b/services/core/java/com/android/server/pm/CrossProfileAppsService.java
index 486282aa2c04bdd1e2c32bf7c2ec40b23c4d0581..0bd165ec91fa76f32de84460cc781b878ccc3d5b 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsService.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsService.java
@@ -21,7 +21,7 @@ import android.content.pm.CrossProfileAppsInternal;
 import com.android.server.SystemService;
 
 public class CrossProfileAppsService extends SystemService {
-    private CrossProfileAppsServiceImpl mServiceImpl;
+    private final CrossProfileAppsServiceImpl mServiceImpl;
 
     public CrossProfileAppsService(Context context) {
         super(context);
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index 5f28e56e996c95049e5db45a6995cb20fc1411f5..f1dc2849a3912065720ade5c5782abbdfd32c766 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -78,8 +78,8 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
 
     private final LocalService mLocalService = new LocalService();
 
-    private Context mContext;
-    private Injector mInjector;
+    private final Context mContext;
+    private final Injector mInjector;
 
     public CrossProfileAppsServiceImpl(Context context) {
         this(context, new InjectorImpl(context));
@@ -783,7 +783,7 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
     }
 
     private static class InjectorImpl implements Injector {
-        private Context mContext;
+        private final Context mContext;
 
         public InjectorImpl(Context context) {
             mContext = context;
diff --git a/services/core/java/com/android/server/pm/CrossProfileDomainInfo.java b/services/core/java/com/android/server/pm/CrossProfileDomainInfo.java
index 72f3afc35e99562f1771e6d91f2c1153e4f209e1..3313e7234a19a272f91b93e847123fae7f84c1e3 100644
--- a/services/core/java/com/android/server/pm/CrossProfileDomainInfo.java
+++ b/services/core/java/com/android/server/pm/CrossProfileDomainInfo.java
@@ -22,10 +22,10 @@ import android.os.UserHandle;
 
 public final class CrossProfileDomainInfo {
     /* ResolveInfo for IntentForwarderActivity to send the intent to the other profile */
-    ResolveInfo mResolveInfo;
+    final ResolveInfo mResolveInfo;
     int mHighestApprovalLevel;
     @UserIdInt
-    int mTargetUserId = UserHandle.USER_CURRENT; // default as current user
+    final int mTargetUserId;
 
     CrossProfileDomainInfo(ResolveInfo resolveInfo, int highestApprovalLevel, @UserIdInt
             int targetUserId) {
@@ -37,6 +37,7 @@ public final class CrossProfileDomainInfo {
     CrossProfileDomainInfo(ResolveInfo resolveInfo, int highestApprovalLevel) {
         this.mResolveInfo = resolveInfo;
         this.mHighestApprovalLevel = highestApprovalLevel;
+        this.mTargetUserId = UserHandle.USER_CURRENT; // default as current user
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilterHelper.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilterHelper.java
index 51214faca730de24de4dd70c5101b8d686c11f33..56e9c0a3544b2ef4d490aaad43cb6f7d6b7d4866 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentFilterHelper.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilterHelper.java
@@ -25,11 +25,11 @@ import android.util.ArraySet;
  * Helper class to manage {@link com.android.server.pm.CrossProfileIntentFilter}s.
  */
 public class CrossProfileIntentFilterHelper {
-    private Context mContext;
-    private UserManagerInternal mUserManagerInternal;
-    private Settings mSettings;
-    private UserManagerService mUserManagerService;
-    private PackageManagerTracedLock mLock;
+    private final Context mContext;
+    private final UserManagerInternal mUserManagerInternal;
+    private final Settings mSettings;
+    private final UserManagerService mUserManagerService;
+    private final PackageManagerTracedLock mLock;
 
     public CrossProfileIntentFilterHelper(Settings settings, UserManagerService userManagerService,
             PackageManagerTracedLock lock, UserManagerInternal userManagerInternal,
diff --git a/services/core/java/com/android/server/pm/CrossProfileResolver.java b/services/core/java/com/android/server/pm/CrossProfileResolver.java
index a8da818932fa0473fc95f9d615f5f5ad3aa0a11b..1d38bbb2a1bdbb47fa3e848f765b9ae45d8820f2 100644
--- a/services/core/java/com/android/server/pm/CrossProfileResolver.java
+++ b/services/core/java/com/android/server/pm/CrossProfileResolver.java
@@ -36,8 +36,8 @@ import java.util.function.Function;
  */
 public abstract class CrossProfileResolver {
 
-    protected ComponentResolverApi mComponentResolver;
-    protected UserManagerService mUserManager;
+    protected final ComponentResolverApi mComponentResolver;
+    protected final UserManagerService mUserManager;
 
     public CrossProfileResolver(ComponentResolverApi componentResolver,
             UserManagerService userManager) {
diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
index 29322e2553e93974d51e31d1ee965bfbc2d1fe28..888e1c26206cc5bef58b79793aa8e9f38ade0f58 100644
--- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java
+++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
@@ -50,7 +50,8 @@ public class DataLoaderManagerService extends SystemService {
     private final HandlerThread mThread;
     private final Handler mHandler;
     private final DataLoaderManagerBinderService mBinderService;
-    private SparseArray<DataLoaderServiceConnection> mServiceConnections = new SparseArray<>();
+    private final SparseArray<DataLoaderServiceConnection> mServiceConnections =
+            new SparseArray<>();
 
     public DataLoaderManagerService(Context context) {
         super(context);
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFilter.java
index dc5915de5551aa1e2aa6f3196e4c05fc7d541cc6..db05dd3949157d3ba0f625485438ab106aee0838 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFilter.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFilter.java
@@ -75,10 +75,10 @@ public final class DefaultCrossProfileIntentFilter {
     }
 
     static final class Builder {
-        private WatchedIntentFilter mFilter = new WatchedIntentFilter();
-        private int mFlags;
-        private @Direction int mDirection;
-        private boolean mLetsPersonalDataIntoProfile;
+        private final WatchedIntentFilter mFilter = new WatchedIntentFilter();
+        private final int mFlags;
+        private final @Direction int mDirection;
+        private final boolean mLetsPersonalDataIntoProfile;
 
         Builder(@Direction int direction, int flags, boolean letsPersonalDataIntoProfile) {
             mDirection = direction;
diff --git a/services/core/java/com/android/server/pm/InstallArgs.java b/services/core/java/com/android/server/pm/InstallArgs.java
index dd96a2b84a9783dfc5ca34fdccd78747d34a26f7..cc6bb007411e379207aa6b0a954b1a72ee6acb43 100644
--- a/services/core/java/com/android/server/pm/InstallArgs.java
+++ b/services/core/java/com/android/server/pm/InstallArgs.java
@@ -67,7 +67,8 @@ final class InstallArgs {
     // The list of instruction sets supported by this app. This is currently
     // only used during the rmdex() phase to clean up resources. We can get rid of this
     // if we move dex files under the common app path.
-    @Nullable String[] mInstructionSets;
+    @Nullable
+    final String[] mInstructionSets;
 
     InstallArgs(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
             int installFlags, int developmentInstallFlags, InstallSource installSource,
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index be4fb5c539a9644ea52e8d3c3354068e5bb0a3c7..fc831205f60e7169ef0ea9cd4d1a83894d73f5a3 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -153,7 +153,7 @@ final class InstallRequest {
     private int[] mUpdateBroadcastInstantUserIds = EMPTY_INT_ARRAY;
 
     @NonNull
-    private ArrayList<String> mWarnings = new ArrayList<>();
+    private final ArrayList<String> mWarnings = new ArrayList<>();
 
     // New install
     InstallRequest(InstallingSession params) {
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index ca8dc2973404e3fd99272ae1fc4f4378e48f6a94..187cadaf4d46927e8a1596e7bb3f689a883897ba 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -68,7 +68,7 @@ class InstallingSession {
     final MoveInfo mMoveInfo;
     final IPackageInstallObserver2 mObserver;
     int mInstallFlags;
-    int mDevelopmentInstallFlags;
+    final int mDevelopmentInstallFlags;
     @NonNull
     final InstallSource mInstallSource;
     final String mVolumeUuid;
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 8b82a1c6e7c954ae193641a9bebd1ef4f735535a..3f4cc4a84ffd7de5018c59257c20bf082cd5ad3b 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -233,7 +233,8 @@ public class LauncherAppsService extends SystemService {
         final LauncherAppsServiceInternal mInternal;
 
         @NonNull
-        private final RemoteCallbackList<IDumpCallback> mDumpCallbacks = new RemoteCallbackList<>();
+        private final RemoteCallbackList<IDumpCallback> mDumpCallbacks =
+                new RemoteCallbackList<>();
 
         public LauncherAppsImpl(Context context) {
             mContext = context;
@@ -2374,8 +2375,8 @@ public class LauncherAppsService extends SystemService {
 
         class PackageLoadingProgressCallback extends
                 PackageManagerInternal.InstalledLoadingProgressCallback {
-            private String mPackageName;
-            private UserHandle mUser;
+            private final String mPackageName;
+            private final UserHandle mUser;
 
             PackageLoadingProgressCallback(String packageName, UserHandle user) {
                 super(mCallbackHandler);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index a4d8632ac0775db82010881ec435b1ee77f65831..98eee4dc3b1dc836374253b9ce5fff1c7879dff5 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1867,7 +1867,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
     }
 
     static class ParentChildSessionMap {
-        private TreeMap<PackageInstallerSession, TreeSet<PackageInstallerSession>> mSessionMap;
+        private final TreeMap<PackageInstallerSession, TreeSet<PackageInstallerSession>>
+                mSessionMap;
 
         private final Comparator<PackageInstallerSession> mSessionCreationComparator =
                 Comparator.comparingLong(
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 1be28ca9e915ddce492005f4bd5267e5fa2f4cbf..4b466be8476b317564af2f57d8a97dd4165736b7 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -491,7 +491,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
     @GuardedBy("mLock")
     private SigningDetails mSigningDetails;
     @GuardedBy("mLock")
-    private SparseArray<PackageInstallerSession> mChildSessions = new SparseArray<>();
+    private final SparseArray<PackageInstallerSession> mChildSessions = new SparseArray<>();
     @GuardedBy("mLock")
     private int mParentSessionId;
 
@@ -535,7 +535,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
     }
 
     @GuardedBy("mLock")
-    private ArraySet<FileEntry> mFiles = new ArraySet<>();
+    private final ArraySet<FileEntry> mFiles = new ArraySet<>();
 
     static class PerFileChecksum {
         private final Checksum[] mChecksums;
@@ -556,7 +556,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
     }
 
     @GuardedBy("mLock")
-    private ArrayMap<String, PerFileChecksum> mChecksums = new ArrayMap<>();
+    private final ArrayMap<String, PerFileChecksum> mChecksums = new ArrayMap<>();
 
     @GuardedBy("mLock")
     private boolean mSessionApplied;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e5f7962e254c6e28cf6e1aaba2dc71c48adaca09..434c00afa8b22198d030bc2e58a9f6f8c23ccdd2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6972,14 +6972,50 @@ public class PackageManagerService implements PackageSender, TestUtilityService
             final Bundle extras = new Bundle();
             extras.putInt(Intent.EXTRA_UID, uid);
             extras.putInt(Intent.EXTRA_USER_HANDLE, userId);
-            extras.putLong(Intent.EXTRA_TIME, SystemClock.elapsedRealtime());
-            mHandler.post(() -> {
+            if (android.content.pm.Flags.stayStopped()) {
+                extras.putLong(Intent.EXTRA_TIME, SystemClock.elapsedRealtime());
+                // Sent async using the PM handler, to maintain ordering with PACKAGE_UNSTOPPED
+                mHandler.post(() -> {
+                    mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_RESTARTED,
+                            packageName, extras,
+                            flags, null, null,
+                            userIds, null, broadcastAllowList, null,
+                            null);
+                });
+            } else {
                 mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_RESTARTED,
                         packageName, extras,
                         flags, null, null,
                         userIds, null, broadcastAllowList, null,
                         null);
-            });
+            }
+            mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_RESTARTED,
+                    packageName, extras, userIds, null /* instantUserIds */,
+                    broadcastAllowList, mHandler);
+        }
+
+        @Override
+        public void sendPackageDataClearedBroadcast(@NonNull String packageName,
+                int uid, int userId, boolean isRestore, boolean isInstantApp) {
+            int[] visibilityAllowList =
+                    snapshotComputer().getVisibilityAllowList(packageName, userId);
+            final Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED,
+                    Uri.fromParts("package", packageName, null /* fragment */));
+            intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+                    | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            intent.putExtra(Intent.EXTRA_UID, uid);
+            intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+            if (isRestore) {
+                intent.putExtra(Intent.EXTRA_IS_RESTORE, true);
+            }
+            if (isInstantApp) {
+                intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+            }
+            mBroadcastHelper.sendPackageBroadcastWithIntent(intent, userId, isInstantApp,
+                    0 /* flags */, visibilityAllowList, null /* finishedReceiver */,
+                    null /* filterExtrasForReceiver */, null /* bOptions */);
+            mPackageMonitorCallbackHelper.notifyPackageMonitorWithIntent(intent, userId,
+                    visibilityAllowList, mHandler);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 9428ef6c8ba9b03bd8c25726b3be4abebd7443ec..655b9c93d9dd817e8426dd70aad27aee35dd35db 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -60,7 +60,7 @@ public final class PackageManagerServiceTestParams {
     public IncrementalManager incrementalManager;
     public PackageInstallerService installerService;
     public InstantAppRegistry instantAppRegistry;
-    public ChangedPackagesTracker changedPackagesTracker = new ChangedPackagesTracker();
+    public final ChangedPackagesTracker changedPackagesTracker = new ChangedPackagesTracker();
     public InstantAppResolverConnection instantAppResolverConnection;
     public ComponentName instantAppResolverSettingsComponent;
     public boolean isPreNmr1Upgrade;
@@ -118,7 +118,7 @@ public final class PackageManagerServiceTestParams {
     public SuspendPackageHelper suspendPackageHelper;
     public DistractingPackageHelper distractingPackageHelper;
     public StorageEventHelper storageEventHelper;
-    public Set<String> initialNonStoppedSystemPackages = new ArraySet<>();
+    public final Set<String> initialNonStoppedSystemPackages = new ArraySet<>();
     public boolean shouldStopSystemPackagesByDefault;
     public FreeStorageHelper freeStorageHelper;
     public PackageMonitorCallbackHelper packageMonitorCallbackHelper;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index d4abad8499f26827a3a733b71b57017829220361..6f45d2befc6b6047dd9ce63bb0e804f0fb4e23ed 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2364,7 +2364,7 @@ class PackageManagerShellCommand extends ShellCommand {
         private boolean mSuccess = false;
         private int mErrCode = -1;
         private ParcelFileDescriptor mProfileReadFd = null;
-        private CountDownLatch mDoneSignal = new CountDownLatch(1);
+        private final CountDownLatch mDoneSignal = new CountDownLatch(1);
 
         @Override
         public void onSuccess(ParcelFileDescriptor profileReadFd) {
@@ -5186,7 +5186,7 @@ class PackageManagerShellCommand extends ShellCommand {
     private static class LocalIntentReceiver {
         private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
 
-        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+        private final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
             @Override
             public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
                     IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
index a09e71311125e6bb3f2927c7a9df54a66708c624..c829e1c79ba8688ece7c88ea69a14edd63b54a9b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
@@ -148,7 +148,8 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService {
         private final byte[] mData;
         private final String mSalt;
 
-        private static AtomicLong sGlobalSalt = new AtomicLong((new SecureRandom()).nextLong());
+        private static final AtomicLong sGlobalSalt =
+                new AtomicLong((new SecureRandom()).nextLong());
         private static Long nextGlobalSalt() {
             return sGlobalSalt.incrementAndGet();
         }
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index 1f12c88ede366456066b747c43f09575a7d1bc98..fa9409f88a13ba2825b856ce3ab9bf83bf1a8489 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -159,16 +159,25 @@ class PackageMonitorCallbackHelper {
             }
 
             if (ArrayUtils.isEmpty(instantUserIds)) {
-                doNotifyCallbacks(
+                doNotifyCallbacksByAction(
                         action, pkg, extras, resolvedUserIds, broadcastAllowList, handler);
             } else {
-                doNotifyCallbacks(action, pkg, extras, instantUserIds, broadcastAllowList, handler);
+                doNotifyCallbacksByAction(action, pkg, extras, instantUserIds, broadcastAllowList,
+                        handler);
             }
         } catch (RemoteException e) {
             // do nothing
         }
     }
 
+    void notifyPackageMonitorWithIntent(Intent intent,
+            int userId, int[] broadcastAllowList, Handler handler) {
+        if (!isAllowedCallbackAction(intent.getAction())) {
+            return;
+        }
+        doNotifyCallbacksByIntent(intent, userId, broadcastAllowList, handler);
+    }
+
     private static boolean isAllowedCallbackAction(String action) {
         return TextUtils.equals(action, Intent.ACTION_PACKAGE_ADDED)
                 || TextUtils.equals(action, Intent.ACTION_PACKAGE_REMOVED)
@@ -177,10 +186,21 @@ class PackageMonitorCallbackHelper {
                 || TextUtils.equals(action, Intent.ACTION_PACKAGES_SUSPENDED)
                 || TextUtils.equals(action, Intent.ACTION_PACKAGES_UNSUSPENDED)
                 || TextUtils.equals(action, Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE)
-                || TextUtils.equals(action, Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+                || TextUtils.equals(action, Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)
+                || TextUtils.equals(action, Intent.ACTION_PACKAGE_DATA_CLEARED)
+                || TextUtils.equals(action, Intent.ACTION_PACKAGE_RESTARTED);
     }
 
-    private void doNotifyCallbacks(String action, String pkg, Bundle extras, int[] userIds,
+    private void doNotifyCallbacksByIntent(Intent intent, int userId,
+            int[] broadcastAllowList, Handler handler) {
+        RemoteCallbackList<IRemoteCallback> callbacks;
+        synchronized (mLock) {
+            callbacks = mCallbacks;
+        }
+        doNotifyCallbacks(callbacks, intent, userId, broadcastAllowList, handler);
+    }
+
+    private void doNotifyCallbacksByAction(String action, String pkg, Bundle extras, int[] userIds,
             SparseArray<int[]> broadcastAllowList, Handler handler) {
         RemoteCallbackList<IRemoteCallback> callbacks;
         synchronized (mLock) {
@@ -200,27 +220,31 @@ class PackageMonitorCallbackHelper {
             intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
 
             final int[] allowUids =
-                    broadcastAllowList != null ? broadcastAllowList.get(userId) : new int[]{};
+                    broadcastAllowList != null ? broadcastAllowList.get(userId) : null;
+            doNotifyCallbacks(callbacks, intent, userId, allowUids, handler);
+        }
+    }
 
-            handler.post(() -> callbacks.broadcast((callback, user) -> {
-                RegisterUser registerUser = (RegisterUser) user;
-                if ((registerUser.getUserId() != UserHandle.USER_ALL) && (registerUser.getUserId()
-                        != userId)) {
-                    return;
-                }
-                int registerUid = registerUser.getUid();
-                if (broadcastAllowList != null && registerUid != Process.SYSTEM_UID
-                        && !ArrayUtils.contains(allowUids, registerUid)) {
-                    if (DEBUG) {
-                        Slog.w("PackageMonitorCallbackHelper",
-                                "Skip invoke PackageMonitorCallback for " + action + ", uid "
-                                        + registerUid);
-                    }
-                    return;
+    private void doNotifyCallbacks(RemoteCallbackList<IRemoteCallback> callbacks,
+            Intent intent, int userId, int[] allowUids, Handler handler) {
+        handler.post(() -> callbacks.broadcast((callback, user) -> {
+            RegisterUser registerUser = (RegisterUser) user;
+            if ((registerUser.getUserId() != UserHandle.USER_ALL) && (registerUser.getUserId()
+                    != userId)) {
+                return;
+            }
+            int registerUid = registerUser.getUid();
+            if (allowUids != null && registerUid != Process.SYSTEM_UID
+                    && !ArrayUtils.contains(allowUids, registerUid)) {
+                if (DEBUG) {
+                    Slog.w("PackageMonitorCallbackHelper",
+                            "Skip invoke PackageMonitorCallback for " + intent.getAction()
+                                    + ", uid " + registerUid);
                 }
-                invokeCallback(callback, intent);
-            }));
-        }
+                return;
+            }
+            invokeCallback(callback, intent);
+        }));
     }
 
     private void invokeCallback(IRemoteCallback callback, Intent intent) {
diff --git a/services/core/java/com/android/server/pm/PreferredComponent.java b/services/core/java/com/android/server/pm/PreferredComponent.java
index 5507e7c7754a61fd171f98f4270abc2949e84c31..18caafdaa56ac728210bc9afd1d651187f884933 100644
--- a/services/core/java/com/android/server/pm/PreferredComponent.java
+++ b/services/core/java/com/android/server/pm/PreferredComponent.java
@@ -48,7 +48,7 @@ public class PreferredComponent {
     public final int mMatch;
     public final ComponentName mComponent;
     // Whether this is to be the one that's always chosen. If false, it's the most recently chosen.
-    public boolean mAlways;
+    public final boolean mAlways;
 
     final String[] mSetPackages;
     final String[] mSetClasses;
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index cf5aa7b1eb02aeb3df280d22ed9f7640a0acbe6c..e667bfe36d186877d1bcef28a9f9900a19b5e0bc 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -68,12 +68,12 @@ public final class SELinuxMMAC {
 
     // All policy stanzas read from mac_permissions.xml. This is also the lock
     // to synchronize access during policy load and access attempts.
-    private static List<Policy> sPolicies = new ArrayList<>();
+    private static final List<Policy> sPolicies = new ArrayList<>();
     /** Whether or not the policy files have been read */
     private static boolean sPolicyRead;
 
     /** Required MAC permissions files */
-    private static List<File> sMacPermissions = new ArrayList<>();
+    private static final List<File> sMacPermissions = new ArrayList<>();
 
     private static final String DEFAULT_SEINFO = "default";
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 440823c436073e9e6bf4d22d4288a617dad9f08b..6338965e4a8001404742762a376763b864004ec4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3944,9 +3944,9 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
         mDisabledSysPackages.put(name, ps);
     }
 
-    private static int PRE_M_APP_INFO_FLAG_HIDDEN = 1<<27;
-    private static int PRE_M_APP_INFO_FLAG_CANT_SAVE_STATE = 1<<28;
-    private static int PRE_M_APP_INFO_FLAG_PRIVILEGED = 1<<30;
+    private static final int PRE_M_APP_INFO_FLAG_HIDDEN = 1 << 27;
+    private static final int PRE_M_APP_INFO_FLAG_CANT_SAVE_STATE = 1 << 28;
+    private static final int PRE_M_APP_INFO_FLAG_PRIVILEGED = 1 << 30;
 
     private void readPackageLPw(TypedXmlPullParser parser, List<UserInfo> users,
             ArrayMap<String, Long> originalFirstInstallTimes)
@@ -5846,7 +5846,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
         @GuardedBy("mLock")
         // Tracking the mutations that haven't yet been written to legacy state.
         // This avoids unnecessary work when writing settings for multiple users.
-        private AtomicBoolean mIsLegacyPermissionStateStale = new AtomicBoolean(false);
+        private final AtomicBoolean mIsLegacyPermissionStateStale = new AtomicBoolean(false);
 
         @GuardedBy("mLock")
         // The mapping keys are user ids.
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 8667888fbb51b3786531f601148ec5c09372da83..12115afcbeec78d3e088b49279c57d062a910d76 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -53,7 +53,7 @@ abstract class ShortcutPackageItem {
     protected ShortcutUser mShortcutUser;
 
     @GuardedBy("mLock")
-    protected ShortcutBitmapSaver mShortcutBitmapSaver;
+    protected final ShortcutBitmapSaver mShortcutBitmapSaver;
 
     protected final Object mLock = new Object();
 
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index c6aba2ab9cbec429f16c6c45b809aa9637d3fa18..3adeb4b5925fad8823ca940f7dcd9fdeed6e6503 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -296,24 +296,26 @@ public class ShortcutService extends IShortcutService.Stub {
     private final Object mNonPersistentUsersLock = new Object();
     private final Object mWtfLock = new Object();
 
-    private static List<ResolveInfo> EMPTY_RESOLVE_INFO = new ArrayList<>(0);
+    private static final List<ResolveInfo> EMPTY_RESOLVE_INFO = new ArrayList<>(0);
 
     // Temporarily reverted to anonymous inner class form due to: b/32554459
-    private static Predicate<ResolveInfo> ACTIVITY_NOT_EXPORTED = new Predicate<ResolveInfo>() {
-        public boolean test(ResolveInfo ri) {
-            return !ri.activityInfo.exported;
-        }
-    };
+    private static final Predicate<ResolveInfo> ACTIVITY_NOT_EXPORTED =
+            new Predicate<ResolveInfo>() {
+                public boolean test(ResolveInfo ri) {
+                    return !ri.activityInfo.exported;
+                }
+            };
 
-    private static Predicate<ResolveInfo> ACTIVITY_NOT_INSTALLED = (ri) ->
+    private static final Predicate<ResolveInfo> ACTIVITY_NOT_INSTALLED = (ri) ->
             !isInstalled(ri.activityInfo);
 
     // Temporarily reverted to anonymous inner class form due to: b/32554459
-    private static Predicate<PackageInfo> PACKAGE_NOT_INSTALLED = new Predicate<PackageInfo>() {
-        public boolean test(PackageInfo pi) {
-            return !isInstalled(pi);
-        }
-    };
+    private static final Predicate<PackageInfo> PACKAGE_NOT_INSTALLED =
+            new Predicate<PackageInfo>() {
+                public boolean test(PackageInfo pi) {
+                    return !isInstalled(pi);
+                }
+            };
 
     private final Handler mHandler;
 
@@ -4656,8 +4658,8 @@ public class ShortcutService extends IShortcutService.Stub {
         private boolean mDumpFiles = false;
 
         private boolean mDumpDetails = true;
-        private List<Pattern> mPackagePatterns = new ArrayList<>();
-        private List<Integer> mUsers = new ArrayList<>();
+        private final List<Pattern> mPackagePatterns = new ArrayList<>();
+        private final List<Integer> mUsers = new ArrayList<>();
 
         void addPackageRegex(String regex) {
             mPackagePatterns.add(Pattern.compile(regex));
diff --git a/services/core/java/com/android/server/pm/SnapshotStatistics.java b/services/core/java/com/android/server/pm/SnapshotStatistics.java
index e04a1e5b3569afbbac18a7d55204d5041c308300..31541d083fbc628c8c286aa0b769796a2cb13549 100644
--- a/services/core/java/com/android/server/pm/SnapshotStatistics.java
+++ b/services/core/java/com/android/server/pm/SnapshotStatistics.java
@@ -157,12 +157,12 @@ public class SnapshotStatistics {
     private static class BinMap {
 
         // The number of bins
-        private int mCount;
+        private final int mCount;
         // The maximum mapped value.  Values at or above this are mapped to the
         // top bin.
-        private int mMaxBin;
+        private final int mMaxBin;
         // A copy of the original key
-        private int[] mUserKey;
+        private final int[] mUserKey;
 
         /**
          * Create a bin map.  The input is an array of integers, which must be
@@ -232,13 +232,13 @@ public class SnapshotStatistics {
          * The build-time histogram.  The total number of rebuilds is the sum over the
          * histogram entries.
          */
-        public int[] mTimes;
+        public final int[] mTimes;
 
         /**
          * The reuse histogram.  The total number of snapshot uses is the sum over the
          * histogram entries.
          */
-        public int[] mUsed;
+        public final int[] mUsed;
 
         /**
          * The total number of rebuilds.  This could be computed by summing over the use
@@ -477,12 +477,12 @@ public class SnapshotStatistics {
     /**
      * Long statistics.  These roll over approximately one day.
      */
-    private Stats[] mLong;
+    private final Stats[] mLong;
 
     /**
      * Short statistics.  These roll over approximately every minute;
      */
-    private Stats[] mShort;
+    private final Stats[] mShort;
 
     /**
      * The time of last logging to the FrameworkStatsLog.
diff --git a/services/core/java/com/android/server/pm/UserJourneyLogger.java b/services/core/java/com/android/server/pm/UserJourneyLogger.java
index 895edce093b5e9f80f80070585ff58f134d58a80..651578dc5debbbc8f386a70e0eaef7654d07b679 100644
--- a/services/core/java/com/android/server/pm/UserJourneyLogger.java
+++ b/services/core/java/com/android/server/pm/UserJourneyLogger.java
@@ -586,7 +586,7 @@ public class UserJourneyLogger {
         public final long mSessionId;
         @UserJourney
         public final int mJourney;
-        public long mStartTimeInMills;
+        public final long mStartTimeInMills;
 
         @VisibleForTesting
         public UserJourneySession(long sessionId, @UserJourney int journey) {
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 6e738daf93158b99e6f8f92038d2a1caee74c6a0..78c13f854fe41ff2a53232c3cb7a799ab31f754a 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -119,10 +119,10 @@ public class DexManager {
     private final int mCriticalBatteryLevel;
 
     // Possible outcomes of a dex search.
-    private static int DEX_SEARCH_NOT_FOUND = 0;  // dex file not found
-    private static int DEX_SEARCH_FOUND_PRIMARY = 1;  // dex file is the primary/base apk
-    private static int DEX_SEARCH_FOUND_SPLIT = 2;  // dex file is a split apk
-    private static int DEX_SEARCH_FOUND_SECONDARY = 3;  // dex file is a secondary dex
+    private static final int DEX_SEARCH_NOT_FOUND = 0;  // dex file not found
+    private static final int DEX_SEARCH_FOUND_PRIMARY = 1;  // dex file is the primary/base apk
+    private static final int DEX_SEARCH_FOUND_SPLIT = 2;  // dex file is a split apk
+    private static final int DEX_SEARCH_FOUND_SECONDARY = 3;  // dex file is a secondary dex
 
     public DexManager(Context context, PackageDexOptimizer pdo, Installer installer,
             Object installLock, DynamicCodeLogger dynamicCodeLogger) {
@@ -959,8 +959,8 @@ public class DexManager {
      * Convenience class to store ownership search results.
      */
     private class DexSearchResult {
-        private String mOwningPackageName;
-        private int mOutcome;
+        private final String mOwningPackageName;
+        private final int mOutcome;
 
         public DexSearchResult(String owningPackageName, int outcome) {
             this.mOwningPackageName = owningPackageName;
diff --git a/services/core/java/com/android/server/pm/parsing/PackageCacher.java b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
index d3a64bb0b9acc6bf17c7a4f84d186bd6165c6795..2ab7db47f8e5708c9fdb916e7057149c19c3977b 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageCacher.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
@@ -49,7 +49,7 @@ public class PackageCacher {
     public static final AtomicInteger sCachedPackageReadCount = new AtomicInteger();
 
     @NonNull
-    private File mCacheDir;
+    private final File mCacheDir;
 
     public PackageCacher(@NonNull File cacheDir) {
         this.mCacheDir = cacheDir;
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 9e20805c287400d914297ab83092fb096ec941a1..c26cf1c9a113830b3e74d7a60c8e55bc615f9e19 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -1132,7 +1132,7 @@ public class PackageInfoUtils {
      */
     public static class CachedApplicationInfoGenerator {
         // Map from a package name to the corresponding app info.
-        private ArrayMap<String, ApplicationInfo> mCache = new ArrayMap<>();
+        private final ArrayMap<String, ApplicationInfo> mCache = new ArrayMap<>();
 
         /**
          * {@link PackageInfoUtils#generateApplicationInfo} with a cache.
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
index d82a5000844fc126a031082fd0474abc0481aa2a..3b10c7f2c2a6f143efef4f6fec37c38376a7991f 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
@@ -96,19 +96,19 @@ public class PackageParser2 implements AutoCloseable {
     private static final boolean LOG_PARSE_TIMINGS = Build.IS_DEBUGGABLE;
     private static final int LOG_PARSE_TIMINGS_THRESHOLD_MS = 100;
 
-    private ThreadLocal<ApplicationInfo> mSharedAppInfo =
+    private final ThreadLocal<ApplicationInfo> mSharedAppInfo =
             ThreadLocal.withInitial(() -> {
                 ApplicationInfo appInfo = new ApplicationInfo();
                 appInfo.uid = -1; // Not a valid UID since the app will not be installed yet
                 return appInfo;
             });
 
-    private ThreadLocal<ParseTypeImpl> mSharedResult;
+    private final ThreadLocal<ParseTypeImpl> mSharedResult;
 
     @Nullable
     protected PackageCacher mCacher;
 
-    private ParsingPackageUtils parsingUtils;
+    private final ParsingPackageUtils parsingUtils;
 
     public PackageParser2(String[] separateProcesses, DisplayMetrics displayMetrics,
             @Nullable File cacheDir, @NonNull Callback callback) {
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index 056aae49ae82a25a7fbd21cbfd5060161e04e40b..75dd67d97620680bab2911932e70c7e3d98e82ff 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -113,21 +113,21 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
     private static final SparseArray<int[]> EMPTY_INT_ARRAY_SPARSE_ARRAY = new SparseArray<>();
     private static final Comparator<ParsedMainComponent> ORDER_COMPARATOR =
             (first, second) -> Integer.compare(second.getOrder(), first.getOrder());
-    public static Parcelling.BuiltIn.ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(
+    public static final Parcelling.BuiltIn.ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(
             Parcelling.BuiltIn.ForBoolean.class);
-    public static ForInternedString sForInternedString = Parcelling.Cache.getOrCreate(
+    public static final ForInternedString sForInternedString = Parcelling.Cache.getOrCreate(
             ForInternedString.class);
-    public static Parcelling.BuiltIn.ForInternedStringArray sForInternedStringArray = Parcelling.Cache.getOrCreate(
-            Parcelling.BuiltIn.ForInternedStringArray.class);
-    public static Parcelling.BuiltIn.ForInternedStringList sForInternedStringList = Parcelling.Cache.getOrCreate(
-            Parcelling.BuiltIn.ForInternedStringList.class);
-    public static Parcelling.BuiltIn.ForInternedStringValueMap sForInternedStringValueMap =
+    public static final Parcelling.BuiltIn.ForInternedStringArray sForInternedStringArray =
+            Parcelling.Cache.getOrCreate(Parcelling.BuiltIn.ForInternedStringArray.class);
+    public static final Parcelling.BuiltIn.ForInternedStringList sForInternedStringList =
+            Parcelling.Cache.getOrCreate(Parcelling.BuiltIn.ForInternedStringList.class);
+    public static final Parcelling.BuiltIn.ForInternedStringValueMap sForInternedStringValueMap =
             Parcelling.Cache.getOrCreate(Parcelling.BuiltIn.ForInternedStringValueMap.class);
-    public static Parcelling.BuiltIn.ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(
-            Parcelling.BuiltIn.ForStringSet.class);
-    public static Parcelling.BuiltIn.ForInternedStringSet sForInternedStringSet =
+    public static final Parcelling.BuiltIn.ForStringSet sForStringSet =
+            Parcelling.Cache.getOrCreate(Parcelling.BuiltIn.ForStringSet.class);
+    public static final Parcelling.BuiltIn.ForInternedStringSet sForInternedStringSet =
             Parcelling.Cache.getOrCreate(Parcelling.BuiltIn.ForInternedStringSet.class);
-    protected static ParsingUtils.StringPairListParceler sForIntentInfoPairs =
+    protected static final ParsingUtils.StringPairListParceler sForIntentInfoPairs =
             new ParsingUtils.StringPairListParceler();
     protected int versionCode;
     protected int versionCodeMajor;
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index d6a7dc6681b924ae1494c52af8403b1e38a7cc08..6f6bb45bad507908c285451a6387510961e7c8ff 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1748,14 +1748,14 @@ final class DefaultPermissionGrantPolicy {
      */
     private class DelayingPackageManagerCache extends PackageManagerWrapper {
         /** uid -> permission -> isGranted, flags */
-        private SparseArray<ArrayMap<String, PermissionState>> mDelayedPermissionState =
+        private final SparseArray<ArrayMap<String, PermissionState>> mDelayedPermissionState =
                 new SparseArray<>();
         /** userId -> context */
-        private SparseArray<Context> mUserContexts = new SparseArray<>();
+        private final SparseArray<Context> mUserContexts = new SparseArray<>();
         /** Permission name -> info */
-        private ArrayMap<String, PermissionInfo> mPermissionInfos = new ArrayMap<>();
+        private final ArrayMap<String, PermissionInfo> mPermissionInfos = new ArrayMap<>();
         /** Package name -> info */
-        private ArrayMap<String, PackageInfo> mPackageInfos = new ArrayMap<>();
+        private final ArrayMap<String, PackageInfo> mPackageInfos = new ArrayMap<>();
 
         /**
          * Apply the cached state
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java
index 70986c3cfbac374bd19d483dc088bda116a51dc6..4c831d36c9e324a631e0e35b9fcd03bef0ded28f 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java
@@ -41,7 +41,8 @@ import java.util.Set;
 public class ParsedPermissionImpl extends ParsedComponentImpl implements ParsedPermission,
         Parcelable {
 
-    private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
+    private static final ForStringSet sForStringSet =
+            Parcelling.Cache.getOrCreate(ForStringSet.class);
 
     @Nullable
     private String backgroundPermission;
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 8240c47a607b6cfa0f05b75e90437be3872af8ce..061698a929d6d707d80a4c823d90abef083ba9d1 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -328,11 +328,11 @@ public class ParsingPackageUtils {
         return input.success(pkg);
     }
 
-    private String[] mSeparateProcesses;
-    private DisplayMetrics mDisplayMetrics;
+    private final String[] mSeparateProcesses;
+    private final DisplayMetrics mDisplayMetrics;
     @NonNull
-    private List<PermissionManager.SplitPermissionInfo> mSplitPermissionInfos;
-    private Callback mCallback;
+    private final List<PermissionManager.SplitPermissionInfo> mSplitPermissionInfos;
+    private final Callback mCallback;
 
     public ParsingPackageUtils(String[] separateProcesses, DisplayMetrics displayMetrics,
             @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
index fac681aaf1c479d285697a2c8ddd557f108d90f7..0ceda421913d825d168bdeead9d2decb5a9d817a 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
@@ -951,7 +951,7 @@ public class ComponentResolver extends ComponentResolverLocked implements
             extends MimeGroupsAwareIntentResolver<Pair<ParsedActivity, ParsedIntentInfo>, ResolveInfo> {
 
         @NonNull
-        private UserNeedsBadgingCache mUserNeedsBadging;
+        private final UserNeedsBadgingCache mUserNeedsBadging;
 
         // Default constructor
         ActivityIntentResolver(@NonNull UserManagerService userManager,
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
index 9115775acd05bb81118a778d6e41a330816a3c86..80cde73ecf1f86b7defeab3147a1a6288d487e3f 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
@@ -68,7 +68,7 @@ public abstract class ComponentResolverBase extends WatchableImpl implements Com
     protected ArrayMap<String, ParsedProvider> mProvidersByAuthority;
 
     @NonNull
-    protected UserManagerService mUserManager;
+    protected final UserManagerService mUserManager;
 
     protected ComponentResolverBase(@NonNull UserManagerService userManager) {
         mUserManager = userManager;
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 11f62e953525b2760242f8de2a3a321dcbb20b96..3d4d4eca7f48776575001ad391feb0db9bccb0be 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -2005,9 +2005,9 @@ public class DomainVerificationService extends SystemService
     private static class GetAttachedResult {
 
         @Nullable
-        private DomainVerificationPkgState mPkgState;
+        private final DomainVerificationPkgState mPkgState;
 
-        private int mErrorCode;
+        private final int mErrorCode;
 
         GetAttachedResult(@Nullable DomainVerificationPkgState pkgState, int errorCode) {
             mPkgState = pkgState;
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
index 6c56360177d84416670552526929d3ccffa1f7ba..d71dbbb5d53b80122bac9c9073edf2db06be98a2 100644
--- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
@@ -40,7 +40,7 @@ public class DomainVerificationPkgState {
     private final String mPackageName;
 
     @NonNull
-    private UUID mId;
+    private final UUID mId;
 
     /**
      * Whether or not the package declares any autoVerify domains. This is separate from an empty
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index c2821aef21421c40ac99655ce0c71fb429862746..b83421fe78d795f17d59d380d10b5b46eb4d6ed1 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -50,12 +50,11 @@ import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.function.HeptFunction;
 import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.NonaFunction;
 import com.android.internal.util.function.QuadFunction;
+import com.android.internal.util.function.QuintConsumer;
 import com.android.internal.util.function.QuintFunction;
-import com.android.internal.util.function.TriConsumer;
-import com.android.internal.util.function.TriFunction;
 import com.android.internal.util.function.UndecFunction;
 import com.android.server.LocalServices;
 
@@ -230,12 +229,10 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
     }
 
     @Override
-    public int checkOperation(int code, AttributionSource attributionSource, boolean raw,
-            TriFunction<Integer, AttributionSource, Boolean, Integer> superImpl) {
-        final int uid = attributionSource.getUid();
-        final AttributionSource resolvedAttributionSource =
-                attributionSource.withUid(resolveUid(code, uid));
-        return superImpl.apply(code, resolvedAttributionSource, raw);
+    public int checkOperation(int code, int uid, String packageName,
+            @Nullable String attributionTag, boolean raw,
+            QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl) {
+        return superImpl.apply(code, resolveUid(code, uid), packageName, attributionTag, raw);
     }
 
     @Override
@@ -245,25 +242,21 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
     }
 
     @Override
-    public SyncNotedAppOp noteOperation(int code, AttributionSource attributionSource,
-            boolean shouldCollectAsyncNotedOp, @Nullable
-             String message, boolean shouldCollectMessage,
-            @NonNull QuintFunction<Integer, AttributionSource, Boolean, String, Boolean,
-                    SyncNotedAppOp> superImpl) {
-        final int uid = attributionSource.getUid();
-        final AttributionSource resolvedAttributionSource =
-                attributionSource.withUid(resolveUid(code, uid));
-        return superImpl.apply(resolveDatasourceOp(code, uid, attributionSource.getPackageName(),
-                attributionSource.getAttributionTag()), resolvedAttributionSource,
-                shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+    public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
+            @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable
+            String message, boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer,
+                    String, String, Boolean, String, Boolean, SyncNotedAppOp> superImpl) {
+        return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag),
+                resolveUid(code, uid), packageName, attributionTag, shouldCollectAsyncNotedOp,
+                message, shouldCollectMessage);
     }
 
     @Override
     public SyncNotedAppOp noteProxyOperation(int code, @NonNull AttributionSource attributionSource,
             boolean shouldCollectAsyncNotedOp, @Nullable String message,
             boolean shouldCollectMessage, boolean skipProxyOperation, @NonNull HexFunction<Integer,
-            AttributionSource, Boolean, String, Boolean, Boolean,
-            SyncNotedAppOp> superImpl) {
+                    AttributionSource, Boolean, String, Boolean, Boolean,
+                    SyncNotedAppOp> superImpl) {
         return superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(),
                 attributionSource.getPackageName(), attributionSource.getAttributionTag()),
                 attributionSource, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
@@ -271,21 +264,17 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
     }
 
     @Override
-    public SyncNotedAppOp startOperation(IBinder token, int code,
-            AttributionSource attributionSource,
+    public SyncNotedAppOp startOperation(IBinder token, int code, int uid,
+            @Nullable String packageName, @Nullable String attributionTag,
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
             boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
-            int attributionChainId,
-            @NonNull NonaFunction<IBinder, Integer, AttributionSource, Boolean, Boolean, String,
-                    Boolean, Integer, Integer,
-                    SyncNotedAppOp> superImpl) {
-        final int uid = attributionSource.getUid();
-        final AttributionSource resolvedAttributionSource =
-                attributionSource.withUid(resolveUid(code, uid));
-        return superImpl.apply(token, resolveDatasourceOp(code, uid,
-                attributionSource.getPackageName(), attributionSource.getAttributionTag()),
-                resolvedAttributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage, attributionFlags, attributionChainId);
+            int attributionChainId, @NonNull UndecFunction<IBinder, Integer, Integer, String,
+                    String, Boolean, Boolean, String, Boolean, Integer, Integer,
+            SyncNotedAppOp> superImpl) {
+        return superImpl.apply(token, resolveDatasourceOp(code, uid, packageName, attributionTag),
+                resolveUid(code, uid), packageName, attributionTag, startIfModeDefault,
+                shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
+                attributionChainId);
     }
 
     @Override
@@ -304,14 +293,11 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
     }
 
     @Override
-    public void finishOperation(IBinder clientId, int code, AttributionSource attributionSource,
-            @NonNull TriConsumer<IBinder, Integer, AttributionSource> superImpl) {
-        final int uid = attributionSource.getUid();
-        final AttributionSource resolvedAttributionSource =
-                attributionSource.withUid(resolveUid(code, uid));
-        superImpl.accept(clientId, resolveDatasourceOp(code, uid,
-                 attributionSource.getPackageName(), attributionSource.getAttributionTag()),
-                 resolvedAttributionSource);
+    public void finishOperation(IBinder clientId, int code, int uid, String packageName,
+            String attributionTag,
+            @NonNull QuintConsumer<IBinder, Integer, Integer, String, String> superImpl) {
+        superImpl.accept(clientId, resolveDatasourceOp(code, uid, packageName, attributionTag),
+                resolveUid(code, uid), packageName, attributionTag);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 45ca690ba20fcd77c6cba194ce0d813acd8b4be2..cf1036c03c83d537ebb277b4c576c3485f8d3d0a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -549,6 +549,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
     int mLidNavigationAccessibility;
     int mShortPressOnPowerBehavior;
     private boolean mShouldEarlyShortPressOnPower;
+    private boolean mShouldEarlyShortPressOnStemPrimary;
     int mLongPressOnPowerBehavior;
     long mLongPressOnPowerAssistantTimeoutMs;
     int mVeryLongPressOnPowerBehavior;
@@ -2748,6 +2749,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
 
         @Override
         void onPress(long downTime) {
+            if (mShouldEarlyShortPressOnStemPrimary) {
+                return;
+            }
             stemPrimaryPress(1 /*count*/);
         }
 
@@ -2760,6 +2764,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
         void onMultiPress(long downTime, int count) {
             stemPrimaryPress(count);
         }
+
+        @Override
+        void onKeyUp(long eventTime, int count) {
+            if (mShouldEarlyShortPressOnStemPrimary && count == 1) {
+                stemPrimaryPress(1 /*pressCount*/);
+            }
+        }
     }
 
     private void initSingleKeyGestureRules(Looper looper) {
@@ -2929,6 +2940,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
             mShouldEarlyShortPressOnPower =
                     mContext.getResources()
                             .getBoolean(com.android.internal.R.bool.config_shortPressEarlyOnPower);
+            mShouldEarlyShortPressOnStemPrimary = mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_shortPressEarlyOnStemPrimary);
 
             mStylusButtonsEnabled = Settings.Secure.getIntForUser(resolver,
                     Secure.STYLUS_BUTTONS_ENABLED, 1, UserHandle.USER_CURRENT) == 1;
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 3dbd2f88385eadee3630e6abf79cbf1847460cab..6a0fe90a1e650a464fc4946c2f6bf872484b8145 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -395,7 +395,8 @@ public class PowerGroup {
 
     @VisibleForTesting
     int getDesiredScreenPolicyLocked(boolean quiescent, boolean dozeAfterScreenOff,
-            boolean bootCompleted, boolean screenBrightnessBoostInProgress) {
+            boolean bootCompleted, boolean screenBrightnessBoostInProgress,
+            boolean brightWhenDozing) {
         final int wakefulness = getWakefulnessLocked();
         final int wakeLockSummary = getWakeLockSummaryLocked();
         if (wakefulness == WAKEFULNESS_ASLEEP || quiescent) {
@@ -407,8 +408,12 @@ public class PowerGroup {
             if (dozeAfterScreenOff) {
                 return DisplayPowerRequest.POLICY_OFF;
             }
+            if (brightWhenDozing) {
+                return DisplayPowerRequest.POLICY_BRIGHT;
+            }
             // Fall through and preserve the current screen policy if not configured to
-            // doze after screen off.  This causes the screen off transition to be skipped.
+            // bright when dozing or doze after screen off.  This causes the screen off transition
+            // to be skipped.
         }
 
         if ((wakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
@@ -429,9 +434,10 @@ public class PowerGroup {
             boolean boostScreenBrightness, int dozeScreenState, float dozeScreenBrightness,
             boolean overrideDrawWakeLock, PowerSaveState powerSaverState, boolean quiescent,
             boolean dozeAfterScreenOff, boolean bootCompleted,
-            boolean screenBrightnessBoostInProgress, boolean waitForNegativeProximity) {
+            boolean screenBrightnessBoostInProgress, boolean waitForNegativeProximity,
+            boolean brightWhenDozing) {
         mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(quiescent, dozeAfterScreenOff,
-                bootCompleted, screenBrightnessBoostInProgress);
+                bootCompleted, screenBrightnessBoostInProgress, brightWhenDozing);
         mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
         mDisplayPowerRequest.useProximitySensor = useProximitySensor;
         mDisplayPowerRequest.boostScreenBrightness = boostScreenBrightness;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 8ce0c72101db95eaf3b252253c430cc8ffae4e0a..ec5172fca5fa3276f1f7604b215f2d6641d24054 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -546,6 +546,10 @@ public final class PowerManagerService extends SystemService
     // True if doze should not be started until after the screen off transition.
     private boolean mDozeAfterScreenOff;
 
+    // True if bright policy should be applied when we have entered dozing wakefulness but haven't
+    // started doze component.
+    private boolean mBrightWhenDozingConfig;
+
     // The minimum screen off timeout, in milliseconds.
     private long mMinimumScreenOffTimeoutConfig;
 
@@ -1492,6 +1496,8 @@ public final class PowerManagerService extends SystemService
                 com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
         mDozeAfterScreenOff = resources.getBoolean(
                 com.android.internal.R.bool.config_dozeAfterScreenOffByDefault);
+        mBrightWhenDozingConfig = resources.getBoolean(
+                com.android.internal.R.bool.config_brightWhenDozing);
         mMinimumScreenOffTimeoutConfig = resources.getInteger(
                 com.android.internal.R.integer.config_minimumScreenOffTimeout);
         mMaximumScreenDimDurationConfig = resources.getInteger(
@@ -3560,7 +3566,8 @@ public final class PowerManagerService extends SystemService
                                         .getBatterySaverPolicy(ServiceType.SCREEN_BRIGHTNESS)
                                 : new PowerSaveState.Builder().build(),
                         sQuiescent, mDozeAfterScreenOff, mBootCompleted,
-                        mScreenBrightnessBoostInProgress, mRequestWaitForNegativeProximity);
+                        mScreenBrightnessBoostInProgress, mRequestWaitForNegativeProximity,
+                        mBrightWhenDozingConfig);
                 int wakefulness = powerGroup.getWakefulnessLocked();
                 if (DEBUG_SPEW) {
                     Slog.d(TAG, "updateDisplayPowerStateLocked: displayReady=" + ready
@@ -3635,7 +3642,7 @@ public final class PowerManagerService extends SystemService
     int getDesiredScreenPolicyLocked(int groupId) {
         return mPowerGroups.get(groupId).getDesiredScreenPolicyLocked(sQuiescent,
                 mDozeAfterScreenOff, mBootCompleted,
-                mScreenBrightnessBoostInProgress);
+                mScreenBrightnessBoostInProgress, mBrightWhenDozingConfig);
     }
 
     @VisibleForTesting
@@ -4655,6 +4662,7 @@ public final class PowerManagerService extends SystemService
             pw.println("  mDreamsActivateOnSleepSetting=" + mDreamsActivateOnSleepSetting);
             pw.println("  mDreamsActivateOnDockSetting=" + mDreamsActivateOnDockSetting);
             pw.println("  mDozeAfterScreenOff=" + mDozeAfterScreenOff);
+            pw.println("  mBrightWhenDozingConfig=" + mBrightWhenDozingConfig);
             pw.println("  mMinimumScreenOffTimeoutConfig=" + mMinimumScreenOffTimeoutConfig);
             pw.println("  mMaximumScreenDimDurationConfig=" + mMaximumScreenDimDurationConfig);
             pw.println("  mMaximumScreenDimRatioConfig=" + mMaximumScreenDimRatioConfig);
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index a388932cb708173ea622a3c208ac4b1638722563..b8e581f32a5832b4af5f1af2e5c2032aa64b5570 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -592,6 +592,10 @@ public class CpuPowerStatsCollector extends PowerStatsCollector {
     public void dumpCpuPowerBracketsLocked(PrintWriter pw) {
         ensureInitialized();
 
+        if (mLayout == null) {
+            return;
+        }
+
         pw.println("CPU power brackets; cluster/freq in MHz(avg current in mA):");
         for (int bracket = 0; bracket < mLayout.getCpuPowerBracketCount(); bracket++) {
             pw.print("    ");
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index d88f4268434b1ee24ecd7b8d8b8dfe9d21e0da5d..83270f6b7b56af9af6748b5411af1b12bcba20f1 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -19,7 +19,6 @@ package com.android.server.timedetector;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.app.ActivityManager;
 import android.app.time.ExternalTimeSuggestion;
 import android.app.time.ITimeDetectorListener;
 import android.app.time.TimeCapabilitiesAndConfig;
@@ -30,7 +29,6 @@ import android.app.timedetector.ITimeDetectorService;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.TelephonyTimeSuggestion;
 import android.content.Context;
-import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.ParcelableException;
@@ -166,8 +164,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub
      */
     boolean updateConfiguration(@UserIdInt int userId, @NonNull TimeConfiguration configuration) {
         // Resolve constants like USER_CURRENT to the true user ID as needed.
-        int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
-                Binder.getCallingUid(), userId, false, false, "updateConfiguration", null);
+        int resolvedUserId = mCallerIdentityInjector.resolveUserId(userId, "updateConfiguration");
 
         enforceManageTimeDetectorPermission();
 
@@ -280,11 +277,11 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub
     public TimeState getTimeState() {
         enforceManageTimeDetectorPermission();
 
-        final long token = Binder.clearCallingIdentity();
+        final long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
             return mTimeDetectorStrategy.getTimeState();
         } finally {
-            Binder.restoreCallingIdentity(token);
+            mCallerIdentityInjector.restoreCallingIdentity(token);
         }
     }
 
@@ -295,11 +292,11 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub
     void setTimeState(@NonNull TimeState timeState) {
         enforceManageTimeDetectorPermission();
 
-        final long token = Binder.clearCallingIdentity();
+        final long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
             mTimeDetectorStrategy.setTimeState(timeState);
         } finally {
-            Binder.restoreCallingIdentity(token);
+            mCallerIdentityInjector.restoreCallingIdentity(token);
         }
     }
 
@@ -308,11 +305,11 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub
         enforceManageTimeDetectorPermission();
         Objects.requireNonNull(time);
 
-        final long token = Binder.clearCallingIdentity();
+        final long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
             return mTimeDetectorStrategy.confirmTime(time);
         } finally {
-            Binder.restoreCallingIdentity(token);
+            mCallerIdentityInjector.restoreCallingIdentity(token);
         }
     }
 
@@ -324,13 +321,13 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub
         // This calls suggestManualTime() as the logic is identical, it only differs in the
         // permission required, which is handled on the line above.
         int userId = mCallerIdentityInjector.getCallingUserId();
-        final long token = Binder.clearCallingIdentity();
+        final long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
             final boolean bypassUserPolicyChecks = false;
             return mTimeDetectorStrategy.suggestManualTime(
                     userId, suggestion, bypassUserPolicyChecks);
         } finally {
-            Binder.restoreCallingIdentity(token);
+            mCallerIdentityInjector.restoreCallingIdentity(token);
         }
     }
 
@@ -377,11 +374,11 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub
     void clearLatestNetworkTime() {
         enforceSuggestNetworkTimePermission();
 
-        final long token = Binder.clearCallingIdentity();
+        final long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
             mTimeDetectorStrategy.clearLatestNetworkSuggestion();
         } finally {
-            Binder.restoreCallingIdentity(token);
+            mCallerIdentityInjector.restoreCallingIdentity(token);
         }
     }
 
@@ -473,7 +470,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub
     void clearNetworkTimeForSystemClockForTests() {
         enforceSuggestNetworkTimePermission();
 
-        final long token = Binder.clearCallingIdentity();
+        final long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
             // TODO(b/222295093): Remove this condition once we can be sure that all uses of
             //  NtpTrustedTime result in a suggestion being made to the time detector.
@@ -485,7 +482,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub
                 mNtpTrustedTime.clearCachedTimeResult();
             }
         } finally {
-            Binder.restoreCallingIdentity(token);
+            mCallerIdentityInjector.restoreCallingIdentity(token);
         }
     }
 
diff --git a/services/core/java/com/android/server/timezonedetector/CallerIdentityInjector.java b/services/core/java/com/android/server/timezonedetector/CallerIdentityInjector.java
index 1500cfaeb3a24df4b046418375ac1efb77375e38..1f1d83fefc593d5085efa0d49ac7b59860f9f7b1 100644
--- a/services/core/java/com/android/server/timezonedetector/CallerIdentityInjector.java
+++ b/services/core/java/com/android/server/timezonedetector/CallerIdentityInjector.java
@@ -17,6 +17,7 @@
 package com.android.server.timezonedetector;
 
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.os.Binder;
 import android.os.UserHandle;
 
@@ -29,6 +30,12 @@ public interface CallerIdentityInjector {
     /** A singleton for the real implementation of {@link CallerIdentityInjector}. */
     CallerIdentityInjector REAL = new Real();
 
+    /**
+     * A {@link ActivityManager#handleIncomingUser} call. This can be used to map the abstract
+     * user ID value USER_CURRENT to the actual user ID.
+     */
+    @UserIdInt int resolveUserId(@UserIdInt int userId, String debugInfo);
+
     /** A {@link UserHandle#getCallingUserId()} call. */
     @UserIdInt int getCallingUserId();
 
@@ -44,6 +51,12 @@ public interface CallerIdentityInjector {
         protected Real() {
         }
 
+        @Override
+        public int resolveUserId(@UserIdInt int userId, String debugName) {
+            return ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, false, false, debugName, null);
+        }
+
         @Override
         public int getCallingUserId() {
             return UserHandle.getCallingUserId();
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index dac4bf8cb6e6d6b6636a312c6a17c68ebaf0a89a..d914544566ff7b47efd973c9ab807290516ca34e 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -19,7 +19,6 @@ package com.android.server.timezonedetector;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.app.ActivityManager;
 import android.app.time.ITimeZoneDetectorListener;
 import android.app.time.TimeZoneCapabilitiesAndConfig;
 import android.app.time.TimeZoneConfiguration;
@@ -28,7 +27,6 @@ import android.app.timezonedetector.ITimeZoneDetectorService;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.content.Context;
-import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -168,8 +166,8 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
         enforceManageTimeZoneDetectorPermission();
 
         // Resolve constants like USER_CURRENT to the true user ID as needed.
-        int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
-                Binder.getCallingUid(), userId, false, false, "getCapabilitiesAndConfig", null);
+        int resolvedUserId =
+                mCallerIdentityInjector.resolveUserId(userId, "getCapabilitiesAndConfig");
 
         final long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
@@ -190,8 +188,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
     boolean updateConfiguration(
             @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration) {
         // Resolve constants like USER_CURRENT to the true user ID as needed.
-        int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
-                Binder.getCallingUid(), userId, false, false, "updateConfiguration", null);
+        int resolvedUserId = mCallerIdentityInjector.resolveUserId(userId, "updateConfiguration");
 
         enforceManageTimeZoneDetectorPermission();
 
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 52ab9b855c7c134456745857f7c5d7f52fe63635..86be6ba5e6ad8568a0d4f55f5a1d11ef6041584f 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -237,22 +237,8 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord
             }
             if (containsFile(code, userId)) {
                 synchronized (mSnapshotPersistQueue.getLock()) {
-                    mSnapshotPersistQueue.sendToQueueLocked(
-                            new SnapshotPersistQueue.WriteQueueItem(mPersistInfoProvider) {
-                                @Override
-                                void write() {
-                                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
-                                            "load_activity_snapshot");
-                                    final TaskSnapshot snapshot = mSnapshotLoader.loadTask(code,
-                                            userId, false /* loadLowResolutionBitmap */);
-                                    synchronized (mService.getWindowManagerLock()) {
-                                        if (snapshot != null && !ar.finishing) {
-                                            mCache.putSnapshot(ar, snapshot);
-                                        }
-                                    }
-                                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-                                }
-                            });
+                    mSnapshotPersistQueue.insertQueueAtFirstLocked(
+                            new LoadActivitySnapshotItem(ar, code, userId, mPersistInfoProvider));
                 }
             }
         }
@@ -273,6 +259,42 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord
         resetTmpFields();
     }
 
+    class LoadActivitySnapshotItem extends SnapshotPersistQueue.WriteQueueItem {
+        private final int mCode;
+        private final int mUserId;
+        private final ActivityRecord mActivityRecord;
+
+        LoadActivitySnapshotItem(@NonNull ActivityRecord ar, int code, int userId,
+                @NonNull PersistInfoProvider persistInfoProvider) {
+            super(persistInfoProvider);
+            mActivityRecord = ar;
+            mCode = code;
+            mUserId = userId;
+        }
+
+        @Override
+        void write() {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+                    "load_activity_snapshot");
+            final TaskSnapshot snapshot = mSnapshotLoader.loadTask(mCode,
+                    mUserId, false /* loadLowResolutionBitmap */);
+            synchronized (mService.getWindowManagerLock()) {
+                if (snapshot != null && !mActivityRecord.finishing) {
+                    mCache.putSnapshot(mActivityRecord, snapshot);
+                }
+            }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null || getClass() != o.getClass()) return false;
+            final LoadActivitySnapshotItem other = (LoadActivitySnapshotItem) o;
+            return mCode == other.mCode && mUserId == other.mUserId
+                    && mPersistInfoProvider == other.mPersistInfoProvider;
+        }
+    }
+
     void recordSnapshot(ActivityRecord activity) {
         if (shouldDisableSnapshots()) {
             return;
@@ -571,7 +593,8 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord
             for (int i = savedFileCount - 1; i > removeTillIndex; --i) {
                 final UserSavedFile usf = mSavedFilesInOrder.remove(i);
                 if (usf != null) {
-                    mUserSavedFiles.remove(usf.mFileId);
+                    final SparseArray<UserSavedFile> records = getUserFiles(usf.mUserId);
+                    records.remove(usf.mFileId);
                     usfs.add(usf);
                 }
             }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a76fe287f31333ea5b521d383154faf21cc6caa9..3c56a4e5eb0488127db7235be5473f49958804f9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3859,10 +3859,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
                     Slog.w(TAG, "takeTaskSnapshot: taskId=" + taskId + " not found or not visible");
                     return null;
                 }
+                // Note that if updateCache is true, ActivityRecord#shouldUseAppThemeSnapshot will
+                // be used to decide whether the task is allowed to be captured because that may
+                // be retrieved by recents. While if updateCache is false, the real snapshot will
+                // always be taken and the snapshot won't be put into SnapshotPersister.
                 if (updateCache) {
                     return mWindowManager.mTaskSnapshotController.recordSnapshot(task);
                 } else {
-                    return mWindowManager.mTaskSnapshotController.captureSnapshot(task);
+                    return mWindowManager.mTaskSnapshotController.snapshot(task);
                 }
             }
         } finally {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index d69c5ef5ee9191f8cea5441d0a52a70d61470672..8a7cc67c3660e1471d0ec0d306e869b6790a6907 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1876,7 +1876,8 @@ public class DisplayPolicy {
                 dc.getDisplayPolicy().simulateLayoutDisplay(df);
                 final InsetsState insetsState = df.mInsetsState;
                 final Rect displayFrame = insetsState.getDisplayFrame();
-                final Insets decor = insetsState.calculateInsets(displayFrame, DECOR_TYPES,
+                final Insets decor = insetsState.calculateInsets(displayFrame,
+                        dc.mWmService.mDecorTypes,
                         true /* ignoreVisibility */);
                 final Insets statusBar = insetsState.calculateInsets(displayFrame,
                         Type.statusBars(), true /* ignoreVisibility */);
@@ -1912,15 +1913,6 @@ public class DisplayPolicy {
             }
         }
 
-
-        static final int DECOR_TYPES = Type.displayCutout() | Type.navigationBars();
-
-        /**
-         * The types that may affect display configuration. This excludes cutout because it is
-         * known from display info.
-         */
-        static final int CONFIG_TYPES = Type.statusBars() | Type.navigationBars();
-
         private final DisplayContent mDisplayContent;
         private final Info[] mInfoForRotation = new Info[4];
         final Info mTmpInfo = new Info();
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index cd114fcf9e217a4c52e83f3e36f7ab12c10885fd..9d5ddf3bf264e2d3ab0b8b78d7e66b18742eebab 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -30,6 +30,7 @@ import static com.android.server.wm.InsetsSourceProviderProto.PENDING_CONTROL_TA
 import static com.android.server.wm.InsetsSourceProviderProto.SEAMLESS_ROTATING;
 import static com.android.server.wm.InsetsSourceProviderProto.SERVER_VISIBLE;
 import static com.android.server.wm.InsetsSourceProviderProto.SOURCE;
+import static com.android.server.wm.InsetsSourceProviderProto.SOURCE_WINDOW_STATE;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_INSETS_CONTROL;
 
 import android.annotation.NonNull;
@@ -304,7 +305,7 @@ class InsetsSourceProvider {
             return mInsetsHint;
         }
         final WindowState win = mWindowContainer.asWindowState();
-        if (win != null && win.mGivenInsetsPending) {
+        if (win != null && win.mGivenInsetsPending && win.mAttrs.providedInsets == null) {
             return mInsetsHint;
         }
         if (mInsetsHintStale) {
@@ -680,6 +681,9 @@ class InsetsSourceProvider {
         proto.write(SERVER_VISIBLE, mServerVisible);
         proto.write(SEAMLESS_ROTATING, mSeamlessRotating);
         proto.write(CONTROLLABLE, mControllable);
+        if (mWindowContainer != null && mWindowContainer.asWindowState() != null) {
+            mWindowContainer.asWindowState().dumpDebug(proto, SOURCE_WINDOW_STATE, logLevel);
+        }
         proto.end(token);
     }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index e82f3221f0a45c4fb685f372295b6abe037a9048..5227a52545f4f49de2e11d39fcb84aeb87fb5aa1 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2095,6 +2095,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
             }
 
             final TaskFragment organizedTf = r.getOrganizedTaskFragment();
+            final TaskFragment taskFragment = r.getTaskFragment();
             final boolean singleActivity = task.getNonFinishingActivityCount() == 1;
             if (singleActivity) {
                 rootTask = task;
@@ -2137,7 +2138,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
                         .setIntent(r.intent)
                         .setDeferTaskAppear(true)
                         .setHasBeenVisible(true)
-                        .setWindowingMode(task.getRequestedOverrideWindowingMode())
+                        // In case the activity is in system split screen, or Activity Embedding
+                        // split, we need to animate the PIP Task from the original TaskFragment
+                        // bounds, so also setting the windowing mode, otherwise the bounds may
+                        // be reset to fullscreen.
+                        .setWindowingMode(taskFragment.getWindowingMode())
                         .build();
                 // Establish bi-directional link between the original and pinned task.
                 r.setLastParentBeforePip(launchIntoPipHostActivity);
@@ -2150,7 +2155,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
                 // current bounds.
                 // Use Task#setBoundsUnchecked to skip checking windowing mode as the windowing mode
                 // will be updated later after this is collected in transition.
-                rootTask.setBoundsUnchecked(r.getTaskFragment().getBounds());
+                rootTask.setBoundsUnchecked(taskFragment.getBounds());
 
                 // Move the last recents animation transaction from original task to the new one.
                 if (task.mLastRecentsAnimationTransaction != null) {
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index f4f641f35395eb2c88f6defa9f33c5683890cdfb..bffdf54e17ce0982f0318952c5b94771e8a9550d 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -23,7 +23,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.NonNull;
-import android.annotation.TestApi;
 import android.graphics.Bitmap;
 import android.os.Process;
 import android.os.SystemClock;
@@ -33,6 +32,7 @@ import android.util.Slog;
 import android.window.TaskSnapshot;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
@@ -100,7 +100,7 @@ class SnapshotPersistQueue {
         }
     }
 
-    @TestApi
+    @VisibleForTesting
     void waitForQueueEmpty() {
         while (true) {
             synchronized (mLock) {
@@ -112,9 +112,20 @@ class SnapshotPersistQueue {
         }
     }
 
-    @GuardedBy("mLock")
-    void sendToQueueLocked(WriteQueueItem item) {
-        mWriteQueue.offer(item);
+    @VisibleForTesting
+    int peekQueueSize() {
+        synchronized (mLock) {
+            return mWriteQueue.size();
+        }
+    }
+
+    private void addToQueueInternal(WriteQueueItem item, boolean insertToFront) {
+        mWriteQueue.removeFirstOccurrence(item);
+        if (insertToFront) {
+            mWriteQueue.addFirst(item);
+        } else {
+            mWriteQueue.addLast(item);
+        }
         item.onQueuedLocked();
         ensureStoreQueueDepthLocked();
         if (!mPaused) {
@@ -122,6 +133,16 @@ class SnapshotPersistQueue {
         }
     }
 
+    @GuardedBy("mLock")
+    void sendToQueueLocked(WriteQueueItem item) {
+        addToQueueInternal(item, false /* insertToFront */);
+    }
+
+    @GuardedBy("mLock")
+    void insertQueueAtFirstLocked(WriteQueueItem item) {
+        addToQueueInternal(item, true /* insertToFront */);
+    }
+
     @GuardedBy("mLock")
     private void ensureStoreQueueDepthLocked() {
         while (mStoreQueueItems.size() > MAX_STORE_QUEUE_DEPTH) {
@@ -235,6 +256,8 @@ class SnapshotPersistQueue {
         @GuardedBy("mLock")
         @Override
         void onQueuedLocked() {
+            // Remove duplicate request.
+            mStoreQueueItems.remove(this);
             mStoreQueueItems.offer(this);
         }
 
@@ -358,6 +381,14 @@ class SnapshotPersistQueue {
 
             return true;
         }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null || getClass() != o.getClass()) return false;
+            final StoreWriteQueueItem other = (StoreWriteQueueItem) o;
+            return mId == other.mId && mUserId == other.mUserId
+                    && mPersistInfoProvider == other.mPersistInfoProvider;
+        }
     }
 
     DeleteWriteQueueItem createDeleteWriteQueueItem(int id, int userId,
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 2f52de4cb765ee9bc29621c53037573cde3dd2fb..197edc30aa8e7ef96256e29877bd2f3d7b61a055 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1535,10 +1535,11 @@ class TaskFragment extends WindowContainer<WindowContainer> {
                         next.getTask().mTaskId, next.shortComponentName);
 
                 mAtmService.getAppWarningsLocked().onResumeActivity(next);
-                next.app.setPendingUiCleanAndForceProcessStateUpTo(mAtmService.mTopProcessState);
+                final int topProcessState = mAtmService.mTopProcessState;
+                next.app.setPendingUiCleanAndForceProcessStateUpTo(topProcessState);
                 next.abortAndClearOptionsAnimation();
                 transaction.setLifecycleStateRequest(
-                        ResumeActivityItem.obtain(next.token, next.app.getReportedProcState(),
+                        ResumeActivityItem.obtain(next.token, topProcessState,
                                 dc.isNextTransitionForward(), next.shouldSendCompatFakeFocus()));
                 mAtmService.getLifecycleManager().scheduleTransaction(transaction);
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index b23ffa8b203e6ff00d604729f05ab76212458e96..c0bf2ce414358ee77bd04569c3ac724ece894c96 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -59,6 +59,7 @@ 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.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
@@ -1272,18 +1273,23 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
             }
         }
         // Commit wallpaper visibility after activity, because usually the wallpaper target token is
-        // an activity, and wallpaper's visibility is depends on activity's visibility.
+        // an activity, and wallpaper's visibility depends on activity's visibility.
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
             final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
             if (wt == null) continue;
             final WindowState target = wt.mDisplayContent.mWallpaperController.getWallpaperTarget();
             final boolean isTargetInvisible = target == null || !target.mToken.isVisible();
-            if (isTargetInvisible || (!wt.isVisibleRequested()
-                    && !mVisibleAtTransitionEndTokens.contains(wt))) {
+            final boolean isWallpaperVisibleAtEnd =
+                    wt.isVisibleRequested() || mVisibleAtTransitionEndTokens.contains(wt);
+            if (isTargetInvisible || !isWallpaperVisibleAtEnd) {
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                         "  Commit wallpaper becoming invisible: %s", wt);
                 wt.commitVisibility(false /* visible */);
             }
+            if (isTargetInvisible) {
+                // Our original target went invisible, so we should look for a new target.
+                wt.mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+            }
         }
         if (committedSomeInvisible) {
             mController.onCommittedInvisibles();
@@ -1408,12 +1414,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
                     false /* forceRelayout */);
         }
         cleanUpInternal();
-        mController.updateAnimatingState();
 
         // Handle back animation if it's already started.
         mController.mAtm.mBackNavigationController.onTransitionFinish(mTargets, this);
         mController.mFinishingTransition = null;
         mController.mSnapshotController.onTransitionFinish(mType, mTargets);
+        // Resume snapshot persist thread after snapshot controller analysis this transition.
+        mController.updateAnimatingState();
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6e3d24b9f7113d5dd5c6c1b728d234e99f964429..ab3ddbd9422459b7e9307cfd117928b982ba377b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -336,6 +336,7 @@ import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
 import com.android.server.power.ShutdownThread;
 import com.android.server.utils.PriorityDump;
+import com.android.window.flags.Flags;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -542,6 +543,12 @@ public class WindowManagerService extends IWindowManager.Stub
     @VisibleForTesting
     boolean mSkipActivityRelaunchWhenDocking;
 
+    /** Device default insets types provided non-decor insets. */
+    final int mDecorTypes;
+
+    /** Device default insets types shall be excluded from config app sizes. */
+    final int mConfigTypes;
+
     final boolean mLimitedAlphaCompositing;
     final int mMaxUiWidth;
 
@@ -1185,6 +1192,16 @@ public class WindowManagerService extends IWindowManager.Stub
                 com.android.internal.R.bool.config_assistantOnTopOfDream);
         mSkipActivityRelaunchWhenDocking = context.getResources()
                 .getBoolean(R.bool.config_skipActivityRelaunchWhenDocking);
+        final boolean isScreenSizeDecoupledFromStatusBarAndCutout = context.getResources()
+                .getBoolean(R.bool.config_decoupleStatusBarAndDisplayCutoutFromScreenSize)
+                && Flags.closeToSquareConfigIncludesStatusBar();
+        if (!isScreenSizeDecoupledFromStatusBarAndCutout) {
+            mDecorTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars();
+            mConfigTypes = WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars();
+        } else {
+            mDecorTypes = WindowInsets.Type.navigationBars();
+            mConfigTypes = WindowInsets.Type.navigationBars();
+        }
 
         mLetterboxConfiguration = new LetterboxConfiguration(
                 // Using SysUI context to have access to Material colors extracted from Wallpaper.
@@ -7319,6 +7336,7 @@ public class WindowManagerService extends IWindowManager.Stub
         }
     }
 
+    /** This is used when there's no app info available and shall return the system default.*/
     void getStableInsetsLocked(int displayId, Rect outInsets) {
         outInsets.setEmpty();
         final DisplayContent dc = mRoot.getDisplayContent(displayId);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ae2df00007f456294cafd89d6e090fd577d925aa..4e17011c71414a393afc9f9f77359188b47990ba 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1381,7 +1381,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
             // This window doesn't provide any insets.
             return;
         }
-        if (mGivenInsetsPending) {
+        if (mGivenInsetsPending && mAttrs.providedInsets == null) {
             // The given insets are pending, and they are not reliable for now. The source frame
             // should be updated after the new given insets are sent to window manager.
             return;
@@ -1829,7 +1829,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
         }
         for (int i = mInsetsSourceProviders.size() - 1; i >= 0; i--) {
             final InsetsSource source = mInsetsSourceProviders.valueAt(i).getSource();
-            if ((source.getType() & DisplayPolicy.DecorInsets.CONFIG_TYPES) != 0) {
+            if ((source.getType() & mWmService.mConfigTypes) != 0) {
                 return true;
             }
         }
diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
index dc05462c1a1c9408982412b3fd97db39130dd13e..80427b346f1a4e26ae865d1276cbfa8063683b0a 100644
--- a/services/core/jni/tvinput/JTvInputHal.cpp
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -25,7 +25,7 @@ JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, std::shared_ptr<ITvInputWrap
     mThiz = env->NewWeakGlobalRef(thiz);
     mTvInput = tvInput;
     mLooper = looper;
-    mTvInputCallback = ::ndk::SharedRefBase::make<TvInputCallback>(this);
+    mTvInputCallback = std::shared_ptr<TvInputCallbackWrapper>(new TvInputCallbackWrapper(this));
     mTvInput->setCallback(mTvInputCallback);
 }
 
@@ -443,18 +443,23 @@ void JTvInputHal::NotifyTvMessageHandler::handleMessage(const Message& message)
     }
 }
 
-JTvInputHal::TvInputCallback::TvInputCallback(JTvInputHal* hal) {
+JTvInputHal::TvInputCallbackWrapper::TvInputCallbackWrapper(JTvInputHal* hal) {
+    aidlTvInputCallback = ::ndk::SharedRefBase::make<AidlTvInputCallback>(hal);
+    hidlTvInputCallback = sp<HidlTvInputCallback>::make(hal);
+}
+
+JTvInputHal::AidlTvInputCallback::AidlTvInputCallback(JTvInputHal* hal) {
     mHal = hal;
 }
 
-::ndk::ScopedAStatus JTvInputHal::TvInputCallback::notify(const AidlTvInputEvent& event) {
+::ndk::ScopedAStatus JTvInputHal::AidlTvInputCallback::notify(const AidlTvInputEvent& event) {
     mHal->mLooper->sendMessage(new NotifyHandler(mHal,
                                                  TvInputEventWrapper::createEventWrapper(event)),
                                static_cast<int>(event.type));
     return ::ndk::ScopedAStatus::ok();
 }
 
-::ndk::ScopedAStatus JTvInputHal::TvInputCallback::notifyTvMessageEvent(
+::ndk::ScopedAStatus JTvInputHal::AidlTvInputCallback::notifyTvMessageEvent(
         const AidlTvMessageEvent& event) {
     const std::string DEVICE_ID_SUBTYPE = "device_id";
     ::ndk::ScopedAStatus status = ::ndk::ScopedAStatus::ok();
@@ -487,11 +492,14 @@ JTvInputHal::ITvInputWrapper::ITvInputWrapper(std::shared_ptr<AidlITvInput>& aid
       : mIsHidl(false), mAidlTvInput(aidlTvInput) {}
 
 ::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::setCallback(
-        const std::shared_ptr<TvInputCallback>& in_callback) {
+        const std::shared_ptr<TvInputCallbackWrapper>& in_callback) {
     if (mIsHidl) {
-        return hidlSetCallback(in_callback);
+        in_callback->aidlTvInputCallback = nullptr;
+        return hidlSetCallback(in_callback == nullptr ? nullptr : in_callback->hidlTvInputCallback);
     } else {
-        return mAidlTvInput->setCallback(in_callback);
+        in_callback->hidlTvInputCallback = nullptr;
+        return mAidlTvInput->setCallback(in_callback == nullptr ? nullptr
+                                                                : in_callback->aidlTvInputCallback);
     }
 }
 
diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h
index 6026a107c67fd4d5915f47c87d5dde9e4968b0f1..2ef94ac4a3b0954083253b1a4316862c96c55c63 100644
--- a/services/core/jni/tvinput/JTvInputHal.h
+++ b/services/core/jni/tvinput/JTvInputHal.h
@@ -168,23 +168,39 @@ private:
         JTvInputHal* mHal;
     };
 
-    class TvInputCallback : public HidlITvInputCallback, public BnTvInputCallback {
+    class AidlTvInputCallback : public BnTvInputCallback {
     public:
-        explicit TvInputCallback(JTvInputHal* hal);
+        explicit AidlTvInputCallback(JTvInputHal* hal);
         ::ndk::ScopedAStatus notify(const AidlTvInputEvent& event) override;
         ::ndk::ScopedAStatus notifyTvMessageEvent(const AidlTvMessageEvent& event) override;
+
+    private:
+        JTvInputHal* mHal;
+    };
+
+    class HidlTvInputCallback : public HidlITvInputCallback {
+    public:
+        explicit HidlTvInputCallback(JTvInputHal* hal);
         Return<void> notify(const HidlTvInputEvent& event) override;
 
     private:
         JTvInputHal* mHal;
     };
 
+    class TvInputCallbackWrapper {
+    public:
+        explicit TvInputCallbackWrapper(JTvInputHal* hal);
+        std::shared_ptr<AidlTvInputCallback> aidlTvInputCallback;
+        sp<HidlTvInputCallback> hidlTvInputCallback;
+    };
+
     class ITvInputWrapper {
     public:
         ITvInputWrapper(std::shared_ptr<AidlITvInput>& aidlTvInput);
         ITvInputWrapper(sp<HidlITvInput>& hidlTvInput);
 
-        ::ndk::ScopedAStatus setCallback(const std::shared_ptr<TvInputCallback>& in_callback);
+        ::ndk::ScopedAStatus setCallback(
+                const std::shared_ptr<TvInputCallbackWrapper>& in_callback);
         ::ndk::ScopedAStatus getStreamConfigurations(int32_t in_deviceId,
                                                      std::vector<AidlTvStreamConfig>* _aidl_return);
         ::ndk::ScopedAStatus openStream(int32_t in_deviceId, int32_t in_streamId,
@@ -198,7 +214,7 @@ private:
         ::ndk::ScopedAStatus getAidlInterfaceVersion(int32_t* _aidl_return);
 
     private:
-        ::ndk::ScopedAStatus hidlSetCallback(const std::shared_ptr<TvInputCallback>& in_callback);
+        ::ndk::ScopedAStatus hidlSetCallback(const sp<HidlTvInputCallback>& in_callback);
         ::ndk::ScopedAStatus hidlGetStreamConfigurations(
                 int32_t in_deviceId, std::vector<AidlTvStreamConfig>* _aidl_return);
         ::ndk::ScopedAStatus hidlOpenStream(int32_t in_deviceId, int32_t in_streamId,
@@ -229,7 +245,7 @@ private:
     KeyedVector<int, KeyedVector<int, Connection> > mConnections;
 
     std::shared_ptr<ITvInputWrapper> mTvInput;
-    std::shared_ptr<TvInputCallback> mTvInputCallback;
+    std::shared_ptr<TvInputCallbackWrapper> mTvInputCallback;
 };
 
 } // namespace android
diff --git a/services/core/jni/tvinput/TvInputHal_hidl.cpp b/services/core/jni/tvinput/TvInputHal_hidl.cpp
index 37cf8445920f88b38840cc36ff9aeb902681b86d..cdd926622dccf17b2918f2956091c989de40c658 100644
--- a/services/core/jni/tvinput/TvInputHal_hidl.cpp
+++ b/services/core/jni/tvinput/TvInputHal_hidl.cpp
@@ -59,7 +59,11 @@ JTvInputHal::TvInputEventWrapper JTvInputHal::TvInputEventWrapper::createEventWr
     return event;
 }
 
-Return<void> JTvInputHal::TvInputCallback::notify(const HidlTvInputEvent& event) {
+JTvInputHal::HidlTvInputCallback::HidlTvInputCallback(JTvInputHal* hal) {
+    mHal = hal;
+}
+
+Return<void> JTvInputHal::HidlTvInputCallback::notify(const HidlTvInputEvent& event) {
     mHal->mLooper->sendMessage(new NotifyHandler(mHal,
                                                  TvInputEventWrapper::createEventWrapper(event)),
                                static_cast<int>(event.type));
@@ -70,9 +74,8 @@ JTvInputHal::ITvInputWrapper::ITvInputWrapper(sp<HidlITvInput>& hidlTvInput)
       : mIsHidl(true), mHidlTvInput(hidlTvInput) {}
 
 ::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::hidlSetCallback(
-        const std::shared_ptr<TvInputCallback>& in_callback) {
-    mHidlTvInput->setCallback(in_callback == nullptr ? nullptr
-                                                     : sp<TvInputCallback>(in_callback.get()));
+        const sp<HidlTvInputCallback>& in_callback) {
+    mHidlTvInput->setCallback(in_callback);
     return ::ndk::ScopedAStatus::ok();
 }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
index 7ceccc57c0f1c27cac54ca08ec7c367892ff4656..47ae97fc5d27a4853a4c2f6b23f5126168a101f2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
@@ -44,7 +44,6 @@ import android.app.AppOpsManager.PackageOps;
 import android.app.IActivityManager;
 import android.app.IUidObserver;
 import android.app.usage.UsageStatsManager;
-import android.content.AttributionSourceState;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -230,20 +229,12 @@ public class AppStateTrackerTest {
     private AppStateTrackerTestable newInstance() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        when(mMockIAppOpsService.checkOperationWithState(eq(TARGET_OP), any()))
-                .thenAnswer(
-                        (Answer<Integer>)
-                                invocation -> {
-                                    AttributionSourceState attribution =
-                                            (AttributionSourceState) invocation.getArguments()[1];
-                                    return mRestrictedPackages.indexOf(
-                                                            Pair.create(
-                                                                    attribution.uid,
-                                                                    attribution.packageName))
-                                                    >= 0
-                                            ? AppOpsManager.MODE_IGNORED
-                                            : AppOpsManager.MODE_ALLOWED;
-                                });
+        when(mMockIAppOpsService.checkOperation(eq(TARGET_OP), anyInt(), anyString()))
+                .thenAnswer(inv -> {
+                    return mRestrictedPackages.indexOf(
+                            Pair.create(inv.getArgument(1), inv.getArgument(2))) >= 0 ?
+                            AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED;
+                });
 
         final AppStateTrackerTestable instance = new AppStateTrackerTestable();
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 032d026648dff82be79786a1de8bd6679aafb2e8..2f909f818bfe31e9cd57047ed9e65a4ca1c2eb3b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -69,7 +69,6 @@ import android.app.BroadcastOptions;
 import android.app.IApplicationThread;
 import android.app.IUidObserver;
 import android.app.SyncNotedAppOp;
-import android.content.AttributionSourceState;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -234,16 +233,12 @@ public class ActivityManagerServiceTest {
         assertThat(sProcessListSettingsListener).isNotNull();
     }
 
-    private void mockNoteOp() {
+    private void mockNoteOperation() {
         SyncNotedAppOp allowed = new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED,
                 AppOpsManager.OP_GET_USAGE_STATS, null, mContext.getPackageName());
-        when(mAppOpsService.noteOperationWithState(
-                        eq(AppOpsManager.OP_GET_USAGE_STATS),
-                        any(AttributionSourceState.class),
-                        any(Boolean.class),
-                        nullable(String.class),
-                        any(Boolean.class)))
-                .thenReturn(allowed);
+        when(mAppOpsService.noteOperation(eq(AppOpsManager.OP_GET_USAGE_STATS), eq(Process.myUid()),
+                nullable(String.class), nullable(String.class), any(Boolean.class),
+                nullable(String.class), any(Boolean.class))).thenReturn(allowed);
     }
 
     @After
@@ -696,7 +691,7 @@ public class ActivityManagerServiceTest {
      */
     @Test
     public void testDispatchUids_dispatchNeededChanges() throws RemoteException {
-        mockNoteOp();
+        mockNoteOperation();
 
         final int[] changesToObserve = {
             ActivityManager.UID_OBSERVER_PROCSTATE,
@@ -905,7 +900,7 @@ public class ActivityManagerServiceTest {
      */
     @Test
     public void testDispatchUidChanges_procStateCutpoint() throws RemoteException {
-        mockNoteOp();
+        mockNoteOperation();
 
         final IUidObserver observer = mock(IUidObserver.Stub.class);
 
@@ -975,7 +970,7 @@ public class ActivityManagerServiceTest {
      */
     @Test
     public void testDispatchUidChanges_validateUidsUpdated() {
-        mockNoteOp();
+        mockNoteOperation();
 
         final int[] changesForPendingItems = UID_RECORD_CHANGES;
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
index dcbee83b839b0257e518de98a7ccb582323c4193..bb91939c430e05fdf0c5722a4927007042088cbe 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -113,8 +113,6 @@ import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.role.RoleManager;
 import android.app.usage.AppStandbyInfo;
-import android.content.AttributionSource;
-import android.content.AttributionSourceState;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -2456,12 +2454,9 @@ public final class BackgroundRestrictionTest {
             doReturn(granted ? MODE_ALLOWED : MODE_IGNORED)
                     .when(mAppOpsManager)
                     .checkOpNoThrow(op, uid, packageName);
-            AttributionSource attributionSource =
-                    new AttributionSource.Builder(uid).setPackageName(packageName).build();
-            AttributionSourceState attributionSourceState = attributionSource.asState();
             doReturn(granted ? MODE_ALLOWED : MODE_IGNORED)
                     .when(mIAppOpsService)
-                    .checkOperationWithState(eq(op), eq(attributionSourceState));
+                    .checkOperation(op, uid, packageName);
         } catch (RemoteException e) {
             // Ignore.
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index daed5df4edd77338b145d98ddfce247114e26027..646f4862d75d5d5dd698866bb71bee9fb0123860 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -49,7 +49,6 @@ import static org.mockito.ArgumentMatchers.nullable;
 import android.app.AppOpsManager;
 import android.app.AppOpsManager.OpEntry;
 import android.app.AppOpsManager.PackageOps;
-import android.content.AttributionSource;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
@@ -217,21 +216,18 @@ public class AppOpsServiceTest {
     }
 
     @Test
-    public void testNoteOpAndGetOpsForPackage() {
+    public void testNoteOperationAndGetOpsForPackage() {
         mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
         mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
-        AttributionSource attributionSource =
-                new AttributionSource.Builder(mMyUid).setPackageName(sMyPackageName).build();
 
         // Note an op that's allowed.
-        mAppOpsService.noteOperationWithState(OP_READ_SMS, attributionSource.asState(), false,
-                null, false);
+        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
         List<PackageOps> loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
 
         // Note another op that's not allowed.
-        mAppOpsService.noteOperationWithState(OP_WRITE_SMS, attributionSource.asState(), false,
-                null, false);
+        mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
+                false);
         loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
         assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
@@ -243,24 +239,20 @@ public class AppOpsServiceTest {
      * ACCESS_COARSE_LOCATION op is used to check whether WIFI_SCAN is allowed.
      */
     @Test
-    public void testNoteOpAndGetOpsForPackage_controlledByDifferentOp() {
+    public void testNoteOperationAndGetOpsForPackage_controlledByDifferentOp() {
         // This op controls WIFI_SCAN
         mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED);
 
-        assertThat(mAppOpsService.noteOperationWithState(OP_WIFI_SCAN,
-                new AttributionSource.Builder(mMyUid).setPackageName(sMyPackageName)
-                        .build().asState(), false, null, false).getOpMode())
-                .isEqualTo(MODE_ALLOWED);
+        assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
+                null, false).getOpMode()).isEqualTo(MODE_ALLOWED);
 
         assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, -1,
                 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
 
         // Now set COARSE_LOCATION to ERRORED -> this will make WIFI_SCAN disabled as well.
         mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED);
-        assertThat(mAppOpsService.noteOperationWithState(OP_WIFI_SCAN,
-                new AttributionSource.Builder(mMyUid).setPackageName(sMyPackageName)
-                        .build().asState(), false, null, false)
-                .getOpMode()).isEqualTo(MODE_ERRORED);
+        assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
+                null, false).getOpMode()).isEqualTo(MODE_ERRORED);
 
         assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, mTestStartMillis,
                 MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
@@ -271,12 +263,9 @@ public class AppOpsServiceTest {
     public void testStatePersistence() {
         mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
         mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
-        AttributionSource attributionSource =
-                new AttributionSource.Builder(mMyUid).setPackageName(sMyPackageName).build();
-        mAppOpsService.noteOperationWithState(OP_READ_SMS, attributionSource.asState(), false,
-                null, false);
-        mAppOpsService.noteOperationWithState(OP_WRITE_SMS, attributionSource.asState(), false,
-                null, false);
+        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+        mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
+                false);
 
         mAppOpsService.shutdown();
 
@@ -294,10 +283,7 @@ public class AppOpsServiceTest {
     @Test
     public void testShutdown() {
         mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        AttributionSource attributionSource =
-                new AttributionSource.Builder(mMyUid).setPackageName(sMyPackageName).build();
-        mAppOpsService.noteOperationWithState(OP_READ_SMS, attributionSource.asState(), false,
-                null, false);
+        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
         mAppOpsService.shutdown();
 
         // Create a new app ops service which will initialize its state from XML.
@@ -311,10 +297,7 @@ public class AppOpsServiceTest {
     @Test
     public void testGetOpsForPackage() {
         mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        AttributionSource attributionSource =
-                new AttributionSource.Builder(mMyUid).setPackageName(sMyPackageName).build();
-        mAppOpsService.noteOperationWithState(OP_READ_SMS, attributionSource.asState(), false,
-                null, false);
+        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
 
         // Query all ops
         List<PackageOps> loggedOps = mAppOpsService.getOpsForPackage(
@@ -343,10 +326,7 @@ public class AppOpsServiceTest {
     @Test
     public void testPackageRemoved() {
         mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        AttributionSource attributionSource =
-                new AttributionSource.Builder(mMyUid).setPackageName(sMyPackageName).build();
-        mAppOpsService.noteOperationWithState(OP_READ_SMS, attributionSource.asState(), false,
-                null, false);
+        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
 
         List<PackageOps> loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
@@ -361,8 +341,7 @@ public class AppOpsServiceTest {
     @Test
     public void testPackageRemovedHistoricalOps() throws InterruptedException {
         mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        mAppOpsService.noteOperationWithState(OP_READ_SMS, mMyUid, sMyPackageName, null, false,
-                null, false);
+        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
 
         AppOpsManager.HistoricalOps historicalOps = new AppOpsManager.HistoricalOps(0, 15000);
         historicalOps.increaseAccessCount(OP_READ_SMS, mMyUid, sMyPackageName, null,
@@ -402,10 +381,7 @@ public class AppOpsServiceTest {
     @Test
     public void testUidRemoved() {
         mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        AttributionSource attributionSource =
-                new AttributionSource.Builder(mMyUid).setPackageName(sMyPackageName).build();
-        mAppOpsService.noteOperationWithState(OP_READ_SMS, attributionSource.asState(),
-                false, null, false);
+        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
 
         List<PackageOps> loggedOps = getLoggedOps();
         assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
@@ -417,10 +393,7 @@ public class AppOpsServiceTest {
     @Test
     public void testUidStateInitializationDoesntClearState() throws InterruptedException {
         mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
-        AttributionSource attributionSource =
-                new AttributionSource.Builder(mMyUid).setPackageName(sMyPackageName).build();
-        mAppOpsService.noteOperationWithState(OP_READ_SMS, attributionSource.asState(), false,
-                null, false);
+        mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
         mAppOpsService.initializeUidStates();
         List<PackageOps> ops = mAppOpsService.getOpsForPackage(mMyUid, sMyPackageName,
                 new int[]{OP_READ_SMS});
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
index fe31b9cbe558554892eaaa6e8fbcfc89037a6318..a776eec4546fb8cba5c1b11c1a48f8f769fd4121 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
@@ -267,7 +267,8 @@ public class PowerGroupTest {
                 /* dozeAfterScreenOff= */ false,
                 /* bootCompleted= */ true,
                 /* screenBrightnessBoostInProgress= */ false,
-                /* waitForNegativeProximity= */ false);
+                /* waitForNegativeProximity= */ false,
+                /* brightWhenDozing= */ false);
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DIM);
@@ -305,7 +306,8 @@ public class PowerGroupTest {
                 /* dozeAfterScreenOff= */ false,
                 /* bootCompleted= */ true,
                 /* screenBrightnessBoostInProgress= */ false,
-                /* waitForNegativeProximity= */ false);
+                /* waitForNegativeProximity= */ false,
+                /* brightWhenDozing= */ false);
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DOZE);
@@ -342,7 +344,8 @@ public class PowerGroupTest {
                 /* dozeAfterScreenOff= */ true,
                 /* bootCompleted= */ true,
                 /* screenBrightnessBoostInProgress= */ false,
-                /* waitForNegativeProximity= */ false);
+                /* waitForNegativeProximity= */ false,
+                /* brightWhenDozing= */ false);
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
@@ -378,7 +381,8 @@ public class PowerGroupTest {
                 /* dozeAfterScreenOff= */ true,
                 /* bootCompleted= */ true,
                 /* screenBrightnessBoostInProgress= */ false,
-                /* waitForNegativeProximity= */ false);
+                /* waitForNegativeProximity= */ false,
+                /* brightWhenDozing= */ false);
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
@@ -414,7 +418,8 @@ public class PowerGroupTest {
                 /* dozeAfterScreenOff= */ false,
                 /* bootCompleted= */ true,
                 /* screenBrightnessBoostInProgress= */ false,
-                /* waitForNegativeProximity= */ false);
+                /* waitForNegativeProximity= */ false,
+                /* brightWhenDozing= */ false);
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
@@ -451,7 +456,8 @@ public class PowerGroupTest {
                 /* dozeAfterScreenOff= */ false,
                 /* bootCompleted= */ true,
                 /* screenBrightnessBoostInProgress= */ false,
-                /* waitForNegativeProximity= */ false);
+                /* waitForNegativeProximity= */ false,
+                /* brightWhenDozing= */ false);
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
@@ -486,7 +492,8 @@ public class PowerGroupTest {
                 /* dozeAfterScreenOff= */ false,
                 /* bootCompleted= */ false,
                 /* screenBrightnessBoostInProgress= */ false,
-                /* waitForNegativeProximity= */ false);
+                /* waitForNegativeProximity= */ false,
+                /* brightWhenDozing= */ false);
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
@@ -522,7 +529,8 @@ public class PowerGroupTest {
                 /* dozeAfterScreenOff= */ false,
                 /* bootCompleted= */ true,
                 /* screenBrightnessBoostInProgress= */ false,
-                /* waitForNegativeProximity= */ false);
+                /* waitForNegativeProximity= */ false,
+                /* brightWhenDozing= */ false);
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
@@ -557,7 +565,8 @@ public class PowerGroupTest {
                 /* dozeAfterScreenOff= */ false,
                 /* bootCompleted= */ true,
                 /* screenBrightnessBoostInProgress= */ true,
-                /* waitForNegativeProximity= */ false);
+                /* waitForNegativeProximity= */ false,
+                /* brightWhenDozing= */ false);
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
diff --git a/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java b/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f537efd9736da427595343da416848d3d06629aa
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.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.pdb;
+
+import static com.android.server.pdb.PersistentDataBlockService.DIGEST_SIZE_BYTES;
+import static com.android.server.pdb.PersistentDataBlockService.MAX_DATA_BLOCK_SIZE;
+import static com.android.server.pdb.PersistentDataBlockService.MAX_FRP_CREDENTIAL_HANDLE_SIZE;
+import static com.android.server.pdb.PersistentDataBlockService.MAX_TEST_MODE_DATA_SIZE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+import static org.junit.Assert.assertThrows;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.service.persistentdata.IPersistentDataBlockService;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.StandardOpenOption;
+
+@RunWith(JUnitParamsRunner.class)
+public class PersistentDataBlockServiceTest {
+    private static final String TAG = "PersistentDataBlockServiceTest";
+
+    private static final byte[] SMALL_DATA = "data to write".getBytes();
+    private static final byte[] ANOTHER_SMALL_DATA = "something else".getBytes();
+
+    private Context mContext;
+    private PersistentDataBlockService mPdbService;
+    private IPersistentDataBlockService mInterface;
+    private PersistentDataBlockManagerInternal mInternalInterface;
+    private File mDataBlockFile;
+    private String mOemUnlockPropertyValue;
+
+    @Mock private UserManager mUserManager;
+
+    private class FakePersistentDataBlockService extends PersistentDataBlockService {
+        FakePersistentDataBlockService(Context context, String dataBlockFile,
+                long blockDeviceSize) {
+            super(context, /* isFileBacked */ true, dataBlockFile, blockDeviceSize);
+        }
+
+        @Override
+        void setProperty(String key, String value) {
+            // Override to capture the value instead of actually setting the property.
+            assertThat(key).isEqualTo("sys.oem_unlock_allowed");
+            mOemUnlockPropertyValue = value;
+        }
+    }
+
+    @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mDataBlockFile = mTemporaryFolder.newFile();
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        mPdbService = new FakePersistentDataBlockService(mContext, mDataBlockFile.getPath(),
+                /* blockDeviceSize */ -1);
+        mPdbService.setAllowedUid(Binder.getCallingUid());
+        mPdbService.formatPartitionLocked(/* setOemUnlockEnabled */ false);
+        mInterface = mPdbService.getInterfaceForTesting();
+        mInternalInterface = mPdbService.getInternalInterfaceForTesting();
+
+        when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
+        when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
+    }
+
+    abstract static class Block {
+        public PersistentDataBlockService service;
+
+        abstract int write(byte[] data) throws RemoteException;
+        abstract byte[] read() throws RemoteException;
+    }
+
+    /**
+     * Configuration for parameterizing tests, including the block name, maximum block size, and
+     * a block implementation for the read/write operations.
+     */
+    public Object[][] getTestParametersForBlocks() {
+        return new Object[][] {
+            {
+                new Block() {
+                    @Override public int write(byte[] data) throws RemoteException {
+                        return service.getInterfaceForTesting().write(data);
+                    }
+
+                    @Override public byte[] read() throws RemoteException {
+                        return service.getInterfaceForTesting().read();
+                    }
+                },
+            },
+            {
+                new Block() {
+                    @Override public int write(byte[] data) {
+                        service.getInternalInterfaceForTesting().setFrpCredentialHandle(data);
+                        // The written size isn't returned. Pretend it's fully written in the
+                        // test for now.
+                        return data.length;
+                    }
+
+                    @Override public byte[] read() {
+                        return service.getInternalInterfaceForTesting().getFrpCredentialHandle();
+                    }
+                },
+            },
+            {
+                new Block() {
+                    @Override public int write(byte[] data) {
+                        service.getInternalInterfaceForTesting().setTestHarnessModeData(data);
+                        // The written size isn't returned. Pretend it's fully written in the
+                        // test for now.
+                        return data.length;
+                    }
+
+                    @Override public byte[] read() {
+                        return service.getInternalInterfaceForTesting().getTestHarnessModeData();
+                    }
+                },
+            },
+        };
+    }
+
+    @Test
+    @Parameters(method = "getTestParametersForBlocks")
+    public void writeThenRead(Block block) throws Exception {
+        block.service = mPdbService;
+        assertThat(block.write(SMALL_DATA)).isEqualTo(SMALL_DATA.length);
+        assertThat(block.read()).isEqualTo(SMALL_DATA);
+    }
+
+    @Test
+    @Parameters(method = "getTestParametersForBlocks")
+    public void writeWhileAlreadyCorrupted(Block block) throws Exception {
+        block.service = mPdbService;
+        assertThat(block.write(SMALL_DATA)).isEqualTo(SMALL_DATA.length);
+        assertThat(block.read()).isEqualTo(SMALL_DATA);
+
+        tamperWithDigest();
+
+        // In the currently implementation, expect the write to not trigger formatting.
+        assertThat(block.write(ANOTHER_SMALL_DATA)).isEqualTo(ANOTHER_SMALL_DATA.length);
+    }
+
+    @Test
+    public void frpWriteOutOfBound() throws Exception {
+        byte[] maxData = new byte[mPdbService.getMaximumFrpDataSize()];
+        assertThat(mInterface.write(maxData)).isEqualTo(maxData.length);
+
+        byte[] overflowData = new byte[mPdbService.getMaximumFrpDataSize() + 1];
+        assertThat(mInterface.write(overflowData)).isLessThan(0);
+    }
+
+    @Test
+    public void frpCredentialWriteOutOfBound() throws Exception {
+        byte[] maxData = new byte[MAX_FRP_CREDENTIAL_HANDLE_SIZE];
+        mInternalInterface.setFrpCredentialHandle(maxData);
+
+        byte[] overflowData = new byte[MAX_FRP_CREDENTIAL_HANDLE_SIZE + 1];
+        assertThrows(IllegalArgumentException.class, () ->
+                mInternalInterface.setFrpCredentialHandle(overflowData));
+    }
+
+    @Test
+    public void testHardnessWriteOutOfBound() throws Exception {
+        byte[] maxData = new byte[MAX_TEST_MODE_DATA_SIZE];
+        mInternalInterface.setTestHarnessModeData(maxData);
+
+        byte[] overflowData = new byte[MAX_TEST_MODE_DATA_SIZE + 1];
+        assertThrows(IllegalArgumentException.class, () ->
+                mInternalInterface.setTestHarnessModeData(overflowData));
+    }
+
+    @Test
+    public void readCorruptedFrpData() throws Exception {
+        assertThat(mInterface.write(SMALL_DATA)).isEqualTo(SMALL_DATA.length);
+        assertThat(mInterface.read()).isEqualTo(SMALL_DATA);
+
+        tamperWithDigest();
+
+        // Expect the read to trigger formatting, resulting in reading empty data.
+        assertThat(mInterface.read()).hasLength(0);
+    }
+
+    @Test
+    public void readCorruptedFrpCredentialData() throws Exception {
+        mInternalInterface.setFrpCredentialHandle(SMALL_DATA);
+        assertThat(mInternalInterface.getFrpCredentialHandle()).isEqualTo(SMALL_DATA);
+
+        tamperWithDigest();
+
+        assertThrows(IllegalStateException.class, () ->
+                mInternalInterface.getFrpCredentialHandle());
+    }
+
+    @Test
+    public void readCorruptedTestHarnessData() throws Exception {
+        mInternalInterface.setTestHarnessModeData(SMALL_DATA);
+        assertThat(mInternalInterface.getTestHarnessModeData()).isEqualTo(SMALL_DATA);
+
+        tamperWithDigest();
+
+        assertThrows(IllegalStateException.class, () ->
+                mInternalInterface.getTestHarnessModeData());
+    }
+
+    @Test
+    public void nullWrite() throws Exception {
+        assertThrows(NullPointerException.class, () -> mInterface.write(null));
+        mInternalInterface.setFrpCredentialHandle(null);  // no exception
+        mInternalInterface.setTestHarnessModeData(null);  // no exception
+    }
+
+    @Test
+    public void emptyDataWrite() throws Exception {
+        var empty = new byte[0];
+        assertThat(mInterface.write(empty)).isEqualTo(0);
+
+        assertThrows(IllegalArgumentException.class, () ->
+                mInternalInterface.setFrpCredentialHandle(empty));
+        assertThrows(IllegalArgumentException.class, () ->
+                mInternalInterface.setTestHarnessModeData(empty));
+    }
+
+    @Test
+    public void frpWriteMoreThan100K() throws Exception {
+        File dataBlockFile = mTemporaryFolder.newFile();
+        PersistentDataBlockService pdbService = new FakePersistentDataBlockService(mContext,
+                dataBlockFile.getPath(), /* blockDeviceSize */ 128 * 1000);
+        pdbService.setAllowedUid(Binder.getCallingUid());
+        pdbService.formatPartitionLocked(/* setOemUnlockEnabled */ false);
+
+        IPersistentDataBlockService service = pdbService.getInterfaceForTesting();
+        int maxDataSize = (int) service.getMaximumDataBlockSize();
+        assertThat(service.write(new byte[maxDataSize])).isEqualTo(maxDataSize);
+        assertThat(service.write(new byte[maxDataSize + 1])).isEqualTo(-MAX_DATA_BLOCK_SIZE);
+    }
+
+    @Test
+    public void frpBlockReadWriteWithoutPermission() throws Exception {
+        mPdbService.setAllowedUid(Binder.getCallingUid() + 1);  // unexpected uid
+        assertThrows(SecurityException.class, () -> mInterface.write(SMALL_DATA));
+        assertThrows(SecurityException.class, () -> mInterface.read());
+    }
+
+    @Test
+    public void getMaximumDataBlockSizeDenied() throws Exception {
+        mPdbService.setAllowedUid(Binder.getCallingUid() + 1);  // unexpected uid
+        assertThrows(SecurityException.class, () -> mInterface.getMaximumDataBlockSize());
+    }
+
+    @Test
+    public void getMaximumDataBlockSize() throws Exception {
+        mPdbService.setAllowedUid(Binder.getCallingUid());
+        assertThat(mInterface.getMaximumDataBlockSize())
+                .isEqualTo(mPdbService.getMaximumFrpDataSize());
+    }
+
+    @Test
+    public void getMaximumDataBlockSizeOfLargerPartition() throws Exception {
+        File dataBlockFile = mTemporaryFolder.newFile();
+        PersistentDataBlockService pdbService = new FakePersistentDataBlockService(mContext,
+                dataBlockFile.getPath(), /* blockDeviceSize */ 128 * 1000);
+        pdbService.setAllowedUid(Binder.getCallingUid());
+        pdbService.formatPartitionLocked(/* setOemUnlockEnabled */ false);
+
+        IPersistentDataBlockService service = pdbService.getInterfaceForTesting();
+        assertThat(service.getMaximumDataBlockSize()).isEqualTo(MAX_DATA_BLOCK_SIZE);
+    }
+
+    @Test
+    public void getFrpDataBlockSizeGrantedByUid() throws Exception {
+        assertThat(mInterface.write(SMALL_DATA)).isEqualTo(SMALL_DATA.length);
+
+        mPdbService.setAllowedUid(Binder.getCallingUid());
+        assertThat(mInterface.getDataBlockSize()).isEqualTo(SMALL_DATA.length);
+
+        // Modify the magic / type marker. In the current implementation, getting the FRP data block
+        // size does not check digest.
+        tamperWithMagic();
+        assertThat(mInterface.getDataBlockSize()).isEqualTo(0);
+    }
+
+    @Test
+    public void getFrpDataBlockSizeGrantedByPermission() throws Exception {
+        assertThat(mInterface.write(SMALL_DATA)).isEqualTo(SMALL_DATA.length);
+
+        mPdbService.setAllowedUid(Binder.getCallingUid() + 1);  // unexpected uid
+        grantAccessPdbStatePermission();
+
+        assertThat(mInterface.getDataBlockSize()).isEqualTo(SMALL_DATA.length);
+
+        // Modify the magic / type marker. In the current implementation, getting the FRP data block
+        // size does not check digest.
+        tamperWithMagic();
+        assertThat(mInterface.getDataBlockSize()).isEqualTo(0);
+    }
+
+    @Test
+    public void wipePermissionCheck() throws Exception {
+        denyOemUnlockPermission();
+        assertThrows(SecurityException.class, () -> mInterface.wipe());
+    }
+
+    @Test
+    public void wipeMakesItNotWritable() throws Exception {
+        grantOemUnlockPermission();
+        mInterface.wipe();
+
+        // Verify that nothing is written.
+        final int headerAndDataBytes = 4 + SMALL_DATA.length;
+        assertThat(mInterface.write(SMALL_DATA)).isLessThan(0);
+        assertThat(readBackingFile(DIGEST_SIZE_BYTES + 4, headerAndDataBytes).array())
+                .isEqualTo(new byte[headerAndDataBytes]);
+
+        mInternalInterface.setFrpCredentialHandle(SMALL_DATA);
+        assertThat(readBackingFile(mPdbService.getFrpCredentialDataOffset() + 4,
+                    headerAndDataBytes)
+                .array())
+                .isEqualTo(new byte[headerAndDataBytes]);
+
+        mInternalInterface.setTestHarnessModeData(SMALL_DATA);
+        assertThat(readBackingFile(mPdbService.getTestHarnessModeDataOffset() + 4,
+                    headerAndDataBytes)
+                .array())
+                .isEqualTo(new byte[headerAndDataBytes]);
+    }
+
+    @Test
+    public void hasFrpCredentialHandleGrantedByUid() throws Exception {
+        mPdbService.setAllowedUid(Binder.getCallingUid());
+
+        assertThat(mInterface.hasFrpCredentialHandle()).isFalse();
+        mInternalInterface.setFrpCredentialHandle(SMALL_DATA);
+        assertThat(mInterface.hasFrpCredentialHandle()).isTrue();
+    }
+
+    @Test
+    public void hasFrpCredentialHandleGrantedByPermission() throws Exception {
+        mPdbService.setAllowedUid(Binder.getCallingUid() + 1);  // unexpected uid
+        grantAccessPdbStatePermission();
+
+        assertThat(mInterface.hasFrpCredentialHandle()).isFalse();
+        mInternalInterface.setFrpCredentialHandle(SMALL_DATA);
+        assertThat(mInterface.hasFrpCredentialHandle()).isTrue();
+    }
+
+    @Test
+    public void clearTestHarnessModeData() throws Exception {
+        mInternalInterface.setTestHarnessModeData(SMALL_DATA);
+        mInternalInterface.clearTestHarnessModeData();
+
+        assertThat(readBackingFile(mPdbService.getTestHarnessModeDataOffset(),
+                    MAX_TEST_MODE_DATA_SIZE).array())
+                .isEqualTo(new byte[MAX_TEST_MODE_DATA_SIZE]);
+    }
+
+    @Test
+    public void getAllowedUid() throws Exception {
+        assertThat(mInternalInterface.getAllowedUid()).isEqualTo(Binder.getCallingUid());
+    }
+
+    @Test
+    public void oemUnlockWithoutPermission() throws Exception {
+        denyOemUnlockPermission();
+
+        assertThrows(SecurityException.class, () -> mInterface.setOemUnlockEnabled(true));
+    }
+
+    @Test
+    public void oemUnlockNotAdmin() throws Exception {
+        grantOemUnlockPermission();
+        makeUserAdmin(false);
+
+        assertThrows(SecurityException.class, () -> mInterface.setOemUnlockEnabled(true));
+    }
+
+    @Test
+    public void oemUnlock() throws Exception {
+        grantOemUnlockPermission();
+        makeUserAdmin(true);
+
+        mInterface.setOemUnlockEnabled(true);
+        assertThat(mInterface.getOemUnlockEnabled()).isTrue();
+        assertThat(mOemUnlockPropertyValue).isEqualTo("1");
+    }
+
+    @Test
+    public void oemUnlockUserRestriction_OemUnlock() throws Exception {
+        grantOemUnlockPermission();
+        makeUserAdmin(true);
+        when(mUserManager.hasUserRestriction(eq(UserManager.DISALLOW_OEM_UNLOCK)))
+                .thenReturn(true);
+
+        assertThrows(SecurityException.class, () -> mInterface.setOemUnlockEnabled(true));
+    }
+
+    @Test
+    public void oemUnlockUserRestriction_FactoryReset() throws Exception {
+        grantOemUnlockPermission();
+        makeUserAdmin(true);
+        when(mUserManager.hasUserRestriction(eq(UserManager.DISALLOW_FACTORY_RESET)))
+                .thenReturn(true);
+
+        assertThrows(SecurityException.class, () -> mInterface.setOemUnlockEnabled(true));
+    }
+
+    @Test
+    public void oemUnlockIgnoreTampering() throws Exception {
+        grantOemUnlockPermission();
+        makeUserAdmin(true);
+
+        // The current implementation does not check digest before set or get the oem unlock bit.
+        tamperWithDigest();
+        mInterface.setOemUnlockEnabled(true);
+        assertThat(mOemUnlockPropertyValue).isEqualTo("1");
+        tamperWithDigest();
+        assertThat(mInterface.getOemUnlockEnabled()).isTrue();
+    }
+
+    @Test
+    public void getOemUnlockEnabledPermissionCheck_NoPermission() throws Exception {
+        assertThrows(SecurityException.class, () -> mInterface.getOemUnlockEnabled());
+    }
+
+    @Test
+    public void getOemUnlockEnabledPermissionCheck_OemUnlcokState() throws Exception {
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mContext)
+                .checkCallingOrSelfPermission(eq(Manifest.permission.OEM_UNLOCK_STATE));
+        assertThat(mInterface.getOemUnlockEnabled()).isFalse();
+    }
+
+    @Test
+    public void getOemUnlockEnabledPermissionCheck_ReadOemUnlcokState() throws Exception {
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mContext)
+                .checkCallingOrSelfPermission(eq(Manifest.permission.READ_OEM_UNLOCK_STATE));
+        assertThat(mInterface.getOemUnlockEnabled()).isFalse();
+    }
+
+    @Test
+    public void forceOemUnlock_RequiresNoPermission() throws Exception {
+        denyOemUnlockPermission();
+
+        mInternalInterface.forceOemUnlockEnabled(true);
+
+        assertThat(mOemUnlockPropertyValue).isEqualTo("1");
+        assertThat(readBackingFile(mPdbService.getOemUnlockDataOffset(), 1).array())
+                .isEqualTo(new byte[] { 1 });
+    }
+
+    @Test
+    public void getFlashLockStatePermissionCheck_NoPermission() throws Exception {
+        assertThrows(SecurityException.class, () -> mInterface.getFlashLockState());
+    }
+
+    @Test
+    public void getFlashLockStatePermissionCheck_OemUnlcokState() throws Exception {
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mContext)
+                .checkCallingOrSelfPermission(eq(Manifest.permission.OEM_UNLOCK_STATE));
+        mInterface.getFlashLockState();  // Do not throw
+    }
+
+    @Test
+    public void getFlashLockStatePermissionCheck_ReadOemUnlcokState() throws Exception {
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mContext)
+                .checkCallingOrSelfPermission(eq(Manifest.permission.READ_OEM_UNLOCK_STATE));
+        mInterface.getFlashLockState();  // Do not throw
+    }
+
+    private void tamperWithDigest() throws Exception {
+        try (var ch = FileChannel.open(mDataBlockFile.toPath(), StandardOpenOption.WRITE)) {
+            ch.write(ByteBuffer.wrap("tampered-digest".getBytes()));
+        }
+    }
+
+    private void tamperWithMagic() throws Exception {
+        try (var ch = FileChannel.open(mDataBlockFile.toPath(), StandardOpenOption.WRITE)) {
+            ch.write(ByteBuffer.wrap("mark".getBytes()), DIGEST_SIZE_BYTES);
+        }
+    }
+
+    private void makeUserAdmin(boolean isAdmin) {
+        when(mUserManager.isUserAdmin(anyInt())).thenReturn(isAdmin);
+    }
+
+    private void grantOemUnlockPermission() {
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mContext)
+                .checkCallingOrSelfPermission(eq(Manifest.permission.OEM_UNLOCK_STATE));
+        doNothing().when(mContext)
+                .enforceCallingOrSelfPermission(eq(Manifest.permission.OEM_UNLOCK_STATE),
+                        anyString());
+    }
+
+    private void denyOemUnlockPermission() {
+        doReturn(PackageManager.PERMISSION_DENIED).when(mContext)
+                .checkCallingOrSelfPermission(eq(Manifest.permission.OEM_UNLOCK_STATE));
+    }
+
+    private void grantAccessPdbStatePermission() {
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mContext)
+                .checkCallingPermission(eq(Manifest.permission.ACCESS_PDB_STATE));
+    }
+
+    private ByteBuffer readBackingFile(long position, int size) throws Exception {
+        try (var ch = FileChannel.open(mDataBlockFile.toPath(), StandardOpenOption.READ)) {
+            var buffer = ByteBuffer.allocate(size);
+            assertThat(ch.read(buffer, position)).isGreaterThan(0);
+            return buffer;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
index 01a91c1db1e6ae4d31b7f43ca29880cb5ce40bd4..398148ff4d3b0d26bc8f1279c11dabe9a89662b1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
@@ -30,7 +30,6 @@ import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.app.AppGlobals;
-import android.content.AttributionSource;
 import android.content.Context;
 import android.content.pm.IPackageManager;
 import android.content.pm.LauncherApps;
@@ -282,16 +281,12 @@ public class SuspendPackagesTest {
         };
         iAppOps.startWatchingMode(code, TEST_APP_PACKAGE_NAME, watcher);
         final int testPackageUid = mPackageManager.getPackageUid(TEST_APP_PACKAGE_NAME, 0);
-        AttributionSource attributionSource =
-                new AttributionSource.Builder(testPackageUid)
-                        .setPackageName(TEST_APP_PACKAGE_NAME)
-                        .build();
-        int opMode = iAppOps.checkOperationWithState(code, attributionSource.asState());
+        int opMode = iAppOps.checkOperation(code, testPackageUid, TEST_APP_PACKAGE_NAME);
         assertEquals("Op " + opToName(code) + " disallowed for unsuspended package", MODE_ALLOWED,
                 opMode);
         suspendTestPackage(null, null, null);
         assertTrue("AppOpsWatcher did not callback", latch.await(5, TimeUnit.SECONDS));
-        opMode = iAppOps.checkOperationWithState(code, attributionSource.asState());
+        opMode = iAppOps.checkOperation(code, testPackageUid, TEST_APP_PACKAGE_NAME);
         assertEquals("Op " + opToName(code) + " allowed for suspended package", MODE_IGNORED,
                 opMode);
         iAppOps.stopWatchingMode(watcher);
diff --git a/services/tests/timetests/AndroidManifest.xml b/services/tests/timetests/AndroidManifest.xml
index 62fbb05ee1e1546af439234783505f0272a378cb..a21d383c3233e476a640e1facc91c842f2798465 100644
--- a/services/tests/timetests/AndroidManifest.xml
+++ b/services/tests/timetests/AndroidManifest.xml
@@ -17,9 +17,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.framework.services.tests.time">
 
-    <!-- Required for user checks -->
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
-
     <application>
         <uses-library android:name="android.test.runner" />
     </application>
diff --git a/services/tests/timetests/src/com/android/server/timezonedetector/TestCallerIdentityInjector.java b/services/tests/timetests/src/com/android/server/timezonedetector/TestCallerIdentityInjector.java
index 67b8cc22fac0aaa6f466b3ca6841e7ca095ed24c..56db9ccd68e70d1ca7da9fc737c61bff17fca7a5 100644
--- a/services/tests/timetests/src/com/android/server/timezonedetector/TestCallerIdentityInjector.java
+++ b/services/tests/timetests/src/com/android/server/timezonedetector/TestCallerIdentityInjector.java
@@ -34,6 +34,11 @@ public class TestCallerIdentityInjector implements CallerIdentityInjector {
         mCurrentCallingUserId = userId;
     }
 
+    @Override
+    public int resolveUserId(int userId, String debugInfo) {
+        return userId;
+    }
+
     @Override
     public int getCallingUserId() {
         assertNotNull("callingUserId has been cleared", mCurrentCallingUserId);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index bf86563e3d86f4c3ed684ebfb66f43f63eeb6a95..c782d3e90e9640688fb63de7bbb964b385714913 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -384,33 +384,36 @@ public class DisplayPolicyTests extends WindowTestsBase {
         assertNotEquals(prevScreenHeightDp, mDisplayContent.getConfiguration().screenHeightDp);
         assertFalse(navbar.providesDisplayDecorInsets() && displayPolicy.updateDecorInsetsInfo());
 
-        navbar.removeIfPossible();
-        assertEquals(0, displayPolicy.getDecorInsetsInfo(di.rotation, di.logicalWidth,
-                di.logicalHeight).mNonDecorInsets.bottom);
-
         final WindowState statusBar = createStatusBarWithProvidedInsets(mDisplayContent);
-        assertTrue(statusBar.providesDisplayDecorInsets()
-                && displayPolicy.updateDecorInsetsInfo());
-        assertEquals(STATUS_BAR_HEIGHT, displayPolicy.getDecorInsetsInfo(di.rotation,
-                di.logicalWidth, di.logicalHeight).mConfigInsets.top);
+        if (mWm.mConfigTypes == WindowInsets.Type.navigationBars()) {
+            assertFalse(statusBar.providesDisplayDecorInsets()
+                    && displayPolicy.updateDecorInsetsInfo());
+            assertEquals(0, displayPolicy.getDecorInsetsInfo(di.rotation,
+                    di.logicalWidth, di.logicalHeight).mConfigInsets.top);
+        } else {
+            assertTrue(statusBar.providesDisplayDecorInsets()
+                    && displayPolicy.updateDecorInsetsInfo());
+            assertEquals(STATUS_BAR_HEIGHT, displayPolicy.getDecorInsetsInfo(di.rotation,
+                    di.logicalWidth, di.logicalHeight).mConfigInsets.top);
+        }
 
         // Add a window that provides the same insets in current rotation. But it specifies
         // different insets in other rotations.
-        final WindowState bar2 = createWindow(null, statusBar.mAttrs.type, "bar2");
+        final WindowState bar2 = createWindow(null, navbar.mAttrs.type, "bar2");
         bar2.mAttrs.providedInsets = new InsetsFrameProvider[] {
-                new InsetsFrameProvider(bar2, 0, WindowInsets.Type.statusBars())
-                        .setInsetsSize(Insets.of(0, STATUS_BAR_HEIGHT, 0, 0))
+                new InsetsFrameProvider(bar2, 0, WindowInsets.Type.navigationBars())
+                        .setInsetsSize(Insets.of(0, 0, 0, NAV_BAR_HEIGHT))
         };
         bar2.mAttrs.setFitInsetsTypes(0);
         bar2.mAttrs.paramsForRotation = new WindowManager.LayoutParams[4];
-        final int doubleHeightFor90 = STATUS_BAR_HEIGHT * 2;
+        final int doubleHeightFor90 = NAV_BAR_HEIGHT * 2;
         for (int i = ROTATION_0; i <= Surface.ROTATION_270; i++) {
             final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
             params.setFitInsetsTypes(0);
             if (i == Surface.ROTATION_90) {
                 params.providedInsets = new InsetsFrameProvider[] {
-                        new InsetsFrameProvider(bar2, 0, WindowInsets.Type.statusBars())
-                                .setInsetsSize(Insets.of(0, doubleHeightFor90, 0, 0))
+                        new InsetsFrameProvider(bar2, 0, WindowInsets.Type.navigationBars())
+                                .setInsetsSize(Insets.of(0, 0, 0, doubleHeightFor90))
                 };
             } else {
                 params.providedInsets = bar2.mAttrs.providedInsets;
@@ -422,7 +425,12 @@ public class DisplayPolicyTests extends WindowTestsBase {
         assertFalse(displayPolicy.updateDecorInsetsInfo());
         // The insets in other rotations should be still updated.
         assertEquals(doubleHeightFor90, displayPolicy.getDecorInsetsInfo(Surface.ROTATION_90,
-                di.logicalHeight, di.logicalWidth).mConfigInsets.top);
+                di.logicalHeight, di.logicalWidth).mConfigInsets.bottom);
+
+        navbar.removeIfPossible();
+        bar2.removeIfPossible();
+        assertEquals(0, displayPolicy.getDecorInsetsInfo(di.rotation, di.logicalWidth,
+                di.logicalHeight).mNonDecorInsets.bottom);
     }
 
     @SetupWindows(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD })
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index c4d03bebc751623989944bf8fc981803bd201125..49a888689e60074f4785c636fed549a242fa2a55 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -20,6 +20,7 @@ import static android.view.SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -282,6 +283,9 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
 
+        if (explicitRefreshRateHints()) {
+            return;
+        }
         overrideWindow.mActivityRecord.mSurfaceAnimator.startAnimation(
                 overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
@@ -320,6 +324,9 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
 
+        if (explicitRefreshRateHints()) {
+            return;
+        }
         overrideWindow.mActivityRecord.mSurfaceAnimator.startAnimation(
                 overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
@@ -342,6 +349,9 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
 
+        if (explicitRefreshRateHints()) {
+            return;
+        }
         window.mActivityRecord.mSurfaceAnimator.startAnimation(
                 window.getPendingTransaction(), mock(AnimationAdapter.class),
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
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 03188f813555fbb2973f1253a57f5d03292f2024..9af5ba57d6c95ff8d869ff008600ecf308576de4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -376,6 +376,9 @@ public class SystemServicesTestRule implements TestRule {
 
         mWmService.onInitReady();
         mAtmService.setWindowManager(mWmService);
+        // Avoid real display events interfering with the test. Also avoid leaking registration.
+        mContext.getSystemService(DisplayManager.class)
+                .unregisterDisplayListener(mAtmService.mRootWindowContainer);
         mWmService.mDisplayEnabled = true;
         mWmService.mDisplayReady = true;
         mAtmService.getTransitionController().mIsWaitingForDisplayEnabled = false;
@@ -413,12 +416,6 @@ public class SystemServicesTestRule implements TestRule {
             }
         }
 
-        if (mAtmService != null) {
-            // Unregister display listener from root to avoid issues with subsequent tests.
-            mContext.getSystemService(DisplayManager.class)
-                    .unregisterDisplayListener(mAtmService.mRootWindowContainer);
-        }
-
         for (int i = mDeviceConfigListeners.size() - 1; i >= 0; i--) {
             DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListeners.get(i));
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index 8fecbb9d4decd7c8403042abdf2fdfebd5ba0355..4b54e4464ca7815941a55226c6a63194aff80c20 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -132,6 +132,10 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa
         mPersister.persistSnapshot(2, mTestUserId, createSnapshot());
         mPersister.persistSnapshot(3, mTestUserId, createSnapshot());
         mPersister.persistSnapshot(4, mTestUserId, createSnapshot());
+        // Verify there should only keep the latest request when received a duplicated id.
+        mPersister.persistSnapshot(4, mTestUserId, createSnapshot());
+        // Expected 3: One remove obsolete request, two persist request.
+        assertEquals(3, mSnapshotPersistQueue.peekQueueSize());
         mSnapshotPersistQueue.setPaused(false);
         mSnapshotPersistQueue.waitForQueueEmpty();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 2689e9d14c00d6941e5342e04cb03e19a3eede22..a83caa4a4e959504bb2eac29c53d449fd9cdbdc3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -2454,6 +2454,7 @@ public class TransitionTests extends WindowTestsBase {
             spyOn(perfHinter);
             doAnswer(invocation -> {
                 session[0] = (SystemPerformanceHinter.HighPerfSession) invocation.callRealMethod();
+                spyOn(session[0]);
                 return session[0];
             }).when(perfHinter).createSession(anyInt(), anyInt(), anyString());
         }
diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt
index f61cce666cca33b8155347c453cfd3b19dce4b7c..daaab3314679e7ce6fb5be46b761299afca3efce 100644
--- a/test-mock/api/current.txt
+++ b/test-mock/api/current.txt
@@ -156,6 +156,7 @@ package android.test.mock {
     method @Deprecated public int getInt(int);
     method @Deprecated public long getLong(int);
     method @Deprecated public android.net.Uri getNotificationUri();
+    method @Deprecated public java.util.List<android.net.Uri> getNotificationUris();
     method @Deprecated public int getPosition();
     method @Deprecated public short getShort(int);
     method @Deprecated public String getString(int);
@@ -179,6 +180,7 @@ package android.test.mock {
     method @Deprecated public android.os.Bundle respond(android.os.Bundle);
     method @Deprecated public void setExtras(android.os.Bundle);
     method @Deprecated public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
+    method @Deprecated public void setNotificationUris(android.content.ContentResolver, java.util.List<android.net.Uri>);
     method @Deprecated public void unregisterContentObserver(android.database.ContentObserver);
     method @Deprecated public void unregisterDataSetObserver(android.database.DataSetObserver);
   }
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
index 790da34b1b0847bb5e46073b9dfe877b1ec4c903..12a57d52491a15319d1be3d53d7adccdf1032f72 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -18,6 +18,7 @@ package com.android.server.wm.flicker.activityembedding.pip
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.datatypes.Rect
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.common.traces.component.ComponentNameMatcher.Companion.TRANSITION_SNAPSHOT
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -149,8 +150,12 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) :
                 ComponentNameMatcher.PIP_CONTENT_OVERLAY.layerMatchesAnyOf(it) && it.isVisible
             }
             pipLayerList.zipWithNext { previous, current ->
-                // TODO(b/290987990): Add checks for visibleRegion.
-                current.screenBounds.isToTheRightBottom(previous.screenBounds.region, 3)
+                if (startDisplayBounds.width > startDisplayBounds.height) {
+                    // Only verify when the display is landscape, because otherwise the final pip
+                    // window can be to the left of the original secondary activity.
+                    current.screenBounds.isToTheRightBottom(previous.screenBounds.region, 3)
+                }
+                current.screenBounds.overlaps(previous.screenBounds.region)
                 current.screenBounds.notBiggerThan(previous.screenBounds.region)
             }
         }
@@ -161,6 +166,61 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) :
         }
     }
 
+    /** The secondary layer should never jump to the left. */
+    @Presubmit
+    @Test
+    fun secondaryLayerNotJumpToLeft() {
+        flicker.assertLayers {
+            invoke("secondaryLayerNotJumpToLeft") {
+                val secondaryVisibleRegion =
+                    it.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                if (secondaryVisibleRegion.region.isNotEmpty) {
+                    check { "left" }
+                        .that(secondaryVisibleRegion.region.bounds.left)
+                        .isGreater(0)
+                }
+            }
+        }
+    }
+
+    /**
+     * The pip overlay layer should cover exactly the secondary activity layer when both are
+     * visible.
+     */
+    @Presubmit
+    @Test
+    fun pipContentOverlayLayerCoversExactlySecondaryLayer() {
+        flicker.assertLayers {
+            isInvisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+                .then()
+                .isVisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+                .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .invoke("pipContentOverlayLayerCoversExactlySecondaryLayer") {
+                    val overlayVisibleRegion =
+                        it.visibleRegion(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+                    val secondaryVisibleRegion =
+                        it.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                    overlayVisibleRegion.coversExactly(secondaryVisibleRegion.region)
+                }
+                .then()
+                .isInvisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+                .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    @Presubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        // Expected for the main activity to become invisible for 1-2 frames because the snapshot
+        // covers it.
+        flicker.assertLayers {
+            visibleLayersShownMoreThanOneConsecutiveEntry(
+                LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS +
+                        listOf(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            )
+        }
+    }
+
     companion object {
         /** {@inheritDoc} */
         private var startDisplayBounds = Rect.EMPTY
diff --git a/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING
index 970362611f53ac2efb3b1e7264740d2e6c39c68b..e02492d68bb4e182519080ca4bbd05890eac16cb 100644
--- a/tools/hoststubgen/TEST_MAPPING
+++ b/tools/hoststubgen/TEST_MAPPING
@@ -1,6 +1,8 @@
 {
     // TODO: Change to presubmit.
     "postsubmit": [
-        { "name": "tiny-framework-dump-test" }
+        { "name": "tiny-framework-dump-test" },
+        { "name": "hoststubgentest" },
+        { "name": "hoststubgen-invoke-test" }
     ]
 }
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index 182940e6f7c5112ab09bfa693238cb1999ee0932..fd4ec8bd5931e1837e14045152a3918eca8d0cde 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -99,6 +99,18 @@ java_binary_host {
     visibility: ["//visibility:public"],
 }
 
+java_test_host {
+    name: "hoststubgentest",
+    // main_class: "com.android.hoststubgen.Main",
+    srcs: ["test/**/*.kt"],
+    static_libs: [
+        "hoststubgen",
+        "truth",
+    ],
+    test_suites: ["general-tests"],
+    visibility: ["//visibility:private"],
+}
+
 // File that contains the standard command line argumetns to hoststubgen.
 // This is only for the prototype. The productionized version is "ravenwood-standard-options".
 filegroup {
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
index d749f076f3b52d129c021820aa1a43c164cc489f..12c7841556fc2246cfdada52f6b3a6d05524914e 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
@@ -20,6 +20,7 @@ import android.os.IBinder;
 import java.io.FileDescriptor;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
@@ -197,9 +198,9 @@ public class Parcel_host {
         if (b == null) {
             nativeWriteInt(nativePtr, -1);
         } else {
-            final var alignedSize = align4(b.length);
+            final var alignedSize = align4(len);
 
-            nativeWriteInt(nativePtr, b.length);
+            nativeWriteInt(nativePtr, len);
 
             p.ensureMoreCapacity(alignedSize);
 
@@ -280,6 +281,7 @@ public class Parcel_host {
                     + data.length + " given=" + destLen);
             return false;
         }
+        System.arraycopy(data, 0, dest, 0, data.length);
         return true;
     }
 
@@ -289,7 +291,12 @@ public class Parcel_host {
             return null;
         }
         var p = getInstance(nativePtr);
-        p.ensureDataAvailable(size);
+        try {
+            p.ensureDataAvailable(align4(size));
+        } catch (Exception e) {
+            System.err.println(e.toString());
+            return null;
+        }
 
         var bytes = new byte[size];
         System.arraycopy(p.mBuffer, p.mPos, bytes, 0, size);
@@ -301,7 +308,10 @@ public class Parcel_host {
     public static int nativeReadInt(long nativePtr) {
         var p = getInstance(nativePtr);
 
-        p.ensureDataAvailable(Integer.BYTES);
+        if (p.mSize - p.mPos < 4) {
+            // Match native impl that returns "0" when not enough data
+            return 0;
+        }
 
         var ret = (((p.mBuffer[p.mPos++] & 0xff) << 24)
                 | ((p.mBuffer[p.mPos++] & 0xff) << 16)
@@ -341,11 +351,16 @@ public class Parcel_host {
     }
 
     public static byte[] nativeMarshall(long nativePtr) {
-        throw new RuntimeException("Not implemented yet");
+        var p = getInstance(nativePtr);
+        return Arrays.copyOf(p.mBuffer, p.mSize);
     }
     public static void nativeUnmarshall(
             long nativePtr, byte[] data, int offset, int length) {
-        throw new RuntimeException("Not implemented yet");
+        var p = getInstance(nativePtr);
+        p.ensureMoreCapacity(length);
+        System.arraycopy(data, offset, p.mBuffer, p.mPos, length);
+        p.mPos += length;
+        p.updateSize();
     }
     public static int nativeCompareData(long thisNativePtr, long otherNativePtr) {
         throw new RuntimeException("Not implemented yet");
diff --git a/tools/hoststubgen/hoststubgen/invoketest/Android.bp b/tools/hoststubgen/hoststubgen/invoketest/Android.bp
new file mode 100644
index 0000000000000000000000000000000000000000..7e90e421a1f9d073cf4637e1d23ed291094ee8a4
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/invoketest/Android.bp
@@ -0,0 +1,20 @@
+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_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+sh_test_host {
+    name: "hoststubgen-invoke-test",
+    src: "hoststubgen-invoke-test.sh",
+    test_suites: ["general-tests"],
+
+    // Note: java_data: ["hoststubgen"] will only install the jar file, but not the command wrapper.
+    java_data: [
+        "hoststubgen",
+        "hoststubgen-test-tiny-framework",
+    ],
+}
diff --git a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
new file mode 100755
index 0000000000000000000000000000000000000000..34b2145b8d2264dbb46ee5bb363ffe5bf10649e3
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
@@ -0,0 +1,163 @@
+#!/bin/bash
+# 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.
+
+set -e # Exit when any command files
+
+# This script runs HostStubGen directly with various arguments and make sure
+# the tool behaves in the expected way.
+
+
+echo "# Listing files in the test environment"
+ls -lR
+
+echo "# Dumping the environment variables"
+env
+
+# Set up the constants and variables
+
+export TEMP=$TEST_TMPDIR
+
+JAR=hoststubgen-test-tiny-framework.jar
+STUB=$TEMP/stub.jar
+IMPL=$TEMP/impl.jar
+
+ANNOTATION_FILTER=$TEMP/annotation-filter.txt
+
+HOSTSTUBGEN_OUT=$TEMP/output.txt
+
+# Because of `set -e`, we can't return non-zero from functions, so we store
+# HostStubGen result in it.
+HOSTSTUBGEN_RC=0
+
+# Define the functions to
+
+
+# Note, because the build rule will only install hoststubgen.jar, but not the wrapper script,
+# we need to execute it manually with the java command.
+hoststubgen() {
+  java -jar ./hoststubgen.jar "$@"
+}
+
+run_hoststubgen() {
+  local test_name="$1"
+  local annotation_filter="$2"
+
+  echo "# Test: $test_name"
+
+  rm -f $HOSTSTUBGEN_OUT
+
+  local filter_arg=""
+
+  if [[ "$annotation_filter" != "" ]] ; then
+    echo "$annotation_filter" > $ANNOTATION_FILTER
+    filter_arg="--annotation-allowed-classes-file $ANNOTATION_FILTER"
+    echo "=== filter ==="
+    cat $ANNOTATION_FILTER
+  fi
+
+  hoststubgen \
+      --debug \
+      --in-jar $JAR \
+      --out-stub-jar $STUB \
+      --out-impl-jar $IMPL \
+      $filter_arg \
+      |& tee $HOSTSTUBGEN_OUT
+  HOSTSTUBGEN_RC=${PIPESTATUS[0]}
+  echo "HostStubGen exited with $HOSTSTUBGEN_RC"
+  return 0
+}
+
+run_hoststubgen_for_success() {
+  run_hoststubgen "$@"
+
+  if (( $HOSTSTUBGEN_RC != 0 )) ; then
+    echo "HostStubGen expected to finish successfully, but failed with $rc"
+    return 1
+  fi
+}
+
+run_hoststubgen_for_failure() {
+  local test_name="$1"
+  local expected_error_message="$2"
+  shift 2
+
+  run_hoststubgen "$test_name" "$@"
+
+  if (( $HOSTSTUBGEN_RC == 0 )) ; then
+    echo "HostStubGen expected to fail, but it didn't fail"
+    return 1
+  fi
+
+  # The output should contain the expected message. (note we se fgrep here.)
+  grep -Fq "$expected_error_message" $HOSTSTUBGEN_OUT
+}
+
+# Start the tests...
+
+# Pass "" as a filter to _not_ add `--annotation-allowed-classes-file`.
+run_hoststubgen_for_success "No annotation filter" ""
+
+# Now, we use " ", so we do add `--annotation-allowed-classes-file`.
+run_hoststubgen_for_failure "No classes are allowed to have annotations" \
+    "not allowed to have Ravenwood annotations" \
+    " "
+
+run_hoststubgen_for_success "All classes allowed (wildcard)" \
+    "
+* # Allow all classes
+"
+
+run_hoststubgen_for_failure "All classes disallowed (wildcard)" \
+    "not allowed to have Ravenwood annotations" \
+    "
+!* # Disallow all classes
+"
+
+run_hoststubgen_for_failure "Some classes not allowed (1)" \
+    "not allowed to have Ravenwood annotations" \
+    "
+android.hosttest.*
+com.android.hoststubgen.*
+com.supported.*
+"
+
+run_hoststubgen_for_failure "Some classes not allowed (2)" \
+    "not allowed to have Ravenwood annotations" \
+    "
+android.hosttest.*
+com.android.hoststubgen.*
+com.unsupported.*
+"
+
+run_hoststubgen_for_success "All classes allowed (package wildcard)" \
+    "
+android.hosttest.*
+com.android.hoststubgen.*
+com.supported.*
+com.unsupported.*
+"
+
+
+run_hoststubgen_for_failure "One specific class disallowed" \
+    "TinyFrameworkClassAnnotations is not allowed to have Ravenwood annotations" \
+    "
+!com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
+* # All other classes allowed
+"
+
+
+
+echo "All tests passed"
+exit 0
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 872d56856c3378593ef88a61bb8171032a72999e..f32dc721873e63e374ced5d5786f3ffde3464e61 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -27,6 +27,7 @@ import com.android.hoststubgen.filters.OutputFilter
 import com.android.hoststubgen.filters.StubIntersectingFilter
 import com.android.hoststubgen.filters.createFilterFromTextPolicyFile
 import com.android.hoststubgen.filters.printAsTextPolicy
+import com.android.hoststubgen.utils.ClassFilter
 import com.android.hoststubgen.visitors.BaseAdapter
 import com.android.hoststubgen.visitors.PackageRedirectRemapper
 import org.objectweb.asm.ClassReader
@@ -167,6 +168,14 @@ class HostStubGen(val options: HostStubGenOptions) {
             filter
         )
 
+        val annotationAllowedClassesFilter = options.annotationAllowedClassesFile.let { filename ->
+            if (filename == null) {
+                ClassFilter.newNullFilter(true) // Allow all classes
+            } else {
+                ClassFilter.loadFromFile(filename, false)
+            }
+        }
+
         // Next, Java annotation based filter.
         filter = AnnotationBasedFilter(
             errors,
@@ -181,7 +190,8 @@ class HostStubGen(val options: HostStubGenOptions) {
             options.nativeSubstituteAnnotations,
             options.classLoadHookAnnotations,
             options.stubStaticInitializerAnnotations,
-            filter
+            annotationAllowedClassesFilter,
+            filter,
         )
 
         // Next, "text based" filter, which allows to override polices without touching
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index d74612d48de29dd54aa33d878abddc538ac3b09e..aab02b8de2548440a9907dfab6e5425c0990bbb7 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -51,6 +51,8 @@ class HostStubGenOptions(
 
         var packageRedirects: MutableList<Pair<String, String>> = mutableListOf(),
 
+        var annotationAllowedClassesFile: String? = null,
+
         var defaultClassLoadHook: String? = null,
         var defaultMethodCallHook: String? = null,
 
@@ -171,6 +173,9 @@ class HostStubGenOptions(
                     "--package-redirect" ->
                         ret.packageRedirects += parsePackageRedirect(ai.nextArgRequired(arg))
 
+                    "--annotation-allowed-classes-file" ->
+                        ret.annotationAllowedClassesFile = ai.nextArgRequired(arg)
+
                     "--default-class-load-hook" ->
                         ret.defaultClassLoadHook = ai.nextArgRequired(arg)
 
@@ -314,6 +319,7 @@ class HostStubGenOptions(
               nativeSubstituteAnnotations=$nativeSubstituteAnnotations,
               classLoadHookAnnotations=$classLoadHookAnnotations,
               packageRedirects=$packageRedirects,
+              $annotationAllowedClassesFile=$annotationAllowedClassesFile,
               defaultClassLoadHook=$defaultClassLoadHook,
               defaultMethodCallHook=$defaultMethodCallHook,
               intersectStubJars=$intersectStubJars,
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
index f75062b3a878d82d14b1ca827fb416b6da5bc5d4..937e56c2cbb566225b733e64984b5a183b0e8f29 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
@@ -58,4 +58,29 @@ fun <T> addNonNullElement(a: List<T>, b: T?): List<T> {
         return listOf(b)
     }
     return a + b
-}
\ No newline at end of file
+}
+
+
+/**
+ * Exception for a parse error in a file
+ */
+class ParseException : Exception, UserErrorException {
+    val hasSourceInfo: Boolean
+
+    constructor(message: String) : super(message) {
+        hasSourceInfo = false
+    }
+
+    constructor(message: String, file: String, line: Int) :
+            super("$message in file $file line $line") {
+        hasSourceInfo = true
+    }
+
+    fun withSourceInfo(filename: String, lineNo: Int): ParseException {
+        if (hasSourceInfo) {
+            return this // Already has source information.
+        } else {
+            return ParseException(this.message ?: "", filename, lineNo)
+        }
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index a51bdcf0c7931a1185b735cf839a54c5329f38fc..1bcf3642082fe7bab2edf65329c3aeafe5b279a7 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -22,6 +22,8 @@ import org.objectweb.asm.Opcodes
 import org.objectweb.asm.Type
 import org.objectweb.asm.tree.AnnotationNode
 import org.objectweb.asm.tree.ClassNode
+import org.objectweb.asm.tree.FieldNode
+import org.objectweb.asm.tree.MethodNode
 
 
 /** Name of the class initializer method. */
@@ -175,3 +177,93 @@ fun isVisibilityPrivateOrPackagePrivate(access: Int): Boolean {
         else -> false
     }
 }
+
+fun ClassNode.isEnum(): Boolean {
+    return (this.access and Opcodes.ACC_ENUM) != 0
+}
+
+fun ClassNode.isAnnotation(): Boolean {
+    return (this.access and Opcodes.ACC_ANNOTATION) != 0
+}
+
+fun ClassNode.isSynthetic(): Boolean {
+    return (this.access and Opcodes.ACC_SYNTHETIC) != 0
+}
+
+fun MethodNode.isSynthetic(): Boolean {
+    return (this.access and Opcodes.ACC_SYNTHETIC) != 0
+}
+
+fun FieldNode.isEnum(): Boolean {
+    return (this.access and Opcodes.ACC_ENUM) != 0
+}
+
+fun FieldNode.isSynthetic(): Boolean {
+    return (this.access and Opcodes.ACC_SYNTHETIC) != 0
+}
+
+/*
+
+Dump of the members of TinyFrameworkEnumSimple:
+
+class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple	keep
+  field Cat	keep (ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM)
+  field Dog	keep
+  field $VALUES	keep (ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC)
+
+  method values	()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;	keep
+    ^- NOT synthetic (ACC_PUBLIC, ACC_STATIC)
+  method valueOf	(Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;	keep
+    ^- NOT synthetic (ACC_PUBLIC, ACC_STATIC)
+  method <init>	(Ljava/lang/String;I)V	keep
+    ^- NOT synthetic (ACC_PRIVATE)
+
+  method $values	()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;	keep
+     (ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC)
+  method <clinit>	()V	keep
+
+Dump of the members of TinyFrameworkEnumSimple:
+
+class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex	keep
+  field RED	keep
+  field BLUE	keep
+  field GREEN	keep
+  field mLongName	keep
+  field mShortName	keep
+  field $VALUES	keep
+  method values	()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;	keep
+  method valueOf	(Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;	keep
+  method <init>	(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V	keep
+  method getLongName	()Ljava/lang/String;	keep
+  method getShortName	()Ljava/lang/String;	keep
+  method $values	()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;	keep
+  method <clinit>	()V	keep
+
+ */
+
+fun isAutoGeneratedEnumMember(mn: MethodNode): Boolean {
+    if (mn.isSynthetic()) {
+        return true
+    }
+    if (mn.name == "<init>" && mn.desc == "(Ljava/lang/String;I)V") {
+        return true
+    }
+    if (mn.name == "<clinit>" && mn.desc == "()V") {
+        return true
+    }
+    if (mn.name == "values" && mn.desc.startsWith("()")) {
+        return true
+    }
+    if (mn.name == "valueOf" && mn.desc.startsWith("(Ljava/lang/String;)")) {
+        return true
+    }
+
+    return false
+}
+
+fun isAutoGeneratedEnumMember(fn: FieldNode): Boolean {
+    if (fn.isSynthetic() || fn.isEnum()) {
+        return true
+    }
+    return false
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
index 9f3ec4d2b4507d215adda09614ad1589cbdd8408..9bb5381eef06e5dc2fe96c7cb0386d2fbcc19d09 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
@@ -25,9 +25,11 @@ import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
 import com.android.hoststubgen.asm.ClassNodes
 import com.android.hoststubgen.asm.findAnnotationValueAsString
 import com.android.hoststubgen.asm.findAnyAnnotation
+import com.android.hoststubgen.asm.toHumanReadableClassName
 import com.android.hoststubgen.asm.toHumanReadableMethodName
 import com.android.hoststubgen.asm.toJvmClassName
 import com.android.hoststubgen.log
+import com.android.hoststubgen.utils.ClassFilter
 import org.objectweb.asm.tree.AnnotationNode
 import org.objectweb.asm.tree.ClassNode
 
@@ -51,6 +53,7 @@ class AnnotationBasedFilter(
         nativeSubstituteAnnotations_: Set<String>,
         classLoadHookAnnotations_: Set<String>,
         stubStaticInitializerAnnotations_: Set<String>,
+        private val annotationAllowedClassesFilter: ClassFilter,
         fallback: OutputFilter,
 ) : DelegatingFilter(fallback) {
     private var stubAnnotations = convertToInternalNames(stubAnnotations_)
@@ -62,7 +65,8 @@ class AnnotationBasedFilter(
     private var substituteAnnotations = convertToInternalNames(substituteAnnotations_)
     private var nativeSubstituteAnnotations = convertToInternalNames(nativeSubstituteAnnotations_)
     private var classLoadHookAnnotations = convertToInternalNames(classLoadHookAnnotations_)
-    private var stubStaticInitializerAnnotations = convertToInternalNames(stubStaticInitializerAnnotations_)
+    private var stubStaticInitializerAnnotations =
+            convertToInternalNames(stubStaticInitializerAnnotations_)
 
     /** Annotations that control API visibility. */
     private var visibilityAnnotations: Set<String> = convertToInternalNames(
@@ -135,15 +139,22 @@ class AnnotationBasedFilter(
      * name1 - 4 are only used in exception messages.
      */
     private fun findAnnotation(
-        visibles: List<AnnotationNode>?,
-        invisibles: List<AnnotationNode>?,
-        type: String,
-        name1: String,
-        name2: String = "",
-        name3: String = "",
+            className: String,
+            visibles: List<AnnotationNode>?,
+            invisibles: List<AnnotationNode>?,
+            type: String,
+            name1: String,
+            name2: String = "",
+            name3: String = "",
     ): FilterPolicyWithReason? {
         detectInvalidAnnotations(visibles, invisibles, type, name1, name2, name3)
 
+        if (!annotationAllowedClassesFilter.matches(className)) {
+            throw InvalidAnnotationException(
+                    "Class ${className.toHumanReadableClassName()} is not allowed to have " +
+                    "Ravenwood annotations. Contact g/ravenwood for more details.")
+        }
+
         findAnyAnnotation(stubAnnotations, visibles, invisibles)?.let {
             return FilterPolicy.Stub.withReason(reasonAnnotation)
         }
@@ -170,6 +181,7 @@ class AnnotationBasedFilter(
         val cn = classes.getClass(className)
 
         findAnnotation(
+            cn.name,
             cn.visibleAnnotations,
             cn.invisibleAnnotations,
             "class",
@@ -193,6 +205,7 @@ class AnnotationBasedFilter(
 
         cn.fields?.firstOrNull { it.name == fieldName }?.let {fn ->
             findAnnotation(
+                cn.name,
                 fn.visibleAnnotations,
                 fn.invisibleAnnotations,
                 "field",
@@ -229,6 +242,7 @@ class AnnotationBasedFilter(
 
             // If there's no substitution, then we check the annotation.
             findAnnotation(
+                cn.name,
                 mn.visibleAnnotations,
                 mn.invisibleAnnotations,
                 "method",
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
index 07a023c66926dcf78e13b50103da1a6473c864ac..9c0fa69f8a14aade75950c9aeaf9c345c9dbb3a7 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
@@ -22,6 +22,9 @@ import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
 import com.android.hoststubgen.asm.isAnonymousInnerClass
 import com.android.hoststubgen.log
 import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.isAnnotation
+import com.android.hoststubgen.asm.isAutoGeneratedEnumMember
+import com.android.hoststubgen.asm.isEnum
 import com.android.hoststubgen.asm.isVisibilityPrivateOrPackagePrivate
 import org.objectweb.asm.tree.ClassNode
 
@@ -57,18 +60,8 @@ class ImplicitOutputFilter(
     override fun getPolicyForClass(className: String): FilterPolicyWithReason {
         val fallback = super.getPolicyForClass(className)
 
-        // TODO: This check should be cached.
         val cn = classes.getClass(className)
 
-        if (cn.superName == "java/lang/Enum" &&
-                fallback.policy == FilterPolicy.Keep) {
-            return FilterPolicy.KeepClass.withReason("enum")
-        }
-        if (cn.interfaces.contains("java/lang/annotation/Annotation") &&
-                fallback.policy == FilterPolicy.Keep) {
-            return FilterPolicy.KeepClass.withReason("annotation")
-        }
-
         // Use the implicit policy, if any.
         getClassImplicitPolicy(className, cn)?.let { return it }
 
@@ -95,16 +88,78 @@ class ImplicitOutputFilter(
             }
         }
 
+        val cn = classes.getClass(className)
+
         // If we throw from the static initializer, the class would be useless, so we convert it
         // "keep" instead.
-        if (methodName == CLASS_INITIALIZER_NAME && descriptor == CLASS_INITIALIZER_DESC &&
-                fallback.policy == FilterPolicy.Throw) {
+        // Unless it's an enum -- in that case, the below code would handle it.
+        if (!cn.isEnum() &&
+                fallback.policy == FilterPolicy.Throw &&
+                methodName == CLASS_INITIALIZER_NAME && descriptor == CLASS_INITIALIZER_DESC) {
             // TODO Maybe show a warning?? But that'd be too noisy with --default-throw.
             return FilterPolicy.Ignore.withReason(
                 "'throw' on static initializer is handled as 'ignore'" +
                         " [original throw reason: ${fallback.reason}]")
         }
 
+        val classPolicy = super.getPolicyForClass(className)
+
+        log.d("Class ${cn.name} Class policy: $classPolicy")
+        if (classPolicy.policy.needsInImpl) {
+            // Do it only when the class needs to be kept...
+
+            // Member policy should be "keep" or "stub".
+            val memberPolicy = classPolicy.policy.resolveClassWidePolicy()
+
+            // Keep (or stub) the generated enum members.
+            if (cn.isEnum()) {
+                classes.findMethod(className, methodName, descriptor)?.let { mn ->
+                    if (isAutoGeneratedEnumMember(mn)) {
+                        return memberPolicy.withReason(classPolicy.reason).wrapReason("enum")
+                    }
+                }
+            }
+
+            // Keep (or stub) all members of annotations.
+            if (cn.isAnnotation()) {
+                return memberPolicy.withReason(classPolicy.reason).wrapReason("annotation")
+            }
+        }
+
+        return fallback
+    }
+
+    override fun getPolicyForField(
+            className: String,
+            fieldName: String
+    ): FilterPolicyWithReason {
+        val fallback = super.getPolicyForField(className, fieldName)
+
+        val cn = classes.getClass(className)
+        val classPolicy = super.getPolicyForClass(className)
+
+        log.d("Class ${cn.name} Class policy: $classPolicy")
+        if (classPolicy.policy.needsInImpl) {
+            // Do it only when the class needs to be kept...
+
+            // Member policy should be "keep" or "stub".
+            val memberPolicy = classPolicy.policy.resolveClassWidePolicy()
+
+            // Keep (or stub) the generated enum members.
+            if (cn.isEnum()) {
+                classes.findField(className, fieldName)?.let { fn ->
+                    if (isAutoGeneratedEnumMember(fn)) {
+                        return memberPolicy.withReason(classPolicy.reason).wrapReason("enum")
+                    }
+                }
+            }
+
+            // Keep (or stub) all members of annotations.
+            if (cn.isAnnotation()) {
+                return memberPolicy.withReason(classPolicy.reason).wrapReason("annotation")
+            }
+        }
+
         return fallback
     }
 }
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index 46546e8b94913abac73c5fd3e71b91e582d7b9d1..416f08505867858311ddcb703ffc8b92f7f52bd6 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -15,7 +15,7 @@
  */
 package com.android.hoststubgen.filters
 
-import com.android.hoststubgen.UserErrorException
+import com.android.hoststubgen.ParseException
 import com.android.hoststubgen.asm.ClassNodes
 import com.android.hoststubgen.log
 import com.android.hoststubgen.normalizeTextLine
@@ -46,30 +46,6 @@ private fun isVisible(access: Int): Boolean {
     return (access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED)) != 0
 }
 
-/**
- * Exception for a parse error.
- */
-private class ParseException : Exception, UserErrorException {
-    val hasSourceInfo: Boolean
-
-    constructor(message: String) : super(message) {
-        hasSourceInfo = false
-    }
-
-    constructor(message: String, file: String, line: Int) :
-            super("$message in file $file line $line") {
-        hasSourceInfo = true
-    }
-
-    fun withSourceInfo(filename: String, lineNo: Int): ParseException {
-        if (hasSourceInfo) {
-            return this // Already has source information.
-        } else {
-            return ParseException(this.message ?: "", filename, lineNo)
-        }
-    }
-}
-
 private const val FILTER_REASON = "file-override"
 
 /**
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/ClassFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/ClassFilter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..01a7ab3eacfa56c2026c05dfc29a42d39230b35a
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/ClassFilter.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.utils
+
+import com.android.hoststubgen.ParseException
+import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.asm.toJvmClassName
+import com.android.hoststubgen.normalizeTextLine
+import java.io.File
+
+/**
+ * General purpose filter for class names.
+ */
+class ClassFilter private constructor (
+        val defaultResult: Boolean,
+) {
+    private data class FilterElement(
+            val allowed: Boolean,
+            val internalName: String,
+            val isPrefix: Boolean,
+    ) {
+        fun matches(classInternalName: String): Boolean {
+            if (isPrefix) {
+                return classInternalName.startsWith(internalName)
+            } else {
+                return classInternalName == internalName
+            }
+        }
+    }
+
+    private val elements: MutableList<FilterElement> = mutableListOf()
+
+    private val cache: MutableMap<String, Boolean> = mutableMapOf()
+
+    /**
+     * Takes an internal class name (e.g. "com/android/hoststubgen/ClassName") and returns if
+     * matches the filter or not.
+     */
+    fun matches(classInternalName: String): Boolean {
+        cache[classInternalName]?.let {
+            return it
+        }
+
+        var result = defaultResult
+        run outer@{
+            elements.forEach { e ->
+                if (e.matches(classInternalName)) {
+                    result = e.allowed
+                    return@outer // break equivalent.
+                }
+            }
+        }
+        cache[classInternalName] = result
+
+        return result
+    }
+
+    fun getCacheSizeForTest(): Int {
+        return cache.size
+    }
+
+    companion object {
+        /**
+         * Return a filter that alawys returns true or false.
+         */
+        fun newNullFilter(defaultResult: Boolean): ClassFilter {
+            return ClassFilter(defaultResult)
+        }
+
+        /** Build a filter from a file. */
+        fun loadFromFile(filename: String, defaultResult: Boolean): ClassFilter {
+            return buildFromString(File(filename).readText(), defaultResult, filename)
+        }
+
+        /** Build a filter from a string (for unit tests). */
+        fun buildFromString(
+                filterString: String,
+                defaultResult: Boolean,
+                filenameForErrorMessage: String
+        ): ClassFilter {
+            val ret = ClassFilter(defaultResult)
+
+            var lineNo = 0
+            filterString.split('\n').forEach { s ->
+                lineNo++
+
+                var line = normalizeTextLine(s)
+
+                if (line.isEmpty()) {
+                    return@forEach // skip empty lines.
+                }
+
+                line = line.toHumanReadableClassName() // Convert all the slashes to periods.
+
+                var allow = true
+                if (line.startsWith("!")) {
+                    allow = false
+                    line = line.substring(1).trimStart()
+                }
+
+                // Special case -- matches any class names.
+                if (line == "*") {
+                    ret.elements.add(FilterElement(allow, "", true))
+                    return@forEach
+                }
+
+                // Handle wildcard -- e.g. "package.name.*"
+                if (line.endsWith(".*")) {
+                    ret.elements.add(FilterElement(
+                            allow, line.substring(0, line.length - 2).toJvmClassName(), true))
+                    return@forEach
+                }
+
+                // Any other uses of "*" would be an error.
+                if (line.contains('*')) {
+                    throw ParseException(
+                            "Wildcard (*) can only show up as the last element",
+                            filenameForErrorMessage,
+                            lineNo
+                    )
+                }
+                ret.elements.add(FilterElement(allow, line.toJvmClassName(), false))
+            }
+
+            return ret
+        }
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp b/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp
index 3dc6da348937face5401e7a23254b25f99c6bad4..e7873d6eecc37b6618a3b1a848d2dd9e9234b63d 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp
@@ -16,6 +16,7 @@ java_library {
     static_libs: [
         "hoststubgen-annotations",
     ],
+    visibility: ["//frameworks/base/tools/hoststubgen:__subpackages__"],
 }
 
 // Create stub/impl jars from "hoststubgen-test-tiny-framework", using the following 3 rules.
@@ -30,6 +31,7 @@ java_genrule_host {
         ":hoststubgen-test-tiny-framework",
         "policy-override-tiny-framework.txt",
     ],
+    visibility: ["//visibility:private"],
 }
 
 java_genrule_host {
@@ -41,6 +43,7 @@ java_genrule_host {
     out: [
         "host_stub.jar",
     ],
+    visibility: ["//visibility:private"],
 }
 
 java_genrule_host {
@@ -52,6 +55,7 @@ java_genrule_host {
     out: [
         "host_impl.jar",
     ],
+    visibility: ["//visibility:private"],
 }
 
 // Same as "hoststubgen-test-tiny-framework-host", but with more options, to test more hoststubgen
@@ -71,6 +75,7 @@ java_genrule_host {
         ":hoststubgen-test-tiny-framework",
         "policy-override-tiny-framework.txt",
     ],
+    visibility: ["//visibility:private"],
 }
 
 java_genrule_host {
@@ -82,6 +87,7 @@ java_genrule_host {
     out: [
         "host_stub.jar",
     ],
+    visibility: ["//visibility:private"],
 }
 
 java_genrule_host {
@@ -93,6 +99,7 @@ java_genrule_host {
     out: [
         "host_impl.jar",
     ],
+    visibility: ["//visibility:private"],
 }
 
 // Compile the test jar, using 2 rules.
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt
new file mode 100644
index 0000000000000000000000000000000000000000..bd9e85e17890f41ef0a53033deaa27a9e7175626
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt
@@ -0,0 +1,29 @@
+# Only classes listed here can use the hoststubgen annotations.
+
+# For each class, we check each item in this file, and when a match is found, we
+# either allow it if the line doesn't have a !, or disallow if the line has a !.
+# All the lines after the matching line will be ignored.
+
+
+# To allow a specific class to use annotations:
+# com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
+
+# To disallow a specific class to use annotations:
+# !com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
+
+# To allow a specific package to use annotations:
+# com.android.hoststubgen.test.*
+
+# To disallow a specific package to use annotations:
+# !com.android.hoststubgen.test.*
+
+
+com.android.hoststubgen.test.tinyframework.*
+com.supported.*
+com.unsupported.*
+
+# Use this to allow all packages
+# *
+
+# Use this to allow all packages
+# !*
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index f627c6e7d7b5311777e37db407b58a5eed066bcd..78a4fa692c217ddb4bb939693516167d4b4be771 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -879,6 +879,314 @@ RuntimeInvisibleAnnotations:
     android.hosttest.annotation.HostSideTestStub
   x: #x()
     android.hosttest.annotation.HostSideTestStaticInitializerStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.class
+  Compiled from "TinyFrameworkEnumComplex.java"
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex>
+  minor version: 0
+  major version: 61
+  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+  super_class: #x                        // java/lang/Enum
+  interfaces: 0, fields: 6, methods: 7, attributes: 3
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex GREEN;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex BLUE;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  private final java.lang.String mLongName;
+    descriptor: Ljava/lang/String;
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private final java.lang.String mShortName;
+    descriptor: Ljava/lang/String;
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $VALUES;
+    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: getstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+         x: invokevirtual #x                 // Method "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;".clone:()Ljava/lang/Object;
+         x: checkcast     #x                 // class "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;"
+         x: areturn
+      LineNumberTable:
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex valueOf(java.lang.String);
+    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: aload_0
+         x: invokestatic  #x                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
+         x: checkcast     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  name   Ljava/lang/String;
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
+    descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=3, locals=5, args_size=5
+         x: aload_0
+         x: aload_1
+         x: iload_2
+         x: invokespecial #x                 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
+         x: aload_0
+         x: aload_3
+         x: putfield      #x                 // Field mLongName:Ljava/lang/String;
+        x: aload_0
+        x: aload         4
+        x: putfield      #x                 // Field mShortName:Ljava/lang/String;
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      18     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+            0      18     3 longName   Ljava/lang/String;
+            0      18     4 shortName   Ljava/lang/String;
+    Signature: #x                          // (Ljava/lang/String;Ljava/lang/String;)V
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public java.lang.String getLongName();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: getfield      #x                 // Field mLongName:Ljava/lang/String;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public java.lang.String getShortName();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: getfield      #x                 // Field mShortName:Ljava/lang/String;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: iconst_3
+         x: anewarray     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: dup
+         x: iconst_0
+         x: getstatic     #x                  // Field RED:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+         x: aastore
+        x: dup
+        x: iconst_1
+        x: getstatic     #x                  // Field GREEN:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: aastore
+        x: dup
+        x: iconst_2
+        x: getstatic     #x                 // Field BLUE:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: aastore
+        x: areturn
+      LineNumberTable:
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=6, locals=0, args_size=0
+         x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: dup
+         x: ldc           #x                 // String RED
+         x: iconst_0
+         x: ldc           #x                 // String Red
+         x: ldc           #x                 // String R
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                  // Field RED:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: dup
+        x: ldc           #x                 // String GREEN
+        x: iconst_1
+        x: ldc           #x                 // String Green
+        x: ldc           #x                 // String G
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                  // Field GREEN:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: dup
+        x: ldc           #x                 // String BLUE
+        x: iconst_2
+        x: ldc           #x                 // String Blue
+        x: ldc           #x                 // String B
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                 // Field BLUE:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: invokestatic  #x                 // Method $values:()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: putstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: return
+      LineNumberTable:
+}
+Signature: #x                          // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;>;
+SourceFile: "TinyFrameworkEnumComplex.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.class
+  Compiled from "TinyFrameworkEnumSimple.java"
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple>
+  minor version: 0
+  major version: 61
+  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+  super_class: #x                        // java/lang/Enum
+  interfaces: 0, fields: 3, methods: 5, attributes: 3
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple DOG;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $VALUES;
+    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: getstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+         x: invokevirtual #x                 // Method "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;".clone:()Ljava/lang/Object;
+         x: checkcast     #x                 // class "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;"
+         x: areturn
+      LineNumberTable:
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple valueOf(java.lang.String);
+    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: aload_0
+         x: invokestatic  #x                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
+         x: checkcast     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  name   Ljava/lang/String;
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
+    descriptor: (Ljava/lang/String;I)V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=3, locals=3, args_size=3
+         x: aload_0
+         x: aload_1
+         x: iload_2
+         x: invokespecial #x                 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       7     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    Signature: #x                          // ()V
+
+  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: iconst_2
+         x: anewarray     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: dup
+         x: iconst_0
+         x: getstatic     #x                  // Field CAT:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+         x: aastore
+        x: dup
+        x: iconst_1
+        x: getstatic     #x                  // Field DOG:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: aastore
+        x: areturn
+      LineNumberTable:
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: dup
+         x: ldc           #x                 // String CAT
+         x: iconst_0
+         x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;I)V
+        x: putstatic     #x                  // Field CAT:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+        x: dup
+        x: ldc           #x                 // String DOG
+        x: iconst_1
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;I)V
+        x: putstatic     #x                  // Field DOG:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: invokestatic  #x                 // Method $values:()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: putstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: return
+      LineNumberTable:
+}
+Signature: #x                          // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;>;
+SourceFile: "TinyFrameworkEnumSimple.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
   Compiled from "TinyFrameworkExceptionTester.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index d7f0149494c0bc70b5d207a4c09f172eae7cdb03..df63815ea0ae7804581fd076dd767f57eeb7bcfa 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -446,6 +446,230 @@ RuntimeInvisibleAnnotations:
     android.hosttest.annotation.HostSideTestStub
   x: #x()
     android.hosttest.annotation.HostSideTestStaticInitializerStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.class
+  Compiled from "TinyFrameworkEnumComplex.java"
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex>
+  minor version: 0
+  major version: 61
+  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+  super_class: #x                         // java/lang/Enum
+  interfaces: 0, fields: 4, methods: 7, attributes: 4
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex GREEN;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex BLUE;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $VALUES;
+    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex valueOf(java.lang.String);
+    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
+    descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=3, locals=5, args_size=5
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    Signature: #x                          // (Ljava/lang/String;Ljava/lang/String;)V
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public java.lang.String getLongName();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public java.lang.String getShortName();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+}
+Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;>;
+SourceFile: "TinyFrameworkEnumComplex.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.class
+  Compiled from "TinyFrameworkEnumSimple.java"
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple>
+  minor version: 0
+  major version: 61
+  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+  super_class: #x                         // java/lang/Enum
+  interfaces: 0, fields: 3, methods: 5, attributes: 4
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple DOG;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $VALUES;
+    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple valueOf(java.lang.String);
+    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
+    descriptor: (Ljava/lang/String;I)V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=3, locals=3, args_size=3
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    Signature: #x                          // ()V
+
+  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+}
+Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;>;
+SourceFile: "TinyFrameworkEnumSimple.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
   Compiled from "TinyFrameworkExceptionTester.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
index 131e0b1f8b48a135f169d76bc6a96b22ffdb77c8..2218d8d0b2e123635eedc9ddb5240f30ccb71303 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -798,6 +798,324 @@ RuntimeInvisibleAnnotations:
     android.hosttest.annotation.HostSideTestStub
   x: #x()
     android.hosttest.annotation.HostSideTestStaticInitializerStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.class
+  Compiled from "TinyFrameworkEnumComplex.java"
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex>
+  minor version: 0
+  major version: 61
+  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+  super_class: #x                         // java/lang/Enum
+  interfaces: 0, fields: 6, methods: 7, attributes: 4
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex GREEN;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex BLUE;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  private final java.lang.String mLongName;
+    descriptor: Ljava/lang/String;
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private final java.lang.String mShortName;
+    descriptor: Ljava/lang/String;
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $VALUES;
+    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: getstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+         x: invokevirtual #x                 // Method "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;".clone:()Ljava/lang/Object;
+         x: checkcast     #x                 // class "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;"
+         x: areturn
+      LineNumberTable:
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex valueOf(java.lang.String);
+    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: aload_0
+         x: invokestatic  #x                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
+         x: checkcast     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  name   Ljava/lang/String;
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
+    descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=3, locals=5, args_size=5
+         x: aload_0
+         x: aload_1
+         x: iload_2
+         x: invokespecial #x                 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
+         x: aload_0
+         x: aload_3
+         x: putfield      #x                 // Field mLongName:Ljava/lang/String;
+        x: aload_0
+        x: aload         4
+        x: putfield      #x                 // Field mShortName:Ljava/lang/String;
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      18     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+            0      18     3 longName   Ljava/lang/String;
+            0      18     4 shortName   Ljava/lang/String;
+    Signature: #x                          // (Ljava/lang/String;Ljava/lang/String;)V
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public java.lang.String getLongName();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: getfield      #x                 // Field mLongName:Ljava/lang/String;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public java.lang.String getShortName();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: getfield      #x                 // Field mShortName:Ljava/lang/String;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: iconst_3
+         x: anewarray     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: dup
+         x: iconst_0
+         x: getstatic     #x                 // Field RED:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+         x: aastore
+        x: dup
+        x: iconst_1
+        x: getstatic     #x                 // Field GREEN:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: aastore
+        x: dup
+        x: iconst_2
+        x: getstatic     #x                 // Field BLUE:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: aastore
+        x: areturn
+      LineNumberTable:
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=6, locals=0, args_size=0
+         x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: dup
+         x: ldc           #x                 // String RED
+         x: iconst_0
+         x: ldc           #x                 // String Red
+         x: ldc           #x                 // String R
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                 // Field RED:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: dup
+        x: ldc           #x                 // String GREEN
+        x: iconst_1
+        x: ldc           #x                 // String Green
+        x: ldc           #x                 // String G
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                 // Field GREEN:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: dup
+        x: ldc           #x                 // String BLUE
+        x: iconst_2
+        x: ldc           #x                 // String Blue
+        x: ldc           #x                 // String B
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                 // Field BLUE:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: invokestatic  #x                 // Method $values:()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: putstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: return
+      LineNumberTable:
+}
+Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;>;
+SourceFile: "TinyFrameworkEnumComplex.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.class
+  Compiled from "TinyFrameworkEnumSimple.java"
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple>
+  minor version: 0
+  major version: 61
+  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+  super_class: #x                         // java/lang/Enum
+  interfaces: 0, fields: 3, methods: 5, attributes: 4
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple DOG;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $VALUES;
+    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: getstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+         x: invokevirtual #x                 // Method "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;".clone:()Ljava/lang/Object;
+         x: checkcast     #x                 // class "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;"
+         x: areturn
+      LineNumberTable:
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple valueOf(java.lang.String);
+    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: aload_0
+         x: invokestatic  #x                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
+         x: checkcast     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  name   Ljava/lang/String;
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
+    descriptor: (Ljava/lang/String;I)V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=3, locals=3, args_size=3
+         x: aload_0
+         x: aload_1
+         x: iload_2
+         x: invokespecial #x                 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       7     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    Signature: #x                          // ()V
+
+  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: iconst_2
+         x: anewarray     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: dup
+         x: iconst_0
+         x: getstatic     #x                 // Field CAT:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+         x: aastore
+        x: dup
+        x: iconst_1
+        x: getstatic     #x                 // Field DOG:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: aastore
+        x: areturn
+      LineNumberTable:
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: dup
+         x: ldc           #x                 // String CAT
+         x: iconst_0
+         x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;I)V
+        x: putstatic     #x                 // Field CAT:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+        x: dup
+        x: ldc           #x                 // String DOG
+        x: iconst_1
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;I)V
+        x: putstatic     #x                 // Field DOG:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: invokestatic  #x                 // Method $values:()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: putstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: return
+      LineNumberTable:
+}
+Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;>;
+SourceFile: "TinyFrameworkEnumSimple.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
   Compiled from "TinyFrameworkExceptionTester.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
index d7f0149494c0bc70b5d207a4c09f172eae7cdb03..df63815ea0ae7804581fd076dd767f57eeb7bcfa 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -446,6 +446,230 @@ RuntimeInvisibleAnnotations:
     android.hosttest.annotation.HostSideTestStub
   x: #x()
     android.hosttest.annotation.HostSideTestStaticInitializerStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.class
+  Compiled from "TinyFrameworkEnumComplex.java"
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex>
+  minor version: 0
+  major version: 61
+  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+  super_class: #x                         // java/lang/Enum
+  interfaces: 0, fields: 4, methods: 7, attributes: 4
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex GREEN;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex BLUE;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $VALUES;
+    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex valueOf(java.lang.String);
+    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
+    descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=3, locals=5, args_size=5
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    Signature: #x                          // (Ljava/lang/String;Ljava/lang/String;)V
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public java.lang.String getLongName();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public java.lang.String getShortName();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+}
+Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;>;
+SourceFile: "TinyFrameworkEnumComplex.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.class
+  Compiled from "TinyFrameworkEnumSimple.java"
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple>
+  minor version: 0
+  major version: 61
+  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+  super_class: #x                         // java/lang/Enum
+  interfaces: 0, fields: 3, methods: 5, attributes: 4
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple DOG;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $VALUES;
+    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple valueOf(java.lang.String);
+    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
+    descriptor: (Ljava/lang/String;I)V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=3, locals=3, args_size=3
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    Signature: #x                          // ()V
+
+  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+}
+Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;>;
+SourceFile: "TinyFrameworkEnumSimple.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
   Compiled from "TinyFrameworkExceptionTester.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
index 3318c7de13f9820521c3aaba81b2116210dd8ef2..3ac9c6a816f15162de3179c221f1353fd141016d 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
@@ -1046,6 +1046,390 @@ RuntimeInvisibleAnnotations:
     android.hosttest.annotation.HostSideTestStub
   x: #x()
     android.hosttest.annotation.HostSideTestStaticInitializerStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.class
+  Compiled from "TinyFrameworkEnumComplex.java"
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex>
+  minor version: 0
+  major version: 61
+  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+  super_class: #x                         // java/lang/Enum
+  interfaces: 0, fields: 6, methods: 7, attributes: 4
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex GREEN;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex BLUE;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  private final java.lang.String mLongName;
+    descriptor: Ljava/lang/String;
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private final java.lang.String mShortName;
+    descriptor: Ljava/lang/String;
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $VALUES;
+    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: ldc           #x                 // String values
+         x: ldc           #x                 // String ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: getstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: invokevirtual #x                 // Method "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;".clone:()Ljava/lang/Object;
+        x: checkcast     #x                 // class "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;"
+        x: areturn
+      LineNumberTable:
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex valueOf(java.lang.String);
+    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: ldc           #x                 // String valueOf
+         x: ldc           #x                 // String (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: aload_0
+        x: invokestatic  #x                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
+        x: checkcast     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      10     0  name   Ljava/lang/String;
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
+    descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=4, locals=5, args_size=5
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: aload_1
+        x: iload_2
+        x: invokespecial #x                 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
+        x: aload_0
+        x: aload_3
+        x: putfield      #x                 // Field mLongName:Ljava/lang/String;
+        x: aload_0
+        x: aload         4
+        x: putfield      #x                 // Field mShortName:Ljava/lang/String;
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      18     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+           11      18     3 longName   Ljava/lang/String;
+           11      18     4 shortName   Ljava/lang/String;
+    Signature: #x                          // (Ljava/lang/String;Ljava/lang/String;)V
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public java.lang.String getLongName();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: ldc           #x                 // String getLongName
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: getfield      #x                 // Field mLongName:Ljava/lang/String;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public java.lang.String getShortName();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: ldc           #x                 // String getShortName
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: getfield      #x                 // Field mShortName:Ljava/lang/String;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: ldc           #x                 // String $values
+         x: ldc           #x                 // String ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iconst_3
+        x: anewarray     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: dup
+        x: iconst_0
+        x: getstatic     #x                 // Field RED:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: aastore
+        x: dup
+        x: iconst_1
+        x: getstatic     #x                 // Field GREEN:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: aastore
+        x: dup
+        x: iconst_2
+        x: getstatic     #x                 // Field BLUE:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: aastore
+        x: areturn
+      LineNumberTable:
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=6, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: ldc           #x                 // String <clinit>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: dup
+        x: ldc           #x                 // String RED
+        x: iconst_0
+        x: ldc           #x                 // String Red
+        x: ldc           #x                 // String R
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                 // Field RED:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: dup
+        x: ldc           #x                 // String GREEN
+        x: iconst_1
+        x: ldc           #x                 // String Green
+        x: ldc           #x                 // String G
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                 // Field GREEN:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: dup
+        x: ldc           #x                 // String BLUE
+        x: iconst_2
+        x: ldc           #x                // String Blue
+        x: ldc           #x                // String B
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                 // Field BLUE:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: invokestatic  #x                // Method $values:()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: putstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: return
+      LineNumberTable:
+}
+Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;>;
+SourceFile: "TinyFrameworkEnumComplex.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.class
+  Compiled from "TinyFrameworkEnumSimple.java"
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple>
+  minor version: 0
+  major version: 61
+  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+  super_class: #x                         // java/lang/Enum
+  interfaces: 0, fields: 3, methods: 5, attributes: 4
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple DOG;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $VALUES;
+    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: ldc           #x                 // String values
+         x: ldc           #x                 // String ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: getstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: invokevirtual #x                 // Method "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;".clone:()Ljava/lang/Object;
+        x: checkcast     #x                 // class "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;"
+        x: areturn
+      LineNumberTable:
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple valueOf(java.lang.String);
+    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: ldc           #x                 // String valueOf
+         x: ldc           #x                 // String (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+        x: aload_0
+        x: invokestatic  #x                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
+        x: checkcast     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      10     0  name   Ljava/lang/String;
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
+    descriptor: (Ljava/lang/String;I)V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=4, locals=3, args_size=3
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String (Ljava/lang/String;I)V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: aload_1
+        x: iload_2
+        x: invokespecial #x                 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       7     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    Signature: #x                          // ()V
+
+  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: ldc           #x                 // String $values
+         x: ldc           #x                 // String ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iconst_2
+        x: anewarray     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+        x: dup
+        x: iconst_0
+        x: getstatic     #x                 // Field CAT:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: aastore
+        x: dup
+        x: iconst_1
+        x: getstatic     #x                 // Field DOG:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: aastore
+        x: areturn
+      LineNumberTable:
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: ldc           #x                 // String <clinit>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+        x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+        x: dup
+        x: ldc           #x                 // String CAT
+        x: iconst_0
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;I)V
+        x: putstatic     #x                 // Field CAT:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+        x: dup
+        x: ldc           #x                 // String DOG
+        x: iconst_1
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;I)V
+        x: putstatic     #x                 // Field DOG:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: invokestatic  #x                 // Method $values:()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: putstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: return
+      LineNumberTable:
+}
+Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;>;
+SourceFile: "TinyFrameworkEnumSimple.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
   Compiled from "TinyFrameworkExceptionTester.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
index e212890592ff151af005a06d9412fb187b650d92..872bbf878de4083a8e55b247889872afe748516a 100755
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
@@ -93,6 +93,7 @@ run $HOSTSTUBGEN \
     --gen-keep-all-file out/tiny-framework_keep_all.txt \
     --gen-input-dump-file out/tiny-framework_dump.txt \
     --package-redirect com.unsupported:com.supported \
+    --annotation-allowed-classes-file annotation-allowed-classes-tiny-framework.txt \
     $HOSTSTUBGEN_OPTS
 
 # Extract the jar files, so we can look into them.
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.java
new file mode 100644
index 0000000000000000000000000000000000000000..51f48188fe74f7d3ef0f220bf8d23b0f35f87f86
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.java
@@ -0,0 +1,51 @@
+/*
+ * 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.hoststubgen.test.tinyframework;
+
+import android.hosttest.annotation.HostSideTestKeep;
+import android.hosttest.annotation.HostSideTestStub;
+
+@HostSideTestStub
+public enum TinyFrameworkEnumComplex {
+    @HostSideTestStub
+    RED("Red", "R"),
+    @HostSideTestStub
+    GREEN("Green", "G"),
+    @HostSideTestStub
+    BLUE("Blue", "B");
+
+    @HostSideTestKeep
+    private final String mLongName;
+
+    @HostSideTestKeep
+    private final String mShortName;
+
+    @HostSideTestStub
+    TinyFrameworkEnumComplex(String longName, String shortName) {
+        mLongName = longName;
+        mShortName = shortName;
+    }
+
+    @HostSideTestStub
+    public String getLongName() {
+        return mLongName;
+    }
+
+    @HostSideTestStub
+    public String getShortName() {
+        return mShortName;
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.java
new file mode 100644
index 0000000000000000000000000000000000000000..f440d8667fb4b86a7d78ab986fcbdda8271f72d7
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+import android.hosttest.annotation.HostSideTestStub;
+
+@HostSideTestStub
+public enum TinyFrameworkEnumSimple {
+    @HostSideTestStub
+    CAT,
+    @HostSideTestStub
+    DOG,
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index 29aabc702809e7f9755febc9691a474802edb6ee..ecb181ba4bbf92ad89b448923188b36a18c204a5 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -199,4 +199,43 @@ public class TinyFrameworkClassTest {
     public void testPackageRedirect() throws Exception {
         assertThat(TinyFrameworkPackageRedirect.foo(1)).isEqualTo(1);
     }
+
+    @Test
+    public void testEnumSimple() throws Exception {
+        assertThat(TinyFrameworkEnumSimple.CAT.ordinal()).isEqualTo(0);
+        assertThat(TinyFrameworkEnumSimple.CAT.name()).isEqualTo("CAT");
+
+        assertThat(TinyFrameworkEnumSimple.DOG.ordinal()).isEqualTo(1);
+        assertThat(TinyFrameworkEnumSimple.DOG.name()).isEqualTo("DOG");
+
+        assertThat(TinyFrameworkEnumSimple.valueOf("DOG").ordinal()).isEqualTo(1);
+
+        assertThat(TinyFrameworkEnumSimple.values()).isEqualTo(
+                new TinyFrameworkEnumSimple[] {
+                        TinyFrameworkEnumSimple.CAT,
+                        TinyFrameworkEnumSimple.DOG,
+                }
+        );
+    }
+
+    @Test
+    public void testEnumComplex() throws Exception {
+        assertThat(TinyFrameworkEnumComplex.RED.ordinal()).isEqualTo(0);
+        assertThat(TinyFrameworkEnumComplex.RED.name()).isEqualTo("RED");
+
+        assertThat(TinyFrameworkEnumComplex.RED.getShortName()).isEqualTo("R");
+
+        assertThat(TinyFrameworkEnumComplex.GREEN.ordinal()).isEqualTo(1);
+        assertThat(TinyFrameworkEnumComplex.GREEN.name()).isEqualTo("GREEN");
+
+        assertThat(TinyFrameworkEnumComplex.valueOf("BLUE").ordinal()).isEqualTo(2);
+
+        assertThat(TinyFrameworkEnumComplex.values()).isEqualTo(
+                new TinyFrameworkEnumComplex[] {
+                        TinyFrameworkEnumComplex.RED,
+                        TinyFrameworkEnumComplex.GREEN,
+                        TinyFrameworkEnumComplex.BLUE,
+                }
+        );
+    }
 }
diff --git a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/ClassFilterTest.kt b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/ClassFilterTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f6515142ccdbbe6c998116f9747d477f7492a699
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/ClassFilterTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.hoststubgen.utils
+
+import com.android.hoststubgen.ParseException
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.fail
+import org.junit.Test
+
+class ClassFilterTest {
+    @Test
+    fun testDefaultTrue() {
+        val f = ClassFilter.newNullFilter(true)
+        assertThat(f.matches("a/b/c")).isEqualTo(true)
+    }
+
+    @Test
+    fun testDefaultFalse() {
+        val f = ClassFilter.newNullFilter(false)
+        assertThat(f.matches("a/b/c")).isEqualTo(false)
+    }
+
+    @Test
+    fun testComplex1() {
+        val f = ClassFilter.buildFromString("""
+            # ** this is a comment **
+            a.b.c       # allow
+            !a.b.d      # disallow
+            *           # allow all
+            """.trimIndent(), false, "X")
+        assertThat(f.getCacheSizeForTest()).isEqualTo(0)
+
+        assertThat(f.matches("a/b/c")).isEqualTo(true)
+        assertThat(f.getCacheSizeForTest()).isEqualTo(1)
+
+        assertThat(f.matches("a/b/d")).isEqualTo(false)
+        assertThat(f.matches("x")).isEqualTo(true)
+
+        assertThat(f.getCacheSizeForTest()).isEqualTo(3)
+
+        // Make sure the cache is working
+        assertThat(f.matches("x")).isEqualTo(true)
+    }
+
+    @Test
+    fun testComplex2() {
+        val f = ClassFilter.buildFromString("""
+            a.b.c       # allow
+            !a.*        # disallow everything else in package "a".
+            !d.e.f      # disallow d.e.f.
+
+            # everything else is allowed by default
+            """.trimIndent(), true, "X")
+        assertThat(f.matches("a/b/c")).isEqualTo(true)
+        assertThat(f.matches("a/x")).isEqualTo(false)
+        assertThat(f.matches("d/e/f")).isEqualTo(false)
+        assertThat(f.matches("d/e/f/g")).isEqualTo(true)
+        assertThat(f.matches("x")).isEqualTo(true)
+    }
+
+    @Test
+    fun testBadFilter1() {
+        try {
+            ClassFilter.buildFromString("""
+                a*
+                """.trimIndent(), true, "FILENAME")
+            fail("ParseException didn't happen")
+        } catch (e: ParseException) {
+            assertThat(e.message).contains("Wildcard")
+            assertThat(e.message).contains("FILENAME")
+            assertThat(e.message).contains("line 1")
+        }
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh
index ba1d404eba68616f72d52c93eae76993a696a9b9..4afa2d7a659a886196eedc6f8e2e870d5ce5da01 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -33,6 +33,9 @@ MUST_BUILD_MODULES=(
 # First, build all the test / etc modules. This shouldn't fail.
 run m "${MUST_BUILD_MODULES[@]}"
 
+# Run the hoststubgen unittests / etc
+run atest hoststubgentest hoststubgen-invoke-test
+
 # Next, run the golden check. This should always pass too.
 # The following scripts _should_ pass too, but they depend on the internal paths to soong generated
 # files, and they may fail when something changes in the build system.