Merge "[flexiglass] Password bouncer isn't side-by-side on unfolded." into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 14869db..323f9e9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4464,6 +4464,7 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
 
   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 @@
   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 @@
   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 @@
 
   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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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 @@
   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 1e6aa49..483d2a4 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/system-current.txt b/core/api/system-current.txt
index b5e9407..eefa36e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10228,6 +10228,7 @@
     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 559db51..3a91e25 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/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
index 6b78d05..5c5083a 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 @@
     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/jni/Android.bp b/core/jni/Android.bp
index 3795fc8..440a332 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -43,7 +43,10 @@
 
         "-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 91e3c921..66bda61 100644
--- a/core/jni/android_content_res_ResourceTimer.cpp
+++ b/core/jni/android_content_res_ResourceTimer.cpp
@@ -44,9 +44,9 @@
 
 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 30e546c..82570be8 100644
--- a/core/jni/android_hardware_camera2_DngCreator.cpp
+++ b/core/jni/android_hardware_camera2_DngCreator.cpp
@@ -1892,8 +1892,8 @@
         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 1390759..4bde87f 100644
--- a/core/jni/android_hardware_camera2_impl_CameraExtensionJpegProcessor.cpp
+++ b/core/jni/android_hardware_camera2_impl_CameraExtensionJpegProcessor.cpp
@@ -599,7 +599,7 @@
             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 4b563d7..7c9dae0 100644
--- a/core/jni/android_media_AudioProductStrategies.cpp
+++ b/core/jni/android_media_AudioProductStrategies.cpp
@@ -88,12 +88,12 @@
     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 6440cc3..3413ede 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2501,7 +2501,7 @@
             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 @@
 
     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 @@
         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 9bd0700..47b4a46 100644
--- a/core/jni/android_net_LocalSocketImpl.cpp
+++ b/core/jni/android_net_LocalSocketImpl.cpp
@@ -221,7 +221,7 @@
 
     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 a9db91b..e554b44 100644
--- a/core/jni/android_os_HwBlob.cpp
+++ b/core/jni/android_os_HwBlob.cpp
@@ -265,7 +265,7 @@
     //   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 4d8dac1..3539476 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -691,7 +691,7 @@
 
 // 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 27c4cd4..95bf49f 100644
--- a/core/jni/android_os_PerformanceHintManager.cpp
+++ b/core/jni/android_os_PerformanceHintManager.cpp
@@ -224,7 +224,7 @@
         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 7ab6e8f2..7071cf2 100644
--- a/core/jni/android_util_CharsetUtils.cpp
+++ b/core/jni/android_util_CharsetUtils.cpp
@@ -25,7 +25,7 @@
 
     // 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 @@
 
     // 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 c64c212..8d3da7b 100644
--- a/core/jni/android_util_FileObserver.cpp
+++ b/core/jni/android_util_FileObserver.cpp
@@ -114,7 +114,7 @@
     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 41c65ae..fef8ad7 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -140,7 +140,7 @@
         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 @@
                                                               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 9833598..a800e6e 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1950,7 +1950,7 @@
 
         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 55995df..bc69d1e6 100644
--- a/core/jni/android_window_WindowInfosListener.cpp
+++ b/core/jni/android_window_WindowInfosListener.cpp
@@ -67,7 +67,7 @@
 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 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 d4f6e18..0c39a69 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 @@
         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 098a4d8..cf70c90 100644
--- a/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
+++ b/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
@@ -55,7 +55,7 @@
 
 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 dfae684..be9013b 100644
--- a/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
+++ b/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
@@ -147,7 +147,7 @@
     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 @@
         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 1f29735..dab47e9 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -288,7 +288,7 @@
     }
 
     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 9c1bea7..7c5885a 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1094,8 +1094,8 @@
       }
       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 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 @@
         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 2b5b8f7..87ab496 100644
--- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
+++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
@@ -76,7 +76,7 @@
             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 @@
     }
     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 @@
   // 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 @@
       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 @@
         }
         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 @@
         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 @@
 
   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/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 7744786..c5ffbb7 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -187,11 +187,12 @@
         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 @@
         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 f0461be..098a746 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -44,8 +44,9 @@
             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 86096d5..12cb69d 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -87,7 +87,7 @@
     }
 
     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 @@
         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 @@
         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 @@
         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 284cde5..e2ea57d 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -44,8 +44,9 @@
             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 9e3bb79..56b52dc 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -664,8 +664,8 @@
     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 6c2cb9d..9c879d5 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -61,6 +61,7 @@
         // 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 @@
                             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 22c5862..e706eb0 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -531,32 +531,21 @@
     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 @@
 
     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 @@
     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 @@
                 }
                 // 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;
         }
+        VkSemaphoreGetFdInfoKHR getFdInfo;
+        getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
+        getFdInfo.pNext = nullptr;
+        getFdInfo.semaphore = semaphore;
+        getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+
+        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::finishFrame(): Semaphore submission failed");
+        mQueueWaitIdle(mGraphicsQueue);
     }
+
     skiapipeline::ShaderCache::get().onVkFrameFlushed(context);
 
-    return submissionTime;
+    return drawResult;
 }
 
-void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect) {
+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);
     }
 
-    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.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-
-        VkResult err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
-        ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd");
-    } else {
-        ALOGE("VulkanManager::swapBuffers(): Semaphore submission failed");
-        mQueueWaitIdle(mGraphicsQueue);
-    }
-    if (mDestroySemaphoreContext) {
-        destroy_semaphore(mDestroySemaphoreContext);
-    }
-
-    surface->presentCurrentBuffer(dirtyRect, fenceFd);
-    mSwapSemaphore = VK_NULL_HANDLE;
-    mDestroySemaphoreContext = nullptr;
+    surface->presentCurrentBuffer(dirtyRect, presentFence.release());
 }
 
 void VulkanManager::destroySurface(VulkanSurface* surface) {
@@ -753,22 +735,17 @@
     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 @@
     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 dbef7fb..b92ebb3 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 @@
     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 @@
     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 1a9ebbc..d607126 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 @@
     }
 
     @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 08a8d89..2769dbc 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -127,7 +127,7 @@
     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/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index f074106..f03263b 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.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 @@
                 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/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 29846ac..a88a9c7 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.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.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 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 @@
     @Mock
     private UsbPortStatus mUsbPortStatus;
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -425,8 +430,38 @@
     }
 
     @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 void containsIncompatibleChargers_emptyComplianceWarnings_returnFalse() {
         setupIncompatibleCharging();
         when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[1]);
