diff options
| author | 2024-11-12 15:45:59 +0000 | |
|---|---|---|
| committer | 2024-11-12 15:45:59 +0000 | |
| commit | cc2ce3d4888dec3d0b8a376e786cc752a553a306 (patch) | |
| tree | 35a6ff6f9d72f784323cab94ce97987f03ca15f8 | |
| parent | 54d2284b9008bec450752ab0de2ceaa47bfbfac5 (diff) | |
| parent | d637dd37cb39e4c46acd63f974b626d4a16d53ae (diff) | |
Merge "Update keyguard presentation logic to take into account shade position" into main
6 files changed, 300 insertions, 158 deletions
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 dd58ea7db2bc..000000000000 --- 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 000000000000..57a67973f34f --- /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 1342dd05d7f2..95830b5f4ed7 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 android.view.WindowManager; 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 @@ public class KeyguardDisplayManager { 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 @@ public class KeyguardDisplayManager { 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 @@ public class KeyguardDisplayManager { 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 @@ public class KeyguardDisplayManager { 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 63510b873951..e15830eb22eb 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 @@ object ShadeDisplayAwareModule { @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 000000000000..37210b90ee78 --- /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 6f492cfaa6c3..c23ff5302b3c 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 @@ object ShadeWindowGoesAround { /** Is the refactor enabled */ @JvmStatic - inline val isEnabled + inline val isEnabled: Boolean get() = Flags.shadeWindowGoesAround() /** |