Merge "Update keyguard presentation logic to take into account shade position" into main
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
deleted file mode 100644
index dd58ea7..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard;
-
-import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.hardware.display.DisplayManagerGlobal;
-import android.testing.TestableLooper;
-import android.view.Display;
-import android.view.DisplayInfo;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.settings.FakeDisplayTracker;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.concurrent.Executor;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@TestableLooper.RunWithLooper
-public class KeyguardDisplayManagerTest extends SysuiTestCase {
-
- @Mock
- private NavigationBarController mNavigationBarController;
- @Mock
- private ConnectedDisplayKeyguardPresentation.Factory
- mConnectedDisplayKeyguardPresentationFactory;
- @Mock
- private ConnectedDisplayKeyguardPresentation mConnectedDisplayKeyguardPresentation;
- @Mock
- private KeyguardDisplayManager.DeviceStateHelper mDeviceStateHelper;
- @Mock
- private KeyguardStateController mKeyguardStateController;
-
- private Executor mMainExecutor = Runnable::run;
- private Executor mBackgroundExecutor = Runnable::run;
- private KeyguardDisplayManager mManager;
- private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
- // The default and secondary displays are both in the default group
- private Display mDefaultDisplay;
- private Display mSecondaryDisplay;
-
- // This display is in a different group from the default and secondary displays.
- private Display mAlwaysUnlockedDisplay;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController,
- mDisplayTracker, mMainExecutor, mBackgroundExecutor, mDeviceStateHelper,
- mKeyguardStateController, mConnectedDisplayKeyguardPresentationFactory));
- doReturn(mConnectedDisplayKeyguardPresentation).when(
- mConnectedDisplayKeyguardPresentationFactory).create(any());
- doReturn(mConnectedDisplayKeyguardPresentation).when(mManager)
- .createPresentation(any());
- mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
- new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
- mSecondaryDisplay = new Display(DisplayManagerGlobal.getInstance(),
- Display.DEFAULT_DISPLAY + 1,
- new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
-
- DisplayInfo alwaysUnlockedDisplayInfo = new DisplayInfo();
- alwaysUnlockedDisplayInfo.displayId = Display.DEFAULT_DISPLAY + 2;
- alwaysUnlockedDisplayInfo.flags = Display.FLAG_ALWAYS_UNLOCKED;
- mAlwaysUnlockedDisplay = new Display(DisplayManagerGlobal.getInstance(),
- Display.DEFAULT_DISPLAY,
- alwaysUnlockedDisplayInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
- }
-
- @Test
- public void testShow_defaultDisplayOnly() {
- mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay});
- mManager.show();
- verify(mManager, never()).createPresentation(any());
- }
-
- @Test
- public void testShow_includeSecondaryDisplay() {
- mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
- mManager.show();
- verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay));
- }
-
- @Test
- public void testShow_includeAlwaysUnlockedDisplay() {
- mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mAlwaysUnlockedDisplay});
-
- mManager.show();
- verify(mManager, never()).createPresentation(any());
- }
-
- @Test
- public void testShow_includeSecondaryAndAlwaysUnlockedDisplays() {
- mDisplayTracker.setAllDisplays(
- new Display[]{mDefaultDisplay, mSecondaryDisplay, mAlwaysUnlockedDisplay});
-
- mManager.show();
- verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay));
- }
-
- @Test
- public void testShow_concurrentDisplayActive_occluded() {
- mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
-
- when(mDeviceStateHelper.isConcurrentDisplayActive(mSecondaryDisplay)).thenReturn(true);
- when(mKeyguardStateController.isOccluded()).thenReturn(true);
- verify(mManager, never()).createPresentation(eq(mSecondaryDisplay));
- }
-
- @Test
- public void testShow_presentationCreated() {
- when(mManager.createPresentation(any())).thenCallRealMethod();
- mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
-
- mManager.show();
-
- verify(mConnectedDisplayKeyguardPresentationFactory).create(eq(mSecondaryDisplay));
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt
new file mode 100644
index 0000000..57a6797
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard
+
+import android.hardware.display.DisplayManagerGlobal
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardDisplayManager.DeviceStateHelper
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.navigationbar.NavigationBarController
+import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.shade.data.repository.FakeShadePositionRepository
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import java.util.concurrent.Executor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
+class KeyguardDisplayManagerTest : SysuiTestCase() {
+ @Mock private val navigationBarController = mock(NavigationBarController::class.java)
+ @Mock
+ private val presentationFactory = mock(ConnectedDisplayKeyguardPresentation.Factory::class.java)
+ @Mock
+ private val connectedDisplayKeyguardPresentation =
+ mock(ConnectedDisplayKeyguardPresentation::class.java)
+ @Mock private val deviceStateHelper = mock(DeviceStateHelper::class.java)
+ @Mock private val keyguardStateController = mock(KeyguardStateController::class.java)
+ private val shadePositionRepository = FakeShadePositionRepository()
+
+ private val mainExecutor = Executor { it.run() }
+ private val backgroundExecutor = Executor { it.run() }
+ private lateinit var manager: KeyguardDisplayManager
+ private val displayTracker = FakeDisplayTracker(mContext)
+ // The default and secondary displays are both in the default group
+ private lateinit var defaultDisplay: Display
+ private lateinit var secondaryDisplay: Display
+
+ private val testScope = TestScope(UnconfinedTestDispatcher())
+
+ // This display is in a different group from the default and secondary displays.
+ private lateinit var alwaysUnlockedDisplay: Display
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ manager =
+ KeyguardDisplayManager(
+ mContext,
+ { navigationBarController },
+ displayTracker,
+ mainExecutor,
+ backgroundExecutor,
+ deviceStateHelper,
+ keyguardStateController,
+ presentationFactory,
+ { shadePositionRepository },
+ testScope.backgroundScope,
+ )
+ whenever(presentationFactory.create(any())).doReturn(connectedDisplayKeyguardPresentation)
+
+ defaultDisplay =
+ Display(
+ DisplayManagerGlobal.getInstance(),
+ Display.DEFAULT_DISPLAY,
+ DisplayInfo(),
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS,
+ )
+ secondaryDisplay =
+ Display(
+ DisplayManagerGlobal.getInstance(),
+ Display.DEFAULT_DISPLAY + 1,
+ DisplayInfo(),
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS,
+ )
+
+ val alwaysUnlockedDisplayInfo = DisplayInfo()
+ alwaysUnlockedDisplayInfo.displayId = Display.DEFAULT_DISPLAY + 2
+ alwaysUnlockedDisplayInfo.flags = Display.FLAG_ALWAYS_UNLOCKED
+ alwaysUnlockedDisplay =
+ Display(
+ DisplayManagerGlobal.getInstance(),
+ Display.DEFAULT_DISPLAY,
+ alwaysUnlockedDisplayInfo,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS,
+ )
+ }
+
+ @Test
+ fun testShow_defaultDisplayOnly() {
+ displayTracker.allDisplays = arrayOf(defaultDisplay)
+ manager.show()
+ verify(presentationFactory, never()).create(any())
+ }
+
+ @Test
+ fun testShow_includeSecondaryDisplay() {
+ displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+ manager.show()
+ verify(presentationFactory).create(eq(secondaryDisplay))
+ }
+
+ @Test
+ fun testShow_includeAlwaysUnlockedDisplay() {
+ displayTracker.allDisplays = arrayOf(defaultDisplay, alwaysUnlockedDisplay)
+
+ manager.show()
+ verify(presentationFactory, never()).create(any())
+ }
+
+ @Test
+ fun testShow_includeSecondaryAndAlwaysUnlockedDisplays() {
+ displayTracker.allDisplays =
+ arrayOf(defaultDisplay, secondaryDisplay, alwaysUnlockedDisplay)
+
+ manager.show()
+ verify(presentationFactory).create(eq(secondaryDisplay))
+ }
+
+ @Test
+ fun testShow_concurrentDisplayActive_occluded() {
+ displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+
+ whenever(deviceStateHelper.isConcurrentDisplayActive(secondaryDisplay)).thenReturn(true)
+ whenever(keyguardStateController.isOccluded).thenReturn(true)
+ verify(presentationFactory, never()).create(eq(secondaryDisplay))
+ }
+
+ @Test
+ fun testShow_presentationCreated() {
+ displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+
+ manager.show()
+
+ verify(presentationFactory).create(eq(secondaryDisplay))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun show_shadeMovesDisplay_newPresentationCreated() {
+ displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+ // Shade in the default display, we expect the presentation to be in the secondary only
+ shadePositionRepository.setDisplayId(defaultDisplay.displayId)
+
+ manager.show()
+
+ verify(presentationFactory).create(eq(secondaryDisplay))
+ verify(presentationFactory, never()).create(eq(defaultDisplay))
+ reset(presentationFactory)
+ whenever(presentationFactory.create(any())).thenReturn(connectedDisplayKeyguardPresentation)
+
+ // Let's move it to the secondary display. We expect it will be added in the default
+ // one.
+ shadePositionRepository.setDisplayId(secondaryDisplay.displayId)
+ testScope.advanceUntilIdle()
+
+ verify(presentationFactory).create(eq(defaultDisplay))
+ reset(presentationFactory)
+ whenever(presentationFactory.create(any())).thenReturn(connectedDisplayKeyguardPresentation)
+
+ // Let's move it back! it should be re-created (it means it was removed before)
+ shadePositionRepository.setDisplayId(defaultDisplay.displayId)
+ testScope.advanceUntilIdle()
+
+ verify(presentationFactory).create(eq(secondaryDisplay))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun show_shadeInSecondaryDisplay_defaultOneHasPresentation() {
+ displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+ shadePositionRepository.setDisplayId(secondaryDisplay.displayId)
+
+ manager.show()
+
+ verify(presentationFactory).create(eq(defaultDisplay))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun show_shadeInDefaultDisplay_secondaryOneHasPresentation() {
+ displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+ shadePositionRepository.setDisplayId(defaultDisplay.displayId)
+
+ manager.show()
+
+ verify(presentationFactory).create(eq(secondaryDisplay))
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 1342dd0..95830b5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -15,6 +15,8 @@
*/
package com.android.keyguard;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+
import android.annotation.NonNull;
import android.app.Presentation;
import android.content.Context;
@@ -36,18 +38,24 @@
import androidx.annotation.Nullable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.views.NavigationBarView;
import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.shade.data.repository.ShadePositionRepository;
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import dagger.Lazy;
+import kotlinx.coroutines.CoroutineScope;
+
import java.util.concurrent.Executor;
import javax.inject.Inject;
+import javax.inject.Provider;
@SysUISingleton
public class KeyguardDisplayManager {
@@ -58,6 +66,7 @@
private final DisplayManager mDisplayService;
private final DisplayTracker mDisplayTracker;
private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
+ private final Provider<ShadePositionRepository> mShadePositionRepositoryProvider;
private final ConnectedDisplayKeyguardPresentation.Factory
mConnectedDisplayKeyguardPresentationFactory;
private final Context mContext;
@@ -102,9 +111,12 @@
DeviceStateHelper deviceStateHelper,
KeyguardStateController keyguardStateController,
ConnectedDisplayKeyguardPresentation.Factory
- connectedDisplayKeyguardPresentationFactory) {
+ connectedDisplayKeyguardPresentationFactory,
+ Provider<ShadePositionRepository> shadePositionRepositoryProvider,
+ @Application CoroutineScope appScope) {
mContext = context;
mNavigationBarControllerLazy = navigationBarControllerLazy;
+ mShadePositionRepositoryProvider = shadePositionRepositoryProvider;
uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class));
mDisplayService = mContext.getSystemService(DisplayManager.class);
mDisplayTracker = displayTracker;
@@ -112,6 +124,17 @@
mDeviceStateHelper = deviceStateHelper;
mKeyguardStateController = keyguardStateController;
mConnectedDisplayKeyguardPresentationFactory = connectedDisplayKeyguardPresentationFactory;
+ if (ShadeWindowGoesAround.isEnabled()) {
+ collectFlow(appScope, shadePositionRepositoryProvider.get().getDisplayId(),
+ (id) -> onShadeWindowMovedToDisplayId(id));
+ }
+ }
+
+ private void onShadeWindowMovedToDisplayId(int shadeDisplayId) {
+ if (mShowing) {
+ hidePresentation(shadeDisplayId);
+ updateDisplays(/* showing= */ true);
+ }
}
private boolean isKeyguardShowable(Display display) {
@@ -119,9 +142,20 @@
if (DEBUG) Log.i(TAG, "Cannot show Keyguard on null display");
return false;
}
- if (display.getDisplayId() == mDisplayTracker.getDefaultDisplayId()) {
- if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on the default display");
- return false;
+ if (ShadeWindowGoesAround.isEnabled()) {
+ int shadeDisplayId = mShadePositionRepositoryProvider.get().getDisplayId().getValue();
+ if (display.getDisplayId() == shadeDisplayId) {
+ if (DEBUG) {
+ Log.i(TAG,
+ "Do not show KeyguardPresentation on the shade window display");
+ }
+ return false;
+ }
+ } else {
+ if (display.getDisplayId() == mDisplayTracker.getDefaultDisplayId()) {
+ if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on the default display");
+ return false;
+ }
}
display.getDisplayInfo(mTmpDisplayInfo);
if ((mTmpDisplayInfo.flags & Display.FLAG_PRIVATE) != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index 63510b8..e15830e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -157,7 +157,6 @@
@SysUISingleton
@Provides
- @ShadeDisplayAware
fun provideShadePositionRepository(impl: ShadePositionRepositoryImpl): ShadePositionRepository {
ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
return impl
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt
new file mode 100644
index 0000000..37210b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.data.repository
+
+import android.view.Display
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeShadePositionRepository : ShadePositionRepository {
+ private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
+
+ override fun setDisplayId(displayId: Int) {
+ _displayId.value = displayId
+ }
+
+ override val displayId: StateFlow<Int>
+ get() = _displayId
+
+ override fun resetDisplayId() {
+ _displayId.value = Display.DEFAULT_DISPLAY
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
index 6f492cf..c23ff53 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
@@ -32,7 +32,7 @@
/** Is the refactor enabled */
@JvmStatic
- inline val isEnabled
+ inline val isEnabled: Boolean
get() = Flags.shadeWindowGoesAround()
/**