-
         assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
     }
 
@@ -476,7 +510,7 @@
     }
 
     private void setupIncompatibleCharging() {
-        setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_OTHER);
+        setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY);
     }
 
     private void setupIncompatibleCharging(int complianceWarningType) {
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 77b844d..1429782 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
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
+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
@@ -48,6 +49,8 @@
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
 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
@@ -73,6 +76,7 @@
                 CommunalContent(
                     modifier = Modifier.fillMaxHeight().width(Dimensions.CardWidth),
                     model = communalContent[index],
+                    viewModel = viewModel,
                     deleteOnClick = viewModel::onDeleteWidget,
                     size =
                         SizeF(
@@ -94,6 +98,7 @@
 @Composable
 private fun CommunalContent(
     model: CommunalContentModel,
+    viewModel: CommunalViewModel,
     size: SizeF,
     deleteOnClick: (id: Int) -> Unit,
     modifier: Modifier = Modifier,
@@ -102,6 +107,7 @@
         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)
     }
 }
 
@@ -155,6 +161,31 @@
     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
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9a3c6d5..8780f58 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/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 6b8009d..32f9c30 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -605,7 +605,6 @@
                             mAodIconsViewModel,
                             mConfigurationState,
                             mConfigurationController,
-                            mDozeParameters,
                             mAodIconViewStore);
                     final DisposableHandle visHandle = KeyguardRootViewBinder.bindAodIconVisibility(
                             nic,
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 c3421de..273adcf 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 @@
     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 0000000..e41c322
--- /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 0000000..2c6d9e4
--- /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/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 524cccf..eb36b19 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
@@ -19,6 +19,7 @@
 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
@@ -37,12 +38,14 @@
 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,
@@ -87,8 +90,8 @@
             if (isTutorialMode) {
                 return@flatMapLatest flowOf(tutorialContent)
             }
-            combine(smartspaceContent, widgetContent) { smartspace, widgets ->
-                smartspace + widgets
+            combine(smartspaceContent, umoContent, widgetContent) { smartspace, umo, widgets ->
+                smartspace + umo + widgets
             }
         }
 
@@ -138,4 +141,13 @@
             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/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 69382a5..bb9b4b5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -53,4 +53,15 @@
     ) : 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/view/layout/blueprints/DefaultCommunalBlueprint.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
index d8d1dc0..702554a 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 @@
     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 3df6e7e..5efe6ce 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
@@ -21,7 +21,10 @@
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.dagger.SysUISingleton
+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
 
@@ -30,6 +33,7 @@
 @Inject
 constructor(
     private val communalInteractor: CommunalInteractor,
+    @Named(MediaModule.COMMUNAL_HUB) val mediaHost: MediaHost,
 ) {
     val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
     fun onSceneChanged(scene: CommunalSceneKey) {
diff --git a/packages/SystemUI/src/com/android/systemui/complication/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/complication/SmartSpaceComplication.java
index b98794e..9bb23d8 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 @@
         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 @@
                 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 8eb80ad..b34b459 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -130,6 +130,7 @@
 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;
@@ -201,6 +202,7 @@
         PrivacyModule.class,
         QRCodeScannerModule.class,
         QSFragmentStartableModule.class,
+        ReferenceModule.class,
         RetailModeModule.class,
         ScreenshotModule.class,
         SensorModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index c9748f9..0e333f2 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.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 @@
 
     /**
      * 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 @@
 
     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 @@
     private final Collection<Complication> mComplications = new HashSet();
 
     private final FeatureFlags mFeatureFlags;
+    private final WeakReferenceFactory mWeakReferenceFactory;
 
     private final int mSupportedTypes;
 
@@ -117,11 +125,13 @@
     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 @@
         mExecutor.execute(() -> {
             if (mComplications.add(complication)) {
                 mLogger.logAddComplication(complication.toString());
-                mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
+                notifyCallbacksLocked(Callback::onComplicationsChanged);
             }
         });
     }
@@ -160,7 +170,7 @@
         mExecutor.execute(() -> {
             if (mComplications.remove(complication)) {
                 mLogger.logRemoveComplication(complication.toString());
-                mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
+                notifyCallbacksLocked(Callback::onComplicationsChanged);
             }
         });
     }
@@ -199,22 +209,33 @@
     }
 
     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 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 @@
 
         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 @@
         mExecutor.execute(() -> {
             mLogger.logAvailableComplicationTypes(types);
             mAvailableComplicationTypes = types;
-            mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
+            notifyCallbacksLocked(Callback::onAvailableComplicationTypesChanged);
         });
     }
 
@@ -393,7 +420,7 @@
         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 1e9be09..e16bb0b 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.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 @@
     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 @@
     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 @@
         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 654f2d1..f5f5571 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 @@
 
 /**
  * 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 @@
 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 @@
             }
         }
 
-    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 @@
             .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 @@
 
     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 f5ef27d..fbd62ce 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 @@
 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 @@
      * @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 @@
     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 6ce9185..7dab84d 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 7fc1911..3440440 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 @@
 /** 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 f1ea446..2e64c41 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.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 @@
     defaultStatusViewSection: DefaultStatusViewSection,
     defaultStatusBarSection: DefaultStatusBarSection,
     defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
-    splitShadeGuidelines: SplitShadeGuidelines,
     aodNotificationIconsSection: AodNotificationIconsSection,
     aodBurnInSection: AodBurnInSection,
     communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
@@ -63,7 +61,7 @@
     override val id: String = DEFAULT
 
     override val sections =
-        setOf(
+        listOf(
             defaultIndicationAreaSection,
             defaultDeviceEntryIconSection,
             defaultShortcutsSection,
@@ -72,7 +70,6 @@
             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 fda4c3d..4f1a754 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
@@ -33,6 +33,12 @@
 
     @Binds
     @IntoSet
+    abstract fun bindSplitShadeBlueprint(
+        splitShadeBlueprint: SplitShadeKeyguardBlueprint
+    ): KeyguardBlueprint
+
+    @Binds
+    @IntoSet
     abstract fun bindShortcutsBesideUdfpsLockscreenBlueprint(
         shortcutsBesideUdfpsLockscreenBlueprint: ShortcutsBesideUdfpsKeyguardBlueprint
     ): KeyguardBlueprint
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 f8dd7c1..d8b368b 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 @@
     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 0000000..35679b8
--- /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/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index 06bb0a6..975d62a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -94,7 +94,6 @@
                     nicAodViewModel,
                     configurationState,
                     configurationController,
-                    dozeParameters,
                     nicAodIconViewStore,
                 )
         } else {
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 1995661..078feff 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.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 @@
         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 0000000..00966f2
--- /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 0000000..bf95c77
--- /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/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 3bf8057..843a454 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -61,6 +61,7 @@
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.NotificationDozeHelper;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
 import com.android.systemui.util.drawable.DrawableSize;
 
 import java.lang.annotation.Retention;
@@ -164,7 +165,6 @@
     private int mDrawableColor;
     private int mIconColor;
     private int mDecorColor;
-    private float mDozeAmount;
     private ValueAnimator mColorAnimator;
     private int mCurrentSetColor = NO_COLOR;
     private int mAnimationStartColor = NO_COLOR;
@@ -174,7 +174,6 @@
                 animation.getAnimatedFraction());
         setColorInternal(newColor);
     };
-    private final NotificationDozeHelper mDozer;
     private int mContrastedDrawableColor;
     private int mCachedContrastBackgroundColor = NO_COLOR;
     private float[] mMatrix;
@@ -184,6 +183,8 @@
     private Runnable mOnDismissListener;
     private boolean mIncreasedSize;
     private boolean mShowsConversation;
+    private float mDozeAmount;
+    private final NotificationDozeHelper mDozer;
 
     public StatusBarIconView(Context context, String slot, StatusBarNotification sbn) {
         this(context, slot, sbn, false);
@@ -958,18 +959,28 @@
         return mDotAppearAmount;
     }
 
-    public void setDozing(boolean dozing, boolean fade, long delay) {
-        setDozing(dozing, fade, delay, /* onChildCompleted= */ null);
+    public void setDozing(boolean dozing, boolean animate, long delay) {
+        setDozing(dozing, animate, delay, /* onChildCompleted= */ null);
     }
 
-    public void setDozing(boolean dozing, boolean fade, long delay,
+    public void setTintAlpha(float tintAlpha) {
+        if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
+        setDozeAmount(tintAlpha);
+    }
+
+    private void setDozeAmount(float dozeAmount) {
+        mDozeAmount = dozeAmount;
+        updateDecorColor();
+        updateIconColor();
+    }
+
+    public void setDozing(boolean dozing, boolean animate, long delay,
             @Nullable Runnable endRunnable) {
+        NotificationIconContainerRefactor.assertInLegacyMode();
         mDozer.setDozing(f -> {
-            mDozeAmount = f;
-            updateDecorColor();
-            updateIconColor();
+            setDozeAmount(f);
             updateAllowAnimation();
-        }, dozing, fade, delay, this, endRunnable);
+        }, dozing, animate, delay, this, endRunnable);
     }
 
     private void updateAllowAnimation() {
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 62c3e9e..2ea7f61 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
@@ -28,15 +28,12 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 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.NotificationIconColors
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData
-import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.NotificationIconContainer
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.onConfigChanged
@@ -44,7 +41,6 @@
 import com.android.systemui.util.kotlin.mapValuesNotNullTo
 import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.kotlin.stateFlow
-import com.android.systemui.util.ui.AnimatedValue
 import com.android.systemui.util.ui.isAnimating
 import com.android.systemui.util.ui.stopAnimating
 import com.android.systemui.util.ui.value
@@ -95,7 +91,11 @@
                         configurationController,
                         viewStore,
                     ) { _, sbiv ->
-                        iconColors.collect { sbiv.updateTintForIcon(it, contrastColorUtil) }
+                        StatusBarIconViewBinder.bindIconColors(
+                            sbiv,
+                            iconColors,
+                            contrastColorUtil,
+                        )
                     }
                 }
                 launch { viewModel.bindIsolatedIcon(view, viewStore) }
