Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cc2ce3d4 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Update keyguard presentation logic to take into account shade position" into main

parents 54d2284b d637dd37
Loading
Loading
Loading
Loading
+0 −152
Original line number Diff line number Diff line
/*
 * 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));
    }
}
+225 −0
Original line number Diff line number Diff line
/*
 * 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))
    }
}
+38 −4
Original line number Diff line number Diff line
@@ -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,10 +142,21 @@ public class KeyguardDisplayManager {
            if (DEBUG) Log.i(TAG, "Cannot show Keyguard on null 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) {
            if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on a private display");
+0 −1
Original line number Diff line number Diff line
@@ -157,7 +157,6 @@ object ShadeDisplayAwareModule {

    @SysUISingleton
    @Provides
    @ShadeDisplayAware
    fun provideShadePositionRepository(impl: ShadePositionRepositoryImpl): ShadePositionRepository {
        ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
        return impl
+36 −0
Original line number Diff line number Diff line
/*
 * 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
    }
}
Loading