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

Commit 92f5c3cf authored by Chris Göllner's avatar Chris Göllner
Browse files

Introduce StatusBarWindowControllerStore

Flag: com.android.systemui.status_bar_connected_displays
Test: Unit tests in this CL
Test: Manually build and run and verify everything works as before
Bug: 367592591
Change-Id: I73ee8e682de2c7a8871e950d50dd12362e4f2a56
parent e92bb92a
Loading
Loading
Loading
Loading
+41 −3
Original line number Diff line number Diff line
@@ -22,15 +22,20 @@ import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.StatusBarDataLayerModule
import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
import com.android.systemui.statusbar.window.MultiDisplayStatusBarWindowControllerStore
import com.android.systemui.statusbar.window.SingleDisplayStatusBarWindowControllerStore
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.statusbar.window.StatusBarWindowControllerImpl
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import dagger.Binds
import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
@@ -62,18 +67,51 @@ abstract class StatusBarModule {
    @ClassKey(StatusBarSignalPolicy::class)
    abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable

    @Binds
    @SysUISingleton
    abstract fun statusBarWindowControllerFactory(
        implFactory: StatusBarWindowControllerImpl.Factory
    ): StatusBarWindowController.Factory

    companion object {

        @Provides
        @SysUISingleton
        fun statusBarWindowController(
            context: Context?,
            viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager?,
        fun defaultStatusBarWindowController(
            context: Context,
            viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
            factory: StatusBarWindowControllerImpl.Factory,
        ): StatusBarWindowController {
            return factory.create(context, viewCaptureAwareWindowManager)
        }

        @Provides
        @SysUISingleton
        fun windowControllerStore(
            multiDisplayImplLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>,
            singleDisplayImplLazy: Lazy<SingleDisplayStatusBarWindowControllerStore>,
        ): StatusBarWindowControllerStore {
            return if (StatusBarConnectedDisplays.isEnabled) {
                multiDisplayImplLazy.get()
            } else {
                singleDisplayImplLazy.get()
            }
        }

        @Provides
        @SysUISingleton
        @IntoMap
        @ClassKey(MultiDisplayStatusBarWindowControllerStore::class)
        fun multiDisplayControllerStoreAsCoreStartable(
            storeLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>
        ): CoreStartable {
            return if (StatusBarConnectedDisplays.isEnabled) {
                storeLazy.get()
            } else {
                CoreStartable.NOP
            }
        }

        @Provides
        @SysUISingleton
        @OngoingCallLog
+9 −0
Original line number Diff line number Diff line
@@ -16,8 +16,10 @@

package com.android.systemui.statusbar.window

import android.content.Context
import android.view.View
import android.view.ViewGroup
import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.fragments.FragmentHostManager
import java.util.Optional
@@ -73,4 +75,11 @@ interface StatusBarWindowController {
     *   this#setForceStatusBarVisible} together and use some sort of ranking system instead.
     */
    fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean)

    interface Factory {
        fun create(
            context: Context,
            viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
        ): StatusBarWindowController
    }
}
+5 −3
Original line number Diff line number Diff line
@@ -354,11 +354,13 @@ public class StatusBarWindowControllerImpl implements StatusBarWindowController
    }

    @AssistedFactory
    public interface Factory {
    public interface Factory extends StatusBarWindowController.Factory {
        /** Creates a new instance. */
        @NonNull
        @Override
        StatusBarWindowControllerImpl create(
                Context context,
                ViewCaptureAwareWindowManager viewCaptureAwareWindowManager);
                @NonNull Context context,
                @NonNull ViewCaptureAwareWindowManager viewCaptureAwareWindowManager);
    }

}
+117 −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.statusbar.window

import android.view.Display
import android.view.WindowManager
import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

/** Store that allows to retrieve per display instances of [StatusBarWindowController]. */
interface StatusBarWindowControllerStore {
    /**
     * The instance for the default/main display of the device. For example, on a phone or a tablet,
     * the default display is the internal/built-in display of the device.
     *
     * Note that the id of the default display is [Display.DEFAULT_DISPLAY].
     */
    val defaultDisplay: StatusBarWindowController

    /**
     * Returns an instance for a specific display id.
     *
     * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing
     *   displays.
     */
    fun forDisplay(displayId: Int): StatusBarWindowController
}