@@ -110,7 +110,6 @@
         viewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
         configuration: ConfigurationState,
         configurationController: ConfigurationController,
-        dozeParameters: DozeParameters,
         viewStore: IconViewStore,
     ): DisposableHandle {
         return view.repeatWhenAttached {
@@ -122,48 +121,37 @@
                         configurationController,
                         viewStore,
                     ) { _, sbiv ->
-                        configuration
-                            .getColorAttr(R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR)
-                            .collect { tint ->
-                                sbiv.staticDrawableColor = tint
-                                sbiv.setDecorColor(tint)
-                            }
+                        viewModel.bindAodStatusBarIconView(sbiv, configuration)
                     }
                 }
-                launch { viewModel.animationsEnabled.bindAnimationsEnabled(view) }
-                launch { viewModel.isDozing.bindIsDozing(view, dozeParameters) }
+                launch { viewModel.areContainerChangesAnimated.bindAnimationsEnabled(view) }
             }
         }
     }
 
+    private suspend fun NotificationIconContainerAlwaysOnDisplayViewModel.bindAodStatusBarIconView(
+        sbiv: StatusBarIconView,
+        configuration: ConfigurationState,
+    ) {
+        coroutineScope {
+            launch {
+                val color: Flow<Int> =
+                    configuration.getColorAttr(
+                        R.attr.wallpaperTextColor,
+                        DEFAULT_AOD_ICON_COLOR,
+                    )
+                StatusBarIconViewBinder.bindColor(sbiv, color)
+            }
+            launch { StatusBarIconViewBinder.bindTintAlpha(sbiv, tintAlpha) }
+            launch { StatusBarIconViewBinder.bindAnimationsEnabled(sbiv, areIconAnimationsEnabled) }
+        }
+    }
+
     /** Binds to [NotificationIconContainer.setAnimationsEnabled] */
     private suspend fun Flow<Boolean>.bindAnimationsEnabled(view: NotificationIconContainer) {
         collect(view::setAnimationsEnabled)
     }
 
-    private suspend fun Flow<AnimatedValue<Boolean>>.bindIsDozing(
-        view: NotificationIconContainer,
-        dozeParameters: DozeParameters,
-    ) {
-        collect { isDozing ->
-            if (isDozing.isAnimating) {
-                val animate = !dozeParameters.displayNeedsBlanking
-                view.setDozing(
-                    /* dozing = */ isDozing.value,
-                    /* fade = */ animate,
-                    /* delay = */ 0,
-                    /* endRunnable = */ isDozing::stopAnimating,
-                )
-            } else {
-                view.setDozing(
-                    /* dozing = */ isDozing.value,
-                    /* fade= */ false,
-                    /* delay= */ 0,
-                )
-            }
-        }
-    }
-
     private suspend fun NotificationIconContainerStatusBarViewModel.bindIsolatedIcon(
         view: NotificationIconContainer,
         viewStore: IconViewStore,
@@ -261,6 +249,7 @@
                 // and added again
                 view.removeTransientView(sbiv)
                 view.addView(sbiv, i, layoutParams)
+                iconBindings.remove(key)?.cancel()
                 iconBindings[key] = launch { bindIcon(key, sbiv) }
             }
 
