summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt110
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/model/SceneContainerPluginTest.kt93
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java23
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt191
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SysUiState.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt14
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/model/SceneContainerPluginKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt3
16 files changed, 596 insertions, 25 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt
new file mode 100644
index 000000000000..299105e2dabd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt
@@ -0,0 +1,110 @@
+/*
+ * 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
+
+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.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 PerDisplayInstanceRepositoryImplTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+ private val fakePerDisplayInstanceProviderWithTeardown =
+ kosmos.fakePerDisplayInstanceProviderWithTeardown
+
+ private val underTest: PerDisplayInstanceRepositoryImpl<TestPerDisplayInstance> =
+ kosmos.fakePerDisplayInstanceRepository
+
+ @Before
+ fun addDisplays() = runBlocking {
+ fakeDisplayRepository += createDisplay(DEFAULT_DISPLAY_ID)
+ fakeDisplayRepository += createDisplay(NON_DEFAULT_DISPLAY_ID)
+ }
+
+ @Test
+ fun forDisplay_defaultDisplay_multipleCalls_returnsSameInstance() =
+ testScope.runTest {
+ val instance = underTest[DEFAULT_DISPLAY_ID]
+
+ assertThat(underTest[DEFAULT_DISPLAY_ID]).isSameInstanceAs(instance)
+ }
+
+ @Test
+ fun forDisplay_nonDefaultDisplay_multipleCalls_returnsSameInstance() =
+ testScope.runTest {
+ val instance = underTest[NON_DEFAULT_DISPLAY_ID]
+
+ assertThat(underTest[NON_DEFAULT_DISPLAY_ID]).isSameInstanceAs(instance)
+ }
+
+ @Test
+ fun forDisplay_nonDefaultDisplay_afterDisplayRemoved_returnsNewInstance() =
+ testScope.runTest {
+ val instance = underTest[NON_DEFAULT_DISPLAY_ID]
+
+ fakeDisplayRepository -= NON_DEFAULT_DISPLAY_ID
+ fakeDisplayRepository += createDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ assertThat(underTest[NON_DEFAULT_DISPLAY_ID]).isNotSameInstanceAs(instance)
+ }
+
+ @Test
+ fun forDisplay_nonExistingDisplayId_returnsNull() =
+ testScope.runTest { assertThat(underTest[NON_EXISTING_DISPLAY_ID]).isNull() }
+
+ @Test
+ fun forDisplay_afterDisplayRemoved_destroyInstanceInvoked() =
+ testScope.runTest {
+ val instance = underTest[NON_DEFAULT_DISPLAY_ID]
+
+ fakeDisplayRepository -= NON_DEFAULT_DISPLAY_ID
+
+ assertThat(fakePerDisplayInstanceProviderWithTeardown.destroyed)
+ .containsExactly(instance)
+ }
+
+ @Test
+ fun forDisplay_withoutDisplayRemoval_destroyInstanceIsNotInvoked() =
+ testScope.runTest {
+ underTest[NON_DEFAULT_DISPLAY_ID]
+
+ assertThat(fakePerDisplayInstanceProviderWithTeardown.destroyed).isEmpty()
+ }
+
+ private fun createDisplay(displayId: Int): Display =
+ display(type = Display.TYPE_INTERNAL, id = displayId)
+
+ companion object {
+ private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
+ private const val NON_DEFAULT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1
+ private const val NON_EXISTING_DISPLAY_ID = DEFAULT_DISPLAY_ID + 2
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SceneContainerPluginTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SceneContainerPluginTest.kt
new file mode 100644
index 000000000000..b2e29cf60c27
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SceneContainerPluginTest.kt
@@ -0,0 +1,93 @@
+/*
+ * 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 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.flags.EnableSceneContainer
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.fakeSceneDataSource
+import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE
+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)
+@EnableSceneContainer
+class SceneContainerPluginTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val shadeDisplayRepository = kosmos.fakeShadeDisplaysRepository
+ private val sceneDataSource = kosmos.fakeSceneDataSource
+
+ private val underTest = kosmos.sceneContainerPlugin
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun flagValueOverride_differentDisplayId_alwaysFalse() {
+ sceneDataSource.changeScene(Scenes.Shade)
+
+ shadeDisplayRepository.setDisplayId(1)
+
+ assertThat(
+ underTest.flagValueOverride(
+ flag = SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
+ displayId = 2,
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun flagValueOverride_sameDisplayId_returnsTrue() {
+ sceneDataSource.changeScene(Scenes.Shade)
+
+ shadeDisplayRepository.setDisplayId(1)
+
+ assertThat(
+ underTest.flagValueOverride(
+ flag = SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
+ displayId = 1,
+ )
+ )
+ .isTrue()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun flagValueOverride_differentDisplayId_shadeGoesAroundFlagOff_returnsTrue() {
+ sceneDataSource.changeScene(Scenes.Shade)
+
+ shadeDisplayRepository.setDisplayId(1)
+
+ assertThat(
+ underTest.flagValueOverride(
+ flag = SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
+ displayId = 2,
+ )
+ )
+ .isTrue()
+ }
+}
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 eb4277a66be8..779e10a9682a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java
@@ -19,6 +19,9 @@ package com.android.systemui.model;
import static android.view.Display.DEFAULT_DISPLAY;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -29,8 +32,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.settings.DisplayTracker;
import org.junit.Before;
import org.junit.Test;
@@ -48,11 +51,11 @@ public class SysUiStateTest extends SysuiTestCase {
private KosmosJavaAdapter mKosmos;
private SysUiState.SysUiStateCallback mCallback;
private SysUiState mFlagsContainer;
- private DisplayTracker mDisplayTracker;
private SceneContainerPlugin mSceneContainerPlugin;
+ private DumpManager mDumpManager;
private SysUiState createInstance(int displayId) {
- var sysuiState = new SysUiStateImpl(displayId, mSceneContainerPlugin);
+ var sysuiState = new SysUiStateImpl(displayId, mSceneContainerPlugin, mDumpManager);
sysuiState.addCallback(mCallback);
return sysuiState;
}
@@ -60,10 +63,10 @@ public class SysUiStateTest extends SysuiTestCase {
@Before
public void setup() {
mKosmos = new KosmosJavaAdapter(this);
- mDisplayTracker = mKosmos.getDisplayTracker();
mFlagsContainer = mKosmos.getSysuiState();
mSceneContainerPlugin = mKosmos.getSceneContainerPlugin();
mCallback = mock(SysUiState.SysUiStateCallback.class);
+ mDumpManager = mock(DumpManager.class);
mFlagsContainer = createInstance(DEFAULT_DISPLAY);
}
@@ -139,6 +142,18 @@ public class SysUiStateTest extends SysuiTestCase {
verify(mCallback, never()).onSystemUiStateChanged(FLAG_1);
}
+ @Test
+ public void init_registersWithDumpManager() {
+ verify(mDumpManager).registerNormalDumpable(any(), eq(mFlagsContainer));
+ }
+
+ @Test
+ public void destroy_unregistersWithDumpManager() {
+ mFlagsContainer.destroy();
+
+ verify(mDumpManager).unregisterDumpable(anyString());
+ }
+
private void setFlags(int... flags) {
setFlags(mFlagsContainer, flags);
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
index 10b930381c44..ade63b1dc9c9 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
@@ -66,7 +66,7 @@ oneway interface ILauncherProxy {
/**
* Sent when some system ui state changes.
*/
- void onSystemUiStateChanged(long stateFlags) = 16;
+ void onSystemUiStateChanged(long stateFlags, int displayId) = 16;
/**
* Sent when suggested rotation button could be shown
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt
new file mode 100644
index 000000000000..39708a743c23
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.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.dagger
+
+import com.android.systemui.display.data.repository.DefaultDisplayOnlyInstanceRepositoryImpl
+import com.android.systemui.display.data.repository.PerDisplayInstanceRepositoryImpl
+import com.android.systemui.display.data.repository.PerDisplayRepository
+import com.android.systemui.model.SysUIStateInstanceProvider
+import com.android.systemui.model.SysUiState
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import dagger.Module
+import dagger.Provides
+
+/** This module is meant to contain all the code to create the various [PerDisplayRepository<>]. */
+@Module
+class PerDisplayRepositoriesModule {
+
+ @SysUISingleton
+ @Provides
+ fun provideSysUiStateRepository(
+ repositoryFactory: PerDisplayInstanceRepositoryImpl.Factory<SysUiState>,
+ instanceProvider: SysUIStateInstanceProvider,
+ ): PerDisplayRepository<SysUiState> {
+ val debugName = "SysUiStatePerDisplayRepo"
+ return if (ShadeWindowGoesAround.isEnabled) {
+ repositoryFactory.create(debugName, instanceProvider)
+ } else {
+ DefaultDisplayOnlyInstanceRepositoryImpl(debugName, instanceProvider)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index bbc470c77ee4..f08126af0a7a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -65,9 +65,9 @@ import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.demomode.dagger.DemoModeModule;
import com.android.systemui.deviceentry.DeviceEntryModule;
import com.android.systemui.display.DisplayModule;
+import com.android.systemui.display.data.repository.PerDisplayRepository;
import com.android.systemui.doze.dagger.DozeComponent;
import com.android.systemui.dreams.dagger.DreamModule;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.FlagDependenciesModule;
import com.android.systemui.flags.FlagsModule;
@@ -88,7 +88,6 @@ import com.android.systemui.mediaprojection.appselector.MediaProjectionActivitie
import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule;
import com.android.systemui.mediarouter.MediaRouterModule;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.model.SysUiStateImpl;
import com.android.systemui.motiontool.MotionToolModule;
import com.android.systemui.navigationbar.NavigationBarComponent;
import com.android.systemui.navigationbar.gestural.dagger.GestureModule;
@@ -289,7 +288,8 @@ import javax.inject.Named;
UtilModule.class,
NoteTaskModule.class,
WalletModule.class,
- LowLightModule.class
+ LowLightModule.class,
+ PerDisplayRepositoriesModule.class
},
subcomponents = {
ComplicationComponent.class,
@@ -326,11 +326,8 @@ public abstract class SystemUIModule {
@SysUISingleton
@Provides
static SysUiState provideSysUiState(
- DumpManager dumpManager,
- SysUiStateImpl.Factory sysUiStateFactory) {
- final SysUiState state = sysUiStateFactory.create(Display.DEFAULT_DISPLAY);
- dumpManager.registerDumpable(state);
- return state;
+ PerDisplayRepository<SysUiState> repository) {
+ return repository.get(Display.DEFAULT_DISPLAY);
}
/**
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
new file mode 100644
index 000000000000..63c46bb30a07
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
@@ -0,0 +1,191 @@
+/*
+ * 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
+
+import android.util.Log
+import android.view.Display
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.app.tracing.traceSection
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.io.PrintWriter
+import java.util.concurrent.ConcurrentHashMap
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+
+/**
+ * Used to create instances of type `T` for a specific display.
+ *
+ * This is useful for resources or objects that need to be managed independently for each connected
+ * display (e.g., UI state, rendering contexts, or display-specific configurations).
+ *
+ * Note that in most cases this can be implemented by a simple `@AssistedFactory` with `displayId`
+ * parameter
+ *
+ * ```kotlin
+ * class SomeType @AssistedInject constructor(@Assisted displayId: Int,..)
+ * @AssistedFactory
+ * interface Factory {
+ * fun create(displayId: Int): SomeType
+ * }
+ * }
+ * ```
+ *
+ * Then it can be used to create a [PerDisplayRepository] as follows:
+ * ```kotlin
+ * // Injected:
+ * val repositoryFactory: PerDisplayRepositoryImpl.Factory
+ * val instanceFactory: PerDisplayRepositoryImpl.Factory
+ * // repository creation:
+ * repositoryFactory.create(instanceFactory::create)
+ * ```
+ *
+ * @see PerDisplayRepository For how to retrieve and manage instances created by this factory.
+ */
+fun interface PerDisplayInstanceProvider<T> {
+ /** Creates an instance for a display. */
+ fun createInstance(displayId: Int): T?
+}
+
+/**
+ * Extends [PerDisplayInstanceProvider], adding support for destroying the instance.
+ *
+ * This is useful for releasing resources associated with a display when it is disconnected or when
+ * the per-display instance is no longer needed.
+ */
+interface PerDisplayInstanceProviderWithTeardown<T> : PerDisplayInstanceProvider<T> {
+ /** Destroys a previously created instance of `T` forever. */
+ fun destroyInstance(instance: T)
+}
+
+/**
+ * Provides access to per-display instances of type `T`.
+ *
+ * Acts as a repository, managing the caching and retrieval of instances created by a
+ * [PerDisplayInstanceProvider]. It ensures that only one instance of `T` exists per display ID.
+ */
+interface PerDisplayRepository<T> {
+ /** Gets the cached instance or create a new one for a given display. */
+ operator fun get(displayId: Int): T?
+
+ /** Debug name for this repository, mainly for tracing and logging. */
+ val debugName: String
+}
+
+/**
+ * Default implementation of [PerDisplayRepository].
+ *
+ * This class manages a cache of per-display instances of type `T`, creating them using a provided
+ * [PerDisplayInstanceProvider] and optionally tearing them down using a
+ * [PerDisplayInstanceProviderWithTeardown] when displays are disconnected.
+ *
+ * It listens to the [DisplayRepository] to detect when displays are added or removed, and
+ * automatically manages the lifecycle of the per-display instances.
+ *
+ * Note that this is a [PerDisplayStoreImpl] 2.0 that doesn't require [CoreStartable] bindings,
+ * providing all args in the constructor.
+ */
+class PerDisplayInstanceRepositoryImpl<T>
+@AssistedInject
+constructor(
+ @Assisted override val debugName: String,
+ @Assisted private val instanceProvider: PerDisplayInstanceProvider<T>,
+ @Background private val backgroundApplicationScope: CoroutineScope,
+ private val displayRepository: DisplayRepository,
+ private val dumpManager: DumpManager,
+) : PerDisplayRepository<T>, Dumpable {
+
+ private val perDisplayInstances = ConcurrentHashMap<Int, T?>()
+
+ init {
+ backgroundApplicationScope.launch("$debugName#start") { start() }
+ }
+
+ private suspend fun start() {
+ dumpManager.registerDumpable(this)
+ displayRepository.displayIds.collectLatest { displayIds ->
+ val toRemove = perDisplayInstances.keys - displayIds
+ toRemove.forEach { displayId ->
+ perDisplayInstances.remove(displayId)?.let { instance ->
+ (instanceProvider as? PerDisplayInstanceProviderWithTeardown)?.destroyInstance(
+ instance
+ )
+ }
+ }
+ }
+ }
+
+ override fun get(displayId: Int): T? {
+ if (displayRepository.getDisplay(displayId) == null) {
+ Log.e(TAG, "<$debugName: Display with id $displayId doesn't exist.")
+ return null
+ }
+
+ // If it doesn't exist, create it and put it in the map.
+ return perDisplayInstances.computeIfAbsent(displayId) { key ->
+ val instance =
+ traceSection({ "creating instance of $debugName for displayId=$key" }) {
+ instanceProvider.createInstance(key)
+ }
+ if (instance == null) {
+ Log.e(
+ TAG,
+ "<$debugName> returning null because createInstance($key) returned null.",
+ )
+ }
+ instance
+ }
+ }
+
+ @AssistedFactory
+ interface Factory<T> {
+ fun create(
+ debugName: String,
+ instanceProvider: PerDisplayInstanceProvider<T>,
+ ): PerDisplayInstanceRepositoryImpl<T>
+ }
+
+ companion object {
+ private const val TAG = "PerDisplayInstanceRepo"
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println(perDisplayInstances)
+ }
+}
+
+/**
+ * Provides an instance of a given class **only** for the default display, even if asked for another
+ * display.
+ *
+ * This is useful in case of flag refactors: it can be provided instead of an instance of
+ * [PerDisplayInstanceRepositoryImpl] when a flag related to multi display refactoring is off.
+ */
+class DefaultDisplayOnlyInstanceRepositoryImpl<T>(
+ override val debugName: String,
+ private val instanceProvider: PerDisplayInstanceProvider<T>,
+) : PerDisplayRepository<T> {
+ private val lazyDefaultDisplayInstance by lazy {
+ instanceProvider.createInstance(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 564588c159bd..81aca27b9c28 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
@@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.CoroutineScope
/** Provides per display instances of [T]. */
+@Deprecated("Use PerDisplayInstanceProvider<T> instead")
interface PerDisplayStore<T> {
/**
@@ -43,6 +44,7 @@ interface PerDisplayStore<T> {
fun forDisplay(displayId: Int): T?
}
+@Deprecated("Use PerDisplayRepository<T> instead")
abstract class PerDisplayStoreImpl<T>(
@Background private val backgroundApplicationScope: CoroutineScope,
private val displayRepository: DisplayRepository,
@@ -106,6 +108,11 @@ abstract class PerDisplayStoreImpl<T>(
* Will be called when the display associated with [instance] was removed. It allows to perform
* any clean up if needed.
*/
+ @Deprecated(
+ "Use PerDisplayInstanceProviderWithTeardown instead, and let " +
+ "PerDisplayInstanceRepositoryImpl decide when to destroy the instance (e.g. on " +
+ "display removal or other conditions."
+ )
open suspend fun onDisplayRemovalAction(instance: T) {}
override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
index ea515c96d3f0..4559a7aea1a2 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
@@ -25,6 +25,8 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_COMMUNAL_HUB_SHOWING
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
@@ -35,6 +37,7 @@ import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_B
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
/**
* A plugin for [SysUiState] that provides overrides for certain state flags that must be pulled
@@ -46,17 +49,28 @@ class SceneContainerPlugin
constructor(
private val sceneInteractor: Lazy<SceneInteractor>,
private val occlusionInteractor: Lazy<SceneContainerOcclusionInteractor>,
+ private val shadeDisplaysRepository: Lazy<ShadeDisplaysRepository>,
) {
+ private val shadeDisplayId: StateFlow<Int> by lazy { shadeDisplaysRepository.get().displayId }
+
/**
* Returns an override value for the given [flag] or `null` if the scene framework isn't enabled
* or if the flag value doesn't need to be overridden.
*/
- fun flagValueOverride(@SystemUiStateFlags flag: Long): Boolean? {
+ fun flagValueOverride(@SystemUiStateFlags flag: Long, displayId: Int): Boolean? {
if (!SceneContainerFlag.isEnabled) {
return null
}
+ if (ShadeWindowGoesAround.isEnabled && shadeDisplayId.value != displayId) {
+ // The shade is in another display. All flags related to the shade container will map to
+ // false on other displays now.
+ //
+ // Note that this assumes there is only one SceneContainer and it is only on the shade
+ // window display. If there will be more, this will need to be revisited
+ return false
+ }
val transitionState = sceneInteractor.get().transitionState.value
val idleTransitionStateOrNull = transitionState as? ObservableTransitionState.Idle
val invisibleDueToOcclusion = occlusionInteractor.get().invisibleDueToOcclusion.value
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
index 432dadbb2136..9e9a1df76b54 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
@@ -18,6 +18,9 @@ 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
+import com.android.systemui.dump.DumpManager
import com.android.systemui.model.SysUiState.SysUiStateCallback
import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
@@ -26,6 +29,7 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dalvik.annotation.optimization.NeverCompile
import java.io.PrintWriter
+import javax.inject.Inject
/** Contains sysUi state flags and notifies registered listeners whenever changes happen. */
interface SysUiState : Dumpable {
@@ -70,6 +74,13 @@ interface SysUiState : Dumpable {
) {}
}
+ /**
+ * Destroys an instance. It shouldn't be used anymore afterwards.
+ *
+ * This is mainly used to clean up instances associated with displays that are removed.
+ */
+ fun destroy()
+
companion object {
const val DEBUG: Boolean = false
}
@@ -80,7 +91,15 @@ class SysUiStateImpl
constructor(
@Assisted private val displayId: Int,
private val sceneContainerPlugin: SceneContainerPlugin?,
+ private val dumpManager: DumpManager,
) : SysUiState {
+
+ private val debugName = "SysUiStateImpl-ForDisplay=$displayId"
+
+ init {
+ dumpManager.registerNormalDumpable(debugName, this)
+ }
+
/** Returns the current sysui state flags. */
@get:SystemUiStateFlags
@SystemUiStateFlags
@@ -113,7 +132,8 @@ constructor(
/** 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)
+ val overrideOrNull =
+ sceneContainerPlugin?.flagValueOverride(flag = flag, displayId = displayId)
if (overrideOrNull != null && enabled != overrideOrNull) {
if (SysUiState.DEBUG) {
Log.d(
@@ -187,6 +207,10 @@ constructor(
pw.println(QuickStepContract.isAssistantGestureDisabled(flags))
}
+ override fun destroy() {
+ dumpManager.unregisterDumpable(debugName)
+ }
+
@AssistedFactory
interface Factory {
/** Creates a new instance of [SysUiStateImpl] for a given [displayId]. */
@@ -197,3 +221,16 @@ constructor(
private val TAG: String = SysUiState::class.java.simpleName
}
}
+
+/** Creates and destroy instances of [SysUiState] */
+@SysUISingleton
+class SysUIStateInstanceProvider @Inject constructor(private val factory: SysUiStateImpl.Factory) :
+ PerDisplayInstanceProviderWithTeardown<SysUiState> {
+ override fun createInstance(displayId: Int): SysUiState {
+ return factory.create(displayId)
+ }
+
+ override fun destroyInstance(instance: SysUiState) {
+ instance.destroy()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
index 9af4630bf492..8253a071dea2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
@@ -66,6 +66,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
+import android.view.Display;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -94,6 +95,7 @@ import com.android.systemui.keyguard.KeyguardWmStateRefactor;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.model.SysUiState.SysUiStateCallback;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.views.NavigationBar;
@@ -584,7 +586,8 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis
// Force-update the systemui state flags
updateSystemUiStateFlags();
- notifySystemUiStateFlags(mSysUiState.getFlags());
+ // TODO b/398011576 - send the state for all displays.
+ notifySystemUiStateFlags(mSysUiState.getFlags(), Display.DEFAULT_DISPLAY);
notifyConnectionChanged();
}
@@ -650,6 +653,17 @@ 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) {
+ notifySystemUiStateFlags(sysUiFlags, displayId);
+ }
+ };
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
public LauncherProxyService(Context context,
@@ -708,8 +722,10 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis
com.android.internal.R.string.config_recentsComponentName));
mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
.setPackage(mRecentsComponentName.getPackageName());
+ // TODO b/398011576 - Here we're still only handling the default display state. We should
+ // have a callback for any sysuiState change.
mSysUiState = sysUiState;
- mSysUiState.addCallback(this::notifySystemUiStateFlags);
+ mSysUiState.addCallback(mSysUiStateCallback);
mUiEventLogger = uiEventLogger;
mDisplayTracker = displayTracker;
mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder;
@@ -815,14 +831,14 @@ public class LauncherProxyService implements CallbackController<LauncherProxyLis
}
}
- private void notifySystemUiStateFlags(@SystemUiStateFlags long flags) {
+ private void notifySystemUiStateFlags(@SystemUiStateFlags long flags, int displayId) {
if (SysUiState.DEBUG) {
Log.d(TAG_OPS, "Notifying sysui state change to launcher service: proxy="
- + mLauncherProxy + " flags=" + flags);
+ + mLauncherProxy + " flags=" + flags + " displayId=" + displayId);
}
try {
if (mLauncherProxy != null) {
- mLauncherProxy.onSystemUiStateChanged(flags);
+ mLauncherProxy.onSystemUiStateChanged(flags, displayId);
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to notify sysui state change", e);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt
index ec37b7592650..0a5efb7bb286 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt
@@ -24,6 +24,7 @@ import android.os.PowerManager
import android.os.UserManager
import android.testing.TestableContext
import android.testing.TestableLooper
+import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.app.AssistUtils
@@ -66,6 +67,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
@@ -165,7 +167,8 @@ class LauncherProxyServiceTest : SysuiTestCase() {
verify(launcherProxy)
.onSystemUiStateChanged(
- longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE },
+ eq(Display.DEFAULT_DISPLAY),
)
}
@@ -175,7 +178,8 @@ class LauncherProxyServiceTest : SysuiTestCase() {
verify(launcherProxy)
.onSystemUiStateChanged(
- longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING },
+ eq(Display.DEFAULT_DISPLAY),
)
}
@@ -185,7 +189,8 @@ class LauncherProxyServiceTest : SysuiTestCase() {
verify(launcherProxy)
.onSystemUiStateChanged(
- longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP },
+ eq(Display.DEFAULT_DISPLAY),
)
}
@@ -197,7 +202,8 @@ class LauncherProxyServiceTest : SysuiTestCase() {
verify(launcherProxy)
.onSystemUiStateChanged(
- longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP },
+ eq(Display.DEFAULT_DISPLAY),
)
}
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 d6f0e06e104d..70b22d7f829d 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
@@ -62,6 +62,14 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
displays.forEach { addDisplay(it) }
}
+ suspend operator fun plusAssign(display: Display) {
+ addDisplay(display)
+ }
+
+ suspend operator fun minusAssign(displayId: Int) {
+ removeDisplay(displayId)
+ }
+
suspend fun addDisplay(display: Display) {
flow.value += display
displayIdFlow.value += display.displayId
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
index e3797260ed6d..aa23aa30b7bc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
@@ -16,8 +16,10 @@
package com.android.systemui.display.data.repository
+import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
import kotlinx.coroutines.CoroutineScope
class FakePerDisplayStore(
@@ -47,3 +49,30 @@ val Kosmos.fakePerDisplayStore by
displayRepository = displayRepository,
)
}
+
+class FakePerDisplayInstanceProviderWithTeardown :
+ PerDisplayInstanceProviderWithTeardown<TestPerDisplayInstance> {
+ val destroyed = mutableListOf<TestPerDisplayInstance>()
+
+ override fun destroyInstance(instance: TestPerDisplayInstance) {
+ destroyed += instance
+ }
+
+ override fun createInstance(displayId: Int): TestPerDisplayInstance? {
+ return TestPerDisplayInstance(displayId)
+ }
+}
+
+val Kosmos.fakePerDisplayInstanceProviderWithTeardown by
+ Kosmos.Fixture { FakePerDisplayInstanceProviderWithTeardown() }
+
+val Kosmos.fakePerDisplayInstanceRepository by
+ Kosmos.Fixture {
+ PerDisplayInstanceRepositoryImpl(
+ debugName = "fakePerDisplayInstanceRepository",
+ instanceProvider = fakePerDisplayInstanceProviderWithTeardown,
+ testScope.backgroundScope,
+ displayRepository,
+ dumpManager,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SceneContainerPluginKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SceneContainerPluginKosmos.kt
index d19dfe8d74fb..79506f9e75a5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SceneContainerPluginKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SceneContainerPluginKosmos.kt
@@ -20,10 +20,12 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
val Kosmos.sceneContainerPlugin by Fixture {
SceneContainerPlugin(
sceneInteractor = { sceneInteractor },
occlusionInteractor = { sceneContainerOcclusionInteractor },
+ shadeDisplaysRepository = { fakeShadeDisplaysRepository },
)
}
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 8aecf886b578..6272aafaa688 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,10 +17,11 @@
package com.android.systemui.model
import android.view.Display
+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))
+ spy(SysUiStateImpl(Display.DEFAULT_DISPLAY, sceneContainerPlugin, dumpManager))
}