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

Commit 0a9c02b2 authored by Johannes Gallmann's avatar Johannes Gallmann Committed by Android (Google) Code Review
Browse files

Merge "Extract top-ui logic into TopUiController" into main

parents 8a620569 882f4b5c
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -2080,6 +2080,16 @@ flag {
    bug: "411452237"
}

flag {
    name: "enable_top_ui_controller"
    namespace: "systemui"
    description: "Extracts top ui requests into TopUiController"
    bug: "411061512"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "qs_tile_transition_interaction_refinement"
    namespace: "systemui"
+2 −1
Original line number Diff line number Diff line
@@ -177,7 +177,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
                mUserTracker,
                mKosmos.getNotificationShadeWindowModel(),
                mKosmos::getCommunalInteractor,
                mKosmos.getShadeLayoutParams());
                mKosmos.getShadeLayoutParams(),
                mKosmos.getTopUiController());
        mNotificationShadeWindowController.setScrimsVisibilityListener((visibility) -> {});
        mNotificationShadeWindowController.fetchWindowRootView();

+195 −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.topui

import android.app.activityManagerInterface
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.dump.dumpManager
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.testKosmos
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions

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

    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val iActivityManager = kosmos.activityManagerInterface

    private lateinit var underTest: TopUiControllerImpl

    @Before
    fun setUp() {
        underTest = TopUiControllerImpl(iActivityManager, kosmos.testDispatcher, kosmos.dumpManager)
    }

    @Test
    fun initialState_noActivityManagerCall() =
        kosmos.runTest {
            // No calls should have happened during initialization
            verifyNoMoreInteractions(activityManagerInterface)
        }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_TOP_UI_CONTROLLER)
    fun firstRequest_setsHasTopUiTrue() =
        kosmos.runTest {
            underTest.setRequestTopUi(true, "tag1")

            // Verify setHasTopUi(true) was called exactly once
            verify(activityManagerInterface, times(1)).setHasTopUi(true)
            verifyNoMoreInteractions(activityManagerInterface) // Ensure no other calls
        }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_TOP_UI_CONTROLLER)
    fun duplicateRequest_noExtraCall() =
        kosmos.runTest {
            // Initial request
            underTest.setRequestTopUi(true, "tag1")
            verify(activityManagerInterface, times(1)).setHasTopUi(true)

            // Duplicate request
            underTest.setRequestTopUi(true, "tag1")

            // No *new* calls should happen
            verifyNoMoreInteractions(activityManagerInterface)
        }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_TOP_UI_CONTROLLER)
    fun secondRequest_differentTag_noExtraCall() =
        kosmos.runTest {
            // Initial request
            underTest.setRequestTopUi(true, "tag1")
            verify(activityManagerInterface, times(1)).setHasTopUi(true)

            // Second request from different component
            underTest.setRequestTopUi(true, "tag2")

            // State is already true, no new calls expected
            verifyNoMoreInteractions(activityManagerInterface)
        }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_TOP_UI_CONTROLLER)
    fun releaseOneOfTwo_noCall() =
        kosmos.runTest {
            // Setup with two requesters
            underTest.setRequestTopUi(true, "tag1")
            verify(activityManagerInterface, times(1)).setHasTopUi(true)
            underTest.setRequestTopUi(true, "tag2")
            verifyNoMoreInteractions(activityManagerInterface) // No new call for tag2

            // Release first tag
            underTest.setRequestTopUi(false, "tag1")

            // State should remain true (tag2 still active), no new calls
            verifyNoMoreInteractions(activityManagerInterface)
        }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_TOP_UI_CONTROLLER)
    fun releaseLast_setsHasTopUiFalse() =
        kosmos.runTest {
            // Setup with two requesters
            underTest.setRequestTopUi(true, "tag1")
            verify(activityManagerInterface, times(1)).setHasTopUi(true)
            underTest.setRequestTopUi(true, "tag2")
            verifyNoMoreInteractions(activityManagerInterface)

            // Release first
            underTest.setRequestTopUi(false, "tag1")
            verifyNoMoreInteractions(activityManagerInterface) // Still true

            // Release second (last)
            underTest.setRequestTopUi(false, "tag2")

            // Should now call setHasTopUi(false) exactly once
            verify(activityManagerInterface, times(1)).setHasTopUi(false)
            verifyNoMoreInteractions(activityManagerInterface)
        }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_TOP_UI_CONTROLLER)
    fun duplicateRelease_noExtraCall() =
        kosmos.runTest {
            // Setup and release all
            underTest.setRequestTopUi(true, "tag1")
            verify(activityManagerInterface).setHasTopUi(true) // Use default times(1)
            underTest.setRequestTopUi(false, "tag1")
            verify(activityManagerInterface).setHasTopUi(false) // Use default times(1)

            // Duplicate release
            underTest.setRequestTopUi(false, "tag1")

            // No new calls expected
            verifyNoMoreInteractions(activityManagerInterface)
        }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_TOP_UI_CONTROLLER)
    fun releaseNonExistent_noCall() =
        kosmos.runTest {
            // Setup with one requester
            underTest.setRequestTopUi(true, "tag1")
            verify(activityManagerInterface).setHasTopUi(true)

            // Release a tag that never requested
            underTest.setRequestTopUi(false, "tagNonExistent")

            // No change expected
            verifyNoMoreInteractions(activityManagerInterface)

            // Release the actual tag should still work
            underTest.setRequestTopUi(false, "tag1")
            verify(activityManagerInterface).setHasTopUi(false)
            verifyNoMoreInteractions(activityManagerInterface)
        }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_TOP_UI_CONTROLLER)
    fun requestReleaseRequest_correctCalls() =
        kosmos.runTest {
            // Request
            underTest.setRequestTopUi(true, "tag1")
            verify(activityManagerInterface, times(1)).setHasTopUi(true)
            verifyNoMoreInteractions(activityManagerInterface)

            // Release
            underTest.setRequestTopUi(false, "tag1")
            verify(activityManagerInterface, times(1)).setHasTopUi(false)
            verifyNoMoreInteractions(activityManagerInterface)

            // Request again
            underTest.setRequestTopUi(true, "tag1")
            // Need to verify setHasTopUi(true) was called a *second* time overall
            verify(activityManagerInterface, times(2)).setHasTopUi(true)
            verifyNoMoreInteractions(activityManagerInterface)
        }
}
+13 −5
Original line number Diff line number Diff line
@@ -30,8 +30,11 @@ import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.notificationShadeWindowController
import com.android.systemui.testKosmos
import com.android.systemui.topui.TopUiController
import com.android.systemui.topui.TopUiControllerRefactor
import com.android.systemui.topui.topUiController
import com.android.systemui.topwindoweffects.data.repository.DEFAULT_INITIAL_DELAY_MILLIS
import com.android.systemui.topwindoweffects.data.repository.DEFAULT_LONG_PRESS_POWER_DURATION_MILLIS
import com.android.systemui.topwindoweffects.data.repository.fakeSqueezeEffectRepository
@@ -63,7 +66,7 @@ class TopLevelWindowEffectsTest : SysuiTestCase() {

    @Mock private lateinit var windowManager: WindowManager

    @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
    @Mock private lateinit var topUiController: TopUiController

    @Mock private lateinit var viewModelFactory: SqueezeEffectViewModel.Factory

@@ -79,7 +82,8 @@ class TopLevelWindowEffectsTest : SysuiTestCase() {
                squeezeEffectInteractor =
                    SqueezeEffectInteractor(squeezeEffectRepository = fakeSqueezeEffectRepository),
                appZoomOutOptional = Optional.empty(),
                notificationShadeWindowController = notificationShadeWindowController,
                notificationShadeWindowController = kosmos.notificationShadeWindowController,
                topUiController = kosmos.topUiController,
                interactionJankMonitor = kosmos.interactionJankMonitor,
            )
        }