@SysUISingleton
class MultiDisplayStatusBarWindowControllerStore
@Inject
constructor(
    @Background private val backgroundApplicationScope: CoroutineScope,
    private val controllerFactory: StatusBarWindowController.Factory,
    private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
    private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory,
    private val displayRepository: DisplayRepository,
) : StatusBarWindowControllerStore, CoreStartable {

    init {
        StatusBarConnectedDisplays.assertInNewMode()
    }

    private val perDisplayControllers = ConcurrentHashMap<Int, StatusBarWindowController>()

    override fun start() {
        backgroundApplicationScope.launch(CoroutineName("StatusBarWindowController#start")) {
            displayRepository.displayRemovalEvent.collect { displayId ->
                perDisplayControllers.remove(displayId)
            }
        }
    }

    override val defaultDisplay: StatusBarWindowController
        get() = forDisplay(Display.DEFAULT_DISPLAY)

    override fun forDisplay(displayId: Int): StatusBarWindowController {
        if (displayRepository.getDisplay(displayId) == null) {
            throw IllegalArgumentException("Display with id $displayId doesn't exist.")
        }
        return perDisplayControllers.computeIfAbsent(displayId) {
            createControllerForDisplay(displayId)
        }
    }

    private fun createControllerForDisplay(displayId: Int): StatusBarWindowController {
        val statusBarDisplayContext =
            displayWindowPropertiesRepository.get(
                displayId = displayId,
                windowType = WindowManager.LayoutParams.TYPE_STATUS_BAR,
            )
        val viewCaptureAwareWindowManager =
            viewCaptureAwareWindowManagerFactory.create(statusBarDisplayContext.windowManager)
        return controllerFactory.create(
            statusBarDisplayContext.context,
            viewCaptureAwareWindowManager,
        )
    }
}

@SysUISingleton
class SingleDisplayStatusBarWindowControllerStore
@Inject
constructor(private val controller: StatusBarWindowController) : StatusBarWindowControllerStore {

    init {
        StatusBarConnectedDisplays.assertInLegacyMode()
    }

    override val defaultDisplay = controller

    override fun forDisplay(displayId: Int) = controller
}
+120 −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.statusbar.window

import android.platform.test.annotations.EnableFlags
import android.view.Display
import android.view.WindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.app.viewcapture.mockViewCaptureAwareWindowManager
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.display.data.repository.fakeDisplayWindowPropertiesRepository
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.unconfinedTestDispatcher
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock

@RunWith(AndroidJUnit4::class)
@SmallTest
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() {

    private val kosmos = testKosmos().also { it.testDispatcher = it.unconfinedTestDispatcher }
    private val testScope = kosmos.testScope
    private val fakeDisplayRepository = kosmos.displayRepository

    private val store =
        MultiDisplayStatusBarWindowControllerStore(
            backgroundApplicationScope = kosmos.applicationCoroutineScope,
            controllerFactory = kosmos.fakeStatusBarWindowControllerFactory,
            displayWindowPropertiesRepository = kosmos.fakeDisplayWindowPropertiesRepository,
            viewCaptureAwareWindowManagerFactory =
                object : ViewCaptureAwareWindowManager.Factory {
                    override fun create(
                        windowManager: WindowManager
                    ): ViewCaptureAwareWindowManager {
                        return kosmos.mockViewCaptureAwareWindowManager
                    }
                },
            displayRepository = fakeDisplayRepository,
        )

    @Before
    fun start() {
        store.start()
    }

    @Before
    fun addDisplays() = runBlocking {
        fakeDisplayRepository.addDisplay(createDisplay(DEFAULT_DISPLAY_ID))
        fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID))
    }

    @Test
    fun forDisplay_defaultDisplay_multipleCalls_returnsSameInstance() =
        testScope.runTest {
            val controller = store.defaultDisplay

            assertThat(store.defaultDisplay).isSameInstanceAs(controller)
        }

    @Test
    fun forDisplay_nonDefaultDisplay_multipleCalls_returnsSameInstance() =
        testScope.runTest {
            val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID)

            assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isSameInstanceAs(controller)
        }

    @Test
    fun forDisplay_nonDefaultDisplay_afterDisplayRemoved_returnsNewInstance() =
        testScope.runTest {
            val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID)

            fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID)
            fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID))

            assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isNotSameInstanceAs(controller)
        }

    @Test(expected = IllegalArgumentException::class)
    fun forDisplay_nonExistingDisplayId_throws() =
        testScope.runTest { store.forDisplay(NON_EXISTING_DISPLAY_ID) }

    private fun createDisplay(displayId: Int): Display = mock {
        on { getDisplayId() } doReturn displayId
    }

    companion object {
        private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
        private const val NON_DEFAULT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1
        private const val NON_EXISTING_DISPLAY_ID = DEFAULT_DISPLAY_ID + 2
    }
}
Loading