@@ -282,18 +271,6 @@
         }
     }
 
-    // TODO(b/305739416): Once StatusBarIconView has its own Recommended Architecture stack, this
-    //  can be moved there and cleaned up.
-    private fun StatusBarIconView.updateTintForIcon(
-        iconColors: NotificationIconColors,
-        contrastColorUtil: ContrastColorUtil,
-    ) {
-        val isPreL = java.lang.Boolean.TRUE == getTag(R.id.icon_is_pre_L)
-        val isColorized = !isPreL || NotificationUtils.isGrayscale(this, contrastColorUtil)
-        staticDrawableColor = iconColors.staticDrawableColor(viewBounds, isColorized)
-        setDecorColor(iconColors.tint)
-    }
-
     /** External storage for [StatusBarIconView] instances. */
     fun interface IconViewStore {
         fun iconView(key: String): StatusBarIconView
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
new file mode 100644
index 0000000..3a2e21a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.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.statusbar.notification.icon.ui.viewbinder
+
+import android.graphics.Rect
+import android.view.View
+import com.android.internal.util.ContrastColorUtil
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColors
+import kotlinx.coroutines.flow.Flow
+
+object StatusBarIconViewBinder {
+
+    // TODO(b/305739416): Once StatusBarIconView has its own Recommended Architecture stack, these
+    //  methods can become private and we can have a single bind() method for SBIV and its
+    //  view-model (which, at the time of this writing, does not yet exist).
+
+    suspend fun bindColor(view: StatusBarIconView, color: Flow<Int>) {
+        color.collect { color ->
+            view.staticDrawableColor = color
+            view.setDecorColor(color)
+        }
+    }
+
+    suspend fun bindTintAlpha(view: StatusBarIconView, tintAlpha: Flow<Float>) {
+        tintAlpha.collect { amt -> view.setTintAlpha(amt) }
+    }
+
+    suspend fun bindAnimationsEnabled(view: StatusBarIconView, allowAnimation: Flow<Boolean>) {
+        allowAnimation.collect(view::setAllowAnimation)
+    }
+
+    suspend fun bindIconColors(
+        view: StatusBarIconView,
+        iconColors: Flow<NotificationIconColors>,
+        contrastColorUtil: ContrastColorUtil,
+    ) {
+        iconColors.collect { colors ->
+            val isPreL = java.lang.Boolean.TRUE == view.getTag(R.id.icon_is_pre_L)
+            val isColorized = !isPreL || NotificationUtils.isGrayscale(view, contrastColorUtil)
+            view.staticDrawableColor = colors.staticDrawableColor(view.viewBounds, isColorized)
+            view.setDecorColor(colors.tint)
+        }
+    }
+}
+
+private val View.viewBounds: Rect
+    get() {
+        val tmpArray = intArrayOf(0, 0)
+        getLocationOnScreen(tmpArray)
+        return Rect(
+            /* left = */ tmpArray[0],
+            /* top = */ tmpArray[1],
+            /* right = */ left + width,
+            /* bottom = */ top + height,
+        )
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
index 835c059..b11eca2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
@@ -19,17 +19,13 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.icon.domain.interactor.AlwaysOnDisplayNotificationIconsInteractor
-import com.android.systemui.util.ui.AnimatableEvent
-import com.android.systemui.util.ui.AnimatedValue
-import com.android.systemui.util.ui.toAnimatedValueFlow
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 
 /** View-model for the row of notification icons displayed on the always-on display. */
 @SysUISingleton
@@ -43,7 +39,7 @@
 ) {
 
     /** Are changes to the icon container animated? */
-    val animationsEnabled: Flow<Boolean> =
+    val areContainerChangesAnimated: Flow<Boolean> =
         combine(
             shadeInteractor.isShadeTouchable,
             keyguardInteractor.isKeyguardVisible,
@@ -51,23 +47,21 @@
             panelTouchesEnabled && isKeyguardVisible
         }
 
-    /** Should icons be rendered in "dozing" mode? */
-    val isDozing: Flow<AnimatedValue<Boolean>> =
-        keyguardTransitionInteractor.startedKeyguardTransitionStep
-            // Determine if we're dozing based on the most recent transition
-            .map { step: TransitionStep ->
-                val isDozing = step.to == KeyguardState.AOD || step.to == KeyguardState.DOZING
-                isDozing to step
-            }
-            // Only emit changes based on whether we've started or stopped dozing
-            .distinctUntilChanged { (wasDozing, _), (isDozing, _) -> wasDozing != isDozing }
-            // Determine whether we need to animate
-            .map { (isDozing, step) ->
-                val animate = step.to == KeyguardState.AOD || step.from == KeyguardState.AOD
-                AnimatableEvent(isDozing, animate)
-            }
-            .distinctUntilChanged()
-            .toAnimatedValueFlow()
+    /** Amount of a "white" tint to be applied to the icons. */
+    val tintAlpha: Flow<Float> =
+        combine(
+            keyguardTransitionInteractor.transitionValue(KeyguardState.AOD).onStart { emit(0f) },
+            keyguardTransitionInteractor.transitionValue(KeyguardState.DOZING).onStart { emit(0f) },
+        ) { aodAmt, dozeAmt ->
+            aodAmt + dozeAmt // If transitioning between them, they should sum to 1f
+        }
+
+    /** Are notification icons animated (ex: animated gif)? */
+    val areIconAnimationsEnabled: Flow<Boolean> =
+        keyguardTransitionInteractor.isFinishedInStateWhere {
+            // Don't animate icons when we're on AOD / dozing
+            it != KeyguardState.AOD && it != KeyguardState.DOZING
+        }
 
     /** [NotificationIconsViewData] indicating which icons to display in the view. */
     val icons: Flow<NotificationIconsViewData> =
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 6785da4..a1a0cca 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.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 @@
         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/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 01f3b63..9e5fd95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -337,7 +337,9 @@
         }
         if (child instanceof StatusBarIconView) {
             ((StatusBarIconView) child).updateIconDimens();
-            ((StatusBarIconView) child).setDozing(mDozing, false, 0);
+            if (!NotificationIconContainerRefactor.isEnabled()) {
+                ((StatusBarIconView) child).setDozing(mDozing, false, 0);
+            }
         }
     }
 