@@ -224,7 +228,11 @@ class TopLevelWindowEffectsTest : SysuiTestCase() {

    private fun verifyAddViewAndTopUi(mode: VerificationMode) {
        verify(windowManager, mode).addView(any<View>(), any<WindowManager.LayoutParams>())
        verify(notificationShadeWindowController, mode)
        if (TopUiControllerRefactor.isEnabled) {
            verify(topUiController, mode).setRequestTopUi(true, TopLevelWindowEffects.TAG)
        } else {
            verify(kosmos.notificationShadeWindowController, mode)
                .setRequestTopUi(true, TopLevelWindowEffects.TAG)
        }
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -126,6 +126,7 @@ import com.android.systemui.startable.Dependencies;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.topui.TopUiController;
import com.android.systemui.statusbar.chips.StatusBarChipsModule;
import com.android.systemui.statusbar.connectivity.ConnectivityModule;
import com.android.systemui.statusbar.dagger.StatusBarModule;
@@ -158,6 +159,7 @@ import com.android.systemui.statusbar.ui.binder.StatusBarViewBinderModule;
import com.android.systemui.statusbar.window.StatusBarWindowModule;
import com.android.systemui.telephony.data.repository.TelephonyRepositoryModule;
import com.android.systemui.temporarydisplay.dagger.TemporaryDisplayModule;
import com.android.systemui.topui.TopUiModule;
import com.android.systemui.touchpad.TouchpadModule;
import com.android.systemui.tuner.dagger.TunerModule;
import com.android.systemui.user.UserModule;
@@ -284,6 +286,7 @@ import javax.inject.Named;
        TelephonyRepositoryModule.class,
        TemporaryDisplayModule.class,
        ShadeDisplayAwareModule.class,
        TopUiModule.class,
        TouchpadModule.class,
        TunerModule.class,
        UserDomainLayerModule.class,
@@ -410,6 +413,7 @@ public abstract class SystemUIModule {
    static Optional<BubblesManager> provideBubblesManager(Context context,
            Optional<Bubbles> bubblesOptional,
            NotificationShadeWindowController notificationShadeWindowController,
            TopUiController topUiController,
            KeyguardStateController keyguardStateController,
            ShadeController shadeController,
            @Nullable IStatusBarService statusBarService,
@@ -430,6 +434,7 @@ public abstract class SystemUIModule {
        return Optional.ofNullable(BubblesManager.create(context,
                bubblesOptional,
                notificationShadeWindowController,
                topUiController,
                keyguardStateController,
                shadeController,
                statusBarService,
Loading