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

Commit 4b080763 authored by Chris Göllner's avatar Chris Göllner Committed by Android (Google) Code Review
Browse files

Merge "Use display specific PhoneStatusBarViewController in Shade" into main

parents 00653f6e e08f023a
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import com.android.systemui.shade.data.repository.ShadeAnimationRepository
import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
import com.android.systemui.shade.domain.interactor.shadeStatusBarComponentsInteractor
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.DragDownHelper
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -67,6 +68,7 @@ import com.android.systemui.statusbar.phone.ConfigurationForwarder
import com.android.systemui.statusbar.phone.DozeScrimController
import com.android.systemui.statusbar.phone.DozeServiceHost
import com.android.systemui.statusbar.window.StatusBarWindowStateController
import com.android.systemui.testKosmos
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.any
@@ -97,6 +99,8 @@ import org.mockito.kotlin.mock
@SmallTest
class NotificationShadeWindowViewTest : SysuiTestCase() {

    private val kosmos = testKosmos()

    @Mock private lateinit var choreographer: Choreographer
    @Mock private lateinit var blurUtils: BlurUtils
    @Mock private lateinit var windowRootViewModelFactory: WindowRootViewModel.Factory
@@ -221,6 +225,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
                { configurationForwarder },
                brightnessMirrorShowingInteractor,
                UnconfinedTestDispatcher(),
                kosmos.shadeStatusBarComponentsInteractor,
            )

        controller.setupExpandedStatusBar()