@@ -633,14 +635,16 @@
         mChangingViewPositions = changingViewPositions;
     }
 
-    public void setDozing(boolean dozing, boolean fade, long delay) {
-        setDozing(dozing, fade, delay, /* endRunnable= */ null);
+    public void setDozing(boolean dozing, boolean animate, long delay) {
+        NotificationIconContainerRefactor.assertInLegacyMode();
+        setDozing(dozing, animate, delay, /* endRunnable= */ null);
     }
 
-    public void setDozing(boolean dozing, boolean fade, long delay,
+    private void setDozing(boolean dozing, boolean animate, long delay,
             @Nullable Runnable endRunnable) {
+        NotificationIconContainerRefactor.assertInLegacyMode();
         mDozing = dozing;
-        mDisallowNextAnimation |= !fade;
+        mDisallowNextAnimation |= !animate;
         final int childCount = getChildCount();
         // Track all the child invocations of setDozing, invoking the top-level endRunnable once
         // they have all completed.
@@ -657,7 +661,7 @@
         for (int i = 0; i < childCount; i++) {
             View view = getChildAt(i);
             if (view instanceof StatusBarIconView) {
-                ((StatusBarIconView) view).setDozing(dozing, fade, delay, onChildCompleted);
+                ((StatusBarIconView) view).setDozing(dozing, animate, delay, onChildCompleted);
             } else if (onChildCompleted != null) {
                 onChildCompleted.run();
             }
@@ -780,15 +784,7 @@
                 StatusBarIconView icon = (StatusBarIconView) view;
                 boolean animate = false;
                 AnimationProperties animationProperties = null;
-                final boolean isLowPriorityIconChange =
-                        (visibleState == StatusBarIconView.STATE_HIDDEN
-                                && icon.getVisibleState() == StatusBarIconView.STATE_DOT)
-                        || (visibleState == StatusBarIconView.STATE_DOT
-                            && icon.getVisibleState() == StatusBarIconView.STATE_HIDDEN);
-                final boolean animationsAllowed = areAnimationsEnabled(icon)
-                        && !mDisallowNextAnimation
-                        && !noAnimations
-                        && !isLowPriorityIconChange;
+                final boolean animationsAllowed = animationsAllowed(icon);
                 if (animationsAllowed) {
                     if (justAdded || justReplaced) {
                         super.applyToView(icon);
@@ -879,6 +875,18 @@
             needsCannedAnimation = false;
         }
 
+        private boolean animationsAllowed(StatusBarIconView icon) {
+            final boolean isLowPriorityIconChange =
+                    (visibleState == StatusBarIconView.STATE_HIDDEN
+                            && icon.getVisibleState() == StatusBarIconView.STATE_DOT)
+                    || (visibleState == StatusBarIconView.STATE_DOT
+                        && icon.getVisibleState() == StatusBarIconView.STATE_HIDDEN);
+            return areAnimationsEnabled(icon)
+                    && !mDisallowNextAnimation
+                    && !noAnimations
+                    && !isLowPriorityIconChange;
+        }
+
         @Nullable
         private Consumer<Property> getEndAction() {
             if (mIsolatedIconAnimationEndRunnable == null) return null;
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 0000000..e7a91e5
--- /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 0000000..658f040
--- /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/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
new file mode 100644
index 0000000..455f986
--- /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 5460a1b..08d54c0 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
@@ -24,6 +24,7 @@
 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
@@ -58,6 +59,7 @@
 
     private lateinit var tutorialRepository: FakeCommunalTutorialRepository
     private lateinit var communalRepository: FakeCommunalRepository
+    private lateinit var mediaRepository: FakeCommunalMediaRepository
     private lateinit var widgetRepository: FakeCommunalWidgetRepository
     private lateinit var smartspaceRepository: FakeSmartspaceRepository
     private lateinit var keyguardRepository: FakeKeyguardRepository
@@ -74,6 +76,7 @@
 
         tutorialRepository = withDeps.tutorialRepository
         communalRepository = withDeps.communalRepository
+        mediaRepository = withDeps.mediaRepository
         widgetRepository = withDeps.widgetRepository
         smartspaceRepository = withDeps.smartspaceRepository
         keyguardRepository = withDeps.keyguardRepository
@@ -237,6 +240,66 @@
         }
 
     @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 {
             var desiredScene = collectLastValue(underTest.desiredScene)
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 40f0ed3..288f3b6 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.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 @@
                 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/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 365f67b..6d5cd49 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.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 @@
 
     final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
+    final FakeWeakReferenceFactory mWeakReferenceFactory = new FakeWeakReferenceFactory();
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -407,12 +410,36 @@
         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 8b572eb..0000000
--- 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 90fd652..4587ea6 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 @@
     fun fingerprintEnrollmentChange() =
         testScope.runTest {
             createBiometricSettingsRepository()
+            biometricsAreEnabledBySettings()
             val fingerprintAllowed = collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
             runCurrent()
 
@@ -170,11 +171,34 @@
         }
 
     @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 @@
             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 @@
             faceAuthIsNonStrongBiometric()
             faceAuthIsEnrolled()
             doNotDisableKeyguardAuthFeatures()
+            biometricsAreEnabledBySettings()
 
             val convenienceBiometricAllowed = collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
             runCurrent()
@@ -258,7 +283,7 @@
             faceAuthIsEnrolled()
             createBiometricSettingsRepository()
             doNotDisableKeyguardAuthFeatures()
-            faceAuthIsEnabledByBiometricManager()
+            biometricsAreEnabledBySettings()
             runCurrent()
 
             val convenienceBiometricAllowed by
@@ -291,6 +316,7 @@
         testScope.runTest {
             fingerprintIsEnrolled(PRIMARY_USER_ID)
             createBiometricSettingsRepository()
+            biometricsAreEnabledBySettings()
 
             val fingerprintEnabledByDevicePolicy =
                 collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
@@ -316,7 +342,7 @@
             createBiometricSettingsRepository()
             val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
 
-            faceAuthIsEnabledByBiometricManager()
+            biometricsAreEnabledBySettings()
 
             doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)
 
@@ -351,12 +377,18 @@
             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 @@
             faceAuthIsEnrolled()
             createBiometricSettingsRepository()
 
-            faceAuthIsEnabledByBiometricManager()
+            biometricsAreEnabledBySettings()
             doNotDisableKeyguardAuthFeatures()
             mobileConnectionsRepository.isAnySimSecure.value = false
             runCurrent()
@@ -454,7 +486,7 @@
             deviceIsInPostureThatSupportsFaceAuth()
             doNotDisableKeyguardAuthFeatures()
             faceAuthIsStrongBiometric()
-            faceAuthIsEnabledByBiometricManager()
+            biometricsAreEnabledBySettings()
             mobileConnectionsRepository.isAnySimSecure.value = false
 
             onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
@@ -636,7 +668,7 @@
             deviceIsInPostureThatSupportsFaceAuth()
             doNotDisableKeyguardAuthFeatures()
             faceAuthIsStrongBiometric()
-            faceAuthIsEnabledByBiometricManager()
+            biometricsAreEnabledBySettings()
 
             onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
             onNonStrongAuthChanged(false, PRIMARY_USER_ID)
@@ -660,7 +692,7 @@
             deviceIsInPostureThatSupportsFaceAuth()
             doNotDisableKeyguardAuthFeatures()
             faceAuthIsNonStrongBiometric()
-            faceAuthIsEnabledByBiometricManager()
+            biometricsAreEnabledBySettings()
 
             onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
             onNonStrongAuthChanged(false, PRIMARY_USER_ID)
@@ -682,6 +714,7 @@
     fun fpAuthCurrentlyAllowed_dependsOnNonStrongAuthBiometricSetting_ifFpIsNotStrong() =
         testScope.runTest {
             createBiometricSettingsRepository()
+            biometricsAreEnabledBySettings()
             val isFingerprintCurrentlyAllowed by
                 collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
 
@@ -723,6 +756,7 @@
     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 66ead14..5852bdb 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.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 @@
     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 @@
     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 @@
     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 b8a2e9d..9fe40d7 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.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 2831053..43d70ad 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 @@
                 defaultStatusViewSection,
                 defaultStatusBarViewSection,
                 defaultNSSLSection,
-                splitShadeGuidelines,
                 aodNotificationIconsSection,
                 aodBurnInSection,
                 communalTutorialIndicatorSection,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index 788cfbc..c2c33de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -45,9 +45,6 @@
 import com.android.systemui.user.domain.UserDomainLayerModule
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.ui.isAnimating
-import com.android.systemui.util.ui.stopAnimating
-import com.android.systemui.util.ui.value
 import com.google.common.truth.Truth.assertThat
 import dagger.BindsInstance
 import dagger.Component
@@ -129,7 +126,7 @@
                     transitionState = TransitionState.STARTED,
                 )
             )
-            val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+            val animationsEnabled by collectLastValue(underTest.areContainerChangesAnimated)
             runCurrent()
             assertThat(animationsEnabled).isFalse()
         }
@@ -152,7 +149,7 @@
                     to = DozeStateModel.DOZE_AOD,
                 )
             )
-            val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+            val animationsEnabled by collectLastValue(underTest.areContainerChangesAnimated)
             runCurrent()
             assertThat(animationsEnabled).isFalse()
         }
@@ -175,7 +172,7 @@
                     to = DozeStateModel.DOZE_PULSING,
                 )
             )
