summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java278
-rw-r--r--core/java/android/app/ContextImpl.java6
-rw-r--r--core/java/android/database/sqlite/SQLiteConnection.java9
-rw-r--r--core/java/android/database/sqlite/flags.aconfig9
-rw-r--r--core/java/android/permission/flags.aconfig11
-rw-r--r--core/java/android/view/ViewConfiguration.java231
-rw-r--r--core/jni/android_database_SQLiteConnection.cpp19
-rw-r--r--core/res/res/values/config.xml37
-rw-r--r--core/res/res/values/symbols.xml11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInteractionHandler.java88
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipInteractionHandlerTest.java95
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java24
-rw-r--r--packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v36/settingslib_expressive_preference_selector_with_widget.xml (renamed from packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v36/preference_selector_with_widget.xml)0
-rw-r--r--packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java5
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig10
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt1
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt47
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt9
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt1
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/SysUIStatePerDisplayInteractorTest.kt113
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateDispatcherTest.kt87
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java38
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java31
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStoreTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt276
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImplTest.kt88
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt82
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SysUIStateDispatcher.kt77
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SysUiState.kt85
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStore.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImpl.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt15
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt26
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreKosmos.kt55
-rw-r--r--services/core/java/com/android/server/vibrator/VendorVibrationSession.java4
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationThread.java4
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorDebugUtils.java37
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java4
79 files changed, 1921 insertions, 497 deletions
diff --git a/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java b/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java
index 7a7250b9e910..8e3ed6d9931c 100644
--- a/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java
@@ -19,24 +19,27 @@ package android.view;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import android.content.Context;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
-import androidx.benchmark.BenchmarkState;
-import androidx.benchmark.junit4.BenchmarkRule;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
-@SmallTest
+@LargeTest
+@RunWith(AndroidJUnit4.class)
public class ViewConfigurationPerfTest {
@Rule
- public final BenchmarkRule mBenchmarkRule = new BenchmarkRule();
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
private final Context mContext = getInstrumentation().getTargetContext();
@Test
public void testGet_newViewConfiguration() {
- final BenchmarkState state = mBenchmarkRule.getState();
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
@@ -50,7 +53,7 @@ public class ViewConfigurationPerfTest {
@Test
public void testGet_cachedViewConfiguration() {
- final BenchmarkState state = mBenchmarkRule.getState();
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
// Do `get` once to make sure there's something cached.
ViewConfiguration.get(mContext);
@@ -58,4 +61,265 @@ public class ViewConfigurationPerfTest {
ViewConfiguration.get(mContext);
}
}
+
+ @Test
+ public void testGetPressedStateDuration_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getPressedStateDuration();
+ }
+ }
+
+ @Test
+ public void testGetPressedStateDuration_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getPressedStateDuration();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getPressedStateDuration();
+ }
+ }
+
+ @Test
+ public void testGetTapTimeout_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetTapTimeout_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getTapTimeout();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetJumpTapTimeout_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getJumpTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetJumpTapTimeout_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getJumpTapTimeout();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getJumpTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetDoubleTapTimeout_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getDoubleTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetDoubleTapTimeout_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getDoubleTapTimeout();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getDoubleTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetDoubleTapMinTime_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getDoubleTapMinTime();
+ }
+ }
+
+ @Test
+ public void testGetDoubleTapMinTime_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getDoubleTapMinTime();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getDoubleTapMinTime();
+ }
+ }
+
+ @Test
+ public void testGetZoomControlsTimeout_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getZoomControlsTimeout();
+ }
+ }
+
+ @Test
+ public void testGetZoomControlsTimeout_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getZoomControlsTimeout();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getZoomControlsTimeout();
+ }
+ }
+
+ @Test
+ public void testGetLongPressTimeout() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getLongPressTimeout();
+ }
+ }
+
+ @Test
+ public void testGetMultiPressTimeout() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getMultiPressTimeout();
+ }
+ }
+
+ @Test
+ public void testGetKeyRepeatTimeout() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getKeyRepeatTimeout();
+ }
+ }
+
+ @Test
+ public void testGetKeyRepeatDelay() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getKeyRepeatDelay();
+ }
+ }
+
+ @Test
+ public void testGetHoverTapSlop_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getHoverTapSlop();
+ }
+ }
+
+ @Test
+ public void testGetHoverTapSlop_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getHoverTapSlop();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getHoverTapSlop();
+ }
+ }
+
+ @Test
+ public void testGetScrollFriction_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getScrollFriction();
+ }
+ }
+
+ @Test
+ public void testGetScrollFriction_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getScrollFriction();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getScrollFriction();
+ }
+ }
+
+ @Test
+ public void testGetDefaultActionModeHideDuration_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getDefaultActionModeHideDuration();
+ }
+ }
+
+ @Test
+ public void testGetDefaultActionModeHideDuration_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getDefaultActionModeHideDuration();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getDefaultActionModeHideDuration();
+ }
+ }
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index dc5974fde0b0..7e5c0fbe1ee1 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2944,7 +2944,11 @@ class ContextImpl extends Context {
private void updateResourceOverlayConstraints() {
if (mResources != null) {
- mResources.getAssets().setOverlayConstraints(getDisplayId(), getDeviceId());
+ // Avoid calling getDisplay() here, as it makes a binder call into
+ // DisplayManagerService if the relevant DisplayInfo is not cached in
+ // DisplayManagerGlobal.
+ int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
+ mResources.getAssets().setOverlayConstraints(displayId, getDeviceId());
}
}
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 75c7e267d477..e43a5fce6cb7 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -138,7 +138,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
private static native long nativeOpen(String path, int openFlags, String label,
boolean enableTrace, boolean enableProfile, int lookasideSlotSize,
int lookasideSlotCount);
- private static native void nativeClose(long connectionPtr);
+ private static native void nativeClose(long connectionPtr, boolean fast);
private static native void nativeRegisterCustomScalarFunction(long connectionPtr,
String name, UnaryOperator<String> function);
private static native void nativeRegisterCustomAggregateFunction(long connectionPtr,
@@ -183,6 +183,11 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
private static native long nativeChanges(long connectionPtr);
private static native long nativeTotalChanges(long connectionPtr);
+ // This method is deprecated and should be removed when it is no longer needed by the
+ // robolectric tests. It should not be called from any frameworks java code.
+ @Deprecated
+ private static native void nativeClose(long connectionPtr);
+
private SQLiteConnection(SQLiteConnectionPool pool,
SQLiteDatabaseConfiguration configuration,
int connectionId, boolean primaryConnection) {
@@ -300,7 +305,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
final int cookie = mRecentOperations.beginOperation("close", null, null);
try {
mPreparedStatementCache.evictAll();
- nativeClose(mConnectionPtr);
+ nativeClose(mConnectionPtr, finalized && Flags.noCheckpointOnFinalize());
mConnectionPtr = 0;
} finally {
mRecentOperations.endOperation(cookie);
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index 1d17a51f3653..9f4f1a16178b 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -5,7 +5,7 @@ flag {
name: "oneway_finalizer_close_fixed"
namespace: "system_performance"
is_fixed_read_only: true
- description: "Make BuildCursorNative.close oneway if in the the finalizer"
+ description: "Make BuildCursorNative.close oneway if in the finalizer"
bug: "368221351"
}
@@ -26,3 +26,10 @@ flag {
description: "Make SQLiteOpenHelper thread-safe"
bug: "335904370"
}
+
+flag {
+ name: "no_checkpoint_on_finalize"
+ namespace: "system_performance"
+ description: "Do not checkpoint WAL if closing in the finalizer"
+ bug: "397982577"
+}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index ca24c0c6c376..0476f62ec263 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -358,7 +358,16 @@ flag {
is_fixed_read_only: true
is_exported: true
namespace: "permissions"
- description: "Enables SQlite for recording discrete and historical AppOp accesses"
+ description: "Enables SQlite for recording individual/discrete AppOp accesses"
+ bug: "377584611"
+}
+
+flag {
+ name: "enable_all_sqlite_appops_accesses"
+ is_fixed_read_only: true
+ is_exported: true
+ namespace: "permissions"
+ description: "Enables SQlite for storing aggregated & individual/discrete AppOp accesses"
bug: "377584611"
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 9e97a8eb58aa..2895bf3f846a 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -21,7 +21,9 @@ import android.annotation.NonNull;
import android.annotation.TestApi;
import android.annotation.UiContext;
import android.app.Activity;
+import android.app.ActivityThread;
import android.app.AppGlobals;
+import android.app.Application;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
@@ -39,14 +41,13 @@ import android.util.SparseArray;
import android.util.TypedValue;
import android.view.flags.Flags;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
/**
* Contains methods to standard constants used in the UI for timeouts, sizes, and distances.
*/
public class ViewConfiguration {
- private static final String TAG = "ViewConfiguration";
-
/**
* Defines the width of the horizontal scrollbar and the height of the vertical scrollbar in
* dips
@@ -349,6 +350,8 @@ public class ViewConfiguration {
*/
private static final int SMART_SELECTION_INITIALIZING_TIMEOUT_IN_MILLISECOND = 500;
+ private static ResourceCache sResourceCache = new ResourceCache();
+
private final boolean mConstructedWithContext;
private final int mEdgeSlop;
private final int mFadingEdgeLength;
@@ -374,7 +377,6 @@ public class ViewConfiguration {
private final int mOverscrollDistance;
private final int mOverflingDistance;
private final boolean mViewTouchScreenHapticScrollFeedbackEnabled;
- @UnsupportedAppUsage
private final boolean mFadingMarqueeEnabled;
private final long mGlobalActionsKeyTimeout;
private final float mVerticalScrollFactor;
@@ -468,14 +470,12 @@ public class ViewConfiguration {
mEdgeSlop = (int) (sizeAndDensity * EDGE_SLOP + 0.5f);
mFadingEdgeLength = (int) (sizeAndDensity * FADING_EDGE_LENGTH + 0.5f);
- mScrollbarSize = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_scrollbarSize);
+ mScrollbarSize = res.getDimensionPixelSize(R.dimen.config_scrollbarSize);
mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f);
mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);
final TypedValue multiplierValue = new TypedValue();
- res.getValue(
- com.android.internal.R.dimen.config_ambiguousGestureMultiplier,
+ res.getValue(R.dimen.config_ambiguousGestureMultiplier,
multiplierValue,
true /*resolveRefs*/);
mAmbiguousGestureMultiplier = Math.max(1.0f, multiplierValue.getFloat());
@@ -488,8 +488,7 @@ public class ViewConfiguration {
mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f);
if (!sHasPermanentMenuKeySet) {
- final int configVal = res.getInteger(
- com.android.internal.R.integer.config_overrideHasPermanentMenuKey);
+ final int configVal = res.getInteger(R.integer.config_overrideHasPermanentMenuKey);
switch (configVal) {
default:
@@ -516,32 +515,27 @@ public class ViewConfiguration {
}
}
- mFadingMarqueeEnabled = res.getBoolean(
- com.android.internal.R.bool.config_ui_enableFadingMarquee);
- mTouchSlop = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewConfigurationTouchSlop);
+ mFadingMarqueeEnabled = res.getBoolean(R.bool.config_ui_enableFadingMarquee);
+ mTouchSlop = res.getDimensionPixelSize(R.dimen.config_viewConfigurationTouchSlop);
mHandwritingSlop = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewConfigurationHandwritingSlop);
- mHoverSlop = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewConfigurationHoverSlop);
+ R.dimen.config_viewConfigurationHandwritingSlop);
+ mHoverSlop = res.getDimensionPixelSize(R.dimen.config_viewConfigurationHoverSlop);
mMinScrollbarTouchTarget = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_minScrollbarTouchTarget);
+ R.dimen.config_minScrollbarTouchTarget);
mPagingTouchSlop = mTouchSlop * 2;
mDoubleTapTouchSlop = mTouchSlop;
mHandwritingGestureLineMargin = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewConfigurationHandwritingGestureLineMargin);
+ R.dimen.config_viewConfigurationHandwritingGestureLineMargin);
- mMinimumFlingVelocity = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewMinFlingVelocity);
- mMaximumFlingVelocity = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewMaxFlingVelocity);
+ mMinimumFlingVelocity = res.getDimensionPixelSize(R.dimen.config_viewMinFlingVelocity);
+ mMaximumFlingVelocity = res.getDimensionPixelSize(R.dimen.config_viewMaxFlingVelocity);
int configMinRotaryEncoderFlingVelocity = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewMinRotaryEncoderFlingVelocity);
+ R.dimen.config_viewMinRotaryEncoderFlingVelocity);
int configMaxRotaryEncoderFlingVelocity = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewMaxRotaryEncoderFlingVelocity);
+ R.dimen.config_viewMaxRotaryEncoderFlingVelocity);
if (configMinRotaryEncoderFlingVelocity < 0 || configMaxRotaryEncoderFlingVelocity < 0) {
mMinimumRotaryEncoderFlingVelocity = NO_FLING_MIN_VELOCITY;
mMaximumRotaryEncoderFlingVelocity = NO_FLING_MAX_VELOCITY;
@@ -551,8 +545,7 @@ public class ViewConfiguration {
}
int configRotaryEncoderHapticScrollFeedbackTickIntervalPixels =
- res.getDimensionPixelSize(
- com.android.internal.R.dimen
+ res.getDimensionPixelSize(R.dimen
.config_rotaryEncoderAxisScrollTickInterval);
mRotaryEncoderHapticScrollFeedbackTickIntervalPixels =
configRotaryEncoderHapticScrollFeedbackTickIntervalPixels > 0
@@ -560,41 +553,31 @@ public class ViewConfiguration {
: NO_HAPTIC_SCROLL_TICK_INTERVAL;
mRotaryEncoderHapticScrollFeedbackEnabled =
- res.getBoolean(
- com.android.internal.R.bool
+ res.getBoolean(R.bool
.config_viewRotaryEncoderHapticScrollFedbackEnabled);
- mGlobalActionsKeyTimeout = res.getInteger(
- com.android.internal.R.integer.config_globalActionsKeyTimeout);
+ mGlobalActionsKeyTimeout = res.getInteger(R.integer.config_globalActionsKeyTimeout);
- mHorizontalScrollFactor = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_horizontalScrollFactor);
- mVerticalScrollFactor = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_verticalScrollFactor);
+ mHorizontalScrollFactor = res.getDimensionPixelSize(R.dimen.config_horizontalScrollFactor);
+ mVerticalScrollFactor = res.getDimensionPixelSize(R.dimen.config_verticalScrollFactor);
mShowMenuShortcutsWhenKeyboardPresent = res.getBoolean(
- com.android.internal.R.bool.config_showMenuShortcutsWhenKeyboardPresent);
+ R.bool.config_showMenuShortcutsWhenKeyboardPresent);
- mMinScalingSpan = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_minScalingSpan);
+ mMinScalingSpan = res.getDimensionPixelSize(R.dimen.config_minScalingSpan);
- mScreenshotChordKeyTimeout = res.getInteger(
- com.android.internal.R.integer.config_screenshotChordKeyTimeout);
+ mScreenshotChordKeyTimeout = res.getInteger(R.integer.config_screenshotChordKeyTimeout);
mSmartSelectionInitializedTimeout = res.getInteger(
- com.android.internal.R.integer.config_smartSelectionInitializedTimeoutMillis);
+ R.integer.config_smartSelectionInitializedTimeoutMillis);
mSmartSelectionInitializingTimeout = res.getInteger(
- com.android.internal.R.integer.config_smartSelectionInitializingTimeoutMillis);
- mPreferKeepClearForFocusEnabled = res.getBoolean(
- com.android.internal.R.bool.config_preferKeepClearForFocus);
+ R.integer.config_smartSelectionInitializingTimeoutMillis);
+ mPreferKeepClearForFocusEnabled = res.getBoolean(R.bool.config_preferKeepClearForFocus);
mViewBasedRotaryEncoderScrollHapticsEnabledConfig =
- res.getBoolean(
- com.android.internal.R.bool.config_viewBasedRotaryEncoderHapticsEnabled);
+ res.getBoolean(R.bool.config_viewBasedRotaryEncoderHapticsEnabled);
mViewTouchScreenHapticScrollFeedbackEnabled =
Flags.enableScrollFeedbackForTouch()
- ? res.getBoolean(
- com.android.internal.R.bool
- .config_viewTouchScreenHapticScrollFeedbackEnabled)
+ ? res.getBoolean(R.bool.config_viewTouchScreenHapticScrollFeedbackEnabled)
: false;
}
@@ -632,6 +615,7 @@ public class ViewConfiguration {
@VisibleForTesting
public static void resetCacheForTesting() {
sConfigurations.clear();
+ sResourceCache = new ResourceCache();
}
/**
@@ -707,7 +691,7 @@ public class ViewConfiguration {
* components.
*/
public static int getPressedStateDuration() {
- return PRESSED_STATE_DURATION;
+ return sResourceCache.getPressedStateDuration();
}
/**
@@ -752,7 +736,7 @@ public class ViewConfiguration {
* considered to be a tap.
*/
public static int getTapTimeout() {
- return TAP_TIMEOUT;
+ return sResourceCache.getTapTimeout();
}
/**
@@ -761,7 +745,7 @@ public class ViewConfiguration {
* considered to be a tap.
*/
public static int getJumpTapTimeout() {
- return JUMP_TAP_TIMEOUT;
+ return sResourceCache.getJumpTapTimeout();
}
/**
@@ -770,7 +754,7 @@ public class ViewConfiguration {
* double-tap.
*/
public static int getDoubleTapTimeout() {
- return DOUBLE_TAP_TIMEOUT;
+ return sResourceCache.getDoubleTapTimeout();
}
/**
@@ -782,7 +766,7 @@ public class ViewConfiguration {
*/
@UnsupportedAppUsage
public static int getDoubleTapMinTime() {
- return DOUBLE_TAP_MIN_TIME;
+ return sResourceCache.getDoubleTapMinTime();
}
/**
@@ -792,7 +776,7 @@ public class ViewConfiguration {
* @hide
*/
public static int getHoverTapTimeout() {
- return HOVER_TAP_TIMEOUT;
+ return sResourceCache.getHoverTapTimeout();
}
/**
@@ -803,7 +787,7 @@ public class ViewConfiguration {
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static int getHoverTapSlop() {
- return HOVER_TAP_SLOP;
+ return sResourceCache.getHoverTapSlop();
}
/**
@@ -1044,7 +1028,7 @@ public class ViewConfiguration {
* in milliseconds.
*/
public static long getZoomControlsTimeout() {
- return ZOOM_CONTROLS_TIMEOUT;
+ return sResourceCache.getZoomControlsTimeout();
}
/**
@@ -1113,14 +1097,14 @@ public class ViewConfiguration {
* friction.
*/
public static float getScrollFriction() {
- return SCROLL_FRICTION;
+ return sResourceCache.getScrollFriction();
}
/**
* @return the default duration in milliseconds for {@link ActionMode#hide(long)}.
*/
public static long getDefaultActionModeHideDuration() {
- return ACTION_MODE_HIDE_DURATION_DEFAULT;
+ return sResourceCache.getDefaultActionModeHideDuration();
}
/**
@@ -1471,8 +1455,137 @@ public class ViewConfiguration {
return HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT;
}
- private static final int getDisplayDensity(Context context) {
+ private static int getDisplayDensity(Context context) {
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return (int) (100.0f * metrics.density);
}
+
+ /**
+ * Fetches resource values statically and caches them locally for fast lookup. Note that these
+ * values will not be updated during the lifetime of a process, even if resource overlays are
+ * applied.
+ */
+ private static final class ResourceCache {
+
+ private int mPressedStateDuration = -1;
+ private int mTapTimeout = -1;
+ private int mJumpTapTimeout = -1;
+ private int mDoubleTapTimeout = -1;
+ private int mDoubleTapMinTime = -1;
+ private int mHoverTapTimeout = -1;
+ private int mHoverTapSlop = -1;
+ private long mZoomControlsTimeout = -1L;
+ private float mScrollFriction = -1f;
+ private long mDefaultActionModeHideDuration = -1L;
+
+ public int getPressedStateDuration() {
+ if (mPressedStateDuration < 0) {
+ Resources resources = getCurrentResources();
+ mPressedStateDuration = resources != null
+ ? resources.getInteger(R.integer.config_pressedStateDurationMillis)
+ : PRESSED_STATE_DURATION;
+ }
+ return mPressedStateDuration;
+ }
+
+ public int getTapTimeout() {
+ if (mTapTimeout < 0) {
+ Resources resources = getCurrentResources();
+ mTapTimeout = resources != null
+ ? resources.getInteger(R.integer.config_tapTimeoutMillis)
+ : TAP_TIMEOUT;
+ }
+ return mTapTimeout;
+ }
+
+ public int getJumpTapTimeout() {
+ if (mJumpTapTimeout < 0) {
+ Resources resources = getCurrentResources();
+ mJumpTapTimeout = resources != null
+ ? resources.getInteger(R.integer.config_jumpTapTimeoutMillis)
+ : JUMP_TAP_TIMEOUT;
+ }
+ return mJumpTapTimeout;
+ }
+
+ public int getDoubleTapTimeout() {
+ if (mDoubleTapTimeout < 0) {
+ Resources resources = getCurrentResources();
+ mDoubleTapTimeout = resources != null
+ ? resources.getInteger(R.integer.config_doubleTapTimeoutMillis)
+ : DOUBLE_TAP_TIMEOUT;
+ }
+ return mDoubleTapTimeout;
+ }
+
+ public int getDoubleTapMinTime() {
+ if (mDoubleTapMinTime < 0) {
+ Resources resources = getCurrentResources();
+ mDoubleTapMinTime = resources != null
+ ? resources.getInteger(R.integer.config_doubleTapMinTimeMillis)
+ : DOUBLE_TAP_MIN_TIME;
+ }
+ return mDoubleTapMinTime;
+ }
+
+ public int getHoverTapTimeout() {
+ if (mHoverTapTimeout < 0) {
+ Resources resources = getCurrentResources();
+ mHoverTapTimeout = resources != null
+ ? resources.getInteger(R.integer.config_hoverTapTimeoutMillis)
+ : HOVER_TAP_TIMEOUT;
+ }
+ return mHoverTapTimeout;
+ }
+
+ public int getHoverTapSlop() {
+ if (mHoverTapSlop < 0) {
+ Resources resources = getCurrentResources();
+ mHoverTapSlop = resources != null
+ ? resources.getDimensionPixelSize(R.dimen.config_hoverTapSlop)
+ : HOVER_TAP_SLOP;
+ }
+ return mHoverTapSlop;
+ }
+
+ public long getZoomControlsTimeout() {
+ if (mZoomControlsTimeout < 0) {
+ Resources resources = getCurrentResources();
+ mZoomControlsTimeout = resources != null
+ ? resources.getInteger(R.integer.config_zoomControlsTimeoutMillis)
+ : ZOOM_CONTROLS_TIMEOUT;
+ }
+ return mZoomControlsTimeout;
+ }
+
+ public float getScrollFriction() {
+ if (mScrollFriction < 0) {
+ Resources resources = getCurrentResources();
+ mScrollFriction = resources != null
+ ? resources.getFloat(R.dimen.config_scrollFriction)
+ : SCROLL_FRICTION;
+ }
+ return mScrollFriction;
+ }
+
+ public long getDefaultActionModeHideDuration() {
+ if (mDefaultActionModeHideDuration < 0) {
+ Resources resources = getCurrentResources();
+ mDefaultActionModeHideDuration = resources != null
+ ? resources.getInteger(R.integer.config_defaultActionModeHideDurationMillis)
+ : ACTION_MODE_HIDE_DURATION_DEFAULT;
+ }
+ return mDefaultActionModeHideDuration;
+ }
+
+ private static Resources getCurrentResources() {
+ if (!android.companion.virtualdevice.flags.Flags
+ .migrateViewconfigurationConstantsToResources()) {
+ return null;
+ }
+ Application application = ActivityThread.currentApplication();
+ Context context = application != null ? application.getApplicationContext() : null;
+ return context != null ? context.getResources() : null;
+ }
+ }
}
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index ba7e70564143..36c08a51c66a 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -204,7 +204,7 @@ static jlong nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFla
return reinterpret_cast<jlong>(connection);
}
-static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) {
+static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr, jboolean fast) {
SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
if (connection) {
@@ -212,6 +212,13 @@ static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) {
if (connection->tableQuery != nullptr) {
sqlite3_finalize(connection->tableQuery);
}
+ if (fast) {
+ // The caller requested a fast close, so do not checkpoint even if this is the last
+ // connection to the database. Note that the change is only to this connection.
+ // Any other connections to the same database are unaffected.
+ int _unused = 0;
+ sqlite3_db_config(connection->db, SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, 1, &_unused);
+ }
int err = sqlite3_close(connection->db);
if (err != SQLITE_OK) {
// This can happen if sub-objects aren't closed first. Make sure the caller knows.
@@ -224,6 +231,12 @@ static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) {
}
}
+// This method is deprecated and should be removed when it is no longer needed by the
+// robolectric tests.
+static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) {
+ nativeClose(env, clazz, connectionPtr, false);
+}
+
static void sqliteCustomScalarFunctionCallback(sqlite3_context *context,
int argc, sqlite3_value **argv) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -959,8 +972,10 @@ static const JNINativeMethod sMethods[] =
/* name, signature, funcPtr */
{ "nativeOpen", "(Ljava/lang/String;ILjava/lang/String;ZZII)J",
(void*)nativeOpen },
+ { "nativeClose", "(JZ)V",
+ (void*) static_cast<void(*)(JNIEnv*,jclass,jlong,jboolean)>(nativeClose) },
{ "nativeClose", "(J)V",
- (void*)nativeClose },
+ (void*) static_cast<void(*)(JNIEnv*,jclass,jlong)>(nativeClose) },
{ "nativeRegisterCustomScalarFunction", "(JLjava/lang/String;Ljava/util/function/UnaryOperator;)V",
(void*)nativeRegisterCustomScalarFunction },
{ "nativeRegisterCustomAggregateFunction", "(JLjava/lang/String;Ljava/util/function/BinaryOperator;)V",
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1cb38bed1388..fbb8e25eeced 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3072,6 +3072,43 @@
{@link MotionEvent#ACTION_SCROLL} event. -->
<dimen name="config_scrollFactor">64dp</dimen>
+ <!-- Duration in milliseconds of the pressed state in child components. -->
+ <integer name="config_pressedStateDurationMillis">64</integer>
+
+ <!-- Duration in milliseconds we will wait to see if a touch event is a tap or a scroll.
+ If the user does not move within this interval, it is considered to be a tap. -->
+ <integer name="config_tapTimeoutMillis">100</integer>
+
+ <!-- Duration in milliseconds we will wait to see if a touch event is a jump tap.
+ If the user does not move within this interval, it is considered to be a tap. -->
+ <integer name="config_jumpTapTimeoutMillis">500</integer>
+
+ <!-- Duration in milliseconds between the first tap's up event and the second tap's down
+ event for an interaction to be considered a double-tap. -->
+ <integer name="config_doubleTapTimeoutMillis">300</integer>
+
+ <!-- Minimum duration in milliseconds between the first tap's up event and the second tap's
+ down event for an interaction to be considered a double-tap. -->
+ <integer name="config_doubleTapMinTimeMillis">40</integer>
+
+ <!-- Maximum duration in milliseconds between a touch pad touch and release for a given touch
+ to be considered a tap (click) as opposed to a hover movement gesture. -->
+ <integer name="config_hoverTapTimeoutMillis">150</integer>
+
+ <!-- The amount of time in milliseconds that the zoom controls should be displayed on the
+ screen. -->
+ <integer name="config_zoomControlsTimeoutMillis">3000</integer>
+
+ <!-- Default duration in milliseconds for {@link ActionMode#hide(long)}. -->
+ <integer name="config_defaultActionModeHideDurationMillis">2000</integer>
+
+ <!-- Maximum distance in pixels that a touch pad touch can move before being released
+ for it to be considered a tap (click) as opposed to a hover movement gesture. -->
+ <dimen name="config_hoverTapSlop">20px</dimen>
+
+ <!-- The amount of friction applied to scrolls and flings. -->
+ <item name="config_scrollFriction" format="float" type="dimen">0.015</item>
+
<!-- Maximum number of grid columns permitted in the ResolverActivity
used for picking activities to handle an intent. -->
<integer name="config_maxResolverActivityColumns">3</integer>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a18c1d4df98b..f5424dbed21a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4159,6 +4159,17 @@
<java-symbol type="string" name="config_headlineFontFamily" />
<java-symbol type="string" name="config_headlineFontFamilyMedium" />
+ <java-symbol type="integer" name="config_pressedStateDurationMillis" />
+ <java-symbol type="integer" name="config_tapTimeoutMillis" />
+ <java-symbol type="integer" name="config_jumpTapTimeoutMillis" />
+ <java-symbol type="integer" name="config_doubleTapTimeoutMillis" />
+ <java-symbol type="integer" name="config_doubleTapMinTimeMillis" />
+ <java-symbol type="integer" name="config_hoverTapTimeoutMillis" />
+ <java-symbol type="integer" name="config_zoomControlsTimeoutMillis" />
+ <java-symbol type="integer" name="config_defaultActionModeHideDurationMillis" />
+ <java-symbol type="dimen" name="config_hoverTapSlop" />
+ <java-symbol type="dimen" name="config_scrollFriction" />
+
<java-symbol type="drawable" name="stat_sys_vitals" />
<java-symbol type="color" name="text_color_primary" />
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index 5b2dd97a338f..bc0a8573f977 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -39,6 +39,7 @@ import com.android.wm.shell.Flags;
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
import java.util.ArrayList;
+import java.util.stream.IntStream;
/**
* Calculates the snap targets and the snap position given a position and a velocity. All positions
@@ -354,10 +355,20 @@ public class DividerSnapAlgorithm {
float ratio = areOffscreenRatiosSupported()
? SplitSpec.OFFSCREEN_ASYMMETRIC_RATIO
: SplitSpec.ONSCREEN_ONLY_ASYMMETRIC_RATIO;
+
+ // The intended size of the smaller app, in pixels
int size = (int) (ratio * (end - start)) - mDividerSize / 2;
- int leftTopPosition = start + pinnedTaskbarShiftStart + size;
- int rightBottomPosition = end - pinnedTaskbarShiftEnd - size - mDividerSize;
+ // If there are insets that interfere with the smaller app (visually or blocking touch
+ // targets), make the smaller app bigger by that amount to compensate. This applies to
+ // pinned taskbar, 3-button nav (both create an opaque bar at bottom) and status bar (blocks
+ // touch targets at top).
+ int extraSpace = IntStream.of(
+ getStartInset(), getEndInset(), pinnedTaskbarShiftStart, pinnedTaskbarShiftEnd
+ ).max().getAsInt();
+
+ int leftTopPosition = start + extraSpace + size;
+ int rightBottomPosition = end - extraSpace - size - mDividerSize;
addNonDismissingTargets(isLeftRightSplit, leftTopPosition, rightBottomPosition, dividerMax);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 720e8e53b218..b7867d0b81b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -1543,11 +1543,28 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
}
+ /**
+ * When IME is triggered on the bottom app in split screen, we want to translate the bottom
+ * app up by a certain amount so that it's not covered too much by the IME. But there's also
+ * an upper limit to the amount we want to translate (since we still need some of the top
+ * app to be visible too). So this function essentially says "try to translate the bottom
+ * app up, but stop before you make the top app too small."
+ */
private int getTargetYOffset() {
- final int desireOffset = Math.abs(mEndImeTop - mStartImeTop);
- // Make sure to keep at least 30% visible for the top split.
- final int maxOffset = (int) (getTopLeftBounds().height() * ADJUSTED_SPLIT_FRACTION_MAX);
- return -Math.min(desireOffset, maxOffset);
+ // We want to translate up the bottom app by this amount.
+ final int desiredOffset = Math.abs(mEndImeTop - mStartImeTop);
+
+ // But we also want to keep this much of the top app visible.
+ final float amountOfTopAppToKeepVisible =
+ getTopLeftBounds().height() * (1 - ADJUSTED_SPLIT_FRACTION_MAX);
+
+ // So the current onscreen size of the top app, minus the minimum size, is the max
+ // translation we will allow.
+ final float currentOnScreenSizeOfTopApp = getTopLeftBounds().bottom;
+ final int maxOffset =
+ (int) Math.max(currentOnScreenSizeOfTopApp - amountOfTopAppToKeepVisible, 0);
+
+ return -Math.min(desiredOffset, maxOffset);
}
@SplitPosition
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 2bd5c72eb34b..6f0919e1d045 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -45,6 +46,7 @@ import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import com.android.wm.shell.pip2.phone.PhonePipMenuController;
import com.android.wm.shell.pip2.phone.PipController;
+import com.android.wm.shell.pip2.phone.PipInteractionHandler;
import com.android.wm.shell.pip2.phone.PipMotionHelper;
import com.android.wm.shell.pip2.phone.PipScheduler;
import com.android.wm.shell.pip2.phone.PipTaskListener;
@@ -88,12 +90,13 @@ public abstract class Pip2Module {
@NonNull PipUiStateChangeController pipUiStateChangeController,
DisplayController displayController,
Optional<SplitScreenController> splitScreenControllerOptional,
- PipDesktopState pipDesktopState) {
+ PipDesktopState pipDesktopState,
+ PipInteractionHandler pipInteractionHandler) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
pipScheduler, pipStackListenerController, pipDisplayLayoutState,
pipUiStateChangeController, displayController, splitScreenControllerOptional,
- pipDesktopState);
+ pipDesktopState, pipInteractionHandler);
}
@WMSingleton
@@ -249,4 +252,14 @@ public abstract class Pip2Module {
@BindsOptionalOf
abstract DragToDesktopTransitionHandler optionalDragToDesktopTransitionHandler();
+
+ @WMSingleton
+ @Provides
+ static PipInteractionHandler providePipInteractionHandler(
+ Context context,
+ @ShellMainThread Handler mainHandler
+ ) {
+ return new PipInteractionHandler(context, mainHandler,
+ InteractionJankMonitor.getInstance());
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInteractionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInteractionHandler.java
new file mode 100644
index 000000000000..321952480094
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInteractionHandler.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static com.android.internal.jank.Cuj.CUJ_PIP_TRANSITION;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.os.Handler;
+import android.view.SurfaceControl;
+
+import com.android.internal.jank.InteractionJankMonitor;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Helps track PIP CUJ interactions
+ */
+public class PipInteractionHandler {
+ @IntDef(prefix = {"INTERACTION_"}, value = {
+ INTERACTION_EXIT_PIP,
+ INTERACTION_EXIT_PIP_TO_SPLIT
+ })
+
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Interaction {}
+
+ public static final int INTERACTION_EXIT_PIP = 0;
+ public static final int INTERACTION_EXIT_PIP_TO_SPLIT = 1;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final InteractionJankMonitor mInteractionJankMonitor;
+
+ public PipInteractionHandler(Context context, Handler handler,
+ InteractionJankMonitor interactionJankMonitor) {
+ mContext = context;
+ mHandler = handler;
+ mInteractionJankMonitor = interactionJankMonitor;
+ }
+
+ /**
+ * Begin tracking PIP CUJ.
+ *
+ * @param leash PIP leash.
+ * @param interaction Tag for interaction.
+ */
+ public void begin(SurfaceControl leash, @Interaction int interaction) {
+ mInteractionJankMonitor.begin(leash, mContext, mHandler, CUJ_PIP_TRANSITION,
+ pipInteractionToString(interaction));
+ }
+
+ /**
+ * End tracking CUJ.
+ */
+ public void end() {
+ mInteractionJankMonitor.end(CUJ_PIP_TRANSITION);
+ }
+
+ /**
+ * Converts an interaction to a string representation used for tagging.
+ *
+ * @param interaction Interaction to track.
+ * @return String representation of the interaction.
+ */
+ public static String pipInteractionToString(@Interaction int interaction) {
+ return switch (interaction) {
+ case INTERACTION_EXIT_PIP -> "EXIT_PIP";
+ case INTERACTION_EXIT_PIP_TO_SPLIT -> "EXIT_PIP_TO_SPLIT";
+ default -> "";
+ };
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index b51a58e604bf..9bb2e38e1526 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -113,6 +113,7 @@ public class PipTransition extends PipTransitionController implements
private final DisplayController mDisplayController;
private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
private final PipDesktopState mPipDesktopState;
+ private final PipInteractionHandler mPipInteractionHandler;
//
// Transition caches
@@ -154,7 +155,8 @@ public class PipTransition extends PipTransitionController implements
PipUiStateChangeController pipUiStateChangeController,
DisplayController displayController,
Optional<SplitScreenController> splitScreenControllerOptional,
- PipDesktopState pipDesktopState) {
+ PipDesktopState pipDesktopState,
+ PipInteractionHandler pipInteractionHandler) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
pipBoundsAlgorithm);
@@ -168,9 +170,11 @@ public class PipTransition extends PipTransitionController implements
mDisplayController = displayController;
mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(mContext);
mPipDesktopState = pipDesktopState;
+ mPipInteractionHandler = pipInteractionHandler;
mExpandHandler = new PipExpandHandler(mContext, pipBoundsState, pipBoundsAlgorithm,
- pipTransitionState, pipDisplayLayoutState, splitScreenControllerOptional);
+ pipTransitionState, pipDisplayLayoutState, pipInteractionHandler,
+ splitScreenControllerOptional);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java
index db4942b2fb95..3274f4ae354a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java
@@ -45,6 +45,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip2.animation.PipExpandAnimator;
+import com.android.wm.shell.pip2.phone.PipInteractionHandler;
import com.android.wm.shell.pip2.phone.PipTransitionState;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -58,6 +59,7 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final PipTransitionState mPipTransitionState;
private final PipDisplayLayoutState mPipDisplayLayoutState;
+ private final PipInteractionHandler mPipInteractionHandler;
private final Optional<SplitScreenController> mSplitScreenControllerOptional;
@Nullable
@@ -72,12 +74,14 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
PipBoundsAlgorithm pipBoundsAlgorithm,
PipTransitionState pipTransitionState,
PipDisplayLayoutState pipDisplayLayoutState,
+ PipInteractionHandler pipInteractionHandler,
Optional<SplitScreenController> splitScreenControllerOptional) {
mContext = context;
mPipBoundsState = pipBoundsState;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipTransitionState = pipTransitionState;
mPipDisplayLayoutState = pipDisplayLayoutState;
+ mPipInteractionHandler = pipInteractionHandler;
mSplitScreenControllerOptional = splitScreenControllerOptional;
mPipExpandAnimatorSupplier = PipExpandAnimator::new;
@@ -183,6 +187,8 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
PipExpandAnimator animator = mPipExpandAnimatorSupplier.get(mContext, pipLeash,
startTransaction, finishTransaction, endBounds, startBounds, endBounds,
sourceRectHint, delta);
+ animator.setAnimationStartCallback(() -> mPipInteractionHandler.begin(pipLeash,
+ PipInteractionHandler.INTERACTION_EXIT_PIP));
animator.setAnimationEndCallback(() -> {
if (parentBeforePip != null) {
// TODO b/377362511: Animate local leash instead to also handle letterbox case.
@@ -190,6 +196,7 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
finishTransaction.setCrop(pipLeash, null);
}
finishTransition();
+ mPipInteractionHandler.end();
});
cacheAndStartTransitionAnimator(animator);
saveReentryState();
@@ -248,6 +255,8 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
splitController.finishEnterSplitScreen(finishTransaction);
});
+ animator.setAnimationStartCallback(() -> mPipInteractionHandler.begin(pipLeash,
+ PipInteractionHandler.INTERACTION_EXIT_PIP_TO_SPLIT));
animator.setAnimationEndCallback(() -> {
if (parentBeforePip == null) {
// After PipExpandAnimator is done modifying finishTransaction, we need to make
@@ -256,6 +265,7 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
finishTransaction.setPosition(pipLeash, 0, 0);
}
finishTransition();
+ mPipInteractionHandler.end();
});
cacheAndStartTransitionAnimator(animator);
saveReentryState();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipInteractionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipInteractionHandlerTest.java
new file mode 100644
index 000000000000..9c0127ea2414
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipInteractionHandlerTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static com.android.internal.jank.Cuj.CUJ_PIP_TRANSITION;
+import static com.android.wm.shell.pip2.phone.PipInteractionHandler.INTERACTION_EXIT_PIP;
+import static com.android.wm.shell.pip2.phone.PipInteractionHandler.INTERACTION_EXIT_PIP_TO_SPLIT;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.kotlin.VerificationKt.times;
+
+import android.content.Context;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.jank.InteractionJankMonitor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test against {@link PipInteractionHandler}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipInteractionHandlerTest {
+ @Mock private Context mMockContext;
+ @Mock private Handler mMockHandler;
+ @Mock private InteractionJankMonitor mMockInteractionJankMonitor;
+
+ private SurfaceControl mTestLeash;
+
+ private PipInteractionHandler mPipInteractionHandler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mPipInteractionHandler = new PipInteractionHandler(mMockContext, mMockHandler,
+ mMockInteractionJankMonitor);
+ mTestLeash = new SurfaceControl.Builder()
+ .setContainerLayer()
+ .setName("PipInteractionHandlerTest")
+ .setCallsite("PipInteractionHandlerTest")
+ .build();
+ }
+
+ @Test
+ public void begin_expand_startsTracking() {
+ mPipInteractionHandler.begin(mTestLeash, INTERACTION_EXIT_PIP);
+
+ verify(mMockInteractionJankMonitor, times(1)).begin(eq(mTestLeash),
+ eq(mMockContext), eq(mMockHandler), eq(CUJ_PIP_TRANSITION),
+ eq(PipInteractionHandler.pipInteractionToString(INTERACTION_EXIT_PIP)));
+ }
+
+ @Test
+ public void begin_expandToSplit_startsTracking() {
+ mPipInteractionHandler.begin(mTestLeash, INTERACTION_EXIT_PIP_TO_SPLIT);
+
+ verify(mMockInteractionJankMonitor, times(1)).begin(eq(mTestLeash),
+ eq(mMockContext), eq(mMockHandler), eq(CUJ_PIP_TRANSITION),
+ eq(PipInteractionHandler.pipInteractionToString(INTERACTION_EXIT_PIP_TO_SPLIT)));
+ }
+
+ @Test
+ public void end_stopsTracking() {
+ mPipInteractionHandler.end();
+
+ verify(mMockInteractionJankMonitor, times(1)).end(CUJ_PIP_TRANSITION);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java
index 2a22842eda1a..cc66f00525b5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java
@@ -23,6 +23,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -48,11 +49,13 @@ import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip2.animation.PipExpandAnimator;
+import com.android.wm.shell.pip2.phone.PipInteractionHandler;
import com.android.wm.shell.pip2.phone.PipTransitionState;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.TransitionInfoBuilder;
@@ -61,6 +64,8 @@ import com.android.wm.shell.util.StubTransaction;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -79,6 +84,7 @@ public class PipExpandHandlerTest {
@Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
@Mock private PipTransitionState mMockPipTransitionState;
@Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
+ @Mock private PipInteractionHandler mMockPipInteractionHandler;
@Mock private SplitScreenController mMockSplitScreenController;
@Mock private IBinder mMockTransitionToken;
@@ -89,6 +95,8 @@ public class PipExpandHandlerTest {
@Mock private PipExpandAnimator mMockPipExpandAnimator;
+ @Captor private ArgumentCaptor<Runnable> mAnimatorCallbackArgumentCaptor;
+
@Surface.Rotation
private static final int DISPLAY_ROTATION = Surface.ROTATION_0;
@@ -108,7 +116,7 @@ public class PipExpandHandlerTest {
mPipExpandHandler = new PipExpandHandler(mMockContext, mMockPipBoundsState,
mMockPipBoundsAlgorithm, mMockPipTransitionState, mMockPipDisplayLayoutState,
- Optional.of(mMockSplitScreenController));
+ mMockPipInteractionHandler, Optional.of(mMockSplitScreenController));
mPipExpandHandler.setPipExpandAnimatorSupplier((context, leash, startTransaction,
finishTransaction, baseBounds, startBounds, endBounds,
sourceRectHint, rotation) -> mMockPipExpandAnimator);
@@ -138,6 +146,13 @@ public class PipExpandHandlerTest {
verify(mMockPipExpandAnimator, times(1)).start();
verify(mMockPipBoundsState, times(1)).saveReentryState(SNAP_FRACTION);
+
+ verify(mMockPipExpandAnimator, times(1))
+ .setAnimationStartCallback(mAnimatorCallbackArgumentCaptor.capture());
+ InstrumentationRegistry.getInstrumentation()
+ .runOnMainSync(mAnimatorCallbackArgumentCaptor.getValue());
+ verify(mMockPipInteractionHandler, times(1)).begin(any(),
+ eq(PipInteractionHandler.INTERACTION_EXIT_PIP));
}
@Test
@@ -158,6 +173,13 @@ public class PipExpandHandlerTest {
verify(mMockSplitScreenController, times(1)).finishEnterSplitScreen(eq(mFinishT));
verify(mMockPipExpandAnimator, times(1)).start();
verify(mMockPipBoundsState, times(1)).saveReentryState(SNAP_FRACTION);
+
+ verify(mMockPipExpandAnimator, times(1))
+ .setAnimationStartCallback(mAnimatorCallbackArgumentCaptor.capture());
+ InstrumentationRegistry.getInstrumentation()
+ .runOnMainSync(mAnimatorCallbackArgumentCaptor.getValue());
+ verify(mMockPipInteractionHandler, times(1)).begin(any(),
+ eq(PipInteractionHandler.INTERACTION_EXIT_PIP_TO_SPLIT));
}
private TransitionInfo getExpandFromPipTransitionInfo(@WindowManager.TransitionType int type,
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v36/preference_selector_with_widget.xml b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v36/settingslib_expressive_preference_selector_with_widget.xml
index a79d69dbff8c..a79d69dbff8c 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v36/preference_selector_with_widget.xml
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v36/settingslib_expressive_preference_selector_with_widget.xml
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java b/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java
index cde8b332f2e7..465b6ccf4d9c 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java
@@ -238,7 +238,10 @@ public class SelectorWithWidgetPreference extends CheckBoxPreference {
} else {
setWidgetLayoutResource(R.layout.settingslib_preference_widget_radiobutton);
}
- setLayoutResource(R.layout.preference_selector_with_widget);
+ int resID = SettingsThemeHelper.isExpressiveTheme(context)
+ ? R.layout.settingslib_expressive_preference_selector_with_widget
+ : R.layout.preference_selector_with_widget;
+ setLayoutResource(resID);
setIconSpaceReserved(false);
final TypedArray a =
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index cb1f1af8b5a6..a90081738062 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -2093,3 +2093,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "always_compose_qs_ui_fragment"
+ namespace: "systemui"
+ description: "Have QQS and QS scenes in the Compose fragment always composed, not just when it should be visible."
+ bug: "389985793"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 404f1b217026..22688d310b44 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -102,6 +102,7 @@ interface SceneTransitionLayoutScope<out CS : ContentScope> {
key: SceneKey,
userActions: Map<UserAction, UserActionResult> = emptyMap(),
effectFactory: OverscrollFactory? = null,
+ alwaysCompose: Boolean = false,
content: @Composable CS.() -> Unit,
)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index e3c4eb0f8bea..4da83c3a6fc9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -207,6 +207,9 @@ internal class SceneTransitionLayoutImpl(
private val nestedScrollDispatcher = NestedScrollDispatcher()
private val nestedScrollConnection = object : NestedScrollConnection {}
+ // TODO(b/399825091): Remove this.
+ private var scenesToAlwaysCompose: MutableList<Scene>? = null
+
init {
updateContents(builder, layoutDirection, defaultEffectFactory)
@@ -312,6 +315,7 @@ internal class SceneTransitionLayoutImpl(
key: SceneKey,
userActions: Map<UserAction, UserActionResult>,
effectFactory: OverscrollFactory?,
+ alwaysCompose: Boolean,
content: @Composable InternalContentScope.() -> Unit,
) {
require(!overlaysDefined) { "all scenes must be defined before overlays" }
@@ -324,6 +328,10 @@ internal class SceneTransitionLayoutImpl(
Content.calculateGlobalZIndex(parentZIndex, ++zIndex, ancestors.size)
val factory = effectFactory ?: defaultEffectFactory
if (scene != null) {
+ check(alwaysCompose == scene.alwaysCompose) {
+ "scene.alwaysCompose can not change"
+ }
+
// Update an existing scene.
scene.content = content
scene.userActions = resolvedUserActions
@@ -332,7 +340,7 @@ internal class SceneTransitionLayoutImpl(
scene.maybeUpdateEffects(factory)
} else {
// New scene.
- scenes[key] =
+ val scene =
Scene(
key,
this@SceneTransitionLayoutImpl,
@@ -341,7 +349,16 @@ internal class SceneTransitionLayoutImpl(
zIndex.toFloat(),
globalZIndex,
factory,
+ alwaysCompose,
)
+
+ scenes[key] = scene
+
+ if (alwaysCompose) {
+ (scenesToAlwaysCompose
+ ?: mutableListOf<Scene>().also { scenesToAlwaysCompose = it })
+ .add(scene)
+ }
}
}
@@ -470,22 +487,24 @@ internal class SceneTransitionLayoutImpl(
@Composable
private fun Scenes() {
- scenesToCompose().fastForEach { scene -> key(scene.key) { scene.Content() } }
+ scenesToCompose().fastForEach { (scene, isInvisible) ->
+ key(scene.key) { scene.Content(isInvisible = isInvisible) }
+ }
}
- private fun scenesToCompose(): List<Scene> {
+ private fun scenesToCompose(): List<SceneToCompose> {
val transitions = state.currentTransitions
- return if (transitions.isEmpty()) {
- listOf(scene(state.transitionState.currentScene))
- } else {
- buildList {
- val visited = mutableSetOf<SceneKey>()
- fun maybeAdd(sceneKey: SceneKey) {
- if (visited.add(sceneKey)) {
- add(scene(sceneKey))
- }
+ return buildList {
+ val visited = mutableSetOf<SceneKey>()
+ fun maybeAdd(sceneKey: SceneKey, isInvisible: Boolean = false) {
+ if (visited.add(sceneKey)) {
+ add(SceneToCompose(scene(sceneKey), isInvisible))
}
+ }
+ if (transitions.isEmpty()) {
+ maybeAdd(state.transitionState.currentScene)
+ } else {
// Compose the new scene we are going to first.
transitions.fastForEachReversed { transition ->
when (transition) {
@@ -504,9 +523,13 @@ internal class SceneTransitionLayoutImpl(
// Make sure that the current scene is always composed.
maybeAdd(transitions.last().currentScene)
}
+
+ scenesToAlwaysCompose?.fastForEach { maybeAdd(it.key, isInvisible = true) }
}
}
+ private data class SceneToCompose(val scene: Scene, val isInvisible: Boolean)
+
@Composable
private fun BoxScope.Overlays() {
val overlaysOrderedByZIndex = overlaysToComposeOrderedByZIndex()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 149a9e7c4705..72ee75ad2d47 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -34,6 +34,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.layout.approachLayout
+import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.zIndex
@@ -154,11 +155,12 @@ internal sealed class Content(
@SuppressLint("NotConstructor")
@Composable
- fun Content(modifier: Modifier = Modifier) {
+ fun Content(modifier: Modifier = Modifier, isInvisible: Boolean = false) {
// If this content has a custom factory, provide it to the content so that the factory is
// automatically used when calling rememberOverscrollEffect().
Box(
modifier
+ .thenIf(isInvisible) { InvisibleModifier }
.zIndex(zIndex)
.approachLayout(
isMeasurementApproachInProgress = { layoutImpl.state.isTransitioning() }
@@ -305,3 +307,8 @@ internal class ContentScopeImpl(
)
}
}
+
+private val InvisibleModifier =
+ Modifier.layout { measurable, constraints ->
+ measurable.measure(constraints).run { layout(width, height) {} }
+ }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt
index 7f57798fb1b3..38acd4be80ae 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt
@@ -35,6 +35,7 @@ internal class Scene(
zIndex: Float,
globalZIndex: Long,
effectFactory: OverscrollFactory,
+ val alwaysCompose: Boolean,
) : Content(key, layoutImpl, content, actions, zIndex, globalZIndex, effectFactory) {
override fun toString(): String {
return "Scene(key=$key)"
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index fa7661b6d102..6538d4340cf3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -45,6 +45,7 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
import androidx.compose.ui.test.assertWidthIsEqualTo
import androidx.compose.ui.test.junit4.createComposeRule
@@ -64,10 +65,13 @@ import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.subjects.assertThat
import com.android.compose.test.assertSizeIsEqualTo
+import com.android.compose.test.setContentAndCreateMainScope
import com.android.compose.test.subjects.DpOffsetSubject
import com.android.compose.test.subjects.assertThat
+import com.android.compose.test.transition
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
@@ -582,4 +586,34 @@ class SceneTransitionLayoutTest {
assertThat((state2 as MutableSceneTransitionLayoutStateImpl).motionScheme)
.isEqualTo(motionScheme2)
}
+
+ @Test
+ fun alwaysCompose() {
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayoutForTesting(state) {
+ scene(SceneA) { Box(Modifier.element(TestElements.Foo).size(20.dp)) }
+ scene(SceneB, alwaysCompose = true) {
+ Box(Modifier.element(TestElements.Bar).size(40.dp))
+ }
+ }
+ }
+
+ // Idle(A): Foo is displayed and Bar exists given that SceneB is always composed but it is
+ // not displayed.
+ rule.onNode(isElement(TestElements.Foo)).assertIsDisplayed().assertSizeIsEqualTo(20.dp)
+ rule.onNode(isElement(TestElements.Bar)).assertExists().assertIsNotDisplayed()
+
+ // Transition(A => B): Foo and Bar are both displayed
+ val aToB = transition(SceneA, SceneB)
+ scope.launch { state.startTransition(aToB) }
+ rule.onNode(isElement(TestElements.Foo)).assertIsDisplayed().assertSizeIsEqualTo(20.dp)
+ rule.onNode(isElement(TestElements.Bar)).assertIsDisplayed().assertSizeIsEqualTo(40.dp)
+
+ // Idle(B): Foo does not exist and Bar is displayed.
+ aToB.finish()
+ rule.onNode(isElement(TestElements.Foo)).assertDoesNotExist()
+ rule.onNode(isElement(TestElements.Bar)).assertIsDisplayed().assertSizeIsEqualTo(40.dp)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/SysUIStatePerDisplayInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/SysUIStatePerDisplayInteractorTest.kt
new file mode 100644
index 000000000000..ed9cd98a825a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/SysUIStatePerDisplayInteractorTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.model.StateChange
+import com.android.systemui.model.fakeSysUIStatePerDisplayRepository
+import com.android.systemui.model.sysUiStateFactory
+import com.android.systemui.model.sysuiStateInteractor
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.Before
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SysUIStatePerDisplayInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ val stateRepository = kosmos.fakeSysUIStatePerDisplayRepository
+ val state0 = kosmos.sysUiStateFactory.create(0)
+ val state1 = kosmos.sysUiStateFactory.create(1)
+ val state2 = kosmos.sysUiStateFactory.create(2)
+
+ val underTest = kosmos.sysuiStateInteractor
+
+ @Before
+ fun setup() {
+ stateRepository.apply {
+ add(0, state0)
+ add(1, state1)
+ add(2, state2)
+ }
+ }
+
+ @Test
+ fun setFlagsExclusivelyToDisplay_setsFlagsOnTargetStateAndClearsTheOthers() {
+ val targetDisplayId = 0
+ val stateChange = StateChange().setFlag(1L, true)
+
+ underTest.setFlagsExclusivelyToDisplay(targetDisplayId, stateChange)
+
+ assertThat(state0.isFlagEnabled(1)).isTrue()
+ assertThat(state1.isFlagEnabled(1)).isFalse()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+
+ underTest.setFlagsExclusivelyToDisplay(1, stateChange)
+
+ assertThat(state0.isFlagEnabled(1)).isFalse()
+ assertThat(state1.isFlagEnabled(1)).isTrue()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+
+ underTest.setFlagsExclusivelyToDisplay(2, stateChange)
+
+ assertThat(state0.isFlagEnabled(1)).isFalse()
+ assertThat(state1.isFlagEnabled(1)).isFalse()
+ assertThat(state2.isFlagEnabled(1)).isTrue()
+
+ underTest.setFlagsExclusivelyToDisplay(3, stateChange)
+
+ assertThat(state0.isFlagEnabled(1)).isFalse()
+ assertThat(state1.isFlagEnabled(1)).isFalse()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+ }
+
+ @Test
+ fun setFlagsExclusivelyToDisplay_multipleFlags_setsFlagsOnTargetStateAndClearsTheOthers() {
+ val stateChange = StateChange().setFlag(1L, true).setFlag(2L, true)
+
+ underTest.setFlagsExclusivelyToDisplay(1, stateChange)
+
+ assertThat(state0.isFlagEnabled(1)).isFalse()
+ assertThat(state0.isFlagEnabled(2)).isFalse()
+ assertThat(state1.isFlagEnabled(1)).isTrue()
+ assertThat(state1.isFlagEnabled(2)).isTrue()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+ }
+
+ @Test
+ fun setFlagsExclusivelyToDisplay_clearsFlags() {
+ state0.setFlag(1, true).setFlag(2, true).commitUpdate()
+ state1.setFlag(1, true).setFlag(2, true).commitUpdate()
+ state2.setFlag(1, true).setFlag(2, true).commitUpdate()
+
+ val stateChange = StateChange().setFlag(1L, false)
+
+ underTest.setFlagsExclusivelyToDisplay(1, stateChange)
+
+ // Sets it as false in display 1, but also the others.
+ assertThat(state0.isFlagEnabled(1)).isFalse()
+ assertThat(state1.isFlagEnabled(1)).isFalse()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateDispatcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateDispatcherTest.kt
new file mode 100644
index 000000000000..b82f5fce9e14
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateDispatcherTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2025 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.model
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SysUIStateDispatcherTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val stateFactory = kosmos.sysUiStateFactory
+ private val state0 = stateFactory.create(Display.DEFAULT_DISPLAY)
+ private val state1 = stateFactory.create(DISPLAY_1)
+ private val state2 = stateFactory.create(DISPLAY_2)
+ private val underTest = kosmos.sysUIStateDispatcher
+
+ private val flagsChanges = mutableMapOf<Int, Long>() // display id -> flag value
+ private val callback =
+ SysUiState.SysUiStateCallback { sysUiFlags, displayId ->
+ flagsChanges[displayId] = sysUiFlags
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun registerUnregisterListener_notifiedOfChanges_receivedForAllDisplayIdsWithOneCallback() {
+ underTest.registerListener(callback)
+
+ state1.setFlag(FLAG_1, true).commitUpdate()
+ state2.setFlag(FLAG_2, true).commitUpdate()
+
+ assertThat(flagsChanges).containsExactly(DISPLAY_1, FLAG_1, DISPLAY_2, FLAG_2)
+
+ underTest.unregisterListener(callback)
+
+ state1.setFlag(0, true).commitUpdate()
+
+ // Didn't change
+ assertThat(flagsChanges).containsExactly(DISPLAY_1, FLAG_1, DISPLAY_2, FLAG_2)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun registerUnregisterListener_notifiedOfChangesForNonDefaultDisplay_NotPropagated() {
+ underTest.registerListener(callback)
+
+ state1.setFlag(FLAG_1, true).commitUpdate()
+
+ assertThat(flagsChanges).isEmpty()
+
+ state0.setFlag(FLAG_1, true).commitUpdate()
+
+ assertThat(flagsChanges).containsExactly(Display.DEFAULT_DISPLAY, FLAG_1)
+ }
+
+ private companion object {
+ const val DISPLAY_1 = 1
+ const val DISPLAY_2 = 2
+ const val FLAG_1 = 10L
+ const val FLAG_2 = 20L
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java
index 779e10a9682a..f6de6295212b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java
@@ -28,6 +28,8 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.view.Display;
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -53,9 +55,11 @@ public class SysUiStateTest extends SysuiTestCase {
private SysUiState mFlagsContainer;
private SceneContainerPlugin mSceneContainerPlugin;
private DumpManager mDumpManager;
+ private SysUIStateDispatcher mSysUIStateDispatcher;
private SysUiState createInstance(int displayId) {
- var sysuiState = new SysUiStateImpl(displayId, mSceneContainerPlugin, mDumpManager);
+ var sysuiState = new SysUiStateImpl(displayId, mSceneContainerPlugin, mDumpManager,
+ mSysUIStateDispatcher);
sysuiState.addCallback(mCallback);
return sysuiState;
}
@@ -67,6 +71,7 @@ public class SysUiStateTest extends SysuiTestCase {
mSceneContainerPlugin = mKosmos.getSceneContainerPlugin();
mCallback = mock(SysUiState.SysUiStateCallback.class);
mDumpManager = mock(DumpManager.class);
+ mSysUIStateDispatcher = mKosmos.getSysUIStateDispatcher();
mFlagsContainer = createInstance(DEFAULT_DISPLAY);
}
@@ -74,7 +79,7 @@ public class SysUiStateTest extends SysuiTestCase {
public void addSingle_setFlag() {
setFlags(FLAG_1);
- verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1, DEFAULT_DISPLAY);
}
@Test
@@ -82,8 +87,8 @@ public class SysUiStateTest extends SysuiTestCase {
setFlags(FLAG_1);
setFlags(FLAG_2);
- verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1);
- verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1 | FLAG_2);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1, DEFAULT_DISPLAY);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1 | FLAG_2, DEFAULT_DISPLAY);
}
@Test
@@ -92,9 +97,9 @@ public class SysUiStateTest extends SysuiTestCase {
setFlags(FLAG_2);
mFlagsContainer.setFlag(FLAG_1, false).commitUpdate(DISPLAY_ID);
- verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1);
- verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1 | FLAG_2);
- verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_2);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1, DEFAULT_DISPLAY);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1 | FLAG_2, DEFAULT_DISPLAY);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_2, DEFAULT_DISPLAY);
}
@Test
@@ -102,7 +107,7 @@ public class SysUiStateTest extends SysuiTestCase {
setFlags(FLAG_1, FLAG_2, FLAG_3, FLAG_4);
int expected = FLAG_1 | FLAG_2 | FLAG_3 | FLAG_4;
- verify(mCallback, times(1)).onSystemUiStateChanged(expected);
+ verify(mCallback, times(1)).onSystemUiStateChanged(expected, DEFAULT_DISPLAY);
}
@Test
@@ -111,9 +116,9 @@ public class SysUiStateTest extends SysuiTestCase {
mFlagsContainer.setFlag(FLAG_2, false).commitUpdate(DISPLAY_ID);
int expected1 = FLAG_1 | FLAG_2 | FLAG_3 | FLAG_4;
- verify(mCallback, times(1)).onSystemUiStateChanged(expected1);
+ verify(mCallback, times(1)).onSystemUiStateChanged(expected1, DEFAULT_DISPLAY);
int expected2 = FLAG_1 | FLAG_3 | FLAG_4;
- verify(mCallback, times(1)).onSystemUiStateChanged(expected2);
+ verify(mCallback, times(1)).onSystemUiStateChanged(expected2, DEFAULT_DISPLAY);
}
@Test
@@ -122,25 +127,16 @@ public class SysUiStateTest extends SysuiTestCase {
setFlags(FLAG_1, FLAG_2, FLAG_3, FLAG_4);
int expected = FLAG_1 | FLAG_2 | FLAG_3 | FLAG_4;
- verify(mCallback, times(0)).onSystemUiStateChanged(expected);
+ verify(mCallback, times(0)).onSystemUiStateChanged(expected, DEFAULT_DISPLAY);
}
@Test
public void setFlag_receivedForDefaultDisplay() {
setFlags(FLAG_1);
- verify(mCallback, times(1)).onSystemUiStateChangedForDisplay(FLAG_1, DEFAULT_DISPLAY);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1, DEFAULT_DISPLAY);
}
- @Test
- public void setFlag_externalDisplayInstance_defaultDisplayCallbackNotPropagated() {
- var instance = createInstance(/* displayId = */ 2);
- reset(mCallback);
- setFlags(instance, FLAG_1);
-
- verify(mCallback, times(1)).onSystemUiStateChangedForDisplay(FLAG_1, /* displayId= */ 2);
- verify(mCallback, never()).onSystemUiStateChanged(FLAG_1);
- }
@Test
public void init_registersWithDumpManager() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index d852a9dc0bb9..2c852c3f6185 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -17,7 +17,9 @@
package com.android.systemui.shade;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Display.TYPE_INTERNAL;
+import static com.android.systemui.display.data.repository.FakeDisplayRepositoryKt.display;
import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
import static com.google.common.truth.Truth.assertThat;
@@ -47,6 +49,7 @@ import android.os.Looper;
import android.os.PowerManager;
import android.os.UserManager;
import android.util.DisplayMetrics;
+import android.view.Display;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -70,6 +73,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.common.domain.interactor.SysUIStateDisplaysInteractor;
import com.android.systemui.common.ui.view.TouchHandlingView;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
@@ -291,6 +295,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Mock private NaturalScrollingSettingObserver mNaturalScrollingSettingObserver;
@Mock private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
@Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
+ @Mock protected SysUIStateDisplaysInteractor mSysUIStateDisplaysInteractor;
protected final int mMaxUdfpsBurnInOffsetY = 5;
protected FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
protected KeyguardClockInteractor mKeyguardClockInteractor;
@@ -435,6 +440,9 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
return null;
}).when(mView).setOnTouchListener(any(NotificationPanelViewController.TouchHandler.class));
+ var displayMock = display(TYPE_INTERNAL, /* flags= */ 0, /* id= */Display.DEFAULT_DISPLAY,
+ /* state= */ null);
+ when(mView.getDisplay()).thenReturn(displayMock);
// Any edge transition
when(mKeyguardTransitionInteractor.transition(any()))
.thenReturn(emptyFlow());
@@ -565,6 +573,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mShadeRepository,
mSysUIUnfoldComponent,
mSysUiState,
+ mSysUIStateDisplaysInteractor,
mKeyguardUnlockAnimationController,
mKeyguardIndicationController,
mNotificationListContainer,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 354c23d48916..f54c36754d31 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -16,10 +16,16 @@
package com.android.systemui.shade;
+import static android.view.Display.TYPE_INTERNAL;
+
+import static com.android.systemui.display.data.repository.FakeDisplayRepositoryKt.display;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -28,6 +34,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.os.Build;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import android.view.HapticFeedbackConstants;
@@ -38,6 +45,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.DejankUtils;
+import com.android.systemui.Flags;
import com.android.systemui.flags.DisableSceneContainer;
import com.google.android.msdl.data.model.MSDLToken;
@@ -182,4 +190,27 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
assertThat(mMSDLPlayer.getLatestTokenPlayed()).isEqualTo(MSDLToken.FAILURE);
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ public void updateSystemUiStateFlags_updatesSysuiStateInteractor() {
+ var DISPLAY_ID = 10;
+ var displayMock = display(TYPE_INTERNAL, /* flags= */ 0, /* id= */DISPLAY_ID,
+ /* state= */ null);
+ when(mView.getDisplay()).thenReturn(displayMock);
+
+ mNotificationPanelViewController.updateSystemUiStateFlags();
+
+ verify(mSysUIStateDisplaysInteractor).setFlagsExclusivelyToDisplay(eq(DISPLAY_ID), any());
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ public void updateSystemUiStateFlags_flagOff_doesNotUpdateSysuiStateInteractor() {
+ mNotificationPanelViewController.updateSystemUiStateFlags();
+
+ verify(mSysUIStateDisplaysInteractor, never()).setFlagsExclusivelyToDisplay(anyInt(),
+ any());
+ }
+
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStoreTest.kt
index 06650f2afe58..b2b28a28ac38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStoreTest.kt
@@ -50,11 +50,11 @@ class MultiDisplayStatusBarOrchestratorStoreTest : SysuiTestCase() {
@Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
index fee939df2cbb..18a2d0794f91 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
@@ -17,8 +17,7 @@
package com.android.systemui.statusbar.core
import android.platform.test.annotations.EnableFlags
-import android.view.Display
-import android.view.mockIWindowManager
+import android.view.Display.DEFAULT_DISPLAY
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -29,6 +28,8 @@ import com.android.systemui.statusbar.data.repository.fakePrivacyDotWindowContro
import com.android.systemui.testKosmos
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -37,11 +38,11 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+@OptIn(ExperimentalCoroutinesApi::class)
class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
@get:Rule val expect: Expect = Expect.create()
@@ -52,293 +53,116 @@ class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
private val fakeInitializerStore = kosmos.fakeStatusBarInitializerStore
private val fakePrivacyDotStore = kosmos.fakePrivacyDotWindowControllerStore
private val fakeLightBarStore = kosmos.fakeLightBarControllerStore
- private val windowManager = kosmos.mockIWindowManager
// Lazy, so that @EnableFlags is set before initializer is instantiated.
private val underTest by lazy { kosmos.multiDisplayStatusBarStarter }
@Before
- fun setup() {
- whenever(windowManager.shouldShowSystemDecors(Display.DEFAULT_DISPLAY)).thenReturn(true)
- whenever(windowManager.shouldShowSystemDecors(DISPLAY_1)).thenReturn(true)
- whenever(windowManager.shouldShowSystemDecors(DISPLAY_2)).thenReturn(true)
- whenever(windowManager.shouldShowSystemDecors(DISPLAY_3)).thenReturn(true)
- whenever(windowManager.shouldShowSystemDecors(DISPLAY_4_NO_SYSTEM_DECOR)).thenReturn(false)
+ fun setUp() = runBlocking {
+ fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.addDisplay(DISPLAY_2)
}
@Test
- fun start_startsInitializersForCurrentDisplays() =
+ fun start_triggerAddDisplaySystemDecoration_startsInitializersForDisplay() =
testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
-
underTest.start()
runCurrent()
- expect
- .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_1).startedByCoreStartable)
- .isTrue()
- expect
- .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_2).startedByCoreStartable)
- .isTrue()
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(
+ displayId = DEFAULT_DISPLAY
+ )
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(displayId = DISPLAY_2)
+
expect
.that(
fakeInitializerStore
- .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
+ .forDisplay(displayId = DEFAULT_DISPLAY)
.startedByCoreStartable
)
- .isFalse()
- }
-
- @Test
- fun start_startsOrchestratorForCurrentDisplays() =
- testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
-
- underTest.start()
- runCurrent()
-
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_1)!!)
- .start()
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_2)!!)
- .start()
- assertThat(
- fakeOrchestratorFactory.createdOrchestratorForDisplay(
- displayId = DISPLAY_4_NO_SYSTEM_DECOR
- )
- )
- .isNull()
- }
-
- @Test
- fun start_startsPrivacyDotForCurrentDisplays() =
- testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
-
- underTest.start()
- runCurrent()
-
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_1)).start()
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_2)).start()
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
- .start()
- }
-
- @Test
- fun start_doesNotStartLightBarControllerForCurrentDisplays() =
- testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
-
- underTest.start()
- runCurrent()
-
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_1), never()).start()
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_2), never()).start()
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
- .start()
- }
-
- @Test
- fun start_createsLightBarControllerForCurrentDisplays() =
- testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
-
- underTest.start()
- runCurrent()
-
- assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(1, DISPLAY_2)
- }
-
- @Test
- fun start_doesNotStartPrivacyDotForDefaultDisplay() =
- testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY)
-
- underTest.start()
- runCurrent()
-
- verify(fakePrivacyDotStore.forDisplay(displayId = Display.DEFAULT_DISPLAY), never())
- .start()
- }
-
- @Test
- fun displayAdded_orchestratorForNewDisplay() =
- testScope.runTest {
- underTest.start()
- runCurrent()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
-
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_3)!!)
- .start()
- assertThat(
- fakeOrchestratorFactory.createdOrchestratorForDisplay(
- displayId = DISPLAY_4_NO_SYSTEM_DECOR
- )
- )
- .isNull()
- }
-
- @Test
- fun displayAdded_initializerForNewDisplay() =
- testScope.runTest {
- underTest.start()
- runCurrent()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
-
- expect
- .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_3).startedByCoreStartable)
.isTrue()
expect
- .that(
- fakeInitializerStore
- .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- .startedByCoreStartable
- )
- .isFalse()
+ .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_2).startedByCoreStartable)
+ .isTrue()
}
@Test
- fun displayAdded_privacyDotForNewDisplay() =
+ fun start_triggerAddDisplaySystemDecoration_startsOrchestratorForDisplay() =
testScope.runTest {
underTest.start()
runCurrent()
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(
+ displayId = DEFAULT_DISPLAY
+ )
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(displayId = DISPLAY_2)
runCurrent()
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_3)).start()
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+ verify(
+ fakeOrchestratorFactory.createdOrchestratorForDisplay(
+ displayId = DEFAULT_DISPLAY
+ )!!
+ )
+ .start()
+ verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_2)!!)
.start()
}
@Test
- fun displayAdded_lightBarForNewDisplayCreate() =
+ fun start_triggerAddDisplaySystemDecoration_startsPrivacyDotForNonDefaultDisplay() =
testScope.runTest {
underTest.start()
runCurrent()
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(displayId = DISPLAY_2)
- assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(DISPLAY_3)
+ verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_2)).start()
}
@Test
- fun displayAdded_lightBarForNewDisplayStart() =
+ fun start_triggerAddDisplaySystemDecoration_doesNotStartPrivacyDotForDefaultDisplay() =
testScope.runTest {
underTest.start()
runCurrent()
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(
+ displayId = DEFAULT_DISPLAY
+ )
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_3), never()).start()
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
- .start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = DEFAULT_DISPLAY), never()).start()
}
@Test
- fun displayAddedDuringStart_initializerForNewDisplay() =
+ fun start_triggerAddDisplaySystemDecoration_doesNotStartLightBarControllerForDisplays() =
testScope.runTest {
underTest.start()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- expect
- .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_3).startedByCoreStartable)
- .isTrue()
- expect
- .that(
- fakeInitializerStore
- .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- .startedByCoreStartable
- )
- .isFalse()
- }
-
- @Test
- fun displayAddedDuringStart_orchestratorForNewDisplay() =
- testScope.runTest {
- underTest.start()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
-
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_3)!!)
- .start()
- assertThat(
- fakeOrchestratorFactory.createdOrchestratorForDisplay(
- displayId = DISPLAY_4_NO_SYSTEM_DECOR
- )
- )
- .isNull()
- }
-
- @Test
- fun displayAddedDuringStart_privacyDotForNewDisplay() =
- testScope.runTest {
- underTest.start()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(
+ displayId = DEFAULT_DISPLAY
+ )
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(displayId = DISPLAY_2)
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_3)).start()
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
- .start()
+ verify(fakeLightBarStore.forDisplay(displayId = DEFAULT_DISPLAY), never()).start()
+ verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_2), never()).start()
}
@Test
- fun displayAddedDuringStart_lightBarForNewDisplayCreate() =
+ fun start_triggerAddDisplaySystemDecoration_createsLightBarControllerForDisplay() =
testScope.runTest {
underTest.start()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(DISPLAY_3)
- }
-
- @Test
- fun displayAddedDuringStart_lightBarForNewDisplayStart() =
- testScope.runTest {
- underTest.start()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(
+ displayId = DEFAULT_DISPLAY
+ )
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(displayId = DISPLAY_2)
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_3), never()).start()
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
- .start()
+ assertThat(fakeLightBarStore.perDisplayMocks.keys)
+ .containsExactly(DEFAULT_DISPLAY, DISPLAY_2)
}
companion object {
- const val DISPLAY_1 = 1
const val DISPLAY_2 = 2
- const val DISPLAY_3 = 3
- const val DISPLAY_4_NO_SYSTEM_DECOR = 4
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
index 884c35c3457d..500332fa4a26 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
@@ -65,11 +65,11 @@ class LightBarControllerStoreImplTest : SysuiTestCase() {
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt
index f37648a639df..62ead9b45adc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt
@@ -62,11 +62,11 @@ class MultiDisplayDarkIconDispatcherStoreTest : SysuiTestCase() {
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt
index e0a1f273aa44..486a84598410 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt
@@ -65,11 +65,11 @@ class MultiDisplayStatusBarContentInsetsProviderStoreTest : SysuiTestCase() {
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
index 11fd902fc50c..2c474da082bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
@@ -59,11 +59,11 @@ class MultiDisplayStatusBarModeRepositoryStoreTest : SysuiTestCase() {
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
index a5b7fc283976..bc7d47c52531 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
@@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.testKosmos
import kotlinx.coroutines.runBlocking
@@ -36,7 +37,7 @@ import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
class PrivacyDotWindowControllerStoreImplTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
private val underTest by lazy { kosmos.privacyDotWindowControllerStoreImpl }
@@ -58,11 +59,11 @@ class PrivacyDotWindowControllerStoreImplTest : SysuiTestCase() {
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DISPLAY_2)!!
- kosmos.displayRepository.removeDisplay(DISPLAY_2)
+ kosmos.displayRepository.triggerRemoveSystemDecorationEvent(DISPLAY_2)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImplTest.kt
new file mode 100644
index 000000000000..41ae377a13f7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImplTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2025 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.data.repository
+
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class StatusBarPerDisplayStoreImplTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+
+ private val store = kosmos.fakeStatusBarPerDisplayStore
+
+ @Before
+ fun start() {
+ store.start()
+ }
+
+ @Before
+ fun addDisplays() = runBlocking {
+ fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY_ID)
+ fakeDisplayRepository.addDisplay(NON_DEFAULT_DISPLAY_ID)
+ }
+
+ @Test
+ fun removeSystemDecoration_onDisplayRemovalActionInvoked() =
+ testScope.runTest {
+ val instance = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(NON_DEFAULT_DISPLAY_ID)
+
+ assertThat(store.removalActions).containsExactly(instance)
+ }
+
+ @Test
+ fun removeSystemDecoration_twice_onDisplayRemovalActionInvokedOnce() =
+ testScope.runTest {
+ val instance = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(NON_DEFAULT_DISPLAY_ID)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(NON_DEFAULT_DISPLAY_ID)
+
+ assertThat(store.removalActions).containsExactly(instance)
+ }
+
+ @Test
+ fun forDisplay_withoutDisplayRemoval_onDisplayRemovalActionIsNotInvoked() =
+ testScope.runTest {
+ store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ assertThat(store.removalActions).isEmpty()
+ }
+
+ companion object {
+ private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
+ private const val NON_DEFAULT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
index 3cc592c94678..94394ede819e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
@@ -62,11 +62,11 @@ class SystemEventChipAnimationControllerStoreImplTest : SysuiTestCase() {
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt
index d16372611e88..b9d9a53fd319 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt
@@ -62,11 +62,11 @@ class MultiDisplayAutoHideControllerStoreTest : SysuiTestCase() {
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
index 769f012bfdf7..8722a484417d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
@@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.testKosmos
import kotlinx.coroutines.runBlocking
@@ -37,7 +38,7 @@ import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val fakeDisplayRepository = kosmos.displayRepository
private val testScope = kosmos.testScope
@@ -59,11 +60,11 @@ class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() {
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt
new file mode 100644
index 000000000000..097d50bb8f9d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.data.repository.PerDisplayRepository
+import com.android.systemui.model.StateChange
+import com.android.systemui.model.SysUiState
+import javax.inject.Inject
+
+/** Handles [SysUiState] changes between displays. */
+@SysUISingleton
+class SysUIStateDisplaysInteractor
+@Inject
+constructor(private val sysUIStateRepository: PerDisplayRepository<SysUiState>) {
+
+ /**
+ * Sets the flags on the given [targetDisplayId] based on the [stateChanges], while making sure
+ * that those flags are not set in any other display.
+ */
+ fun setFlagsExclusivelyToDisplay(targetDisplayId: Int, stateChanges: StateChange) {
+ sysUIStateRepository.forEachInstance { displayId, instance ->
+ if (displayId == targetDisplayId) {
+ stateChanges.applyTo(instance)
+ } else {
+ stateChanges.clearAllChangedFlagsIn(instance)
+ }
+ }
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt
new file mode 100644
index 000000000000..083191c8ecde
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2025 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.display.data.repository
+
+class FakePerDisplayRepository<T> : PerDisplayRepository<T> {
+
+ private val instances = mutableMapOf<Int, T>()
+
+ fun add(displayId: Int, instance: T) {
+ instances[displayId] = instance
+ }
+
+ fun remove(displayId: Int) {
+ instances.remove(displayId)
+ }
+
+ override fun get(displayId: Int): T? {
+ return instances[displayId]
+ }
+
+ override val displayIds: Set<Int>
+ get() = instances.keys
+
+ override val debugName: String
+ get() = "FakePerDisplayRepository"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
index 63c46bb30a07..04f245e91914 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
@@ -86,8 +86,23 @@ interface PerDisplayRepository<T> {
/** Gets the cached instance or create a new one for a given display. */
operator fun get(displayId: Int): T?
+ /** List of display ids for which this repository has an instance. */
+ val displayIds: Set<Int>
+
/** Debug name for this repository, mainly for tracing and logging. */
val debugName: String
+
+ /**
+ * Invokes the specified action on each instance held by this repository.
+ *
+ * The action will receive the displayId and the instance associated with that display.
+ * If there is no instance for the display, the action is not called.
+ */
+ fun forEachInstance(action: (Int, T) -> Unit) {
+ displayIds.forEach { displayId ->
+ get(displayId)?.let { instance -> action(displayId, instance) }
+ }
+ }
}
/**
@@ -119,6 +134,9 @@ constructor(
backgroundApplicationScope.launch("$debugName#start") { start() }
}
+ override val displayIds: Set<Int>
+ get() = perDisplayInstances.keys
+
private suspend fun start() {
dumpManager.registerDumpable(this)
displayRepository.displayIds.collectLatest { displayIds ->
@@ -186,6 +204,7 @@ class DefaultDisplayOnlyInstanceRepositoryImpl<T>(
private val lazyDefaultDisplayInstance by lazy {
instanceProvider.createInstance(Display.DEFAULT_DISPLAY)
}
+ override val displayIds: Set<Int> = setOf(Display.DEFAULT_DISPLAY)
override fun get(displayId: Int): T? = lazyDefaultDisplayInstance
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt
index 81aca27b9c28..46048868f503 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt
@@ -50,7 +50,7 @@ abstract class PerDisplayStoreImpl<T>(
private val displayRepository: DisplayRepository,
) : PerDisplayStore<T>, CoreStartable {
- private val perDisplayInstances = ConcurrentHashMap<Int, T>()
+ protected val perDisplayInstances = ConcurrentHashMap<Int, T>()
/**
* The instance for the default/main display of the device. For example, on a phone or a tablet,
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt b/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt
new file mode 100644
index 000000000000..f29b15730986
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2025 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.model
+
+/**
+ * Represents a set of state changes. A bit can either be set to `true` or `false`.
+ *
+ * This is used in [SysUIStateDisplaysInteractor] to selectively change bits.
+ */
+class StateChange {
+ private var flagsToSet: Long = 0
+ private var flagsToClear: Long = 0
+
+ /**
+ * Sets the [state] of the given [bit].
+ *
+ * @return `this` for chaining purposes
+ */
+ fun setFlag(bit: Long, state: Boolean): StateChange {
+ if (state) {
+ flagsToSet = flagsToSet or bit
+ flagsToClear = flagsToClear and bit.inv()
+ } else {
+ flagsToClear = flagsToClear or bit
+ flagsToSet = flagsToSet and bit.inv()
+ }
+ return this
+ }
+
+ fun hasChanges() = flagsToSet != 0L || flagsToClear != 0L
+
+ /** Applies all changed flags to [sysUiState]. */
+ fun applyTo(sysUiState: SysUiState) {
+ iterateBits(flagsToSet or flagsToClear) { bit ->
+ val isBitSetInNewState = flagsToSet and bit != 0L
+ sysUiState.setFlag(bit, isBitSetInNewState)
+ }
+ sysUiState.commitUpdate()
+ }
+
+ fun applyTo(sysUiState: Long): Long {
+ var newState = sysUiState
+ newState = newState or flagsToSet
+ newState = newState and flagsToClear.inv()
+ return newState
+ }
+
+ private inline fun iterateBits(flags: Long, action: (bit: Long) -> Unit) {
+ var remaining = flags
+ while (remaining != 0L) {
+ val lowestBit = remaining and -remaining
+ action(lowestBit)
+
+ remaining -= lowestBit
+ }
+ }
+
+ /** Clears all the flags changed in a [sysUiState] */
+ fun clearAllChangedFlagsIn(sysUiState: SysUiState) {
+ iterateBits(flagsToSet or flagsToClear) { bit -> sysUiState.setFlag(bit, false) }
+ sysUiState.commitUpdate()
+ }
+
+ fun clear() {
+ flagsToSet = 0
+ flagsToClear = 0
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUIStateDispatcher.kt b/packages/SystemUI/src/com/android/systemui/model/SysUIStateDispatcher.kt
new file mode 100644
index 000000000000..f95ae2501acb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUIStateDispatcher.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2025 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.model
+
+import android.view.Display
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import java.util.concurrent.CopyOnWriteArrayList
+import javax.inject.Inject
+
+/**
+ * Channels changes from several [SysUiState]s to a single callback.
+ *
+ * There are several [SysUiState]s (one per display). This class allows for listeners to listen to
+ * sysui state updates from any of those [SysUiState] instances.
+ *
+ * ┌────────────────────┐
+ * │ SysUIStateOverride │
+ * │ displayId=2 │
+ * └┬───────────────────┘
+ * │ ▲
+ * ┌───────────────┐ │ │ ┌────────────────────┐
+ * │ SysUIState │ │ │ │ SysUIStateOverride │
+ * │ displayId=0 │ │ │ │ displayId=1 │
+ * └────────────┬──┘ │ │ └┬───────────────────┘
+ * │ │ │ │ ▲
+ * ▼ ▼ │ ▼ │
+ * ┌─────────────┴─────┴─┐
+ * │SysUiStateDispatcher │
+ * └────────┬────────────┘
+ * │
+ * ▼
+ * ┌──────────────────┐
+ * │ listeners for │
+ * │ all displays │
+ * └──────────────────┘
+ */
+@SysUISingleton
+class SysUIStateDispatcher @Inject constructor() {
+
+ private val listeners = CopyOnWriteArrayList<SysUiState.SysUiStateCallback>()
+
+ /** Called from each [SysUiState] to propagate new state changes. */
+ fun dispatchSysUIStateChange(sysUiFlags: Long, displayId: Int) {
+ if (displayId != Display.DEFAULT_DISPLAY && !ShadeWindowGoesAround.isEnabled) return;
+ listeners.forEach { listener ->
+ listener.onSystemUiStateChanged(sysUiFlags = sysUiFlags, displayId = displayId)
+ }
+ }
+
+ /**
+ * Registers a listener to listen for system UI state changes.
+ *
+ * Listeners will have [SysUiState.SysUiStateCallback.onSystemUiStateChanged] called whenever a
+ * system UI state changes.
+ */
+ fun registerListener(listener: SysUiState.SysUiStateCallback) {
+ listeners += listener
+ }
+
+ fun unregisterListener(listener: SysUiState.SysUiStateCallback) {
+ listeners -= listener
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
index 9e9a1df76b54..53105b2c0f6a 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
@@ -16,7 +16,6 @@
package com.android.systemui.model
import android.util.Log
-import android.view.Display
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.display.data.repository.PerDisplayInstanceProviderWithTeardown
@@ -44,7 +43,9 @@ interface SysUiState : Dumpable {
fun removeCallback(callback: SysUiStateCallback)
/** Returns whether a flag is enabled in this state. */
- fun isFlagEnabled(@SystemUiStateFlags flag: Long): Boolean
+ fun isFlagEnabled(@SystemUiStateFlags flag: Long): Boolean {
+ return (flags and flag) != 0L
+ }
/** Returns the current sysui state flags. */
val flags: Long
@@ -59,19 +60,11 @@ interface SysUiState : Dumpable {
/** Call to save all the flags updated from [setFlag]. */
fun commitUpdate()
- /** Notify all those who are registered that the state has changed. */
- fun notifyAndSetSystemUiStateChanged(newFlags: Long, oldFlags: Long)
-
/** Callback to be notified whenever system UI state flags are changed. */
- interface SysUiStateCallback {
- /** To be called when any SysUiStateFlag gets updated **for the default display** */
- fun onSystemUiStateChanged(@SystemUiStateFlags sysUiFlags: Long)
+ fun interface SysUiStateCallback {
/** To be called when any SysUiStateFlag gets updated for a specific [displayId]. */
- fun onSystemUiStateChangedForDisplay(
- @SystemUiStateFlags sysUiFlags: Long,
- displayId: Int,
- ) {}
+ fun onSystemUiStateChanged(@SystemUiStateFlags sysUiFlags: Long, displayId: Int)
}
/**
@@ -86,12 +79,15 @@ interface SysUiState : Dumpable {
}
}
+private const val TAG = "SysUIState"
+
class SysUiStateImpl
@AssistedInject
constructor(
@Assisted private val displayId: Int,
private val sceneContainerPlugin: SceneContainerPlugin?,
private val dumpManager: DumpManager,
+ private val stateDispatcher: SysUIStateDispatcher,
) : SysUiState {
private val debugName = "SysUiStateImpl-ForDisplay=$displayId"
@@ -107,45 +103,30 @@ constructor(
get() = _flags
private var _flags: Long = 0
- private val callbacks: MutableList<SysUiStateCallback> = ArrayList()
private var flagsToSet: Long = 0
private var flagsToClear: Long = 0
/**
* Add listener to be notified of changes made to SysUI state. The callback will also be called
* as part of this function.
+ *
+ * Note that the listener would receive updates for all displays.
*/
override fun addCallback(callback: SysUiStateCallback) {
- callbacks.add(callback)
- callback.onSystemUiStateChanged(flags)
+ stateDispatcher.registerListener(callback)
+ callback.onSystemUiStateChanged(flags, displayId)
}
/** Callback will no longer receive events on state change */
override fun removeCallback(callback: SysUiStateCallback) {
- callbacks.remove(callback)
- }
-
- override fun isFlagEnabled(@SystemUiStateFlags flag: Long): Boolean {
- return (flags and flag) != 0L
+ stateDispatcher.unregisterListener(callback)
}
/** Methods to this call can be chained together before calling [.commitUpdate]. */
override fun setFlag(@SystemUiStateFlags flag: Long, enabled: Boolean): SysUiState {
- var enabled = enabled
- val overrideOrNull =
- sceneContainerPlugin?.flagValueOverride(flag = flag, displayId = displayId)
- if (overrideOrNull != null && enabled != overrideOrNull) {
- if (SysUiState.DEBUG) {
- Log.d(
- TAG,
- "setFlag for flag $flag and value $enabled overridden to $overrideOrNull by scene container plugin",
- )
- }
-
- enabled = overrideOrNull
- }
+ val toSet = flagWithOptionalOverrides(flag, enabled, displayId, sceneContainerPlugin)
- if (enabled) {
+ if (toSet) {
flagsToSet = flagsToSet or flag
} else {
flagsToClear = flagsToClear or flag
@@ -176,22 +157,13 @@ constructor(
}
/** Notify all those who are registered that the state has changed. */
- override fun notifyAndSetSystemUiStateChanged(newFlags: Long, oldFlags: Long) {
+ private fun notifyAndSetSystemUiStateChanged(newFlags: Long, oldFlags: Long) {
if (SysUiState.DEBUG) {
Log.d(TAG, "SysUiState changed: old=$oldFlags new=$newFlags")
}
if (newFlags != oldFlags) {
- callbacks.forEach { callback: SysUiStateCallback ->
- if (displayId == Display.DEFAULT_DISPLAY) {
- callback.onSystemUiStateChanged(newFlags)
- }
- callback.onSystemUiStateChangedForDisplay(
- sysUiFlags = newFlags,
- displayId = displayId,
- )
- }
-
_flags = newFlags
+ stateDispatcher.dispatchSysUIStateChange(newFlags, displayId)
}
}
@@ -222,6 +194,29 @@ constructor(
}
}
+/** Returns the flag value taking into account [SceneContainerPlugin] potential overrides. */
+fun flagWithOptionalOverrides(
+ flag: Long,
+ enabled: Boolean,
+ displayId: Int,
+ sceneContainerPlugin: SceneContainerPlugin?,
+): Boolean {
+ var toSet = enabled
+ val overrideOrNull = sceneContainerPlugin?.flagValueOverride(flag = flag, displayId = displayId)
+ if (overrideOrNull != null && toSet != overrideOrNull) {
+ if (SysUiState.DEBUG) {
+ Log.d(
+ TAG,
+ "setFlag for flag $flag and value $toSet overridden to " +
+ "$overrideOrNull by scene container plugin",
+ )
+ }
+
+ toSet = overrideOrNull
+ }
+ return toSet
+}
+
/** Creates and destroy instances of [SysUiState] */
@SysUISingleton
class SysUIStateInstanceProvider @Inject constructor(private val factory: SysUiStateImpl.Factory) :
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index f44c2c01951c..7af538105cae 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -355,11 +355,12 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private final SysUiState.SysUiStateCallback mSysUiStateCallback =
new SysUiState.SysUiStateCallback() {
- @Override
- public void onSystemUiStateChanged(@SystemUiStateFlags long sysUiFlags) {
- mSysUiFlags = sysUiFlags;
- }
- };
+ @Override
+ public void onSystemUiStateChanged(@SystemUiStateFlags long sysUiFlags,
+ int displayId) {
+ mSysUiFlags = sysUiFlags;
+ }
+ };
private final Consumer<Boolean> mOnIsInPipStateChangedListener =
(isInPip) -> mIsInPip = isInPip;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
index 8253a071dea2..7a6426c741a6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
@@ -656,11 +656,7 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis
private final SysUiStateCallback mSysUiStateCallback =
new SysUiStateCallback() {
@Override
- public void onSystemUiStateChanged(long sysUiFlags) {
- }
-
- @Override
- public void onSystemUiStateChangedForDisplay(long sysUiFlags, int displayId) {
+ public void onSystemUiStateChanged(long sysUiFlags, int displayId) {
notifySystemUiStateFlags(sysUiFlags, displayId);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 446935bf6594..17fb50aa6890 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -65,6 +65,7 @@ import android.os.Trace;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.MathUtils;
+import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -96,6 +97,7 @@ import com.android.systemui.Gefingerpoken;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.common.domain.interactor.SysUIStateDisplaysInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
@@ -119,6 +121,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
+import com.android.systemui.model.StateChange;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -450,7 +453,9 @@ public final class NotificationPanelViewController implements
private final MediaDataManager mMediaDataManager;
@PanelState
private int mCurrentPanelState = STATE_CLOSED;
+ @Deprecated // Use SysUIStateInteractor instead
private final SysUiState mSysUiState;
+ private final SysUIStateDisplaysInteractor mSysUIStateDisplaysInteractor;
private final NotificationShadeDepthController mDepthController;
private final NavigationBarController mNavigationBarController;
private final int mDisplayId;
@@ -607,6 +612,7 @@ public final class NotificationPanelViewController implements
ShadeRepository shadeRepository,
Optional<SysUIUnfoldComponent> unfoldComponent,
SysUiState sysUiState,
+ SysUIStateDisplaysInteractor sysUIStateDisplaysInteractor,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
KeyguardIndicationController keyguardIndicationController,
NotificationListContainer notificationListContainer,
@@ -738,6 +744,7 @@ public final class NotificationPanelViewController implements
mMediaDataManager = mediaDataManager;
mTapAgainViewController = tapAgainViewController;
mSysUiState = sysUiState;
+ mSysUIStateDisplaysInteractor = sysUIStateDisplaysInteractor;
mKeyguardBypassController = bypassController;
mUpdateMonitor = keyguardUpdateMonitor;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
@@ -2701,13 +2708,41 @@ public final class NotificationPanelViewController implements
Log.d(TAG, "Updating panel sysui state flags: fullyExpanded="
+ isFullyExpanded() + " inQs=" + mQsController.getExpanded());
}
+ if (ShadeWindowGoesAround.isEnabled()) {
+ setPerDisplaySysUIStateFlags();
+ } else {
+ setDefaultDisplayFlags();
+ }
+ }
+
+ private int getShadeDisplayId() {
+ if (mView != null && mView.getDisplay() != null) return mView.getDisplay().getDisplayId();
+ return Display.DEFAULT_DISPLAY;
+ }
+
+ private void setPerDisplaySysUIStateFlags() {
+ mSysUIStateDisplaysInteractor.setFlagsExclusivelyToDisplay(
+ getShadeDisplayId(),
+ new StateChange()
+ .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
+ isPanelExpanded() && !isCollapsing())
+ .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
+ isFullyExpanded() && !mQsController.getExpanded())
+ .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
+ isFullyExpanded() && mQsController.getExpanded())
+ );
+ }
+
+ @Deprecated
+ private void setDefaultDisplayFlags() {
mSysUiState
.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
isPanelExpanded() && !isCollapsing())
.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
isFullyExpanded() && !mQsController.getExpanded())
.setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
- isFullyExpanded() && mQsController.getExpanded()).commitUpdate(mDisplayId);
+ isFullyExpanded() && mQsController.getExpanded()).commitUpdate(
+ mDisplayId);
}
private void debugLog(String fmt, Object... args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStore.kt
index 90c6a8a9f51c..7964950a2917 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStore.kt
@@ -16,25 +16,19 @@
package com.android.systemui.statusbar.core
-import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayScopeRepository
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import com.android.systemui.statusbar.data.repository.StatusBarPerDisplayStoreImpl
import com.android.systemui.statusbar.phone.AutoHideControllerStore
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
-import dagger.Lazy
-import dagger.Module
-import dagger.Provides
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-/** [PerDisplayStoreImpl] for providing display specific [StatusBarOrchestrator]. */
+/** [StatusBarPerDisplayStoreImpl] for providing display specific [StatusBarOrchestrator]. */
@SysUISingleton
class MultiDisplayStatusBarOrchestratorStore
@Inject
@@ -48,7 +42,11 @@ constructor(
private val autoHideControllerStore: AutoHideControllerStore,
private val displayScopeRepository: DisplayScopeRepository,
private val statusBarWindowStateRepositoryStore: StatusBarWindowStateRepositoryStore,
-) : PerDisplayStoreImpl<StatusBarOrchestrator>(backgroundApplicationScope, displayRepository) {
+) :
+ StatusBarPerDisplayStoreImpl<StatusBarOrchestrator>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
StatusBarConnectedDisplays.unsafeAssertInNewMode()
@@ -79,21 +77,3 @@ constructor(
instance.stop()
}
}
-
-@Module
-interface MultiDisplayStatusBarOrchestratorStoreModule {
-
- @Provides
- @SysUISingleton
- @IntoMap
- @ClassKey(MultiDisplayStatusBarOrchestratorStore::class)
- fun storeAsCoreStartable(
- multiDisplayLazy: Lazy<MultiDisplayStatusBarOrchestratorStore>
- ): CoreStartable {
- return if (StatusBarConnectedDisplays.isEnabled) {
- multiDisplayLazy.get()
- } else {
- CoreStartable.NOP
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
index 4d92814ef82d..30c4ca5269d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.core
import android.view.Display
-import android.view.IWindowManager
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
@@ -44,7 +43,6 @@ constructor(
private val statusBarInitializerStore: StatusBarInitializerStore,
private val privacyDotWindowControllerStore: PrivacyDotWindowControllerStore,
private val lightBarControllerStore: LightBarControllerStore,
- private val windowManager: IWindowManager,
) : CoreStartable {
init {
@@ -53,19 +51,13 @@ constructor(
override fun start() {
applicationScope.launch {
- displayRepository.displays
+ displayRepository.displayIdsWithSystemDecorations
.pairwiseBy { previousDisplays, currentDisplays ->
currentDisplays - previousDisplays
}
- .onStart { emit(displayRepository.displays.value) }
+ .onStart { emit(displayRepository.displayIdsWithSystemDecorations.value) }
.collect { newDisplays ->
- newDisplays.forEach {
- // TODO(b/393191204): Split navbar, status bar, etc. functionality
- // from WindowManager#shouldShowSystemDecors.
- if (windowManager.shouldShowSystemDecors(it.displayId)) {
- createAndStartComponentsForDisplay(it.displayId)
- }
- }
+ newDisplays.forEach { createAndStartComponentsForDisplay(it) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
index 69bbb28d94bc..48955cc4ab21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
@@ -20,11 +20,11 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerStore
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import com.android.systemui.statusbar.data.repository.StatusBarPerDisplayStoreImpl
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -45,7 +45,10 @@ constructor(
private val darkIconDispatcherStore: DarkIconDispatcherStore,
) :
StatusBarInitializerStore,
- PerDisplayStoreImpl<StatusBarInitializer>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<StatusBarInitializer>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
StatusBarConnectedDisplays.unsafeAssertInNewMode()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt
index 2eeeea8ad93c..f353c0198b1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt
@@ -24,7 +24,6 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
@@ -59,7 +58,10 @@ constructor(
private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
) :
SysuiDarkIconDispatcherStore,
- PerDisplayStoreImpl<SysuiDarkIconDispatcher>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<SysuiDarkIconDispatcher>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
StatusBarConnectedDisplays.unsafeAssertInNewMode()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
index c629d10b90b0..3c0d6c3b8df3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
@@ -22,7 +22,6 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayScopeRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.LightBarControllerImpl
import dagger.Binds
@@ -47,7 +46,10 @@ constructor(
private val darkIconDispatcherStore: DarkIconDispatcherStore,
) :
LightBarControllerStore,
- PerDisplayStoreImpl<LightBarController>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<LightBarController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
override fun createInstanceForDisplay(displayId: Int): LightBarController? {
val darkIconDispatcher = darkIconDispatcherStore.forDisplay(displayId) ?: return null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt
index d48c94bd0893..32dc8407ac90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt
@@ -22,7 +22,6 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayScopeRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.events.PrivacyDotViewController
@@ -50,7 +49,10 @@ constructor(
private val contentInsetsProviderStore: StatusBarContentInsetsProviderStore,
) :
PrivacyDotViewControllerStore,
- PerDisplayStoreImpl<PrivacyDotViewController>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<PrivacyDotViewController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
override fun createInstanceForDisplay(displayId: Int): PrivacyDotViewController? {
val configurationController =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
index 60579318e667..7fc5e8abe904 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
@@ -25,7 +25,6 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.events.PrivacyDotWindowController
import dagger.Binds
@@ -52,7 +51,10 @@ constructor(
private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory,
) :
PrivacyDotWindowControllerStore,
- PerDisplayStoreImpl<PrivacyDotWindowController>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<PrivacyDotWindowController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
StatusBarConnectedDisplays.unsafeAssertInNewMode()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt
index 751ddf4520eb..36b11ac60827 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt
@@ -24,7 +24,6 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
@@ -53,7 +52,7 @@ constructor(
private val configurationControllerFactory: ConfigurationControllerImpl.Factory,
) :
StatusBarConfigurationControllerStore,
- PerDisplayStoreImpl<StatusBarConfigurationController>(
+ StatusBarPerDisplayStoreImpl<StatusBarConfigurationController>(
backgroundApplicationScope,
displayRepository,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt
index 5ea12110b00c..3cd4b5c885de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt
@@ -25,7 +25,6 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider
@@ -54,7 +53,7 @@ constructor(
private val cameraProtectionLoaderFactory: CameraProtectionLoaderImpl.Factory,
) :
StatusBarContentInsetsProviderStore,
- PerDisplayStoreImpl<StatusBarContentInsetsProvider>(
+ StatusBarPerDisplayStoreImpl<StatusBarContentInsetsProvider>(
backgroundApplicationScope,
displayRepository,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
index e82711d12399..5980c9c57214 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
@@ -22,7 +22,6 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.core.StatusBarInitializer
import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
@@ -48,7 +47,7 @@ constructor(
displayRepository: DisplayRepository,
) :
StatusBarModeRepositoryStore,
- PerDisplayStoreImpl<StatusBarModePerDisplayRepository>(
+ StatusBarPerDisplayStoreImpl<StatusBarModePerDisplayRepository>(
backgroundApplicationScope,
displayRepository,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImpl.kt
new file mode 100644
index 000000000000..e873c02fd4d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImpl.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2025 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.data.repository
+
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.util.kotlin.pairwiseBy
+import kotlinx.coroutines.CoroutineScope
+
+/** [PerDisplayStoreImpl] for Status Bar related classes. */
+abstract class StatusBarPerDisplayStoreImpl<T>(
+ @Background private val backgroundApplicationScope: CoroutineScope,
+ private val displayRepository: DisplayRepository,
+) : PerDisplayStoreImpl<T>(backgroundApplicationScope, displayRepository) {
+
+ override fun start() {
+ val instanceType = instanceClass.simpleName
+ backgroundApplicationScope.launch("StatusBarPerDisplayStore#<$instanceType>start") {
+ displayRepository.displayIdsWithSystemDecorations
+ .pairwiseBy { previousDisplays, currentDisplays ->
+ previousDisplays - currentDisplays
+ }
+ .collect { removedDisplayIds ->
+ removedDisplayIds.forEach { removedDisplayId ->
+ val removedInstance = perDisplayInstances.remove(removedDisplayId)
+ removedInstance?.let { onDisplayRemovalAction(it) }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
index 439925a6d4b8..7b9ea697a9ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
@@ -23,7 +23,6 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.events.SystemEventChipAnimationController
import com.android.systemui.statusbar.events.SystemEventChipAnimationControllerImpl
@@ -53,7 +52,7 @@ constructor(
private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
) :
SystemEventChipAnimationControllerStore,
- PerDisplayStoreImpl<SystemEventChipAnimationController>(
+ StatusBarPerDisplayStoreImpl<SystemEventChipAnimationController>(
backgroundApplicationScope,
displayRepository,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt
index d2dccc49ffd7..d7e9bb2ad64a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt
@@ -21,10 +21,10 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
+import com.android.systemui.statusbar.data.repository.StatusBarPerDisplayStoreImpl
import dagger.Lazy
import dagger.Module
import dagger.Provides
@@ -45,7 +45,7 @@ constructor(
private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
) :
StatusBarContentInsetsViewModelStore,
- PerDisplayStoreImpl<StatusBarContentInsetsViewModel>(
+ StatusBarPerDisplayStoreImpl<StatusBarContentInsetsViewModel>(
backgroundApplicationScope,
displayRepository,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 4e916804318d..fcb63df1a528 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -409,12 +409,12 @@ constructor(
if (counter != null) {
if (NotificationBundleUi.isEnabled) {
- val entry = (currentNotification as? ExpandableNotificationRow)?.entry
- counter.incrementForBucket(entry?.bucket)
- } else {
val entryAdapter =
(currentNotification as? ExpandableNotificationRow)?.entryAdapter
counter.incrementForBucket(entryAdapter?.sectionBucket)
+ } else {
+ val entry = (currentNotification as? ExpandableNotificationRow)?.entry
+ counter.incrementForBucket(entry?.bucket)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt
index c7bbe2cbc846..616bab60e02f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt
@@ -22,9 +22,9 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.data.repository.StatusBarPerDisplayStoreImpl
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -41,7 +41,10 @@ constructor(
private val autoHideControllerFactory: AutoHideControllerImpl.Factory,
) :
AutoHideControllerStore,
- PerDisplayStoreImpl<AutoHideController>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<AutoHideController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
StatusBarConnectedDisplays.unsafeAssertInNewMode()
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 323b7d8eaaeb..ba5570026c1c 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
@@ -21,6 +21,7 @@ import com.android.systemui.dagger.qualifiers.Default
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.core.CommandQueueInitializer
import com.android.systemui.statusbar.core.MultiDisplayStatusBarInitializerStore
+import com.android.systemui.statusbar.core.MultiDisplayStatusBarOrchestratorStore
import com.android.systemui.statusbar.core.MultiDisplayStatusBarStarter
import com.android.systemui.statusbar.core.SingleDisplayStatusBarInitializerStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
@@ -197,5 +198,19 @@ interface StatusBarPhoneModule {
CoreStartable.NOP
}
}
+
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(MultiDisplayStatusBarOrchestratorStore::class)
+ fun orchestratorStoreAsCoreStartable(
+ multiDisplayLazy: Lazy<MultiDisplayStatusBarOrchestratorStore>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
index f33367245ee6..39afc38dad11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
@@ -24,11 +24,11 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerStore
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
+import com.android.systemui.statusbar.data.repository.StatusBarPerDisplayStoreImpl
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -48,7 +48,10 @@ constructor(
displayRepository: DisplayRepository,
) :
StatusBarWindowControllerStore,
- PerDisplayStoreImpl<StatusBarWindowController>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<StatusBarWindowController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
StatusBarConnectedDisplays.unsafeAssertInNewMode()
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index feaf1a630a53..8e713495902a 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -302,7 +302,7 @@ public final class WMShell implements
.commitUpdate(mDisplayTracker.getDefaultDisplayId());
}
});
- mSysUiState.addCallback(sysUiStateFlag -> {
+ mSysUiState.addCallback((sysUiStateFlag, displayId) -> {
mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
});
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index a6730ed5c9be..341bd3a38999 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -465,7 +465,7 @@ public class BubblesTest extends SysuiTestCase {
when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
mSysUiState = mKosmos.getSysuiState();
- mSysUiState.addCallback(sysUiFlags -> {
+ mSysUiState.addCallback((sysUiFlags, displayId) -> {
mSysUiStateBubblesManageMenuExpanded =
(sysUiFlags
& QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 70b22d7f829d..663a85330f70 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -53,6 +53,7 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
MutableSharedFlow<DisplayRepository.PendingDisplay?>(replay = 1)
private val displayAdditionEventFlow = MutableSharedFlow<Display?>(replay = 0)
private val displayRemovalEventFlow = MutableSharedFlow<Int>(replay = 0)
+ private val displayIdsWithSystemDecorationsFlow = MutableStateFlow<Set<Int>>(emptySet())
suspend fun addDisplay(displayId: Int, type: Int = Display.TYPE_EXTERNAL) {
addDisplay(display(type, id = displayId))
@@ -74,6 +75,7 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
flow.value += display
displayIdFlow.value += display.displayId
displayAdditionEventFlow.emit(display)
+ displayIdsWithSystemDecorationsFlow.value += display.displayId
}
suspend fun removeDisplay(displayId: Int) {
@@ -82,6 +84,16 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
displayRemovalEventFlow.emit(displayId)
}
+ suspend fun triggerAddDisplaySystemDecorationEvent(displayId: Int) {
+ displayIdsWithSystemDecorationsFlow.value += displayId
+ displayIdsWithSystemDecorationsFlow.emit(displayIdsWithSystemDecorationsFlow.value)
+ }
+
+ suspend fun triggerRemoveSystemDecorationEvent(displayId: Int) {
+ displayIdsWithSystemDecorationsFlow.value -= displayId
+ displayIdsWithSystemDecorationsFlow.emit(displayIdsWithSystemDecorationsFlow.value)
+ }
+
/** Emits [value] as [displayAdditionEvent] flow value. */
suspend fun emit(value: Display?) = displayAdditionEventFlow.emit(value)
@@ -112,7 +124,8 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
private val _displayChangeEvent = MutableSharedFlow<Int>(replay = 1)
override val displayChangeEvent: Flow<Int> = _displayChangeEvent
- override val displayIdsWithSystemDecorations: StateFlow<Set<Int>> = MutableStateFlow(emptySet())
+ override val displayIdsWithSystemDecorations: StateFlow<Set<Int>> =
+ displayIdsWithSystemDecorationsFlow
suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 8543aaadbdfc..af89403c5397 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -53,6 +53,7 @@ import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.model.sceneContainerPlugin
+import com.android.systemui.model.sysUIStateDispatcher
import com.android.systemui.model.sysUiState
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.power.data.repository.fakePowerRepository
@@ -202,4 +203,5 @@ class KosmosJavaAdapter() {
val windowRootViewBlurInteractor by lazy { kosmos.windowRootViewBlurInteractor }
val sysuiState by lazy { kosmos.sysUiState }
val displayTracker by lazy { kosmos.displayTracker }
+ val sysUIStateDispatcher by lazy { kosmos.sysUIStateDispatcher }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt
index 6272aafaa688..00deaafd7009 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt
@@ -17,11 +17,33 @@
package com.android.systemui.model
import android.view.Display
+import com.android.systemui.common.domain.interactor.SysUIStateDisplaysInteractor
+import com.android.systemui.display.data.repository.FakePerDisplayRepository
import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import org.mockito.Mockito.spy
-val Kosmos.sysUiState by Fixture {
- spy(SysUiStateImpl(Display.DEFAULT_DISPLAY, sceneContainerPlugin, dumpManager))
+val Kosmos.sysUiState by Fixture { sysUiStateFactory.create(Display.DEFAULT_DISPLAY) }
+val Kosmos.sysUIStateDispatcher by Fixture { SysUIStateDispatcher() }
+
+val Kosmos.sysUiStateFactory by Fixture {
+ object : SysUiStateImpl.Factory {
+ override fun create(displayId: Int): SysUiStateImpl {
+ return spy(
+ SysUiStateImpl(
+ displayId,
+ sceneContainerPlugin,
+ dumpManager,
+ sysUIStateDispatcher,
+ )
+ )
+ }
+ }
+}
+
+val Kosmos.fakeSysUIStatePerDisplayRepository by Fixture { FakePerDisplayRepository<SysUiState>() }
+
+val Kosmos.sysuiStateInteractor by Fixture {
+ SysUIStateDisplaysInteractor(fakeSysUIStatePerDisplayRepository)
}
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
index 9776fd91134d..66a17514c106 100644
--- 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
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.core
import android.content.testableContext
-import android.view.mockIWindowManager
import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.display.data.repository.displayScopeRepository
@@ -94,6 +93,5 @@ val Kosmos.multiDisplayStatusBarStarter by
statusBarInitializerStore,
privacyDotWindowControllerStore,
lightBarControllerStore,
- mockIWindowManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreKosmos.kt
new file mode 100644
index 000000000000..0db8ee8a9109
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreKosmos.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2025 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.data.repository
+
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import kotlinx.coroutines.CoroutineScope
+
+class FakeStatusBarPerDisplayStore(
+ backgroundApplicationScope: CoroutineScope,
+ displayRepository: DisplayRepository,
+) :
+ StatusBarPerDisplayStoreImpl<TestPerDisplayInstance>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
+
+ val removalActions = mutableListOf<TestPerDisplayInstance>()
+
+ override fun createInstanceForDisplay(displayId: Int): TestPerDisplayInstance {
+ return TestPerDisplayInstance(displayId)
+ }
+
+ override val instanceClass = TestPerDisplayInstance::class.java
+
+ override suspend fun onDisplayRemovalAction(instance: TestPerDisplayInstance) {
+ removalActions += instance
+ }
+}
+
+data class TestPerDisplayInstance(val displayId: Int)
+
+val Kosmos.fakeStatusBarPerDisplayStore by
+ Kosmos.Fixture {
+ FakeStatusBarPerDisplayStore(
+ backgroundApplicationScope = applicationCoroutineScope,
+ displayRepository = displayRepository,
+ )
+ }
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
index 621a128a736e..94e8ca5464b0 100644
--- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
@@ -51,7 +51,9 @@ import java.util.NoSuchElementException;
final class VendorVibrationSession extends IVibrationSession.Stub
implements VibrationSession, CancellationSignal.OnCancelListener, IBinder.DeathRecipient {
private static final String TAG = "VendorVibrationSession";
- private static final boolean DEBUG = false;
+ // To enable these logs, run:
+ // 'adb shell setprop persist.log.tag.VendorVibrationSession DEBUG && adb reboot'
+ private static final boolean DEBUG = VibratorDebugUtils.isDebuggable(TAG);
/** Calls into VibratorManager functionality needed for playing an {@link ExternalVibration}. */
interface VibratorManagerHooks {
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index cb9988fd698e..ab30cdc730eb 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -36,7 +36,9 @@ import java.util.Objects;
/** Plays a {@link HalVibration} in dedicated thread. */
final class VibrationThread extends Thread {
static final String TAG = "VibrationThread";
- static final boolean DEBUG = false;
+ // To enable these logs, run:
+ // 'adb shell setprop persist.log.tag.VibrationThread DEBUG && adb reboot'
+ static final boolean DEBUG = VibratorDebugUtils.isDebuggable(TAG);
/** Calls into VibratorManager functionality needed for playing a {@link HalVibration}. */
interface VibratorManagerHooks {
diff --git a/services/core/java/com/android/server/vibrator/VibratorDebugUtils.java b/services/core/java/com/android/server/vibrator/VibratorDebugUtils.java
new file mode 100644
index 000000000000..9f37e76f0d6d
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibratorDebugUtils.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.util.Log;
+
+class VibratorDebugUtils {
+
+ /**
+ * Checks if debugging is enabled for the specified tag or globally.
+ *
+ * <p>To enable debugging:<br>
+ * {@code adb shell setprop persist.log.tag.Vibrator_All DEBUG}<br>
+ * To disable debugging:<br>
+ * {@code adb shell setprop persist.log.tag.Vibrator_All \"\" }
+ *
+ * @param tag The tag to check for debugging. Use the tag name from the calling class.
+ * @return True if debugging is enabled for the tag or globally (Vibrator_All), false otherwise.
+ */
+ public static boolean isDebuggable(String tag) {
+ return Log.isLoggable(tag, Log.DEBUG) || Log.isLoggable("Vibrator_All", Log.DEBUG);
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index ce91e63b4849..b9530978e850 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -108,7 +108,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
private static final String VIBRATOR_CONTROL_SERVICE =
"android.frameworks.vibrator.IVibratorControlService/default";
- private static final boolean DEBUG = false;
+ // To enable these logs, run:
+ // 'adb shell setprop persist.log.tag.VibratorManagerService DEBUG && adb reboot'
+ private static final boolean DEBUG = VibratorDebugUtils.isDebuggable(TAG);
private static final VibrationAttributes DEFAULT_ATTRIBUTES =
new VibrationAttributes.Builder().build();
private static final int ATTRIBUTES_ALL_BYPASS_FLAGS =