Merge "Add is/setAppFunctionEnabled to sidecar" into main
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index b0e38e2..cff42fb 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -399,13 +399,10 @@
                 return -1;
             }
             case "enable-text" -> {
-                if (mViewerConfigReader != null) {
-                    mViewerConfigReader.loadViewerConfig(groups, logger);
-                }
-                return setTextLogging(true, logger, groups);
+                return startLoggingToLogcat(groups, logger);
             }
             case "disable-text" -> {
-                return setTextLogging(false, logger, groups);
+                return stopLoggingToLogcat(groups, logger);
             }
             default -> {
                 return unknownCommand(pw);
diff --git a/core/java/com/android/internal/protolog/Utils.java b/core/java/com/android/internal/protolog/Utils.java
index 1e6ba30..00ef80a 100644
--- a/core/java/com/android/internal/protolog/Utils.java
+++ b/core/java/com/android/internal/protolog/Utils.java
@@ -93,8 +93,7 @@
                     os.write(TAG, tag);
                     break;
                 default:
-                    throw new RuntimeException(
-                            "Unexpected field id " + pis.getFieldNumber());
+                    Log.e(LOG_TAG, "Unexpected field id " + pis.getFieldNumber());
             }
         }
 
@@ -126,8 +125,7 @@
                     os.write(LOCATION, pis.readString(LOCATION));
                     break;
                 default:
-                    throw new RuntimeException(
-                            "Unexpected field id " + pis.getFieldNumber());
+                    Log.e(LOG_TAG, "Unexpected field id " + pis.getFieldNumber());
             }
         }
 
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml
index 40dbbac..c8df15d 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
index 85715db..706c632 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
index 6c903a2..7df1675 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
index 6c903a2..7df1675 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
index f69a90c..d87c179 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
index b76d065..99969e7 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
index 041978c..19c3e40 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
index bf040d2..7505860 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
@@ -24,6 +24,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="on"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="on"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index bbb1420..f0bcfe53 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -36,7 +36,7 @@
 
 MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData,
                                  size_t fontSize, std::string_view filePath, int ttcIndex,
-                                 const std::vector<minikin::FontVariation>& axes)
+                                 const minikin::VariationSettings& axes)
         : mTypeface(std::move(typeface))
         , mSourceId(sourceId)
         , mFontData(fontData)
@@ -123,12 +123,12 @@
     return mTtcIndex;
 }
 
-const std::vector<minikin::FontVariation>& MinikinFontSkia::GetAxes() const {
+const minikin::VariationSettings& MinikinFontSkia::GetAxes() const {
     return mAxes;
 }
 
 std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation(
-        const std::vector<minikin::FontVariation>& variations) const {
+        const minikin::VariationSettings& variations) const {
     SkFontArguments args;
 
     std::vector<SkFontArguments::VariationPosition::Coordinate> skVariation;
diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h
index de9a5c2..7fe5978bfd 100644
--- a/libs/hwui/hwui/MinikinSkia.h
+++ b/libs/hwui/hwui/MinikinSkia.h
@@ -32,7 +32,7 @@
 public:
     MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData, size_t fontSize,
                     std::string_view filePath, int ttcIndex,
-                    const std::vector<minikin::FontVariation>& axes);
+                    const minikin::VariationSettings& axes);
 
     float GetHorizontalAdvance(uint32_t glyph_id, const minikin::MinikinPaint& paint,
                                const minikin::FontFakery& fakery) const override;
@@ -59,9 +59,9 @@
     size_t GetFontSize() const;
     int GetFontIndex() const;
     const std::string& getFilePath() const { return mFilePath; }
-    const std::vector<minikin::FontVariation>& GetAxes() const;
+    const minikin::VariationSettings& GetAxes() const;
     std::shared_ptr<minikin::MinikinFont> createFontWithVariation(
-            const std::vector<minikin::FontVariation>&) const;
+            const minikin::VariationSettings&) const;
     int GetSourceId() const override { return mSourceId; }
 
     static uint32_t packFontFlags(const SkFont&);
@@ -80,7 +80,7 @@
     const void* mFontData;
     size_t mFontSize;
     int mTtcIndex;
-    std::vector<minikin::FontVariation> mAxes;
+    minikin::VariationSettings mAxes;
     std::string mFilePath;
 };
 
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index a9d1a2a..2d812d6 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -92,8 +92,8 @@
     return result;
 }
 
-Typeface* Typeface::createFromTypefaceWithVariation(
-        Typeface* src, const std::vector<minikin::FontVariation>& variations) {
+Typeface* Typeface::createFromTypefaceWithVariation(Typeface* src,
+                                                    const minikin::VariationSettings& variations) {
     const Typeface* resolvedFace = Typeface::resolveDefault(src);
     Typeface* result = new Typeface();
     if (result != nullptr) {
@@ -192,9 +192,8 @@
     sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(fontData));
     LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont);
 
-    std::shared_ptr<minikin::MinikinFont> font =
-            std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, kRobotoFont,
-                                              0, std::vector<minikin::FontVariation>());
+    std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>(
+            std::move(typeface), 0, data, st.st_size, kRobotoFont, 0, minikin::VariationSettings());
     std::vector<std::shared_ptr<minikin::Font>> fonts;
     fonts.push_back(minikin::Font::Builder(font).build());
 
diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h
index 565136e..2c96c1a 100644
--- a/libs/hwui/hwui/Typeface.h
+++ b/libs/hwui/hwui/Typeface.h
@@ -74,8 +74,8 @@
     static Typeface* createRelative(Typeface* src, Style desiredStyle);
     static Typeface* createAbsolute(Typeface* base, int weight, bool italic);
 
-    static Typeface* createFromTypefaceWithVariation(
-            Typeface* src, const std::vector<minikin::FontVariation>& variations);
+    static Typeface* createFromTypefaceWithVariation(Typeface* src,
+                                                     const minikin::VariationSettings& variations);
 
     static Typeface* createFromFamilies(
             std::vector<std::shared_ptr<minikin::FontFamily>>&& families, int weight, int italic,
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index e6d790f..9922ff3 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -133,9 +133,9 @@
         builder->axes.clear();
         return false;
     }
-    std::shared_ptr<minikin::MinikinFont> minikinFont =
-            std::make_shared<MinikinFontSkia>(std::move(face), fonts::getNewSourceId(), fontPtr,
-                                              fontSize, "", ttcIndex, builder->axes);
+    std::shared_ptr<minikin::MinikinFont> minikinFont = std::make_shared<MinikinFontSkia>(
+            std::move(face), fonts::getNewSourceId(), fontPtr, fontSize, "", ttcIndex,
+            minikin::VariationSettings(builder->axes, false));
     minikin::Font::Builder fontBuilder(minikinFont);
 
     if (weight != RESOLVE_BY_FONT_TABLE) {
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 209b35c..0f458dd 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -80,7 +80,8 @@
         AxisHelper axis(env, axisObject);
         variations.push_back(minikin::FontVariation(axis.getTag(), axis.getStyleValue()));
     }
-    return toJLong(Typeface::createFromTypefaceWithVariation(toTypeface(familyHandle), variations));
+    return toJLong(Typeface::createFromTypefaceWithVariation(
+            toTypeface(familyHandle), minikin::VariationSettings(variations, false /* sorted */)));
 }
 
 static jlong Typeface_createWeightAlias(JNIEnv* env, jobject, jlong familyHandle, jint weight) {
@@ -273,7 +274,7 @@
     const std::string& path = typeface->GetFontPath();
     writer->writeString(path);
     writer->write<int>(typeface->GetFontIndex());
-    const std::vector<minikin::FontVariation>& axes = typeface->GetAxes();
+    const minikin::VariationSettings& axes = typeface->GetAxes();
     writer->writeArray<minikin::FontVariation>(axes.data(), axes.size());
     bool hasVerity = getVerity(path);
     writer->write<int8_t>(static_cast<int8_t>(hasVerity));
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index f405aba..6a05b6c 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -142,7 +142,7 @@
     std::shared_ptr<minikin::MinikinFont> newMinikinFont = std::make_shared<MinikinFontSkia>(
             std::move(newTypeface), minikinSkia->GetSourceId(), minikinSkia->GetFontData(),
             minikinSkia->GetFontSize(), minikinSkia->getFilePath(), minikinSkia->GetFontIndex(),
-            builder->axes);
+            minikin::VariationSettings(builder->axes, false));
     std::shared_ptr<minikin::Font> newFont = minikin::Font::Builder(newMinikinFont)
               .setWeight(weight)
               .setSlant(static_cast<minikin::FontStyle::Slant>(italic))
@@ -303,7 +303,7 @@
         var = reader.readArray<minikin::FontVariation>().first[index];
     } else {
         const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
-        var = minikinFont->GetAxes().at(index);
+        var = minikinFont->GetAxes()[index];
     }
     uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
     return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary);
diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp
index 91f78ce..0c07b2a 100644
--- a/native/android/system_fonts.cpp
+++ b/native/android/system_fonts.cpp
@@ -327,7 +327,7 @@
     result->mWeight = font->style().weight();
     result->mItalic = font->style().slant() == minikin::FontStyle::Slant::ITALIC;
     result->mCollectionIndex = minikinFontSkia->GetFontIndex();
-    const std::vector<minikin::FontVariation>& axes = minikinFontSkia->GetAxes();
+    const minikin::VariationSettings& axes = minikinFontSkia->GetAxes();
     result->mAxes.reserve(axes.size());
     for (auto axis : axes) {
         result->mAxes.push_back(std::make_pair(axis.axisTag, axis.value));
diff --git a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
index 47ce587..b06052a 100644
--- a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
+++ b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
@@ -78,15 +78,6 @@
             android:textAppearance="?android:attr/textAppearanceSmall"
             android:textColor="?android:attr/textColorSecondary"
             android:visibility="gone"/>
-
-        <ProgressBar
-            android:id="@android:id/progress"
-            style="?android:attr/progressBarStyleHorizontal"
-            android:layout_width="match_parent"
-            android:layout_height="4dp"
-            android:layout_marginTop="4dp"
-            android:max="100"
-            android:visibility="gone"/>
     </LinearLayout>
 
     <LinearLayout
diff --git a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
index e65f7de..ac57228 100644
--- a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
+++ b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
@@ -74,15 +74,6 @@
             android:textAppearance="?android:attr/textAppearanceSmall"
             android:textColor="?android:attr/textColorSecondary"
             android:visibility="gone"/>
-
-        <ProgressBar
-            android:id="@android:id/progress"
-            style="?android:attr/progressBarStyleHorizontal"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="4dp"
-            android:max="100"
-            android:visibility="gone"/>
     </LinearLayout>
 
     <LinearLayout
diff --git a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java
index f1d162e..3b52df7 100644
--- a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java
+++ b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java
@@ -18,11 +18,8 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.view.View;
-import android.widget.ProgressBar;
 
 import androidx.preference.Preference;
-import androidx.preference.PreferenceViewHolder;
 
 import com.android.settingslib.widget.preference.app.R;
 
@@ -31,9 +28,6 @@
  */
 public class AppPreference extends Preference {
 
-    private int mProgress;
-    private boolean mProgressVisible;
-
     public AppPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         setLayoutResource(R.layout.preference_app);
@@ -53,29 +47,4 @@
         super(context, attrs);
         setLayoutResource(R.layout.preference_app);
     }
-
-    /**
-     * Sets the current progress.
-     * @param amount the current progress
-     *
-     * @see ProgressBar#setProgress(int)
-     */
-    public void setProgress(int amount) {
-        mProgress = amount;
-        mProgressVisible = true;
-        notifyChanged();
-    }
-
-    @Override
-    public void onBindViewHolder(PreferenceViewHolder view) {
-        super.onBindViewHolder(view);
-
-        final ProgressBar progress = (ProgressBar) view.findViewById(android.R.id.progress);
-        if (mProgressVisible) {
-            progress.setProgress(mProgress);
-            progress.setVisibility(View.VISIBLE);
-        } else {
-            progress.setVisibility(View.GONE);
-        }
-    }
 }
diff --git a/packages/SettingsLib/Preference/Android.bp b/packages/SettingsLib/Preference/Android.bp
index 17852e8..e83e17c 100644
--- a/packages/SettingsLib/Preference/Android.bp
+++ b/packages/SettingsLib/Preference/Android.bp
@@ -22,3 +22,16 @@
     ],
     kotlincflags: ["-Xjvm-default=all"],
 }
+
+android_library {
+    name: "SettingsLibPreference-testutils",
+    srcs: ["testutils/**/*.kt"],
+    static_libs: [
+        "SettingsLibPreference",
+        "androidx.fragment_fragment-testing",
+        "androidx.test.core",
+        "androidx.test.ext.junit",
+        "flag-junit",
+        "truth",
+    ],
+}
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
index 5fcf478..5e69895 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
@@ -68,10 +68,13 @@
                 preference.icon = null
             }
             val context = preference.context
+            val isPreferenceScreen = preference is PreferenceScreen
             preference.peekExtras()?.clear()
             extras(context)?.let { preference.extras.putAll(it) }
             preference.title = getPreferenceTitle(context)
-            preference.summary = getPreferenceSummary(context)
+            if (!isPreferenceScreen) {
+                preference.summary = getPreferenceSummary(context)
+            }
             preference.isEnabled = isEnabled(context)
             preference.isVisible =
                 (this as? PreferenceAvailabilityProvider)?.isAvailable(context) != false
@@ -81,7 +84,7 @@
             // dependency here. This simplifies dependency management and avoid the
             // IllegalStateException when call Preference.setDependency
             preference.dependency = null