-            val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+            val animationsEnabled by collectLastValue(underTest.areContainerChangesAnimated)
             runCurrent()
             assertThat(animationsEnabled).isTrue()
         }
@@ -196,7 +193,7 @@
                 )
             )
             whenever(dozeParams.shouldControlScreenOff()).thenReturn(false)
-            val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+            val animationsEnabled by collectLastValue(underTest.areContainerChangesAnimated)
             runCurrent()
             assertThat(animationsEnabled).isFalse()
         }
@@ -217,7 +214,7 @@
                 )
             )
             whenever(dozeParams.shouldControlScreenOff()).thenReturn(true)
-            val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+            val animationsEnabled by collectLastValue(underTest.areContainerChangesAnimated)
             runCurrent()
             assertThat(animationsEnabled).isTrue()
         }
@@ -235,7 +232,7 @@
                     transitionState = TransitionState.STARTED,
                 )
             )
-            val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+            val animationsEnabled by collectLastValue(underTest.areContainerChangesAnimated)
             runCurrent()
             assertThat(animationsEnabled).isTrue()
         }
@@ -248,7 +245,7 @@
                     transitionState = TransitionState.STARTED,
                 )
             )
-            val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+            val animationsEnabled by collectLastValue(underTest.areContainerChangesAnimated)
             runCurrent()
 
             keyguardRepository.setKeyguardShowing(true)
@@ -270,91 +267,151 @@
         }
 
     @Test
-    fun isDozing_startAodTransition() =
+    fun tintAlpha_isZero_whenNotOnAodOrDozing() =
         testComponent.runTest {
-            val isDozing by collectLastValue(underTest.isDozing)
+            val tintAlpha by collectLastValue(underTest.tintAlpha)
             runCurrent()
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.GONE,
-                    to = KeyguardState.AOD,
-                    transitionState = TransitionState.STARTED,
-                )
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.DOZING,
+                to = KeyguardState.GONE,
+                testScope,
             )
             runCurrent()
-            assertThat(isDozing?.value).isTrue()
-            assertThat(isDozing?.isAnimating).isTrue()
+            assertThat(tintAlpha).isZero()
         }
 
     @Test
-    fun isDozing_startDozeTransition() =
+    fun tintAlpha_isOne_whenOnAod() =
         testComponent.runTest {
-            val isDozing by collectLastValue(underTest.isDozing)
+            val tintAlpha by collectLastValue(underTest.tintAlpha)
             runCurrent()
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.AOD,
+                testScope,
+            )
+            runCurrent()
+            assertThat(tintAlpha).isEqualTo(1f)
+        }
+
+    @Test
+    fun tintAlpha_isOne_whenDozing() =
+        testComponent.runTest {
+            val tintAlpha by collectLastValue(underTest.tintAlpha)
+            runCurrent()
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.DOZING,
+                testScope,
+            )
+            assertThat(tintAlpha).isEqualTo(1f)
+        }
+
+    @Test
+    fun tintAlpha_isOne_whenTransitionFromAodToDoze() =
+        testComponent.runTest {
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.AOD,
+                testScope,
+            )
+            val tintAlpha by collectLastValue(underTest.tintAlpha)
+            runCurrent()
+
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.GONE,
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
                     to = KeyguardState.DOZING,