+185 −0
Original line number Diff line number Diff line
/*
 * 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.shade.domain.interactor

import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.data.repository.homeStatusBarComponentsRepository
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidJUnit4::class)
class ShadeStatusBarComponentsInteractorTest : SysuiTestCase() {

    private val kosmos = testKosmos().useUnconfinedTestDispatcher()

    private val Kosmos.underTest by Kosmos.Fixture { shadeStatusBarComponentsInteractor }

    @Test
    fun phoneStatusBarViewController_initiallyNullWhenNoComponents() =
        kosmos.runTest {
            val controller by collectLastValue(underTest.phoneStatusBarViewController)
            assertThat(controller).isNull()
        }

    @Test
    @EnableFlags(ShadeWindowGoesAround.FLAG_NAME)
    fun phoneStatusBarViewController_returnsCorrectControllerWhenComponentAdded() =
        kosmos.runTest {
            fakeShadeDisplaysRepository.setDisplayId(DEFAULT_DISPLAY)
            val mockController = mock<PhoneStatusBarViewController>()
            val mockComponent = createMockHomeStatusBarComponent(DEFAULT_DISPLAY, mockController)

            val controller by collectLastValue(underTest.phoneStatusBarViewController)
            assertThat(controller).isNull() // Ensure it's null before adding

            homeStatusBarComponentsRepository.onStatusBarViewInitialized(mockComponent)
            assertThat(controller).isEqualTo(mockController)
        }

    @Test
    @EnableFlags(ShadeWindowGoesAround.FLAG_NAME)
    fun phoneStatusBarViewController_updatesAfterDisplayChangeToComponentPresent() =
        kosmos.runTest {
            val defaultController = mock<PhoneStatusBarViewController>()
            homeStatusBarComponentsRepository.onStatusBarViewInitialized(
                createMockHomeStatusBarComponent(DEFAULT_DISPLAY, defaultController)
            )
            val secondaryController = mock<PhoneStatusBarViewController>()
            homeStatusBarComponentsRepository.onStatusBarViewInitialized(
                createMockHomeStatusBarComponent(SECONDARY_DISPLAY, secondaryController)
            )
            val controller by collectLastValue(underTest.phoneStatusBarViewController)

            fakeShadeDisplaysRepository.setDisplayId(SECONDARY_DISPLAY)
            assertThat(controller).isEqualTo(secondaryController)

            fakeShadeDisplaysRepository.setDisplayId(DEFAULT_DISPLAY)
            assertThat(controller).isEqualTo(defaultController)
        }

    @Test
    @EnableFlags(ShadeWindowGoesAround.FLAG_NAME)
    fun phoneStatusBarViewController_becomesNullAfterDisplayChangeToComponentAbsent() =
        kosmos.runTest {
            val defaultController = mock<PhoneStatusBarViewController>()
            homeStatusBarComponentsRepository.onStatusBarViewInitialized(
                createMockHomeStatusBarComponent(DEFAULT_DISPLAY, defaultController)
            )
            fakeShadeDisplaysRepository.setDisplayId(DEFAULT_DISPLAY)
            val controller by collectLastValue(underTest.phoneStatusBarViewController)
            assertThat(controller).isEqualTo(defaultController)

            // Change to secondary display which has no component
            fakeShadeDisplaysRepository.setDisplayId(SECONDARY_DISPLAY)
            assertThat(controller).isNull()
        }

    @Test
    @DisableFlags(ShadeWindowGoesAround.FLAG_NAME)
    fun phoneStatusBarViewController_returnsDefaultControllerWhenFlagDisabled() =
        kosmos.runTest {
            // GIVEN components for default and secondary displays exist
            val defaultController = mock<PhoneStatusBarViewController>()
            homeStatusBarComponentsRepository.onStatusBarViewInitialized(
                createMockHomeStatusBarComponent(DEFAULT_DISPLAY, defaultController)
            )
            val secondaryController = mock<PhoneStatusBarViewController>()
            homeStatusBarComponentsRepository.onStatusBarViewInitialized(
                createMockHomeStatusBarComponent(SECONDARY_DISPLAY, secondaryController)
            )
            val controller by collectLastValue(underTest.phoneStatusBarViewController)

            // THEN it should be the controller for the DEFAULT display
            assertThat(controller).isEqualTo(defaultController)
        }

    @Test
    @DisableFlags(ShadeWindowGoesAround.FLAG_NAME)
    fun phoneStatusBarViewController_ignoresDisplayChangesWhenFlagDisabled() =
        kosmos.runTest {
            val defaultController = mock<PhoneStatusBarViewController>()
            homeStatusBarComponentsRepository.onStatusBarViewInitialized(
                createMockHomeStatusBarComponent(DEFAULT_DISPLAY, defaultController)
            )
            val controller by collectLastValue(underTest.phoneStatusBarViewController)
            assertThat(controller).isEqualTo(defaultController)

            // WHEN the shade repository reports a display change
            fakeShadeDisplaysRepository.setDisplayId(SECONDARY_DISPLAY)

            // THEN the controller remains the default one, because the interactor is locked to it
            assertThat(controller).isEqualTo(defaultController)
        }

    @Test
    @DisableFlags(ShadeWindowGoesAround.FLAG_NAME)
    fun phoneStatusBarViewController_becomesNullWhenDefaultComponentIsRemovedWhenFlagDisabled() =
        kosmos.runTest {
            val defaultComponent =
                createMockHomeStatusBarComponent(
                    DEFAULT_DISPLAY,
                    mock<PhoneStatusBarViewController>(),
                )
            homeStatusBarComponentsRepository.onStatusBarViewInitialized(defaultComponent)
            // Add a secondary component to ensure it's ignored
            homeStatusBarComponentsRepository.onStatusBarViewInitialized(
                createMockHomeStatusBarComponent(SECONDARY_DISPLAY, mock())
            )
            val controller by collectLastValue(underTest.phoneStatusBarViewController)
            assertThat(controller).isNotNull()

            // WHEN the default component is removed
            homeStatusBarComponentsRepository.onStatusBarViewDestroyed(defaultComponent)

            // THEN the controller becomes null, even though the secondary component still exists
            assertThat(controller).isNull()
        }

    /** Helper to create a mock HomeStatusBarComponent */
    private fun createMockHomeStatusBarComponent(
        displayId: Int,
        controller: PhoneStatusBarViewController? = null,
    ): HomeStatusBarComponent {
        val mockComponent = mock<HomeStatusBarComponent>()
        whenever(mockComponent.getDisplayId()).thenReturn(displayId)
        whenever(mockComponent.phoneStatusBarViewController).thenReturn(controller)
        return mockComponent
    }

    private companion object {
        const val DEFAULT_DISPLAY = Display.DEFAULT_DISPLAY
        const val SECONDARY_DISPLAY = DEFAULT_DISPLAY + 1
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -78,7 +78,7 @@ class StatusBarInitializerTest : SysuiTestCase() {
            collapsedStatusBarFragmentProvider = { mock(CollapsedStatusBarFragment::class.java) },
            statusBarRootFactory = mock(StatusBarRootFactory::class.java),
            componentFactory = mock(HomeStatusBarComponent.Factory::class.java),
            creationListeners = setOf(),
            lifecycleListeners = setOf(),
            statusBarModePerDisplayRepository = statusBarModePerDisplayRepository,
            darkIconDispatcher = kosmos.fakeDarkIconDispatcher,
            statusBarConfigurationController = kosmos.statusBarConfigurationController,
+0 −11
Original line number Diff line number Diff line
@@ -31,7 +31,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.shade.mockNotificationShadeWindowViewController
import com.android.systemui.shade.mockShadeSurface
import com.android.systemui.statusbar.data.model.StatusBarMode
import com.android.systemui.statusbar.data.model.StatusBarMode.LIGHTS_OUT
@@ -64,8 +63,6 @@ class StatusBarOrchestratorTest : SysuiTestCase() {
    private val testScope = kosmos.testScope
    private val fakeStatusBarModePerDisplayRepository = kosmos.fakeStatusBarModePerDisplayRepository
    private val mockPluginDependencyProvider = kosmos.mockPluginDependencyProvider
    private val mockNotificationShadeWindowViewController =
        kosmos.mockNotificationShadeWindowViewController
    private val mockShadeSurface = kosmos.mockShadeSurface
    private val fakeBouncerRepository = kosmos.fakeKeyguardBouncerRepository
    private val fakeStatusBarWindowStatePerDisplayRepository =
@@ -95,14 +92,6 @@ class StatusBarOrchestratorTest : SysuiTestCase() {
        assertThat(fakeStatusBarWindowController.isAttached).isTrue()
    }

    @Test
    fun start_setsStatusBarControllerOnShade() {
        orchestrator.start()

        verify(mockNotificationShadeWindowViewController)
            .setStatusBarViewController(fakeStatusBarInitializer.statusBarViewController)
    }

    @Test
    fun start_updatesShadeExpansion() {
        orchestrator.start()
+27 −6
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ import com.android.systemui.scene.ui.view.WindowRootViewKeyEventHandler;
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
import com.android.systemui.shade.domain.interactor.ShadeStatusBarComponentsInteractor;
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
import com.android.systemui.statusbar.BlurUtils;
@@ -68,6 +69,7 @@ import com.android.systemui.statusbar.NotificationInsetsController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -117,6 +119,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
    private final AlternateBouncerInteractor mAlternateBouncerInteractor;
    private final QuickSettingsController mQuickSettingsController;
    private final CoroutineDispatcher mMainDispatcher;
    private final ShadeStatusBarComponentsInteractor mShadeStatusBarComponentsInteractor;
    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
    private final GlanceableHubContainerController
            mGlanceableHubContainerController;
@@ -134,6 +137,11 @@ public class NotificationShadeWindowViewController implements Dumpable {
     */
    private long mLaunchAnimationTimeout;
    private NotificationStackScrollLayout mStackScrollLayout;
    /**
     * @deprecated Don't use this field directly. Instead retrieve it through
     * statusBarViewController()
     */
    @Deprecated
    private PhoneStatusBarViewController mStatusBarViewController;
    private final CentralSurfaces mService;
    private final DozeServiceHost mDozeServiceHost;
@@ -212,7 +220,8 @@ public class NotificationShadeWindowViewController implements Dumpable {
            BouncerViewBinder bouncerViewBinder,
            @ShadeDisplayAware Provider<ConfigurationForwarder> configurationForwarder,
            BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor,
            @Main CoroutineDispatcher mainDispatcher) {
            @Main CoroutineDispatcher mainDispatcher,
            ShadeStatusBarComponentsInteractor shadeStatusBarComponentsInteractor) {
        mLockscreenShadeTransitionController = transitionController;
        mFalsingCollector = falsingCollector;
        mStatusBarStateController = statusBarStateController;
@@ -240,6 +249,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
        mAlternateBouncerInteractor = alternateBouncerInteractor;
        mQuickSettingsController = quickSettingsController;
        mMainDispatcher = mainDispatcher;
        mShadeStatusBarComponentsInteractor = shadeStatusBarComponentsInteractor;

        // This view is not part of the newly inflated expanded status bar.
        mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -380,7 +390,9 @@ public class NotificationShadeWindowViewController implements Dumpable {

            @Override
            public Boolean handleDispatchTouchEvent(MotionEvent ev) {
                if (mStatusBarViewController == null) { // Fix for b/192490822
                PhoneStatusBarViewController phoneStatusBarViewController =
                        statusBarViewController();
                if (phoneStatusBarViewController == null) { // Fix for b/192490822
                    return logDownOrFalseResultDispatch(ev,
                            "Ignoring touch while statusBarView not yet set", false);
                }
@@ -473,20 +485,20 @@ public class NotificationShadeWindowViewController implements Dumpable {
                if (expandingBelowNotch) {
                    return logDownOrFalseResultDispatch(ev,
                            "expand below notch. sending touch to status bar",
                            mStatusBarViewController.sendTouchToView(ev));
                            phoneStatusBarViewController.sendTouchToView(ev));
                }

                if (!mIsTrackingBarGesture && isDown
                        && mPanelExpansionInteractor.isFullyCollapsed()) {
                    float x = ev.getRawX();
                    float y = ev.getRawY();
                    if (mStatusBarViewController.touchIsWithinView(x, y)) {
                    if (phoneStatusBarViewController.touchIsWithinView(x, y)) {
                        if (!mPrimaryBouncerInteractor.isBouncerShowing()) {
                            if (mStatusBarWindowStateController.windowIsShowing()) {
                                mIsTrackingBarGesture = true;
                                return logDownOrFalseResultDispatch(ev,
                                        "sending touch to status bar",
                                        mStatusBarViewController.sendTouchToView(ev));
                                        phoneStatusBarViewController.sendTouchToView(ev));
                            } else {
                                return logDownOrFalseResultDispatch(ev, "hidden or hiding", true);
                            }
@@ -497,7 +509,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
                        mShadeLogger.d("NSWVC: touch not within view");
                    }
                } else if (mIsTrackingBarGesture) {
                    final boolean sendToStatusBar = mStatusBarViewController.sendTouchToView(ev);
                    boolean sendToStatusBar = phoneStatusBarViewController.sendTouchToView(ev);
                    if (isUp || isCancel) {
                        mIsTrackingBarGesture = false;
                    }
@@ -754,9 +766,18 @@ public class NotificationShadeWindowViewController implements Dumpable {
    }

    public void setStatusBarViewController(PhoneStatusBarViewController statusBarViewController) {
        StatusBarConnectedDisplays.assertInLegacyMode();
        mStatusBarViewController = statusBarViewController;
    }

    private PhoneStatusBarViewController statusBarViewController() {
        if (StatusBarConnectedDisplays.isEnabled()) {
            return mShadeStatusBarComponentsInteractor.getPhoneStatusBarViewController().getValue();
        } else {
            return mStatusBarViewController;
        }
    }

    @VisibleForTesting
    void setDragDownHelper(DragDownHelper dragDownHelper) {
        mDragDownHelper = dragDownHelper;
Loading