-            if (preference !is PreferenceScreen) { // avoid recursive loop when build graph
+            if (!isPreferenceScreen) { // avoid recursive loop when build graph
                 preference.fragment = (this as? PreferenceScreenCreator)?.fragmentClass()?.name
                 preference.intent = intent(context)
             }
diff --git a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt
new file mode 100644
index 0000000..4d5f85f
--- /dev/null
+++ b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.preference
+
+import android.content.Context
+import android.platform.test.flag.junit.SetFlagsRule
+import android.util.Log
+import androidx.fragment.app.testing.FragmentScenario
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.PreferenceGroup
+import androidx.preference.PreferenceScreen
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Test case for catalyst screen. */
+@RunWith(AndroidJUnit4::class)
+abstract class CatalystScreenTestCase {
+    @get:Rule val setFlagsRule = SetFlagsRule()
+
+    protected val context: Context = ApplicationProvider.getApplicationContext()
+
+    /** Catalyst screen. */
+    protected abstract val preferenceScreenCreator: PreferenceScreenCreator
+
+    /** Flag to control catalyst screen. */
+    protected abstract val flagName: String
+
+    /**
+     * Test to compare the preference screen hierarchy between legacy screen (flag is disabled) and
+     * catalyst screen (flag is enabled).
+     */
+    @Test
+    fun migration() {
+        enableCatalystScreen()
+        assertThat(preferenceScreenCreator.isFlagEnabled(context)).isTrue()
+        val catalystScreen = stringifyPreferenceScreen()
+        Log.i("Catalyst", catalystScreen)
+
+        disableCatalystScreen()
+        assertThat(preferenceScreenCreator.isFlagEnabled(context)).isFalse()
+        val legacyScreen = stringifyPreferenceScreen()
+
+        assertThat(catalystScreen).isEqualTo(legacyScreen)
+    }
+
+    /**
+     * Enables the catalyst screen.
+     *
+     * By default, enable the [flagName]. Override for more complex situation.
+     */
+    @Suppress("DEPRECATION")
+    protected open fun enableCatalystScreen() {
+        setFlagsRule.enableFlags(flagName)
+    }
+
+    /**
+     * Disables the catalyst screen (legacy screen is shown).
+     *
+     * By default, disable the [flagName]. Override for more complex situation.
+     */
+    @Suppress("DEPRECATION")
+    protected open fun disableCatalystScreen() {
+        setFlagsRule.disableFlags(flagName)
+    }
+
+    private fun stringifyPreferenceScreen(): String {
+        @Suppress("UNCHECKED_CAST")
+        val clazz = preferenceScreenCreator.fragmentClass() as Class<PreferenceFragmentCompat>
+        val builder = StringBuilder()
+        FragmentScenario.launch(clazz).use {
+            it.onFragment { fragment -> fragment.preferenceScreen.toString(builder) }
+        }
+        return builder.toString()
+    }
+
+    private fun Preference.toString(builder: StringBuilder, indent: String = "") {
+        val clazz = javaClass
+        builder.append(indent).append(clazz).append(" {\n")
+        val indent2 = "$indent  "
+        if (clazz != PreferenceScreen::class.java) {
+            key?.let { builder.append(indent2).append("key: \"$it\"\n") }
+        }
+        title?.let { builder.append(indent2).append("title: \"$it\"\n") }
+        summary?.let { builder.append(indent2).append("summary: \"$it\"\n") }
+        fragment?.let { builder.append(indent2).append("fragment: \"$it\"\n") }
+        builder.append(indent2).append("order: $order\n")
+        builder.append(indent2).append("isCopyingEnabled: $isCopyingEnabled\n")
+        builder.append(indent2).append("isEnabled: $isEnabled\n")
+        builder.append(indent2).append("isIconSpaceReserved: $isIconSpaceReserved\n")
+        if (clazz != Preference::class.java && clazz != PreferenceScreen::class.java) {
+            builder.append(indent2).append("isPersistent: $isPersistent\n")
+        }
+        builder.append(indent2).append("isSelectable: $isSelectable\n")
+        if (this is PreferenceGroup) {
+            val count = preferenceCount
+            builder.append(indent2).append("preferenceCount: $count\n")
+            val indent4 = "$indent2  "
+            for (index in 0..<count) {
+                getPreference(index).toString(builder, indent4)
+            }
+        }
+        builder.append(indent).append("}\n")
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppPreferenceTest.java
deleted file mode 100644
index 6c8fd50..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppPreferenceTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.widget;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.view.View;
-
-import androidx.preference.PreferenceViewHolder;
-
-import com.android.settingslib.widget.preference.app.R;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-@RunWith(RobolectricTestRunner.class)
-public class AppPreferenceTest {
-
-    private Context mContext;
-    private View mRootView;
-    private AppPreference mPref;
-    private PreferenceViewHolder mHolder;
-
-    @Before
-    public void setUp() {
-        mContext = RuntimeEnvironment.application;
-        mRootView = View.inflate(mContext, R.layout.preference_app, null /* parent */);
-        mHolder = PreferenceViewHolder.createInstanceForTests(mRootView);
-        mPref = new AppPreference(mContext);
-    }
-
-    @Test
-    public void setProgress_showProgress() {
-        mPref.setProgress(1);
-        mPref.onBindViewHolder(mHolder);
-
-        assertThat(mHolder.findViewById(android.R.id.progress).getVisibility())
-                .isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    public void foobar_testName() {
-        float iconSize = mContext.getResources().getDimension(com.android.settingslib.widget.theme.R.dimen.secondary_app_icon_size);
-        assertThat(Float.floatToIntBits(iconSize)).isEqualTo(Float.floatToIntBits(32));
-    }
-}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a9d4c89..cb1411b 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -559,6 +559,13 @@
 }
 
 flag {
+    name: "volume_redesign"
+    namespace: "systemui"
+    description: "Enables Volume BC25 visuals update"
+    bug: "368308908"
+}
+
+flag {
     name: "clipboard_shared_transitions"
     namespace: "systemui"
     description: "Show shared transitions from clipboard"
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 9891025..367faed 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -188,38 +188,47 @@
         return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation)
     }
 
+    private fun resolveSwipeSource(startedPosition: Offset?): SwipeSource.Resolved? {
+        if (startedPosition == null) return null
+        return layoutImpl.swipeSourceDetector.source(
+            layoutSize = layoutImpl.lastSize,
+            position = startedPosition.round(),
+            density = layoutImpl.density,
+            orientation = orientation,
+        )
+    }
+
+    private fun resolveSwipe(
+        pointersDown: Int,
+        fromSource: SwipeSource.Resolved?,
+        isUpOrLeft: Boolean,
+    ): Swipe.Resolved {
+        return Swipe.Resolved(
+            direction =
+                when (orientation) {
+                    Orientation.Horizontal ->
+                        if (isUpOrLeft) {
+                            SwipeDirection.Resolved.Left
+                        } else {
+                            SwipeDirection.Resolved.Right
+                        }
+
+                    Orientation.Vertical ->
+                        if (isUpOrLeft) {
+                            SwipeDirection.Resolved.Up
+                        } else {
+                            SwipeDirection.Resolved.Down
+                        }
+                },
+            pointerCount = pointersDown,
+            fromSource = fromSource,
+        )
+    }
+
     private fun computeSwipes(startedPosition: Offset?, pointersDown: Int): Swipes {
-        val fromSource =
-            startedPosition?.let { position ->
-                layoutImpl.swipeSourceDetector.source(
-                    layoutImpl.lastSize,
-                    position.round(),
-                    layoutImpl.density,
-                    orientation,
-                )
-            }
-
-        val upOrLeft =
-            Swipe.Resolved(
-                direction =
-                    when (orientation) {
-                        Orientation.Horizontal -> SwipeDirection.Resolved.Left
-                        Orientation.Vertical -> SwipeDirection.Resolved.Up
-                    },
-                pointerCount = pointersDown,
-                fromSource = fromSource,
-            )
-
-        val downOrRight =
-            Swipe.Resolved(
-                direction =
-                    when (orientation) {
-                        Orientation.Horizontal -> SwipeDirection.Resolved.Right
-                        Orientation.Vertical -> SwipeDirection.Resolved.Down
-                    },
-                pointerCount = pointersDown,
-                fromSource = fromSource,
-            )
+        val fromSource = resolveSwipeSource(startedPosition)
+        val upOrLeft = resolveSwipe(pointersDown, fromSource, isUpOrLeft = true)
+        val downOrRight = resolveSwipe(pointersDown, fromSource, isUpOrLeft = false)
 
         return if (fromSource == null) {
             Swipes(
diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
index 55ccaa6..92bc95a 100644
--- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java
+++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
@@ -70,4 +70,12 @@
      * {@link #onBootCompleted()} will never be called before {@link #start()}. */
     default void onBootCompleted() {
     }
+
+    /** No op implementation that can be used when feature flagging on the Dagger Module level. */
+    CoreStartable NOP = new Nop();
+
+    class Nop implements CoreStartable {
+        @Override
+        public void start() {}
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
new file mode 100644
index 0000000..57c8bc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 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.core
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.os.Binder
+import android.os.RemoteException
+import android.view.WindowInsets
+import com.android.internal.statusbar.IStatusBarService
+import com.android.internal.statusbar.RegisterStatusBarResult
+import com.android.systemui.CoreStartable
+import com.android.systemui.InitController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.navigationbar.NavigationBarController
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import dagger.Lazy
+import javax.inject.Inject
+
+@SysUISingleton
+class CommandQueueInitializer
+@Inject
+constructor(
+    private val context: Context,
+    private val commandQueue: CommandQueue,
+    private val commandQueueCallbacksLazy: Lazy<CommandQueue.Callbacks>,
+    private val statusBarModeRepository: StatusBarModeRepositoryStore,
+    private val initController: InitController,
+    private val barService: IStatusBarService,
+    private val navigationBarController: NavigationBarController,
+) : CoreStartable {
+
+    override fun start() {
+        StatusBarSimpleFragment.assertInNewMode()
+        val result: RegisterStatusBarResult =
+            try {
+                barService.registerStatusBar(commandQueue)
+            } catch (ex: RemoteException) {
+                ex.rethrowFromSystemServer()
+                return
+            }
+
+        createNavigationBar(result)
+
+        if ((result.mTransientBarTypes and WindowInsets.Type.statusBars()) != 0) {
+            statusBarModeRepository.defaultDisplay.showTransient()
+        }
+        val displayId = context.display.displayId
+        val commandQueueCallbacks = commandQueueCallbacksLazy.get()
+        commandQueueCallbacks.onSystemBarAttributesChanged(
+            displayId,
+            result.mAppearance,
+            result.mAppearanceRegions,
+            result.mNavbarColorManagedByIme,
+            result.mBehavior,
+            result.mRequestedVisibleTypes,
+            result.mPackageName,
+            result.mLetterboxDetails,
+        )
+
+        // StatusBarManagerService has a back up of IME token and it's restored here.
+        commandQueueCallbacks.setImeWindowStatus(
+            displayId,
+            result.mImeWindowVis,
+            result.mImeBackDisposition,
+            result.mShowImeSwitcher,
+        )
+
+        // Set up the initial icon state
+        val numIcons: Int = result.mIcons.size
+        for (i in 0 until numIcons) {
+            commandQueue.setIcon(result.mIcons.keyAt(i), result.mIcons.valueAt(i))
+        }
+
+        // set the initial view visibility
+        val disabledFlags1 = result.mDisabledFlags1
+        val disabledFlags2 = result.mDisabledFlags2
+        initController.addPostInitTask {
+            commandQueue.disable(displayId, disabledFlags1, disabledFlags2, /* animate= */ false)
+            try {
+                // NOTE(b/262059863): Force-update the disable flags after applying the flags
+                // returned from registerStatusBar(). The result's disabled flags may be stale
+                // if StatusBarManager's disabled flags are updated between registering the bar
+                // and this handling this post-init task. We force an update in this case, and use a
+                // new token to not conflict with any other disabled flags already requested by
+                // SysUI
+                val token = Binder()
+                barService.disable(StatusBarManager.DISABLE_HOME, token, context.packageName)
+                barService.disable(0, token, context.packageName)
+            } catch (ex: RemoteException) {
+                ex.rethrowFromSystemServer()
+            }
+        }
+    }
+
+    private fun createNavigationBar(result: RegisterStatusBarResult) {
+        navigationBarController.createNavigationBars(/* includeDefaultDisplay= */ true, result)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
new file mode 100644
index 0000000..8bd990b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2024 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.core
+
+import android.view.View
+import com.android.systemui.CoreStartable
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.plugins.PluginDependencyProvider
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.shade.NotificationShadeWindowViewController
+import com.android.systemui.shade.ShadeSurface
+import com.android.systemui.statusbar.AutoHideUiElement
+import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import com.android.systemui.statusbar.phone.AutoHideController
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
+import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.data.model.StatusBarWindowState
+import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
+import com.android.wm.shell.bubbles.Bubbles
+import dagger.Lazy
+import java.io.PrintWriter
+import java.util.Optional
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.launch
+
+/**
+ * Class responsible for managing the lifecycle and state of the status bar.
+ *
+ * It is a temporary class, created to pull status bar related logic out of CentralSurfacesImpl. The
+ * plan is break it out into individual classes.
+ */
+@SysUISingleton
+class StatusBarOrchestrator
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val statusBarInitializer: StatusBarInitializer,
+    private val statusBarWindowController: StatusBarWindowController,
+    private val statusBarModeRepository: StatusBarModeRepositoryStore,
+    private val demoModeController: DemoModeController,
+    private val pluginDependencyProvider: PluginDependencyProvider,
+    private val autoHideController: AutoHideController,
+    private val remoteInputManager: NotificationRemoteInputManager,
+    private val notificationShadeWindowViewControllerLazy:
+    Lazy<NotificationShadeWindowViewController>,
+    private val shadeSurface: ShadeSurface,
+    private val bubblesOptional: Optional<Bubbles>,
+    private val statusBarWindowStateRepositoryStore: StatusBarWindowStateRepositoryStore,
+    powerInteractor: PowerInteractor,
+    primaryBouncerInteractor: PrimaryBouncerInteractor,
+) : CoreStartable {
+
+    private val phoneStatusBarViewController =
+        MutableStateFlow<PhoneStatusBarViewController?>(value = null)
+
+    private val phoneStatusBarTransitions =
+        MutableStateFlow<PhoneStatusBarTransitions?>(value = null)
+
+    private val shouldAnimateNextBarModeChange =
+        combine(
+            statusBarModeRepository.defaultDisplay.isTransientShown,
+            powerInteractor.isAwake,
+            statusBarWindowStateRepositoryStore.defaultDisplay.windowState,
+        ) { isTransientShown, isDeviceAwake, statusBarWindowState ->
+            !isTransientShown &&
+                isDeviceAwake &&
+                statusBarWindowState != StatusBarWindowState.Hidden
+        }
+
+    private val controllerAndBouncerShowing =
+        combine(
+            phoneStatusBarViewController.filterNotNull(),
+            primaryBouncerInteractor.isShowing,
+            ::Pair,
+        )
+
+    private val barTransitionsAndDeviceAsleep =
+        combine(phoneStatusBarTransitions.filterNotNull(), powerInteractor.isAsleep, ::Pair)
+
+    private val statusBarVisible =
+        combine(
+            statusBarModeRepository.defaultDisplay.statusBarMode,
+            statusBarWindowStateRepositoryStore.defaultDisplay.windowState,
+        ) { mode, statusBarWindowState ->
+            mode != StatusBarMode.LIGHTS_OUT &&
+                mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT &&
+                statusBarWindowState != StatusBarWindowState.Hidden
+        }
+
+    private val barModeUpdate =
+        combine(
+                shouldAnimateNextBarModeChange,
+                phoneStatusBarTransitions.filterNotNull(),
+                statusBarModeRepository.defaultDisplay.statusBarMode,
+                ::Triple,
+            )
+            .distinctUntilChangedBy { (_, barTransitions, statusBarMode) ->
+                // We only want to collect when either bar transitions or status bar mode
+                // changed.
+                Pair(barTransitions, statusBarMode)
+            }
+
+    override fun start() {
+        StatusBarSimpleFragment.assertInNewMode()
+        applicationScope.launch {
+            launch {
+                controllerAndBouncerShowing.collect { (controller, bouncerShowing) ->
+                    setBouncerShowingForStatusBarComponents(controller, bouncerShowing)
+                }
+            }
+            launch {
+                barTransitionsAndDeviceAsleep.collect { (barTransitions, deviceAsleep) ->
+                    if (deviceAsleep) {
+                        barTransitions.finishAnimations()
+                    }
+                }
+            }
+            launch { statusBarVisible.collect { updateBubblesVisibility(it) } }
+            launch {
+                barModeUpdate.collect { (animate, barTransitions, statusBarMode) ->
+                    updateBarMode(animate, barTransitions, statusBarMode)
+                }
+            }
+        }
+        createAndAddWindow()
+        setupPluginDependencies()
+        setUpAutoHide()
+    }
+
+    private fun createAndAddWindow() {
+        initializeStatusBarFragment()
+        statusBarWindowController.attach()
+    }
+
+    private fun initializeStatusBarFragment() {
+        statusBarInitializer.statusBarViewUpdatedListener =
+            object : StatusBarInitializer.OnStatusBarViewUpdatedListener {
+                override fun onStatusBarViewUpdated(
+                    statusBarViewController: PhoneStatusBarViewController,
+                    statusBarTransitions: PhoneStatusBarTransitions,
+                ) {
+                    phoneStatusBarViewController.value = statusBarViewController
+                    phoneStatusBarTransitions.value = statusBarTransitions
+
+                    notificationShadeWindowViewControllerLazy
+                        .get()
+                        .setStatusBarViewController(statusBarViewController)
+                    // Ensure we re-propagate panel expansion values to the panel controller and
+                    // any listeners it may have, such as PanelBar. This will also ensure we
+                    // re-display the notification panel if necessary (for example, if
+                    // a heads-up notification was being displayed and should continue being
+                    // displayed).
+                    shadeSurface.updateExpansionAndVisibility()
+                }
+            }
+    }
+
+    private fun setupPluginDependencies() {
+        pluginDependencyProvider.allowPluginDependency(DarkIconDispatcher::class.java)
+        pluginDependencyProvider.allowPluginDependency(StatusBarStateController::class.java)
+    }
+
+    private fun setUpAutoHide() {
+        autoHideController.setStatusBar(
+            object : AutoHideUiElement {
+                override fun synchronizeState() {}
+
+                override fun shouldHideOnTouch(): Boolean {
+                    return !remoteInputManager.isRemoteInputActive
+                }
+
+                override fun isVisible(): Boolean {
+                    return statusBarModeRepository.defaultDisplay.isTransientShown.value
+                }
+
+                override fun hide() {
+                    statusBarModeRepository.defaultDisplay.clearTransient()
+                }
+            })
+    }
+
+    private fun updateBarMode(
+        animate: Boolean,
+        barTransitions: PhoneStatusBarTransitions,
+        barMode: StatusBarMode,
+    ) {
+        if (!demoModeController.isInDemoMode) {
+            barTransitions.transitionTo(barMode.toTransitionModeInt(), animate)
+        }
+        autoHideController.touchAutoHide()
+    }
+
+    private fun updateBubblesVisibility(statusBarVisible: Boolean) {
+        bubblesOptional.ifPresent { bubbles: Bubbles ->
+            bubbles.onStatusBarVisibilityChanged(statusBarVisible)
+        }
+    }
+
+    private fun setBouncerShowingForStatusBarComponents(
+        controller: PhoneStatusBarViewController,
+        bouncerShowing: Boolean,
+    ) {
+        val importance =
+            if (bouncerShowing) {
+                View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+            } else {
+                View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+            }
+        controller.setImportantForAccessibility(importance)
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println(statusBarWindowStateRepositoryStore.defaultDisplay.windowState.value)
+        CentralSurfaces.dumpBarTransitions(
+            pw,
+            "PhoneStatusBarTransitions",
+            phoneStatusBarTransitions.value,
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index 3903ff3..cf238d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -46,6 +46,7 @@
  */
 @Module(includes = [StatusBarDataLayerModule::class, SystemBarUtilsProxyImpl.Module::class])
 abstract class StatusBarModule {
+
     @Binds
     @IntoMap
     @ClassKey(OngoingCallController::class)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 1d3f0e1..5f4f72f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -241,10 +241,10 @@
 import com.android.wm.shell.startingsurface.SplashscreenContentDrawer;
 import com.android.wm.shell.startingsurface.StartingSurface;
 
-import dagger.Lazy;
-
 import dalvik.annotation.optimization.NeverCompile;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.Map;
@@ -304,6 +304,7 @@
             };
 
     void onStatusBarWindowStateChanged(@WindowVisibleState int state) {
+        StatusBarSimpleFragment.assertInLegacyMode();
         mStatusBarWindowState = state;
         updateBubblesVisibility();
     }
@@ -813,8 +814,9 @@
         mStartingSurfaceOptional = startingSurfaceOptional;
         mDreamManager = dreamManager;
         lockscreenShadeTransitionController.setCentralSurfaces(this);
-        statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged);
-
+        if (!StatusBarSimpleFragment.isEnabled()) {
+            statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged);
+        }
         mScreenOffAnimationController = screenOffAnimationController;
 
         ShadeExpansionListener shadeExpansionListener = this::onPanelExpansionChanged;
@@ -901,10 +903,12 @@
         mWallpaperSupported = mWallpaperManager.isWallpaperSupported();
 
         RegisterStatusBarResult result = null;
-        try {
-            result = mBarService.registerStatusBar(mCommandQueue);
-        } catch (RemoteException ex) {
-            ex.rethrowFromSystemServer();
+        if (!StatusBarSimpleFragment.isEnabled()) {
+            try {
+                result = mBarService.registerStatusBar(mCommandQueue);
+            } catch (RemoteException ex) {
+                ex.rethrowFromSystemServer();
+            }
         }
 
         createAndAddWindows(result);
@@ -912,30 +916,45 @@
         // Set up the initial notification state. This needs to happen before CommandQueue.disable()
         setUpPresenter();
 
-        if ((result.mTransientBarTypes & WindowInsets.Type.statusBars()) != 0) {
-            mStatusBarModeRepository.getDefaultDisplay().showTransient();
-        }
-        mCommandQueueCallbacks.onSystemBarAttributesChanged(mDisplayId, result.mAppearance,
-                result.mAppearanceRegions, result.mNavbarColorManagedByIme, result.mBehavior,
-                result.mRequestedVisibleTypes, result.mPackageName, result.mLetterboxDetails);
-
-        // StatusBarManagerService has a back up of IME token and it's restored here.
-        mCommandQueueCallbacks.setImeWindowStatus(mDisplayId, result.mImeWindowVis,
-                result.mImeBackDisposition, result.mShowImeSwitcher);
-
-        // Set up the initial icon state
-        int numIcons = result.mIcons.size();
-        for (int i = 0; i < numIcons; i++) {
-            mCommandQueue.setIcon(result.mIcons.keyAt(i), result.mIcons.valueAt(i));
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, String.format(
-                    "init: icons=%d disabled=0x%08x lights=0x%08x imeButton=0x%08x",
-                    numIcons,
-                    result.mDisabledFlags1,
+        // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+        // StatusBarOrchestrator
+        if (!StatusBarSimpleFragment.isEnabled()) {
+            if ((result.mTransientBarTypes & WindowInsets.Type.statusBars()) != 0) {
+                mStatusBarModeRepository.getDefaultDisplay().showTransient();
+            }
+            mCommandQueueCallbacks.onSystemBarAttributesChanged(
+                    mDisplayId,
                     result.mAppearance,
-                    result.mImeWindowVis));
+                    result.mAppearanceRegions,
+                    result.mNavbarColorManagedByIme,
+                    result.mBehavior,
+                    result.mRequestedVisibleTypes,
+                    result.mPackageName,
+                    result.mLetterboxDetails);
+
+            // StatusBarManagerService has a back up of IME token and it's restored here.
+            mCommandQueueCallbacks.setImeWindowStatus(
+                    mDisplayId,
+                    result.mImeWindowVis,
+                    result.mImeBackDisposition,
+                    result.mShowImeSwitcher);
+
+            // Set up the initial icon state
+            int numIcons = result.mIcons.size();
+            for (int i = 0; i < numIcons; i++) {
+                mCommandQueue.setIcon(result.mIcons.keyAt(i), result.mIcons.valueAt(i));
+            }
+
+            if (DEBUG) {
+                Log.d(
+                        TAG,
+                        String.format(
+                                "init: icons=%d disabled=0x%08x lights=0x%08x imeButton=0x%08x",
+                                numIcons,
+                                result.mDisabledFlags1,
+                                result.mAppearance,
+                                result.mImeWindowVis));
+            }
         }
 
         IntentFilter internalFilter = new IntentFilter();
@@ -1005,24 +1024,30 @@
 
         mAccessibilityFloatingMenuController.init();
 
-        // set the initial view visibility
-        int disabledFlags1 = result.mDisabledFlags1;
-        int disabledFlags2 = result.mDisabledFlags2;
-        mInitController.addPostInitTask(() -> {
-            setUpDisableFlags(disabledFlags1, disabledFlags2);
-            try {
-                // NOTE(b/262059863): Force-update the disable flags after applying the flags
-                // returned from registerStatusBar(). The result's disabled flags may be stale
-                // if StatusBarManager's disabled flags are updated between registering the bar and
-                // this handling this post-init task. We force an update in this case, and use a new
-                // token to not conflict with any other disabled flags already requested by SysUI
-                Binder token = new Binder();
-                mBarService.disable(DISABLE_HOME, token, mContext.getPackageName());
-                mBarService.disable(0, token, mContext.getPackageName());
-            } catch (RemoteException ex) {
-                ex.rethrowFromSystemServer();
-            }
-        });
+        // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+        // StatusBarOrchestrator
+        if (!StatusBarSimpleFragment.isEnabled()) {
+            // set the initial view visibility
+            int disabledFlags1 = result.mDisabledFlags1;
+            int disabledFlags2 = result.mDisabledFlags2;
+            mInitController.addPostInitTask(
+                    () -> {
+                        setUpDisableFlags(disabledFlags1, disabledFlags2);
+                        try {
+                            // NOTE(b/262059863): Force-update the disable flags after applying the
+                            // flags returned from registerStatusBar(). The result's disabled flags
+                            // may be stale if StatusBarManager's disabled flags are updated between
+                            // registering the bar and this handling this post-init task. We force
+                            // an update in this case, and use a new token to not conflict with any
+                            // other disabled flags already requested by SysUI
+                            Binder token = new Binder();
+                            mBarService.disable(DISABLE_HOME, token, mContext.getPackageName());
+                            mBarService.disable(0, token, mContext.getPackageName());
+                        } catch (RemoteException ex) {
+                            ex.rethrowFromSystemServer();
+                        }
+                    });
+        }
 
         registerCallbacks();
 
@@ -1101,7 +1126,7 @@
 
     /**
      * @deprecated use {@link
-     * WindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible} instead.
+     * WindowRootViewVisibilityInteractor#isLockscreenOrShadeVisible()} instead.
      */    @VisibleForTesting
     @Deprecated
     void initShadeVisibilityListener() {
@@ -1168,13 +1193,16 @@
         mWallpaperController.setRootView(getNotificationShadeWindowView());
 
         mDemoModeController.addCallback(mDemoModeCallback);
-        mJavaAdapter.alwaysCollectFlow(
-                mStatusBarModeRepository.getDefaultDisplay().isTransientShown(),
-                this::onTransientShownChanged);
-        mJavaAdapter.alwaysCollectFlow(
-                mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode(),
-                this::updateBarMode);
-
+        // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+        // StatusBarOrchestrator.
+        if (!StatusBarSimpleFragment.isEnabled()) {
+            mJavaAdapter.alwaysCollectFlow(
+                    mStatusBarModeRepository.getDefaultDisplay().isTransientShown(),
+                    this::onTransientShownChanged);
+            mJavaAdapter.alwaysCollectFlow(
+                    mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode(),
+                    this::updateBarMode);
+        }
         mCommandQueueCallbacks = mCommandQueueCallbacksLazy.get();
         mCommandQueue.addCallback(mCommandQueueCallbacks);
 
@@ -1184,59 +1212,70 @@
                 mShadeExpansionStateManager.addExpansionListener(mWakeUpCoordinator);
         mWakeUpCoordinator.onPanelExpansionChanged(currentState);
 
-        // Allow plugins to reference DarkIconDispatcher and StatusBarStateController
-        mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class);
-        mPluginDependencyProvider.allowPluginDependency(StatusBarStateController.class);
-
-        // Set up CollapsedStatusBarFragment and PhoneStatusBarView
-        mStatusBarInitializer.setStatusBarViewUpdatedListener(
-                (statusBarViewController, statusBarTransitions) -> {
-                    mPhoneStatusBarViewController = statusBarViewController;
-                    mStatusBarTransitions = statusBarTransitions;
-                    getNotificationShadeWindowViewController()
-                            .setStatusBarViewController(mPhoneStatusBarViewController);
-                    // Ensure we re-propagate panel expansion values to the panel controller and
-                    // any listeners it may have, such as PanelBar. This will also ensure we
-                    // re-display the notification panel if necessary (for example, if
-                    // a heads-up notification was being displayed and should continue being
-                    // displayed).
-                    mShadeSurface.updateExpansionAndVisibility();
-                    setBouncerShowingForStatusBarComponents(mBouncerShowing);
-                    checkBarModes();
-                });
-        // When the flag is on, we register the fragment as a core startable and this is not needed
+        // When the StatusBarSimpleFragment flag is enabled, all this logic will be done in
+        // StatusBarOrchestrator.
         if (!StatusBarSimpleFragment.isEnabled()) {
+            // Allow plugins to reference DarkIconDispatcher and StatusBarStateController
+            mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class);
+            mPluginDependencyProvider.allowPluginDependency(StatusBarStateController.class);
+
+            // Set up CollapsedStatusBarFragment and PhoneStatusBarView
+            mStatusBarInitializer.setStatusBarViewUpdatedListener(
+                    (statusBarViewController, statusBarTransitions) -> {
+
+                        mPhoneStatusBarViewController = statusBarViewController;
+                        mStatusBarTransitions = statusBarTransitions;
+                        getNotificationShadeWindowViewController()
+                                .setStatusBarViewController(mPhoneStatusBarViewController);
+                        // Ensure we re-propagate panel expansion values to the panel controller and
+                        // any listeners it may have, such as PanelBar. This will also ensure we
+                        // re-display the notification panel if necessary (for example, if
+                        // a heads-up notification was being displayed and should continue being
+                        // displayed).
+                        mShadeSurface.updateExpansionAndVisibility();
+                        setBouncerShowingForStatusBarComponents(mBouncerShowing);
+                        checkBarModes();
+                    });
+            // When the flag is on, we register the fragment as a core startable and this is not
+            // needed
             mStatusBarInitializer.initializeStatusBar();
         }
 
         mStatusBarTouchableRegionManager.setup(getNotificationShadeWindowView());
 
-        createNavigationBar(result);
+        if (!StatusBarSimpleFragment.isEnabled()) {
+            createNavigationBar(result);
+        }
 
         mAmbientIndicationContainer = getNotificationShadeWindowView().findViewById(
                 R.id.ambient_indication_container);
 
-        mAutoHideController.setStatusBar(new AutoHideUiElement() {
-            @Override
-            public void synchronizeState() {
-                checkBarModes();
-            }
+        // When the StatusBarSimpleFragment flag is enabled, all this logic will be done in
+        // StatusBarOrchestrator.
+        if (!StatusBarSimpleFragment.isEnabled()) {
+            mAutoHideController.setStatusBar(
+                    new AutoHideUiElement() {
+                        @Override
+                        public void synchronizeState() {
+                            checkBarModes();
+                        }
 
-            @Override
-            public boolean shouldHideOnTouch() {
-                return !mRemoteInputManager.isRemoteInputActive();
-            }
+                        @Override
+                        public boolean shouldHideOnTouch() {
+                            return !mRemoteInputManager.isRemoteInputActive();
+                        }
 
-            @Override
-            public boolean isVisible() {
-                return isTransientShown();
-            }
+                        @Override
+                        public boolean isVisible() {
+                            return isTransientShown();
+                        }
 
-            @Override
-            public void hide() {
-                mStatusBarModeRepository.getDefaultDisplay().clearTransient();
-            }
-        });
+                        @Override
+                        public void hide() {
+                            mStatusBarModeRepository.getDefaultDisplay().clearTransient();
+                        }
+                    });
+        }
 
         ScrimView scrimBehind = getNotificationShadeWindowView().findViewById(R.id.scrim_behind);
         ScrimView notificationsScrim = getNotificationShadeWindowView()
@@ -1479,12 +1518,14 @@
      * @param state2 disable2 flags
      */
     protected void setUpDisableFlags(int state1, int state2) {
+        StatusBarSimpleFragment.assertInLegacyMode();
         mCommandQueue.disable(mDisplayId, state1, state2, false /* animate */);
     }
 
     // TODO(b/117478341): This was left such that CarStatusBar can override this method.
     // Try to remove this.
     protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
+        StatusBarSimpleFragment.assertInLegacyMode();
         mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result);
     }
 
@@ -1697,14 +1738,16 @@
     @Override
     public void checkBarModes() {
         if (mDemoModeController.isInDemoMode()) return;
-        if (mStatusBarTransitions != null) {
+        // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+        // StatusBarOrchestrator.
+        if (!StatusBarSimpleFragment.isEnabled() && mStatusBarTransitions != null) {
             checkBarMode(
                     mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode().getValue(),
                     mStatusBarWindowState,
                     mStatusBarTransitions);
+            mNoAnimationOnNextBarModeChange = false;
         }
         mNavigationBarController.checkNavBarModes(mDisplayId);
-        mNoAnimationOnNextBarModeChange = false;
     }
 
     /** Temporarily hides Bubbles if the status bar is hidden. */
@@ -1728,7 +1771,9 @@
     }
 
     private void finishBarAnimations() {
-        if (mStatusBarTransitions != null) {
+        // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+        // StatusBarOrchestrator.
+        if (!StatusBarSimpleFragment.isEnabled() && mStatusBarTransitions != null) {
             mStatusBarTransitions.finishAnimations();
         }
         mNavigationBarController.finishBarAnimations(mDisplayId);
@@ -1770,14 +1815,17 @@
         }
 
         pw.print("  mInteractingWindows="); pw.println(mInteractingWindows);
-        pw.print("  mStatusBarWindowState=");
-        pw.println(windowStateToString(mStatusBarWindowState));
+        if (!StatusBarSimpleFragment.isEnabled()) {
+            pw.print("  mStatusBarWindowState=");
+            pw.println(windowStateToString(mStatusBarWindowState));
+        }
         pw.print("  mDozing="); pw.println(mDozing);
         pw.print("  mWallpaperSupported= "); pw.println(mWallpaperSupported);
 
-        CentralSurfaces.dumpBarTransitions(
-                pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
-
+        if (!StatusBarSimpleFragment.isEnabled()) {
+            CentralSurfaces.dumpBarTransitions(
+                    pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
+        }
         pw.println("  mMediaManager: ");
         if (mMediaManager != null) {
             mMediaManager.dump(pw, args);
@@ -1850,7 +1898,11 @@
     private void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
         makeStatusBarView(result);
         mNotificationShadeWindowController.attach();
-        mStatusBarWindowController.attach();
+        // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+        // StatusBarOrchestrator
+        if (!StatusBarSimpleFragment.isEnabled()) {
+            mStatusBarWindowController.attach();
+        }
     }
 
     // called by makeStatusbar and also by PhoneStatusBarView
@@ -2475,7 +2527,7 @@
         int importance = bouncerShowing
                 ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
                 : IMPORTANT_FOR_ACCESSIBILITY_AUTO;
-        if (mPhoneStatusBarViewController != null) {
+        if (!StatusBarSimpleFragment.isEnabled() && mPhoneStatusBarViewController != null) {
             mPhoneStatusBarViewController.setImportantForAccessibility(importance);
         }
         mShadeSurface.setImportantForAccessibility(importance);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index ee96195..93db2db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -20,6 +20,7 @@
 import static android.service.notification.NotificationListenerService.REASON_CLICK;
 
 import static com.android.systemui.statusbar.phone.CentralSurfaces.getActivityOptions;
+import static com.android.systemui.util.kotlin.NullabilityKt.expectNotNull;
 
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -112,6 +113,8 @@
                 boolean showOverTheLockScreen);
     }
 
+    private final static String TAG = "StatusBarNotificationActivityStarter";
+
     private final Context mContext;
     private final int mDisplayId;
 
@@ -229,6 +232,7 @@
      */
     @Override
     public void onNotificationBubbleIconClicked(NotificationEntry entry) {
+        expectNotNull(TAG, "entry", entry);
         Runnable action = () -> {
             mBubblesManagerOptional.ifPresent(bubblesManager ->
                     bubblesManager.onUserChangedBubble(entry, !entry.isBubble()));
@@ -255,6 +259,8 @@
      */
     @Override
     public void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row) {
+        expectNotNull(TAG, "entry", entry);
+        expectNotNull(TAG, "row", row);
         mLogger.logStartingActivityFromClick(entry, row.isHeadsUpState(),
                 mKeyguardStateController.isVisible(),
                 mNotificationShadeWindowController.getPanelExpanded());
@@ -437,6 +443,7 @@
      */
     @Override
     public void onDragSuccess(NotificationEntry entry) {
+        expectNotNull(TAG, "entry", entry);
         // this method is not responsible for intent sending.
         // will focus follow operation only after drag-and-drop that notification.
         final NotificationVisibility nv = mVisibilityProvider.obtain(entry, true);
@@ -529,6 +536,8 @@
     @Override
     public void startNotificationGutsIntent(final Intent intent, final int appUid,
             ExpandableNotificationRow row) {
+        expectNotNull(TAG, "intent", intent);
+        expectNotNull(TAG, "row", row);
         boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
         ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index 13b651e8..5b03198 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -16,10 +16,20 @@
 package com.android.systemui.statusbar.phone.dagger
 
 import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.core.CommandQueueInitializer
 import com.android.systemui.statusbar.core.StatusBarInitializer
 import com.android.systemui.statusbar.core.StatusBarInitializerImpl
+import com.android.systemui.statusbar.core.StatusBarOrchestrator
+import com.android.systemui.statusbar.core.StatusBarSimpleFragment
+import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks
+import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
+import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStoreImpl
 import dagger.Binds
+import dagger.Lazy
 import dagger.Module
+import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
 
@@ -27,6 +37,16 @@
 @Module
 interface StatusBarPhoneModule {
 
+    @Binds
+    abstract fun windowStateRepoStore(
+        impl: StatusBarWindowStateRepositoryStoreImpl
+    ): StatusBarWindowStateRepositoryStore
+
+    @Binds
+    abstract fun commandQCallbacks(
+        impl: CentralSurfacesCommandQueueCallbacks
+    ): CommandQueue.Callbacks
+
     /** Binds {@link StatusBarInitializer} as a {@link CoreStartable}. */
     @Binds
     @IntoMap
@@ -34,4 +54,34 @@
     fun bindStatusBarInitializer(impl: StatusBarInitializerImpl): CoreStartable
 
     @Binds fun statusBarInitializer(impl: StatusBarInitializerImpl): StatusBarInitializer
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        @IntoMap
+        @ClassKey(StatusBarOrchestrator::class)
+        fun orchestratorCoreStartable(
+            orchestratorLazy: Lazy<StatusBarOrchestrator>
+        ): CoreStartable {
+            return if (StatusBarSimpleFragment.isEnabled) {
+                orchestratorLazy.get()
+            } else {
+                CoreStartable.NOP
+            }
+        }
+
+        @Provides
+        @SysUISingleton
+        @IntoMap
+        @ClassKey(CommandQueueInitializer::class)
+        fun commandQueueInitializerCoreStartable(
+            initializerLazy: Lazy<CommandQueueInitializer>
+        ): CoreStartable {
+            return if (StatusBarSimpleFragment.isEnabled) {
+                initializerLazy.get()
+            } else {
+                CoreStartable.NOP
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Nullability.kt
similarity index 67%
rename from packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
rename to packages/SystemUI/src/com/android/systemui/util/kotlin/Nullability.kt
index 298dacd..1c760be 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Nullability.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.util.kotlin
 
+import android.util.Log
 import java.util.Optional
 
 /**
@@ -28,3 +29,14 @@
  */
 @Suppress("NOTHING_TO_INLINE")
 inline fun <T> Optional<T>.getOrNull(): T? = orElse(null)
+
+/**
+ * Utility method to check if a value that is technically nullable is actually null. If it is null,
+ * this will crash development builds (but just log on production/droidfood builds). It can be used
+ * as a first step to verify if a nullable value can be made non-nullable instead.
+ */
+fun <T> expectNotNull(logTag: String, name: String, nullable: T?) {
+    if (nullable == null) {
+        Log.wtf(logTag, "Expected value of $name to not be null.")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
new file mode 100644
index 0000000..2a196c6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 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.core
+
+import android.internal.statusbar.fakeStatusBarService
+import android.platform.test.annotations.EnableFlags
+import android.view.WindowInsets
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.initController
+import com.android.systemui.keyguard.data.repository.fakeCommandQueue
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
+import com.android.systemui.statusbar.mockCommandQueueCallbacks
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.verify
+
+@EnableFlags(StatusBarSimpleFragment.FLAG_NAME)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommandQueueInitializerTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val initController = kosmos.initController
+    private val commandQueue = kosmos.fakeCommandQueue
+    private val commandQueueCallbacks = kosmos.mockCommandQueueCallbacks
+    private val statusBarModeRepository = kosmos.fakeStatusBarModeRepository
+    private val fakeStatusBarService = kosmos.fakeStatusBarService
+    private val initializer = kosmos.commandQueueInitializer
+
+    @Test
+    fun start_registersStatusBar() {
+        initializer.start()
+
+        assertThat(fakeStatusBarService.registeredStatusBar).isNotNull()
+    }
+
+    @Test
+    fun start_barResultHasTransientStatusBar_transientStateIsTrue() {
+        fakeStatusBarService.transientBarTypes = WindowInsets.Type.statusBars()
+
+        initializer.start()
+
+        assertThat(statusBarModeRepository.defaultDisplay.isTransientShown.value).isTrue()
+    }
+
+    @Test
+    fun start_barResultDoesNotHaveTransientStatusBar_transientStateIsFalse() {
+        fakeStatusBarService.transientBarTypes = WindowInsets.Type.navigationBars()
+
+        initializer.start()
+
+        assertThat(statusBarModeRepository.defaultDisplay.isTransientShown.value).isFalse()
+    }
+
+    @Test
+    fun start_callsOnSystemBarAttributesChanged_basedOnRegisterBarResult() {
+        initializer.start()
+
+        verify(commandQueueCallbacks)
+            .onSystemBarAttributesChanged(
+                context.displayId,
+                fakeStatusBarService.appearance,
+                fakeStatusBarService.appearanceRegions,
+                fakeStatusBarService.navbarColorManagedByIme,
+                fakeStatusBarService.behavior,
+                fakeStatusBarService.requestedVisibleTypes,
+                fakeStatusBarService.packageName,
+                fakeStatusBarService.letterboxDetails,
+            )
+    }
+
+    @Test
+    fun start_callsSetIcon_basedOnRegisterBarResult() {
+        initializer.start()
+
+        assertThat(commandQueue.icons).isEqualTo(fakeStatusBarService.statusBarIcons)
+    }
+
+    @Test
+    fun start_callsSetImeWindowStatus_basedOnRegisterBarResult() {
+        initializer.start()
+
+        verify(commandQueueCallbacks)
+            .setImeWindowStatus(
+                context.displayId,
+                fakeStatusBarService.imeWindowVis,
+                fakeStatusBarService.imeBackDisposition,
+                fakeStatusBarService.showImeSwitcher,
+            )
+    }
+
+    @Test
+    fun start_afterPostInitTaskExecuted_callsDisableFlags_basedOnRegisterBarResult() {
+        initializer.start()
+
+        initController.executePostInitTasks()
+
+        assertThat(commandQueue.disableFlags1ForDisplay(context.displayId))
+            .isEqualTo(fakeStatusBarService.disabledFlags1)
+        assertThat(commandQueue.disableFlags2ForDisplay(context.displayId))
+            .isEqualTo(fakeStatusBarService.disabledFlags2)
+    }
+
+    @Test
+    fun start_beforePostInitTaskExecuted_doesNotCallsDisableFlags() {
+        initializer.start()
+
+        assertThat(commandQueue.disableFlags1ForDisplay(context.displayId)).isNull()
+        assertThat(commandQueue.disableFlags2ForDisplay(context.displayId)).isNull()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt
new file mode 100644
index 0000000..5803365
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2024 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.core
+
+import android.platform.test.annotations.EnableFlags
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.unconfinedTestDispatcher
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.plugins.mockPluginDependencyProvider
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.shade.mockNotificationShadeWindowViewController
+import com.android.systemui.shade.mockShadeSurface
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.data.model.StatusBarMode.LIGHTS_OUT
+import com.android.systemui.statusbar.data.model.StatusBarMode.LIGHTS_OUT_TRANSPARENT
+import com.android.systemui.statusbar.data.model.StatusBarMode.OPAQUE
+import com.android.systemui.statusbar.data.model.StatusBarMode.TRANSPARENT
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
+import com.android.systemui.statusbar.phone.mockPhoneStatusBarTransitions
+import com.android.systemui.statusbar.phone.mockPhoneStatusBarViewController
+import com.android.systemui.statusbar.window.data.model.StatusBarWindowState
+import com.android.systemui.statusbar.window.data.repository.fakeStatusBarWindowStateRepositoryStore
+import com.android.systemui.statusbar.window.data.repository.statusBarWindowStateRepositoryStore
+import com.android.systemui.statusbar.window.fakeStatusBarWindowController
+import com.android.systemui.testKosmos
+import com.android.wm.shell.bubbles.bubbles
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+@EnableFlags(StatusBarSimpleFragment.FLAG_NAME)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class StatusBarOrchestratorTest : SysuiTestCase() {
+
+    private val kosmos =
+        testKosmos().also {
+            it.testDispatcher = it.unconfinedTestDispatcher
+            it.statusBarWindowStateRepositoryStore = it.fakeStatusBarWindowStateRepositoryStore
+        }
+    private val testScope = kosmos.testScope
+    private val statusBarViewController = kosmos.mockPhoneStatusBarViewController
+    private val statusBarWindowController = kosmos.fakeStatusBarWindowController
+    private val statusBarModeRepository = kosmos.fakeStatusBarModeRepository
+    private val pluginDependencyProvider = kosmos.mockPluginDependencyProvider
+    private val notificationShadeWindowViewController =
+        kosmos.mockNotificationShadeWindowViewController
+    private val shadeSurface = kosmos.mockShadeSurface
+    private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+    private val fakeStatusBarWindowStateRepositoryStore =
+        kosmos.fakeStatusBarWindowStateRepositoryStore
+    private val fakePowerRepository = kosmos.fakePowerRepository
+    private val mockPhoneStatusBarTransitions = kosmos.mockPhoneStatusBarTransitions
+    private val mockBubbles = kosmos.bubbles
+
+    private val orchestrator = kosmos.statusBarOrchestrator
+
+    @Test
+    fun start_setsUpPluginDependencies() {
+        orchestrator.start()
+
+        verify(pluginDependencyProvider).allowPluginDependency(DarkIconDispatcher::class.java)
+        verify(pluginDependencyProvider).allowPluginDependency(StatusBarStateController::class.java)
+    }
+
+    @Test
+    fun start_attachesWindow() {
+        orchestrator.start()
+
+        assertThat(statusBarWindowController.isAttached).isTrue()
+    }
+
+    @Test
+    fun start_setsStatusBarControllerOnShade() {
+        orchestrator.start()
+
+        verify(notificationShadeWindowViewController)
+            .setStatusBarViewController(statusBarViewController)
+    }
+
+    @Test
+    fun start_updatesShadeExpansion() {
+        orchestrator.start()
+
+        verify(shadeSurface).updateExpansionAndVisibility()
+    }
+
+    @Test
+    fun bouncerShowing_setsImportanceForA11yToNoHideDescendants() =
+        testScope.runTest {
+            orchestrator.start()
+
+            bouncerRepository.setPrimaryShow(isShowing = true)
+
+            verify(statusBarViewController)
+                .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
+        }
+
+    @Test
+    fun bouncerNotShowing_setsImportanceForA11yToNoHideDescendants() =
+        testScope.runTest {
+            orchestrator.start()
+
+            bouncerRepository.setPrimaryShow(isShowing = false)
+
+            verify(statusBarViewController)
+                .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO)
+        }
+
+    @Test
+    fun deviceGoesToSleep_barTransitionsAnimationsAreFinished() =
+        testScope.runTest {
+            putDeviceToSleep()
+
+            orchestrator.start()
+
+            verify(mockPhoneStatusBarTransitions).finishAnimations()
+        }
+
+    @Test
+    fun deviceIsAwake_barTransitionsAnimationsAreNotFinished() =
+        testScope.runTest {
+            awakeDevice()
+
+            orchestrator.start()
+
+            verify(mockPhoneStatusBarTransitions, never()).finishAnimations()
+        }
+
+    @Test
+    fun statusBarVisible_notifiesBubbles() =
+        testScope.runTest {
+            setStatusBarMode(TRANSPARENT)
+            setStatusBarWindowState(StatusBarWindowState.Showing)
+
+            orchestrator.start()
+
+            verify(mockBubbles).onStatusBarVisibilityChanged(/* visible= */ true)
+        }
+
+    @Test
+    fun statusBarInLightsOutMode_notifiesBubblesWithStatusBarInvisible() =
+        testScope.runTest {
+            setStatusBarMode(LIGHTS_OUT)
+            setStatusBarWindowState(StatusBarWindowState.Showing)
+
+            orchestrator.start()
+
+            verify(mockBubbles).onStatusBarVisibilityChanged(/* visible= */ false)
+        }
+
+    @Test
+    fun statusBarInLightsOutTransparentMode_notifiesBubblesWithStatusBarInvisible() =
+        testScope.runTest {
+            setStatusBarMode(LIGHTS_OUT_TRANSPARENT)
+            setStatusBarWindowState(StatusBarWindowState.Showing)
+
+            orchestrator.start()
+
+            verify(mockBubbles).onStatusBarVisibilityChanged(/* visible= */ false)
+        }
+
+    @Test
+    fun statusBarWindowNotShowing_notifiesBubblesWithStatusBarInvisible() =
+        testScope.runTest {
+            setStatusBarMode(TRANSPARENT)
+            setStatusBarWindowState(StatusBarWindowState.Hidden)
+
+            orchestrator.start()
+
+            verify(mockBubbles).onStatusBarVisibilityChanged(/* visible= */ false)
+        }
+
+    @Test
+    fun statusBarModeChange_transitionsToModeWithAnimation() =
+        testScope.runTest {
+            awakeDevice()
+            clearTransientStatusBar()
+            setStatusBarWindowState(StatusBarWindowState.Showing)
+            setStatusBarMode(TRANSPARENT)
+
+            orchestrator.start()
+
+            verify(mockPhoneStatusBarTransitions)
+                .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
+        }
+
+    @Test
+    fun statusBarModeChange_keepsTransitioningAsModeChanges() =
+        testScope.runTest {
+            awakeDevice()
+            clearTransientStatusBar()
+            setStatusBarWindowState(StatusBarWindowState.Showing)
+            setStatusBarMode(TRANSPARENT)
+
+            orchestrator.start()
+
+            verify(mockPhoneStatusBarTransitions)
+                .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
+
+            setStatusBarMode(OPAQUE)
+            verify(mockPhoneStatusBarTransitions)
+                .transitionTo(OPAQUE.toTransitionModeInt(), /* animate= */ true)
+
+            setStatusBarMode(LIGHTS_OUT)
+            verify(mockPhoneStatusBarTransitions)
+                .transitionTo(LIGHTS_OUT.toTransitionModeInt(), /* animate= */ true)
+
+            setStatusBarMode(LIGHTS_OUT_TRANSPARENT)
+            verify(mockPhoneStatusBarTransitions)
+                .transitionTo(LIGHTS_OUT_TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
+        }
+
+    @Test
+    fun statusBarModeChange_transientIsShown_transitionsToModeWithoutAnimation() =
+        testScope.runTest {
+            awakeDevice()
+            setTransientStatusBar()
+            setStatusBarWindowState(StatusBarWindowState.Showing)
+            setStatusBarMode(TRANSPARENT)
+
+            orchestrator.start()
+
+            verify(mockPhoneStatusBarTransitions)
+                .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false)
+        }
+
+    @Test
+    fun statusBarModeChange_windowIsHidden_transitionsToModeWithoutAnimation() =
+        testScope.runTest {
+            awakeDevice()
+            clearTransientStatusBar()
+            setStatusBarWindowState(StatusBarWindowState.Hidden)
+            setStatusBarMode(TRANSPARENT)
+
+            orchestrator.start()
+
+            verify(mockPhoneStatusBarTransitions)
+                .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false)
+        }
+
+    @Test
+    fun statusBarModeChange_deviceIsAsleep_transitionsToModeWithoutAnimation() =
+        testScope.runTest {
+            putDeviceToSleep()
+            clearTransientStatusBar()
+            setStatusBarWindowState(StatusBarWindowState.Showing)
+            setStatusBarMode(TRANSPARENT)
+
+            orchestrator.start()
+
+            verify(mockPhoneStatusBarTransitions)
+                .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false)
+        }
+
+    @Test
+    fun statusBarModeAnimationConditionsChange_withoutBarModeChange_noNewTransitionsHappen() =
+        testScope.runTest {
+            awakeDevice()
+            clearTransientStatusBar()
+            setStatusBarWindowState(StatusBarWindowState.Showing)
+            setStatusBarMode(TRANSPARENT)
+
+            orchestrator.start()
+
+            putDeviceToSleep()
+            awakeDevice()
+            setTransientStatusBar()
+            clearTransientStatusBar()
+
+            verify(mockPhoneStatusBarTransitions, times(1))
+                .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
+        }
+
+    private fun putDeviceToSleep() {
+        fakePowerRepository.updateWakefulness(
+            rawState = WakefulnessState.ASLEEP,
+            lastWakeReason = WakeSleepReason.KEY,
+            lastSleepReason = WakeSleepReason.KEY,
+            powerButtonLaunchGestureTriggered = true,
+        )
+    }
+
+    private fun awakeDevice() {
+        fakePowerRepository.updateWakefulness(
+            rawState = WakefulnessState.AWAKE,
+            lastWakeReason = WakeSleepReason.KEY,
+            lastSleepReason = WakeSleepReason.KEY,
+            powerButtonLaunchGestureTriggered = true,
+        )
+    }
+
+    private fun setTransientStatusBar() {
+        statusBarModeRepository.defaultDisplay.showTransient()
+    }
+
+    private fun clearTransientStatusBar() {
+        statusBarModeRepository.defaultDisplay.clearTransient()
+    }
+
+    private fun setStatusBarWindowState(state: StatusBarWindowState) {
+        fakeStatusBarWindowStateRepositoryStore.defaultDisplay.setWindowState(state)
+    }
+
+    private fun setStatusBarMode(statusBarMode: StatusBarMode) {
+        statusBarModeRepository.defaultDisplay.statusBarMode.value = statusBarMode
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index c710c56..15ea811 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -169,6 +169,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
+import com.android.systemui.statusbar.core.StatusBarOrchestrator;
 import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -346,6 +347,7 @@
     @Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
     @Mock private NotificationSettingsInteractor mNotificationSettingsInteractor;
     @Mock private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
+    @Mock private StatusBarOrchestrator mStatusBarOrchestrator;
     private ShadeController mShadeController;
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
diff --git a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
new file mode 100644
index 0000000..cc0597b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.internal.statusbar
+
+import android.app.Notification
+import android.content.ComponentName
+import android.graphics.Rect
+import android.graphics.drawable.Icon
+import android.hardware.biometrics.IBiometricContextListener
+import android.hardware.biometrics.IBiometricSysuiReceiver
+import android.hardware.biometrics.PromptInfo
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback
+import android.media.INearbyMediaDevicesProvider
+import android.media.MediaRoute2Info
+import android.net.Uri
+import android.os.Bundle
+import android.os.IBinder
+import android.os.UserHandle
+import android.util.ArrayMap
+import android.view.KeyEvent
+import com.android.internal.logging.InstanceId
+import com.android.internal.statusbar.IAddTileResultCallback
+import com.android.internal.statusbar.ISessionListener
+import com.android.internal.statusbar.IStatusBar
+import com.android.internal.statusbar.IStatusBarService
+import com.android.internal.statusbar.IUndoMediaTransferCallback
+import com.android.internal.statusbar.LetterboxDetails
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.internal.statusbar.RegisterStatusBarResult
+import com.android.internal.statusbar.StatusBarIcon
+import com.android.internal.view.AppearanceRegion
+import org.mockito.kotlin.mock
+
+class FakeStatusBarService : IStatusBarService.Stub() {
+
+    var registeredStatusBar: IStatusBar? = null
+        private set
+
+    var statusBarIcons =
+        ArrayMap<String, StatusBarIcon>().also {
+            it["slot1"] = mock<StatusBarIcon>()
+            it["slot2"] = mock<StatusBarIcon>()
+        }
+    var disabledFlags1 = 1234567
+    var appearance = 123
+    var appearanceRegions =
+        arrayOf(
+            AppearanceRegion(
+                /* appearance = */ 123,
+                /* bounds = */ Rect(/* left= */ 4, /* top= */ 3, /* right= */ 2, /* bottom= */ 1),
+            ),
+            AppearanceRegion(
+                /* appearance = */ 345,
+                /* bounds = */ Rect(/* left= */ 1, /* top= */ 2, /* right= */ 3, /* bottom= */ 4),
+            ),
+        )
+    var imeWindowVis = 987
+    var imeBackDisposition = 654
+    var showImeSwitcher = true
+    var disabledFlags2 = 7654321
+    var navbarColorManagedByIme = true
+    var behavior = 234
+    var requestedVisibleTypes = 345
+    var packageName = "fake.bar.ser.vice"
+    var transientBarTypes = 0
+    var letterboxDetails =
+        arrayOf(
+            LetterboxDetails(
+                /* letterboxInnerBounds = */ Rect(
+                    /* left= */ 5,
+                    /* top= */ 6,
+                    /* right= */ 7,
+                    /* bottom= */ 8,
+                ),
+                /* letterboxFullBounds = */ Rect(
+                    /* left= */ 1,
+                    /* top= */ 2,
+                    /* right= */ 3,
+                    /* bottom= */ 4,
+                ),
+                /* appAppearance = */ 123,
+            )
+        )
+
+    override fun expandNotificationsPanel() {}
+
+    override fun collapsePanels() {}
+
+    override fun togglePanel() {}
+
+    override fun disable(what: Int, token: IBinder, pkg: String) {
+        disableForUser(what, token, pkg, userId = 0)
+    }
+
+    override fun disableForUser(what: Int, token: IBinder, pkg: String, userId: Int) {}
+
+    override fun disable2(what: Int, token: IBinder, pkg: String) {
+        disable2ForUser(what, token, pkg, userId = 0)
+    }
+
+    override fun disable2ForUser(what: Int, token: IBinder, pkg: String, userId: Int) {}
+
+    override fun getDisableFlags(token: IBinder, userId: Int): IntArray {
+        return intArrayOf(disabledFlags1, disabledFlags2)
+    }
+
+    override fun setIcon(
+        slot: String,
+        iconPackage: String,
+        iconId: Int,
+        iconLevel: Int,
+        contentDescription: String,
+    ) {}
+
+    override fun setIconVisibility(slot: String, visible: Boolean) {}
+
+    override fun removeIcon(slot: String) {}
+
+    override fun setImeWindowStatus(
+        displayId: Int,
+        vis: Int,
+        backDisposition: Int,
+        showImeSwitcher: Boolean,
+    ) {}
+
+    override fun expandSettingsPanel(subPanel: String) {}
+
+    override fun registerStatusBar(callbacks: IStatusBar): RegisterStatusBarResult {
+        registeredStatusBar = callbacks
+        return RegisterStatusBarResult(
+            statusBarIcons,
+            disabledFlags1,
+            appearance,
+            appearanceRegions,
+            imeWindowVis,
+            imeBackDisposition,
+            showImeSwitcher,
+            disabledFlags2,
+            navbarColorManagedByIme,
+            behavior,
+            requestedVisibleTypes,
+            packageName,
+            transientBarTypes,
+            letterboxDetails,
+        )
+    }
+
+    override fun onPanelRevealed(clearNotificationEffects: Boolean, numItems: Int) {}
+
+    override fun onPanelHidden() {}
+
+    override fun clearNotificationEffects() {}
+
+    override fun onNotificationClick(key: String, nv: NotificationVisibility) {}
+
+    override fun onNotificationActionClick(
+        key: String,
+        actionIndex: Int,
+        action: Notification.Action,
+        nv: NotificationVisibility,
+        generatedByAssistant: Boolean,
+    ) {}
+
+    override fun onNotificationError(
+        pkg: String,
+        tag: String,
+        id: Int,
+        uid: Int,
+        initialPid: Int,
+        message: String,
+        userId: Int,
+    ) {}
+
+    override fun onClearAllNotifications(userId: Int) {}
+
+    override fun onNotificationClear(
+        pkg: String,
+        userId: Int,
+        key: String,
+        dismissalSurface: Int,
+        dismissalSentiment: Int,
+        nv: NotificationVisibility,
+    ) {}
+
+    override fun onNotificationVisibilityChanged(
+        newlyVisibleKeys: Array<NotificationVisibility>,
+        noLongerVisibleKeys: Array<NotificationVisibility>,
+    ) {}
+
+    override fun onNotificationExpansionChanged(
+        key: String,
+        userAction: Boolean,
+        expanded: Boolean,
+        notificationLocation: Int,
+    ) {}
+
+    override fun onNotificationDirectReplied(key: String) {}
+
+    override fun onNotificationSmartSuggestionsAdded(
+        key: String,
+        smartReplyCount: Int,
+        smartActionCount: Int,
+        generatedByAssistant: Boolean,
+        editBeforeSending: Boolean,
+    ) {}
+
+    override fun onNotificationSmartReplySent(
+        key: String,
+        replyIndex: Int,
+        reply: CharSequence,
+        notificationLocation: Int,
+        modifiedBeforeSending: Boolean,
+    ) {}
+
+    override fun onNotificationSettingsViewed(key: String) {}
+
+    override fun onNotificationBubbleChanged(key: String, isBubble: Boolean, flags: Int) {}
+
+    override fun onBubbleMetadataFlagChanged(key: String, flags: Int) {}
+
+    override fun hideCurrentInputMethodForBubbles(displayId: Int) {}
+
+    override fun grantInlineReplyUriPermission(
+        key: String,
+        uri: Uri,
+        user: UserHandle,
+        packageName: String,
+    ) {}
+
+    override fun clearInlineReplyUriPermissions(key: String) {}
+
+    override fun onNotificationFeedbackReceived(key: String, feedback: Bundle) {}
+
+    override fun onGlobalActionsShown() {}
+
+    override fun onGlobalActionsHidden() {}
+
+    override fun shutdown() {}
+
+    override fun reboot(safeMode: Boolean) {}
+
+    override fun restart() {}
+
+    override fun addTile(tile: ComponentName) {}
+
+    override fun remTile(tile: ComponentName) {}
+
+    override fun clickTile(tile: ComponentName) {}
+
+    override fun handleSystemKey(key: KeyEvent) {}
+
+    override fun getLastSystemKey(): Int {
+        return -1
+    }
+
+    override fun showPinningEnterExitToast(entering: Boolean) {}
+
+    override fun showPinningEscapeToast() {}
+
+    override fun showAuthenticationDialog(
+        promptInfo: PromptInfo,
+        sysuiReceiver: IBiometricSysuiReceiver,
+        sensorIds: IntArray,
+        credentialAllowed: Boolean,
+        requireConfirmation: Boolean,
+        userId: Int,
+        operationId: Long,
+        opPackageName: String,
+        requestId: Long,
+    ) {}
+
+    override fun onBiometricAuthenticated(modality: Int) {}
+
+    override fun onBiometricHelp(modality: Int, message: String) {}
+
+    override fun onBiometricError(modality: Int, error: Int, vendorCode: Int) {}
+
+    override fun hideAuthenticationDialog(requestId: Long) {}
+
+    override fun setBiometicContextListener(listener: IBiometricContextListener) {}
+
+    override fun setUdfpsRefreshRateCallback(callback: IUdfpsRefreshRateRequestCallback) {}
+
+    override fun showInattentiveSleepWarning() {}
+
+    override fun dismissInattentiveSleepWarning(animated: Boolean) {}
+
+    override fun startTracing() {}
+
+    override fun stopTracing() {}
+
+    override fun isTracing(): Boolean {
+        return false
+    }
+
+    override fun suppressAmbientDisplay(suppress: Boolean) {}
+
+    override fun requestTileServiceListeningState(componentName: ComponentName, userId: Int) {}
+
+    override fun requestAddTile(
+        componentName: ComponentName,
+        label: CharSequence,
+        icon: Icon,
+        userId: Int,
+        callback: IAddTileResultCallback,
+    ) {}
+
+    override fun cancelRequestAddTile(packageName: String) {}
+
+    override fun setNavBarMode(navBarMode: Int) {}
+
+    override fun getNavBarMode(): Int {
+        return -1
+    }
+
+    override fun registerSessionListener(sessionFlags: Int, listener: ISessionListener) {}
+
+    override fun unregisterSessionListener(sessionFlags: Int, listener: ISessionListener) {}
+
+    override fun onSessionStarted(sessionType: Int, instanceId: InstanceId) {}
+
+    override fun onSessionEnded(sessionType: Int, instanceId: InstanceId) {}
+
+    override fun updateMediaTapToTransferSenderDisplay(
+        displayState: Int,
+        routeInfo: MediaRoute2Info,
+        undoCallback: IUndoMediaTransferCallback,
+    ) {}
+
+    override fun updateMediaTapToTransferReceiverDisplay(
+        displayState: Int,
+        routeInfo: MediaRoute2Info,
+        appIcon: Icon,
+        appName: CharSequence,
+    ) {}
+
+    override fun registerNearbyMediaDevicesProvider(provider: INearbyMediaDevicesProvider) {}
+
+    override fun unregisterNearbyMediaDevicesProvider(provider: INearbyMediaDevicesProvider) {}
+
+    override fun showRearDisplayDialog(currentBaseState: Int) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/android/internal/statusbar/StatusBarServiceKosmos.kt b/packages/SystemUI/tests/utils/src/android/internal/statusbar/StatusBarServiceKosmos.kt
new file mode 100644
index 0000000..1304161
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/internal/statusbar/StatusBarServiceKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.internal.statusbar
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeStatusBarService by Kosmos.Fixture { FakeStatusBarService() }
+
+var Kosmos.statusBarService by Kosmos.Fixture { fakeStatusBarService }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/DemoModeKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/DemoModeKosmos.kt
new file mode 100644
index 0000000..39384fd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/DemoModeKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 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
+
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.mockDemoModeController by Kosmos.Fixture { mock<DemoModeController>() }
+
+var Kosmos.demoModeController by Kosmos.Fixture { mockDemoModeController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/InitControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/InitControllerKosmos.kt
new file mode 100644
index 0000000..13169e1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/InitControllerKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 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
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.initController by Kosmos.Fixture { InitController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt
index 3a59f6a..601c145 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt
@@ -18,6 +18,8 @@
 package com.android.systemui.keyguard.data.repository
 
 import android.content.Context
+import androidx.collection.ArrayMap
+import com.android.internal.statusbar.StatusBarIcon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.settings.DisplayTracker
 import com.android.systemui.statusbar.CommandQueue
@@ -31,6 +33,11 @@
     CommandQueue(mock(Context::class.java), mock(DisplayTracker::class.java)) {
     private val callbacks = mutableListOf<Callbacks>()
 
+    val icons = ArrayMap<String, StatusBarIcon>()
+
+    private val perDisplayDisableFlags1 = mutableMapOf<Int, Int>()
+    private val perDisplayDisableFlags2 = mutableMapOf<Int, Int>()
+
     override fun addCallback(callback: Callbacks) {
         callbacks.add(callback)
     }
@@ -44,6 +51,23 @@
     }
 
     fun callbackCount(): Int = callbacks.size
+
+    override fun setIcon(slot: String, icon: StatusBarIcon) {
+        icons[slot] = icon
+    }
+
+    override fun disable(displayId: Int, state1: Int, state2: Int, animate: Boolean) {
+        perDisplayDisableFlags1[displayId] = state1
+        perDisplayDisableFlags2[displayId] = state2
+    }
+
+    override fun disable(displayId: Int, state1: Int, state2: Int) {
+        disable(displayId, state1, state2, /* animate= */ false)
+    }
+
+    fun disableFlags1ForDisplay(displayId: Int) = perDisplayDisableFlags1[displayId]
+
+    fun disableFlags2ForDisplay(displayId: Int) = perDisplayDisableFlags2[displayId]
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/NavigationBarControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/NavigationBarControllerKosmos.kt
new file mode 100644
index 0000000..9e2039e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/NavigationBarControllerKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.mockNavigationBarController by Kosmos.Fixture { mock<NavigationBarController>() }
+
+var Kosmos.navigationBarController by Kosmos.Fixture { mockNavigationBarController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/PluginDependencyKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/PluginDependencyKosmos.kt
new file mode 100644
index 0000000..f1388e9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/PluginDependencyKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 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.plugins
+
+import android.testing.LeakCheck
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.utils.leaks.FakePluginManager
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.mock
+
+val Kosmos.leakCheck by Kosmos.Fixture { LeakCheck() }
+
+val Kosmos.fakePluginManager by Kosmos.Fixture { FakePluginManager(leakCheck) }
+
+var Kosmos.pluginManager by Kosmos.Fixture { fakePluginManager }
+
+val Kosmos.pluginDependencyProvider by Kosmos.Fixture { PluginDependencyProvider { pluginManager } }
+
+val Kosmos.mockPluginDependencyProvider by Kosmos.Fixture { mock<PluginDependencyProvider>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt
index 1ceab68..a9f9c82 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt
@@ -20,3 +20,13 @@
 import com.android.systemui.util.mockito.mock
 
 var Kosmos.shadeViewController by Kosmos.Fixture { mock<ShadeViewController>() }
+
+val Kosmos.mockNotificationShadeWindowViewController by
+    Kosmos.Fixture { mock<NotificationShadeWindowViewController>() }
+
+var Kosmos.notificationShadeWindowViewController by
+    Kosmos.Fixture { mockNotificationShadeWindowViewController }
+
+val Kosmos.mockShadeSurface by Kosmos.Fixture { mock<ShadeSurface>() }
+
+var Kosmos.shadeSurface by Kosmos.Fixture { mockShadeSurface }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt
index 27f7f68..f571c1b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt
@@ -19,4 +19,10 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.commandQueue by Kosmos.Fixture { mock<CommandQueue>() }
+val Kosmos.mockCommandQueue by Kosmos.Fixture { mock<CommandQueue>() }
+
+var Kosmos.commandQueue by Kosmos.Fixture { mockCommandQueue }
+
+val Kosmos.mockCommandQueueCallbacks by Kosmos.Fixture { mock<CommandQueue.Callbacks>() }
+
+var Kosmos.commandQueueCallbacks by Kosmos.Fixture { mockCommandQueue }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt
index 554bdbe..d436cd4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt
@@ -19,5 +19,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.notificationRemoteInputManager by
+val Kosmos.mockNotificationRemoteInputManager by
     Kosmos.Fixture { mock<NotificationRemoteInputManager>() }
+
+var Kosmos.notificationRemoteInputManager by Kosmos.Fixture { mockNotificationRemoteInputManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/CommandQueueInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/CommandQueueInitializerKosmos.kt
new file mode 100644
index 0000000..cba4e8e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/CommandQueueInitializerKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 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.core
+
+import android.content.testableContext
+import android.internal.statusbar.fakeStatusBarService
+import com.android.systemui.initController
+import com.android.systemui.keyguard.data.repository.fakeCommandQueue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.navigationbar.mockNavigationBarController
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
+import com.android.systemui.statusbar.mockCommandQueueCallbacks
+
+var Kosmos.commandQueueInitializer by
+    Kosmos.Fixture {
+        CommandQueueInitializer(
+            testableContext,
+            fakeCommandQueue,
+            { mockCommandQueueCallbacks },
+            fakeStatusBarModeRepository,
+            initController,
+            fakeStatusBarService,
+            mockNavigationBarController,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt
new file mode 100644
index 0000000..edd6604
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 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.core
+
+import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener
+import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
+import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
+
+class FakeStatusBarInitializer(
+    private val statusBarViewController: PhoneStatusBarViewController,
+    private val statusBarTransitions: PhoneStatusBarTransitions,
+) : StatusBarInitializer {
+
+    override var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null
+        set(value) {
+            field = value
+            value?.onStatusBarViewUpdated(statusBarViewController, statusBarTransitions)
+        }
+
+    override fun initializeStatusBar() {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
new file mode 100644
index 0000000..d103200
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 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.core
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.phone.phoneStatusBarTransitions
+import com.android.systemui.statusbar.phone.phoneStatusBarViewController
+
+val Kosmos.fakeStatusBarInitializer by
+    Kosmos.Fixture {
+        FakeStatusBarInitializer(phoneStatusBarViewController, phoneStatusBarTransitions)
+    }
+
+var Kosmos.statusBarInitializer by Kosmos.Fixture { fakeStatusBarInitializer }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
new file mode 100644
index 0000000..c53e44d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 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.core
+
+import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.mockDemoModeController
+import com.android.systemui.plugins.mockPluginDependencyProvider
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.shade.mockNotificationShadeWindowViewController
+import com.android.systemui.shade.mockShadeSurface
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
+import com.android.systemui.statusbar.mockNotificationRemoteInputManager
+import com.android.systemui.statusbar.phone.mockAutoHideController
+import com.android.systemui.statusbar.window.data.repository.statusBarWindowStateRepositoryStore
+import com.android.systemui.statusbar.window.fakeStatusBarWindowController
+import com.android.wm.shell.bubbles.bubblesOptional
+
+val Kosmos.statusBarOrchestrator by
+    Kosmos.Fixture {
+        StatusBarOrchestrator(
+            applicationCoroutineScope,
+            fakeStatusBarInitializer,
+            fakeStatusBarWindowController,
+            fakeStatusBarModeRepository,
+            mockDemoModeController,
+            mockPluginDependencyProvider,
+            mockAutoHideController,
+            mockNotificationRemoteInputManager,
+            { mockNotificationShadeWindowViewController },
+            mockShadeSurface,
+            bubblesOptional,
+            statusBarWindowStateRepositoryStore,
+            powerInteractor,
+            primaryBouncerInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt
new file mode 100644
index 0000000..090ce31
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.mockAutoHideController by Kosmos.Fixture { mock<AutoHideController>() }
+
+var Kosmos.autoHideController by Kosmos.Fixture { mockAutoHideController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/PhoneStatusBarKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/PhoneStatusBarKosmos.kt
new file mode 100644
index 0000000..603ee08
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/PhoneStatusBarKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.Mockito.mock
+
+val Kosmos.mockPhoneStatusBarViewController: PhoneStatusBarViewController by
+    Kosmos.Fixture { mock(PhoneStatusBarViewController::class.java) }
+
+var Kosmos.phoneStatusBarViewController by Kosmos.Fixture { mockPhoneStatusBarViewController }
+
+val Kosmos.mockPhoneStatusBarTransitions: PhoneStatusBarTransitions by
+    Kosmos.Fixture { mock(PhoneStatusBarTransitions::class.java) }
+
+var Kosmos.phoneStatusBarTransitions by Kosmos.Fixture { mockPhoneStatusBarTransitions }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt
new file mode 100644
index 0000000..528c9d9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 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.window
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.fragments.FragmentHostManager
+import java.util.Optional
+
+class FakeStatusBarWindowController : StatusBarWindowController {
+
+    var isAttached = false
+        private set
+
+    override val statusBarHeight: Int = 0
+
+    override fun refreshStatusBarHeight() {}
+
+    override fun attach() {
+        isAttached = true
+    }
+
+    override fun addViewToWindow(view: View, layoutParams: ViewGroup.LayoutParams) {}
+
+    override val backgroundView: View
+        get() = throw NotImplementedError()
+
+    override val fragmentHostManager: FragmentHostManager
+        get() = throw NotImplementedError()
+
+    override fun wrapAnimationControllerIfInStatusBar(
+        rootView: View,
+        animationController: ActivityTransitionAnimator.Controller,
+    ): Optional<ActivityTransitionAnimator.Controller> = Optional.empty()
+
+    override fun setForceStatusBarVisible(forceStatusBarVisible: Boolean) {}
+
+    override fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
new file mode 100644
index 0000000..c198b35
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.window
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeStatusBarWindowController by Kosmos.Fixture { FakeStatusBarWindowController() }
+
+var Kosmos.statusBarWindowController by Kosmos.Fixture { fakeStatusBarWindowController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/FakeStatusBarWindowStatePerDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/FakeStatusBarWindowStatePerDisplayRepository.kt
new file mode 100644
index 0000000..6532a7e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/FakeStatusBarWindowStatePerDisplayRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 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.window.data.repository
+
+import android.view.Display
+import com.android.systemui.statusbar.window.data.model.StatusBarWindowState
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeStatusBarWindowStateRepositoryStore : StatusBarWindowStateRepositoryStore {
+
+    private val perDisplayRepos = mutableMapOf<Int, FakeStatusBarWindowStatePerDisplayRepository>()
+
+    override val defaultDisplay: FakeStatusBarWindowStatePerDisplayRepository =
+        forDisplay(Display.DEFAULT_DISPLAY)
+
+    override fun forDisplay(displayId: Int): FakeStatusBarWindowStatePerDisplayRepository =
+        perDisplayRepos.computeIfAbsent(displayId) {
+            FakeStatusBarWindowStatePerDisplayRepository()
+        }
+}
+
+class FakeStatusBarWindowStatePerDisplayRepository : StatusBarWindowStatePerDisplayRepository {
+
+    private val _windowState = MutableStateFlow(StatusBarWindowState.Hidden)
+
+    override val windowState = _windowState.asStateFlow()
+
+    fun setWindowState(state: StatusBarWindowState) {
+        _windowState.value = state
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt
index e2b7f5f..2205a3b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt
@@ -21,6 +21,9 @@
 import com.android.systemui.settings.displayTracker
 import com.android.systemui.statusbar.commandQueue
 
+val Kosmos.fakeStatusBarWindowStateRepositoryStore by
+    Kosmos.Fixture { FakeStatusBarWindowStateRepositoryStore() }
+
 class KosmosStatusBarWindowStatePerDisplayRepositoryFactory(private val kosmos: Kosmos) :
     StatusBarWindowStatePerDisplayRepositoryFactory {
     override fun create(displayId: Int): StatusBarWindowStatePerDisplayRepositoryImpl {
@@ -32,7 +35,7 @@
     }
 }
 
-val Kosmos.statusBarWindowStateRepositoryStore by
+var Kosmos.statusBarWindowStateRepositoryStore: StatusBarWindowStateRepositoryStore by
     Kosmos.Fixture {
         StatusBarWindowStateRepositoryStoreImpl(
             displayId = displayTracker.defaultDisplayId,
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 5b271a3..7474df2 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -87,7 +87,7 @@
 # replaces 27510 with a row per notification
 27531 notification_visibility (key|3),(visibile|1),(lifespan|1),(freshness|1),(exposure|1),(rank|1)
 # a notification emited noise, vibration, or light
-27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1),(politeness|1)
+27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1),(politeness|1),(mute_reason|1)
 # a notification was added to a autogroup
 27533 notification_autogrouped (key|3)
 # notification was removed from an autogroup
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index fbe593f..682eb76 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -25,6 +25,7 @@
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -91,6 +92,7 @@
  * Monitors the health of packages on the system and notifies interested observers when packages
  * fail. On failure, the registered observer with the least user impacting mitigation will
  * be notified.
+ * @hide
  */
 public class PackageWatchdog {
     private static final String TAG = "PackageWatchdog";
@@ -108,13 +110,25 @@
     private static final long NUMBER_OF_NATIVE_CRASH_POLLS = 10;
 
 
+    /** Reason for package failure could not be determined. */
     public static final int FAILURE_REASON_UNKNOWN = 0;
+
+    /** The package had a native crash. */
     public static final int FAILURE_REASON_NATIVE_CRASH = 1;
+
+    /** The package failed an explicit health check. */
     public static final int FAILURE_REASON_EXPLICIT_HEALTH_CHECK = 2;
+
+    /** The app crashed. */
     public static final int FAILURE_REASON_APP_CRASH = 3;
+
+    /** The app was not responding. */
     public static final int FAILURE_REASON_APP_NOT_RESPONDING = 4;
+
+    /** The device was boot looping. */
     public static final int FAILURE_REASON_BOOT_LOOP = 5;
 
+    /** @hide */
     @IntDef(prefix = { "FAILURE_REASON_" }, value = {
             FAILURE_REASON_UNKNOWN,
             FAILURE_REASON_NATIVE_CRASH,
@@ -186,7 +200,8 @@
     // aborted.
     private static final String METADATA_FILE = "/metadata/watchdog/mitigation_count.txt";
 
-    @GuardedBy("PackageWatchdog.class")
+    private static final Object sPackageWatchdogLock = new Object();
+    @GuardedBy("sPackageWatchdogLock")
     private static PackageWatchdog sPackageWatchdog;
 
     private final Object mLock = new Object();
@@ -278,8 +293,8 @@
     }
 
     /** Creates or gets singleton instance of PackageWatchdog. */
-    public static PackageWatchdog getInstance(Context context) {
-        synchronized (PackageWatchdog.class) {
+    public static  @NonNull PackageWatchdog getInstance(@NonNull Context context) {
+        synchronized (sPackageWatchdogLock) {
             if (sPackageWatchdog == null) {
                 new PackageWatchdog(context);
             }
@@ -290,6 +305,7 @@
     /**
      * Called during boot to notify when packages are ready on the device so we can start
      * binding.
+     * @hide
      */
     public void onPackagesReady() {
         synchronized (mLock) {
@@ -311,6 +327,7 @@
      *
      * <p>Observers are expected to call this on boot. It does not specify any packages but
      * it will resume observing any packages requested from a previous boot.
+     * @hide
      */
     public void registerHealthObserver(PackageHealthObserver observer) {
         synchronized (mLock) {
@@ -344,6 +361,7 @@
      *
      * <p>If {@code durationMs} is less than 1, a default monitoring duration
      * {@link #DEFAULT_OBSERVING_DURATION_MS} will be used.
+     * @hide
      */
     public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
             long durationMs) {
@@ -407,6 +425,7 @@
      * Unregisters {@code observer} from listening to package failure.
      * Additionally, this stops observing any packages that may have previously been observed
      * even from a previous boot.
+     * @hide
      */
     public void unregisterHealthObserver(PackageHealthObserver observer) {
         mLongTaskHandler.post(() -> {
@@ -425,7 +444,7 @@
      *
      * <p>This method could be called frequently if there is a severe problem on the device.
      */
-    public void onPackageFailure(List<VersionedPackage> packages,
+    public void onPackageFailure(@NonNull List<VersionedPackage> packages,
             @FailureReasons int failureReason) {
         if (packages == null) {
             Slog.w(TAG, "Could not resolve a list of failing packages");
@@ -566,6 +585,7 @@
      *
      * Note: PackageWatchdog considers system_server restart loop as bootloop. Full reboots
      * are not counted in bootloop.
+     * @hide
      */
     @SuppressWarnings("GuardedBy")
     public void noteBoot() {
@@ -620,7 +640,7 @@
     // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? Also
     // avoid holding lock?
     // This currently adds about 7ms extra to shutdown thread
-    /** Writes the package information to file during shutdown. */
+    /** @hide Writes the package information to file during shutdown. */
     public void writeNow() {
         synchronized (mLock) {
             // Must only run synchronous tasks as this runs on the ShutdownThread and no other
@@ -674,6 +694,7 @@
      * Since this method can eventually trigger a rollback, it should be called
      * only once boot has completed {@code onBootCompleted} and not earlier, because the install
      * session must be entirely completed before we try to rollback.
+     * @hide
      */
     public void scheduleCheckAndMitigateNativeCrashes() {
         Slog.i(TAG, "Scheduling " + mNumberOfNativeCrashPollsRemaining + " polls to check "
@@ -695,7 +716,9 @@
         return mPackagesExemptFromImpactLevelThreshold;
     }
 
-    /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
+    /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}.
+     * @hide
+     */
     @Retention(SOURCE)
     @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
                      PackageHealthObserverImpact.USER_IMPACT_LEVEL_10,
@@ -787,7 +810,7 @@
          * Identifier for the observer, should not change across device updates otherwise the
          * watchdog may drop observing packages with the old name.
          */
-        String getUniqueIdentifier();
+        @NonNull String getUniqueIdentifier();
 
         /**
          * An observer will not be pruned if this is set, even if the observer is not explicitly
@@ -804,7 +827,7 @@
          * <p> A persistent observer may choose to start observing certain failing packages, even if
          * it has not explicitly asked to watch the package with {@link #startObservingHealth}.
          */
-        default boolean mayObservePackage(String packageName) {
+        default boolean mayObservePackage(@NonNull String packageName) {
             return false;
         }
     }
@@ -1240,7 +1263,7 @@
         }
     }
 
-    /** Convert a {@code LongArrayQueue} to a String of comma-separated values. */
+    /** @hide Convert a {@code LongArrayQueue} to a String of comma-separated values. */
     public static String longArrayQueueToString(LongArrayQueue queue) {
         if (queue.size() > 0) {
             StringBuilder sb = new StringBuilder();
@@ -1254,7 +1277,7 @@
         return "";
     }
 
-    /** Parse a comma-separated String of longs into a LongArrayQueue. */
+    /** @hide Parse a comma-separated String of longs into a LongArrayQueue. */
     public static LongArrayQueue parseLongArrayQueue(String commaSeparatedValues) {
         LongArrayQueue result = new LongArrayQueue();
         if (!TextUtils.isEmpty(commaSeparatedValues)) {
@@ -1268,7 +1291,7 @@
 
 
     /** Dump status of every observer in mAllObservers. */
-    public void dump(PrintWriter pw) {
+    public void dump(@NonNull PrintWriter pw) {
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
         ipw.println("Package Watchdog status");
         ipw.increaseIndent();
@@ -1395,6 +1418,7 @@
         /**
          * Increments failure counts of {@code packageName}.
          * @returns {@code true} if failure threshold is exceeded, {@code false} otherwise
+         * @hide
          */
         @GuardedBy("mLock")
         public boolean onPackageFailureLocked(String packageName) {
@@ -1514,6 +1538,7 @@
         }
     }
 
+    /** @hide */
     @Retention(SOURCE)
     @IntDef(value = {
             HealthCheckState.ACTIVE,
@@ -1603,7 +1628,9 @@
             updateHealthCheckStateLocked();
         }
 
-        /** Writes the salient fields to disk using {@code out}. */
+        /** Writes the salient fields to disk using {@code out}.
+         * @hide
+         */
         @GuardedBy("mLock")
         public void writeLocked(TypedXmlSerializer out) throws IOException {
             out.startTag(null, TAG_PACKAGE);
diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java
index 5f2fbce..8a81aaa 100644
--- a/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java
+++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java
@@ -23,7 +23,10 @@
 import com.android.server.SystemService;
 
 
-/** This class encapsulate the lifecycle methods of CrashRecovery module. */
+/** This class encapsulate the lifecycle methods of CrashRecovery module.
+ *
+ * @hide
+ */
 public class CrashRecoveryModule {
     private static final String TAG = "CrashRecoveryModule";
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 62d8761..03fec011 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1480,29 +1480,24 @@
             brightnessState = clampScreenBrightness(brightnessState);
         }
 
-        if (useDozeBrightness) {
-            // TODO(b/329676661): Introduce a config property to choose between this brightness
-            //  strategy and DOZE_DEFAULT
-            // On some devices, when auto-brightness is disabled and the device is dozing, we use
-            // the current brightness setting scaled by the doze scale factor
-            if ((Float.isNaN(brightnessState)
-                    || displayBrightnessState.getDisplayBrightnessStrategyName()
-                    .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME))
-                    && mFlags.isDisplayOffloadEnabled()
-                    && mDisplayOffloadSession != null
+        if (useDozeBrightness && (Float.isNaN(brightnessState)
+                || displayBrightnessState.getDisplayBrightnessStrategyName()
+                .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME))) {
+            if (mFlags.isDisplayOffloadEnabled() && mDisplayOffloadSession != null
                     && (mAutomaticBrightnessController == null
                     || !mAutomaticBrightnessStrategy.shouldUseAutoBrightness())) {
+                // TODO(b/329676661): Introduce a config property to choose between this brightness
+                //  strategy and DOZE_DEFAULT
+                // On some devices, when auto-brightness is disabled and the device is dozing, we
+                // use the current brightness setting scaled by the doze scale factor
                 rawBrightnessState = getDozeBrightnessForOffload();
                 brightnessState = clampScreenBrightness(rawBrightnessState);
                 updateScreenBrightnessSetting = false;
                 mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_MANUAL);
                 mTempBrightnessEvent.setFlags(
                         mTempBrightnessEvent.getFlags() | BrightnessEvent.FLAG_DOZE_SCALE);
-            }
-
-            // Use default brightness when dozing unless overridden.
-            if (Float.isNaN(brightnessState)
-                    && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()) {
+            } else if (!mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()) {
+                // Use default brightness when dozing unless overridden.
                 rawBrightnessState = mScreenBrightnessDozeConfig;
                 brightnessState = clampScreenBrightness(rawBrightnessState);
                 mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index abb2132..06f419a 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -118,6 +118,37 @@
             Intent.ACTION_MANAGED_PROFILE_AVAILABLE, new Pair<>(Intent.EXTRA_QUIET_MODE, false)
     );
 
+    // Bits 1, 2, 3, 4 are already taken by: beep|buzz|blink|cooldown
+    static final int MUTE_REASON_NOT_MUTED = 0;
+    static final int MUTE_REASON_NOT_AUDIBLE = 1 << 5;
+    static final int MUTE_REASON_SILENT_UPDATE = 1 << 6;
+    static final int MUTE_REASON_POST_SILENTLY = 1 << 7;
+    static final int MUTE_REASON_LISTENER_HINT = 1 << 8;
+    static final int MUTE_REASON_DND = 1 << 9;
+    static final int MUTE_REASON_GROUP_ALERT = 1 << 10;
+    static final int MUTE_REASON_FLAG_SILENT = 1 << 11;
+    static final int MUTE_REASON_RATE_LIMIT = 1 << 12;
+    static final int MUTE_REASON_OTHER_INSISTENT_PLAYING = 1 << 13;
+    static final int MUTE_REASON_SUPPRESSED_BUBBLE = 1 << 14;
+    static final int MUTE_REASON_COOLDOWN = 1 << 15;
+
+    @IntDef(prefix = { "MUTE_REASON_" }, value = {
+        MUTE_REASON_NOT_MUTED,
+        MUTE_REASON_NOT_AUDIBLE,
+        MUTE_REASON_SILENT_UPDATE,
+        MUTE_REASON_POST_SILENTLY,
+        MUTE_REASON_LISTENER_HINT,
+        MUTE_REASON_DND,
+        MUTE_REASON_GROUP_ALERT,
+        MUTE_REASON_FLAG_SILENT,
+        MUTE_REASON_RATE_LIMIT,
+        MUTE_REASON_OTHER_INSISTENT_PLAYING,
+        MUTE_REASON_SUPPRESSED_BUBBLE,
+        MUTE_REASON_COOLDOWN,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface MuteReason {}
+
     private final Context mContext;
     private final PackageManager mPackageManager;
     private final TelephonyManager mTelephonyManager;
@@ -388,6 +419,7 @@
         boolean buzz = false;
         boolean beep = false;
         boolean blink = false;
+        @MuteReason int shouldMuteReason = MUTE_REASON_NOT_MUTED;
 
         final String key = record.getKey();
 
@@ -395,10 +427,6 @@
             Log.d(TAG, "buzzBeepBlinkLocked " + record);
         }
 
-        if (isPoliteNotificationFeatureEnabled(record)) {
-            mStrategy.onNotificationPosted(record);
-        }
-
         // Should this notification make noise, vibe, or use the LED?
         final boolean aboveThreshold =
                 mIsAutomotive
@@ -443,7 +471,8 @@
                 boolean vibrateOnly =
                         hasValidVibrate && mNotificationCooldownVibrateUnlocked && mUserPresent;
                 boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
-                if (hasAudibleAlert && !shouldMuteNotificationLocked(record, signals)) {
+                shouldMuteReason = shouldMuteNotificationLocked(record, signals, hasAudibleAlert);
+                if (shouldMuteReason == MUTE_REASON_NOT_MUTED) {
                     if (!sentAccessibilityEvent) {
                         sendAccessibilityEvent(record);
                         sentAccessibilityEvent = true;
@@ -541,15 +570,17 @@
             }
         }
         final int buzzBeepBlinkLoggingCode =
-                (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0) | getPoliteBit(record);
+                (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0)
+                | getPoliteBit(record) | shouldMuteReason;
         if (buzzBeepBlinkLoggingCode > 0) {
             MetricsLogger.action(record.getLogMaker()
                     .setCategory(MetricsEvent.NOTIFICATION_ALERT)
                     .setType(MetricsEvent.TYPE_OPEN)
                     .setSubtype(buzzBeepBlinkLoggingCode));
             EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0,
-                    getPolitenessState(record));
+                    getPolitenessState(record), shouldMuteReason);
         }
+
         if (Flags.politeNotifications()) {
             // Update last alert time
             if (buzz || beep) {
@@ -594,41 +625,46 @@
                 mNMP.getNotificationByKey(mVibrateNotificationKey));
     }
 
-    boolean shouldMuteNotificationLocked(final NotificationRecord record, final Signals signals) {
+    @MuteReason int shouldMuteNotificationLocked(final NotificationRecord record,
+            final Signals signals, boolean hasAudibleAlert) {
+        // Suppressed because no audible alert
+        if (!hasAudibleAlert) {
+            return MUTE_REASON_NOT_AUDIBLE;
+        }
         // Suppressed because it's a silent update
         final Notification notification = record.getNotification();
         if (record.isUpdate && (notification.flags & FLAG_ONLY_ALERT_ONCE) != 0) {
-            return true;
+            return MUTE_REASON_SILENT_UPDATE;
         }
 
         // Suppressed because a user manually unsnoozed something (or similar)
         if (record.shouldPostSilently()) {
-            return true;
+            return MUTE_REASON_POST_SILENTLY;
         }
 
         // muted by listener
         final String disableEffects = disableNotificationEffects(record, signals.listenerHints);
         if (disableEffects != null) {
             ZenLog.traceDisableEffects(record, disableEffects);
-            return true;
+            return MUTE_REASON_LISTENER_HINT;
         }
 
         // suppressed due to DND
         if (record.isIntercepted()) {
-            return true;
+            return MUTE_REASON_DND;
         }
 
         // Suppressed because another notification in its group handles alerting
         if (record.getSbn().isGroup()) {
             if (notification.suppressAlertingDueToGrouping()) {
-                return true;
+                return MUTE_REASON_GROUP_ALERT;
             }
         }
 
         // Suppressed because notification was explicitly flagged as silent
         if (android.service.notification.Flags.notificationSilentFlag()) {
             if (notification.isSilent()) {
-                return true;
+                return MUTE_REASON_FLAG_SILENT;
             }
         }
 
@@ -636,12 +672,12 @@
         final String pkg = record.getSbn().getPackageName();
         if (mUsageStats.isAlertRateLimited(pkg)) {
             Slog.e(TAG, "Muting recently noisy " + record.getKey());
-            return true;
+            return MUTE_REASON_RATE_LIMIT;
         }
 
         // A different looping ringtone, such as an incoming call is playing
         if (isCurrentlyInsistent() && !isInsistentUpdate(record)) {
-            return true;
+            return MUTE_REASON_OTHER_INSISTENT_PLAYING;
         }
 
         // Suppressed since it's a non-interruptive update to a bubble-suppressed notification
@@ -650,11 +686,23 @@
         if (record.isUpdate && !record.isInterruptive() && isBubbleOrOverflowed
                 && record.getNotification().getBubbleMetadata() != null) {
             if (record.getNotification().getBubbleMetadata().isNotificationSuppressed()) {
-                return true;
+                return MUTE_REASON_SUPPRESSED_BUBBLE;
             }
         }
 
-        return false;
+        if (isPoliteNotificationFeatureEnabled(record)) {
+            // Notify the politeness strategy that an alerting notification is posted
+            if (!isInsistentUpdate(record)) {
+                mStrategy.onNotificationPosted(record);
+            }
+
+            // Suppress if politeness is muted and it's not an update for insistent
+            if (getPolitenessState(record) == PolitenessStrategy.POLITE_STATE_MUTED) {
+                return MUTE_REASON_COOLDOWN;
+            }
+        }
+
+        return MUTE_REASON_NOT_MUTED;
     }
 
     private boolean isLoopingRingtoneNotification(final NotificationRecord playingRecord) {
@@ -1201,12 +1249,6 @@
             mApplyPerPackage = applyPerPackage;
         }
 
-        boolean shouldIgnoreNotification(final NotificationRecord record) {
-            // Ignore auto-group summaries => don't count them as app-posted notifications
-            // for the cooldown budget
-            return (record.getSbn().isGroup() && GroupHelper.isAggregatedGroup(record));
-        }
-
         /**
          * Get the key that determines the grouping for the cooldown behavior.
          *
@@ -1358,10 +1400,6 @@
 
         @Override
         public void onNotificationPosted(final NotificationRecord record) {
-            if (shouldIgnoreNotification(record)) {
-                return;
-            }
-
             long timeSinceLastNotif =
                     System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
 
@@ -1434,10 +1472,6 @@
         @Override
         void onNotificationPosted(NotificationRecord record) {
             if (isAvalancheActive()) {
-                if (shouldIgnoreNotification(record)) {
-                    return;
-                }
-
                 long timeSinceLastNotif =
                     System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
 
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index f78c448..d206c66 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -99,6 +99,7 @@
     // True if needing to roll back only rebootless apexes when native crash happens
     private boolean mTwoPhaseRollbackEnabled;
 
+    /** @hide */
     @VisibleForTesting
     public RollbackPackageHealthObserver(Context context, ApexManager apexManager) {
         mContext = context;
@@ -123,7 +124,7 @@
         }
     }
 
-    RollbackPackageHealthObserver(Context context) {
+    public RollbackPackageHealthObserver(@NonNull Context context) {
         this(context, ApexManager.getInstance());
     }
 
@@ -239,8 +240,8 @@
         return false;
     }
 
-
     @Override
+    @NonNull
     public String getUniqueIdentifier() {
         return NAME;
     }
@@ -251,7 +252,7 @@
     }
 
     @Override
-    public boolean mayObservePackage(String packageName) {
+    public boolean mayObservePackage(@NonNull String packageName) {
         if (getAvailableRollbacks().isEmpty()) {
             return false;
         }
@@ -281,12 +282,14 @@
      * This may cause {@code packages} to be rolled back if they crash too freqeuntly.
      */
     @AnyThread
-    void startObservingHealth(List<String> packages, long durationMs) {
+    @NonNull
+    public void startObservingHealth(@NonNull List<String> packages, @NonNull long durationMs) {
         PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
     }
 
     @AnyThread
-    void notifyRollbackAvailable(RollbackInfo rollback) {
+    @NonNull
+    public void notifyRollbackAvailable(@NonNull RollbackInfo rollback) {
         mHandler.post(() -> {
             // Enable two-phase rollback when a rebootless apex rollback is made available.
             // We assume the rebootless apex is stable and is less likely to be the cause
@@ -314,7 +317,7 @@
      * to check for native crashes and mitigate them if needed.
      */
     @AnyThread
-    void onBootCompletedAsync() {
+    public void onBootCompletedAsync() {
         mHandler.post(()->onBootCompleted());
     }
 
diff --git a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
index 79560ce..9cfed02 100644
--- a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -51,6 +51,7 @@
 
 /**
  * This class handles the logic for logging Watchdog-triggered rollback events.
+ * @hide
  */
 public final class WatchdogRollbackLogger {
     private static final String TAG = "WatchdogRollbackLogger";
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index e6f6215..1ac0bb0 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2156,6 +2156,11 @@
         }
         mDecorInsets.invalidate();
         mDecorInsets.mInfoForRotation[rotation].set(newInfo);
+        if (!mService.mDisplayEnabled) {
+            // There could be other pending changes during booting. It might be better to let the
+            // clients receive the new states earlier.
+            return true;
+        }
         return !sameConfigFrame;
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 62e5b9a..45cd571 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -31,6 +31,12 @@
 import static android.media.AudioAttributes.USAGE_NOTIFICATION;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
 
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_COOLDOWN;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_FLAG_SILENT;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_GROUP_ALERT;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_NOT_MUTED;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_OTHER_INSISTENT_PLAYING;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -106,6 +112,7 @@
 import com.android.internal.config.sysui.TestableFlagResolver;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.logging.InstanceIdSequenceFake;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.IntPair;
 import com.android.server.UiServiceTestCase;
 import com.android.server.lights.LightsManager;
@@ -1276,7 +1283,8 @@
         verifyNeverBeep();
         assertFalse(r.isInterruptive());
         assertEquals(-1, r.getLastAudiblyAlertedMs());
-        assertTrue(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS));
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS,
+                true)).isEqualTo(MUTE_REASON_FLAG_SILENT);
     }
 
     @Test
@@ -1295,7 +1303,8 @@
         verifyNeverBeep();
         assertFalse(r.isInterruptive());
         assertEquals(-1, r.getLastAudiblyAlertedMs());
-        assertTrue(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS));
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS,
+                true)).isEqualTo(MUTE_REASON_GROUP_ALERT);
     }
 
     @Test
@@ -1861,7 +1870,9 @@
         verifyBeepLooped();
 
         NotificationRecord interrupter = getBeepyOtherNotification();
-        assertTrue(mAttentionHelper.shouldMuteNotificationLocked(interrupter, DEFAULT_SIGNALS));
+        assertThat(
+                mAttentionHelper.shouldMuteNotificationLocked(interrupter, DEFAULT_SIGNALS,
+                true)).isEqualTo(MUTE_REASON_OTHER_INSISTENT_PLAYING);
         mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS);
 
         verifyBeep(1);
@@ -1879,16 +1890,16 @@
         ringtoneChannel.enableVibration(true);
         NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
         mService.addNotification(ringtoneNotification);
-        assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
-            DEFAULT_SIGNALS));
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+                DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
         mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
         verifyBeepLooped();
         verifyDelayedVibrateLooped();
         Mockito.reset(mVibrator);
         Mockito.reset(mRingtonePlayer);
 
-        assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
-            DEFAULT_SIGNALS));
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+                DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
         mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
 
         // beep wasn't reset
@@ -1907,8 +1918,8 @@
         ringtoneChannel.enableVibration(true);
         NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
         mService.addNotification(ringtoneNotification);
-        assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
-            DEFAULT_SIGNALS));
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+                DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
         mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
         verifyBeepLooped();
         verifyDelayedVibrateLooped();
@@ -1930,8 +1941,8 @@
         ringtoneChannel.enableVibration(true);
         NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
         mService.addNotification(ringtoneNotification);
-        assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
-            DEFAULT_SIGNALS));
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+                DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
         mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
         verifyBeepLooped();
         verifyNeverVibrate();
@@ -1951,14 +1962,15 @@
             new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
         ringtoneChannel.enableVibration(true);
         NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
-        assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
-            DEFAULT_SIGNALS));
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+                DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
 
         mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
         verifyVibrateLooped();
 
         NotificationRecord interrupter = getBuzzyOtherNotification();
-        assertTrue(mAttentionHelper.shouldMuteNotificationLocked(interrupter, DEFAULT_SIGNALS));
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(interrupter,
+                DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_OTHER_INSISTENT_PLAYING);
         mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS);
 
         verifyVibrate(1);
@@ -2260,10 +2272,13 @@
 
         // 2nd update should beep at 0% volume
         Mockito.reset(mRingtonePlayer);
-        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
-        verifyBeepVolume(0.0f);
+        int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS, true))
+                .isEqualTo(MUTE_REASON_COOLDOWN);
 
-        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+        verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
         assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
@@ -2305,8 +2320,9 @@
 
         // 2nd update should beep at 0% volume
         Mockito.reset(mRingtonePlayer);
-        mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
-        verifyBeepVolume(0.0f);
+        int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
 
         verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
         assertEquals(-1, r3.getLastAudiblyAlertedMs());
@@ -2381,9 +2397,10 @@
                 false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
 
         // update should beep at 0% volume
-        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+        int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
         assertEquals(-1, r2.getLastAudiblyAlertedMs());
-        verifyBeepVolume(0.0f);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
 
         // Use different package for next notifications
         NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
@@ -2392,8 +2409,9 @@
 
         // 2nd update should beep at 0% volume
         Mockito.reset(mRingtonePlayer);
-        mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
-        verifyBeepVolume(0.0f);
+        buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
 
         verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
         assertEquals(-1, r3.getLastAudiblyAlertedMs());
@@ -2493,8 +2511,9 @@
 
         // Regular notification: should beep at 0% volume
         NotificationRecord r = getBeepyNotification();
-        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
-        verifyBeepVolume(0.0f);
+        int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
         assertEquals(-1, r.getLastAudiblyAlertedMs());
         Mockito.reset(mRingtonePlayer);
 
@@ -2525,8 +2544,9 @@
 
         // 2nd update should beep at 0% volume
         Mockito.reset(mRingtonePlayer);
-        mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
-        verifyBeepVolume(0.0f);
+        buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
 
         // Set important conversation
         mChannel.setImportantConversation(true);
@@ -2751,9 +2771,10 @@
         Mockito.reset(mRingtonePlayer);
 
         // next update at 0% volume
-        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
         assertEquals(-1, summary.getLastAudiblyAlertedMs());
-        verifyBeepVolume(0.0f);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
 
         verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
     }
@@ -2823,9 +2844,10 @@
 
         // 2nd update should beep at 0% volume
         Mockito.reset(mRingtonePlayer);
-        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+        int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
         assertEquals(-1, r2.getLastAudiblyAlertedMs());
-        verifyBeepVolume(0.0f);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
 
         // Use different package for next notifications
         NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
@@ -2891,6 +2913,94 @@
     }
 
     @Test
+    public void testBeepVolume_politeNotif_groupAlertSummary() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        // NOTIFICATION_COOLDOWN_ALL setting is enabled
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+        initAttentionHelper(flagResolver);
+
+        // child should beep at 0% volume
+        NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
+        mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertFalse(child.isInterruptive());
+        assertEquals(-1, child.getLastAudiblyAlertedMs());
+        Mockito.reset(mRingtonePlayer);
+
+        // child should beep at 0% volume
+        child = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
+        mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertFalse(child.isInterruptive());
+        assertEquals(-1, child.getLastAudiblyAlertedMs());
+        Mockito.reset(mRingtonePlayer);
+
+        // summary 100% volume (GROUP_ALERT_SUMMARY)
+        NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
+        summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+        verifyBeepVolume(1.0f);
+        Mockito.reset(mRingtonePlayer);
+
+        // next update at 50% volume because only summary was tracked as alerting
+        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+        verifyBeepVolume(0.5f);
+
+        verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
+    }
+
+    @Test
+    public void testBeepVolume_politeNotif_groupAlertChildren() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        // NOTIFICATION_COOLDOWN_ALL setting is enabled
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+        initAttentionHelper(flagResolver);
+
+        // summary 0% volume (GROUP_ALERT_CHILDREN)
+        NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
+        summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertFalse(summary.isInterruptive());
+        assertEquals(-1, summary.getLastAudiblyAlertedMs());
+        Mockito.reset(mRingtonePlayer);
+
+        // child should beep at 100% volume
+        NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
+        mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+        assertNotEquals(-1, child.getLastAudiblyAlertedMs());
+        verifyBeepVolume(1.0f);
+        Mockito.reset(mRingtonePlayer);
+
+        // child should beep at 50% volume
+        child = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
+        mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+        assertNotEquals(-1, child.getLastAudiblyAlertedMs());
+        verifyBeepVolume(0.5f);
+        Mockito.reset(mRingtonePlayer);
+
+        // child should beep at 0% volume
+        mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertTrue(child.isInterruptive());
+        assertEquals(-1, child.getLastAudiblyAlertedMs());
+
+        verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
+    }
+
+    @Test
     public void testVibrationIntensity_politeNotif() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
         TestableFlagResolver flagResolver = new TestableFlagResolver();
@@ -2914,8 +3024,9 @@
         Mockito.reset(vibratorHelper);
 
         // 2nd update should buzz at 0% intensity
-        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
-        verify(vibratorHelper, times(1)).scale(any(), eq(0.0f));
+        int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyNeverVibrate();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
     }
 
     @Test
@@ -3007,10 +3118,11 @@
 
         // 2nd update should beep at 0% volume
         Mockito.reset(mRingtonePlayer);
-        mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
-        verifyBeepVolume(0.0f);
+        int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
 
-        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+        verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
         assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
index 82de070..8b65efd 100644
--- a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
index 4ffb11a..3382c1e 100644
--- a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
index 0fa4d07..e941e79 100644
--- a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
index 4d9fefb..4e06dca 100644
--- a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml
index b879c54..0cadd68 100644
--- a/tests/FlickerTests/IME/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- enable AOD -->
         <option name="set-secure-setting" key="doze_always_on" value="1" />
         <!-- prevents the phone from restarting -->
diff --git a/tests/FlickerTests/Notification/AndroidTestTemplate.xml b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
index 04b312a..f32e8bed 100644
--- a/tests/FlickerTests/Notification/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
index 8acdabc..68ae4f1 100644
--- a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
index 91ece21..ec186723 100644
--- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->