-                    transitionState = TransitionState.STARTED,
+                    value = 0f,
                 )
             )
             runCurrent()
-            assertThat(isDozing?.value).isTrue()
-            assertThat(isDozing?.isAnimating).isFalse()
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.DOZING,
+                    value = 0.5f,
+                )
+            )
+            runCurrent()
+
+            assertThat(tintAlpha).isEqualTo(1f)
         }
 
     @Test
-    fun isDozing_startDozeToAodTransition() =
+    fun tintAlpha_isFraction_midTransitionToAod() =
         testComponent.runTest {
-            val isDozing by collectLastValue(underTest.isDozing)
+            val tintAlpha by collectLastValue(underTest.tintAlpha)
             runCurrent()
+
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.DOZING,
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.GONE,
                     to = KeyguardState.AOD,
-                    transitionState = TransitionState.STARTED,
+                    value = 0f,
                 )
             )
             runCurrent()
-            assertThat(isDozing?.value).isTrue()
-            assertThat(isDozing?.isAnimating).isTrue()
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = 0.5f,
+                )
+            )
+            runCurrent()
+
+            assertThat(tintAlpha).isEqualTo(0.5f)
         }
 
     @Test
-    fun isNotDozing_startAodToGoneTransition() =
+    fun iconAnimationsEnabled_whenOnLockScreen() =
         testComponent.runTest {
-            val isDozing by collectLastValue(underTest.isDozing)
+            val iconAnimationsEnabled by collectLastValue(underTest.areIconAnimationsEnabled)
             runCurrent()
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.AOD,
-                    to = KeyguardState.GONE,
-                    transitionState = TransitionState.STARTED,
-                )
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.LOCKSCREEN,
+                testScope,
             )
-            runCurrent()
-            assertThat(isDozing?.value).isFalse()
-            assertThat(isDozing?.isAnimating).isTrue()
+
+            assertThat(iconAnimationsEnabled).isTrue()
         }
 
     @Test
-    fun isDozing_stopAnimation() =
+    fun iconAnimationsDisabled_whenOnAod() =
         testComponent.runTest {
-            val isDozing by collectLastValue(underTest.isDozing)
+            val iconAnimationsEnabled by collectLastValue(underTest.areIconAnimationsEnabled)
             runCurrent()
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.AOD,
-                    to = KeyguardState.GONE,
-                    transitionState = TransitionState.STARTED,
-                )
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.AOD,
+                testScope,
             )
+
+            assertThat(iconAnimationsEnabled).isFalse()
+        }
+
+    @Test
+    fun iconAnimationsDisabled_whenDozing() =
+        testComponent.runTest {
+            val iconAnimationsEnabled by collectLastValue(underTest.areIconAnimationsEnabled)
             runCurrent()
 
-            assertThat(isDozing?.isAnimating).isEqualTo(true)
-            isDozing?.stopAnimating()
-            runCurrent()
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.DOZING,
+                testScope,
+            )
 
-            assertThat(isDozing?.isAnimating).isEqualTo(false)
+            assertThat(iconAnimationsEnabled).isFalse()
         }
 }
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 58d93c9..2f79955 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 @@
         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/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 0000000..3ab1b6c
--- /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/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
index 6c3882f..0c821ea 100644
--- 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
@@ -18,6 +18,7 @@
 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
@@ -35,6 +36,7 @@
         testScope: TestScope = TestScope(),
         communalRepository: FakeCommunalRepository = FakeCommunalRepository(),
         widgetRepository: FakeCommunalWidgetRepository = FakeCommunalWidgetRepository(),
+        mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(),
         smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(),
         tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(),
         appWidgetHost: AppWidgetHost = mock(),
@@ -48,6 +50,7 @@
         return WithDependencies(
             communalRepository,
             widgetRepository,
+            mediaRepository,
             smartspaceRepository,
             tutorialRepository,
             withDeps.keyguardRepository,
@@ -57,6 +60,7 @@
             CommunalInteractor(
                 communalRepository,
                 widgetRepository,
+                mediaRepository,
                 smartspaceRepository,
                 withDeps.communalTutorialInteractor,
                 appWidgetHost,
@@ -67,6 +71,7 @@
     data class WithDependencies(
         val communalRepository: FakeCommunalRepository,
         val widgetRepository: FakeCommunalWidgetRepository,
+        val mediaRepository: FakeCommunalMediaRepository,
         val smartspaceRepository: FakeSmartspaceRepository,
         val tutorialRepository: FakeCommunalTutorialRepository,
         val keyguardRepository: FakeKeyguardRepository,
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 0000000..f0a8fd0
--- /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/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index b37bbd6..ab678d9 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -20,3 +20,10 @@
   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/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index afc0dd1..cd9c53b 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/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index b006ac8..a8cba53 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 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.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 @@
     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 @@
 
     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 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 @@
         super.onBootPhase(phase);
     }
 
+    @VisibleForTesting
+    void setAllowedUid(int uid) {
+        mAllowedUid = uid;
+    }
+
     private void formatIfOemUnlockEnabled() {
         boolean enabled = doGetOemUnlockEnabled();
         if (enabled) {
@@ -220,7 +244,7 @@
             }
         }
 
-        SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
+        setProperty(OEM_UNLOCK_PROP, enabled ? "1" : "0");
     }
 
     private void enforceOemUnlockReadPermission() {
@@ -278,7 +302,7 @@
     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 @@
         return mBlockDeviceSize;
     }
 
-    private long getFrpCredentialDataOffset() {
-        return getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE;
+    @VisibleForTesting
+    int getMaximumFrpDataSize() {
+        return (int) (getTestHarnessModeDataOffset() - DIGEST_SIZE_BYTES - HEADER_SIZE);
     }
 
-    private long getTestHarnessModeDataOffset() {
+    @VisibleForTesting
+    long getFrpCredentialDataOffset() {
+        return getOemUnlockDataOffset() - FRP_CREDENTIAL_RESERVED_SIZE;
+    }
+
+    @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 @@
         return md.digest();
     }
 
-    private void formatPartitionLocked(boolean setOemUnlockEnabled) {
+    @VisibleForTesting
+    void formatPartitionLocked(boolean setOemUnlockEnabled) {
 
         try {
             FileChannel channel = getBlockOutputChannel();
@@ -448,10 +485,15 @@
             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 @@
     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 @@
             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 @@
             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 26e70c0..21284a0 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 @@
          * 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 @@
          * 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 5b32a94..a5bc2c3 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 @@
 
     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 9bca9f0..36677df 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 @@
 
     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 @@
 
     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/CrossProfileAppsService.java b/services/core/java/com/android/server/pm/CrossProfileAppsService.java
index 486282a..0bd165e 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 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 5f28e56..f1dc284 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 @@
 
     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 @@
     }
 
     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 72f3afc..3313e72 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 @@
 
 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 @@
     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 51214fa..56e9c0a 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 @@
  * 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 a8da818..1d38bbb 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 @@
  */
 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 29322e2..888e1c2 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 @@
     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 dc5915d..db05dd3 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 @@
     }
 
     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 dd96a2b..cc6bb00 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 @@
     // 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 be4fb5c..fc83120 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 @@
     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 ca8dc29..187cada 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 @@
     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 8b82a1c..3f4cc4a 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 @@
         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 @@
 
         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 a4d8632..98eee4d 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 @@
     }
 
     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 1be28ca..4b466be 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 @@
     @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 @@
     }
 
     @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 @@
     }
 
     @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/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 9428ef6..655b9c9 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 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 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 d4abad8..6f45d2b 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 @@
         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 @@
     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 a09e713..c829e1c 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 @@
         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/PreferredComponent.java b/services/core/java/com/android/server/pm/PreferredComponent.java
index 5507e7c..18caafd 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 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 cf5aa7b..e667bfe 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 @@
 
     // 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 440823c..6338965 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 @@
         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 @@
         @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 8667888..12115af 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 @@
     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 c6aba2a..3adeb4b 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 @@
     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 @@
         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 e04a1e5..31541d0 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 @@
     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 @@
          * 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 @@
     /**
      * 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 895edce..651578d 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 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 6e738da..78c13f8 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 @@
     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 @@
      * 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 d3a64bb..2ab7db4 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 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 9e20805..c26cf1c 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 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 d82a500..3b10c7f 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 @@
     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 056aae4..75dd67d 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 @@
     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 d6a7dc6..6f6bb45 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 @@
      */
     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 70986c3..4c831d3 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 @@
 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 8240c47..061698a 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 @@
         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 fac681a..0ceda42 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 @@
             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 9115775..80cde73 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 @@
     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 11f62e9..3d4d4ec 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 @@
     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 6c56360..d71dbbb 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 @@
     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/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index a388932..b8e581f 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 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/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
index dc05462..80427b3 100644
--- a/services/core/jni/tvinput/JTvInputHal.cpp
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -25,7 +25,7 @@
     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 @@
     }
 }
 
-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 @@
       : 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 6026a10..2ef94ac 100644
--- a/services/core/jni/tvinput/JTvInputHal.h
+++ b/services/core/jni/tvinput/JTvInputHal.h
@@ -168,23 +168,39 @@
         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 @@
         ::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 @@
     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 37cf844..cdd9266 100644
--- a/services/core/jni/tvinput/TvInputHal_hidl.cpp
+++ b/services/core/jni/tvinput/TvInputHal_hidl.cpp
@@ -59,7 +59,11 @@
     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 @@
       : 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/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java b/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java
new file mode 100644
index 0000000..f537efd
--- /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/test-mock/api/current.txt b/test-mock/api/current.txt
index f61cce6..daaab33 100644
--- a/test-mock/api/current.txt
+++ b/test-mock/api/current.txt
@@ -156,6 +156,7 @@
     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 @@
     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/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING
index 9703626..e02492d 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 182940e..fd4ec8b 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -99,6 +99,18 @@
     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/invoketest/Android.bp b/tools/hoststubgen/hoststubgen/invoketest/Android.bp
new file mode 100644
index 0000000..7e90e42
--- /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 0000000..34b2145
--- /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 872d568..f32dc72 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.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 @@
             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 @@
             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 d74612d..aab02b8 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 @@
 
         var packageRedirects: MutableList<Pair<String, String>> = mutableListOf(),
 
+        var annotationAllowedClassesFile: String? = null,
+
         var defaultClassLoadHook: String? = null,
         var defaultMethodCallHook: String? = null,
 
@@ -171,6 +173,9 @@
                     "--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 @@
               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 f75062b..937e56c 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 @@
         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/filters/AnnotationBasedFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
index 9f3ec4d..9bb5381e 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.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 @@
         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 @@
     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 @@
      * 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 @@
         val cn = classes.getClass(className)
 
         findAnnotation(
+            cn.name,
             cn.visibleAnnotations,
             cn.invisibleAnnotations,
             "class",
@@ -193,6 +205,7 @@
 
         cn.fields?.firstOrNull { it.name == fieldName }?.let {fn ->
             findAnnotation(
+                cn.name,
                 fn.visibleAnnotations,
                 fn.invisibleAnnotations,
                 "field",
@@ -229,6 +242,7 @@
 
             // 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/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index 46546e8..416f085 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 @@
     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 0000000..01a7ab3
--- /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 3dc6da3..e7873d6 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp
@@ -16,6 +16,7 @@
     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 @@
         ":hoststubgen-test-tiny-framework",
         "policy-override-tiny-framework.txt",
     ],
+    visibility: ["//visibility:private"],
 }
 
 java_genrule_host {
@@ -41,6 +43,7 @@
     out: [
         "host_stub.jar",
     ],
+    visibility: ["//visibility:private"],
 }
 
 java_genrule_host {
@@ -52,6 +55,7 @@
     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 @@
         ":hoststubgen-test-tiny-framework",
         "policy-override-tiny-framework.txt",
     ],
+    visibility: ["//visibility:private"],
 }
 
 java_genrule_host {
@@ -82,6 +87,7 @@
     out: [
         "host_stub.jar",
     ],
+    visibility: ["//visibility:private"],
 }
 
 java_genrule_host {
@@ -93,6 +99,7 @@
     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 0000000..bd9e85e
--- /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/run-test-manually.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
index e212890..872bbf8 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 @@
     --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/com/android/hoststubgen/utils/ClassFilterTest.kt b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/ClassFilterTest.kt
new file mode 100644
index 0000000..f651514
--- /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 ba1d404..4afa2d7 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -33,6 +33,9 @@
 # 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.