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

Commit 0bd1a9fe authored by Nicolò Mazzucato's avatar Nicolò Mazzucato
Browse files

Allow dialogs to be created from the shade when on external displays

This introduces ShadeDialogContextRepository, responsible of creating a dialog context for the shade every time it moves to a separate display.

This is needed for several reasons:
- Just using the shade context would be wrong, as it is a TYPE_NOTIFICATION_SHADE window context that doesn't allow dialogs to be added
- We need to create the context before it is needed, in the background. This is expensive, and if we do it when needed on demand we might delay dialog creation on the main thread, generating jank.

This only uses the class in a couple of dialogs: more will be refactored in follow ups.

Bug: 362719719
Bug: 383294128
Test: ShadeDialogContextRepository
Flag: com.android.systemui.shade_window_goes_around
Change-Id: I48d5e17c5530523e37c7e1a31935ac5ee1867883
parent 3b86d10b
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import com.android.systemui.kosmos.mainCoroutineContext
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.runOnMainThreadAndWaitForIdleSync
import com.android.systemui.shade.data.repository.shadeDialogContextInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.systemUIDialogFactory
import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel
@@ -78,6 +79,7 @@ class ModesDialogDelegateTest : SysuiTestCase() {
                { kosmos.modesDialogViewModel },
                mockDialogEventLogger,
                kosmos.mainCoroutineContext,
                kosmos.shadeDialogContextInteractor,
            )
    }

+8 −5
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import com.android.internal.R as InternalR
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.time.SystemClock
import dagger.assisted.Assisted
@@ -68,6 +69,7 @@ internal constructor(
    private val uiEventLogger: UiEventLogger,
    private val logger: BluetoothTileDialogLogger,
    private val systemuiDialogFactory: SystemUIDialog.Factory,
    private val shadeDialogContextInteractor: ShadeDialogContextInteractor,
) : SystemUIDialog.Delegate {

    private val mutableBluetoothStateToggle: MutableStateFlow<Boolean?> = MutableStateFlow(null)
@@ -105,7 +107,7 @@ internal constructor(
    }

    override fun createDialog(): SystemUIDialog {
        return systemuiDialogFactory.create(this)
        return systemuiDialogFactory.create(this, shadeDialogContextInteractor.context)
    }

    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
@@ -405,7 +407,8 @@ internal constructor(
                    }

                    // updating icon colors
                    val tintColor = context.getColor(
                    val tintColor =
                        context.getColor(
                            if (item.isActive) InternalR.color.materialColorOnPrimaryContainer
                            else InternalR.color.materialColorOnSurface
                        )
+21 −0
Original line number Diff line number Diff line
@@ -35,6 +35,8 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository
import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractorImpl
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.data.repository.ShadeDisplaysRepositoryImpl
import com.android.systemui.shade.display.ShadeDisplayPolicyModule
@@ -215,6 +217,25 @@ object ShadeDisplayAwareModule {
        return impl
    }

    @Provides
    @SysUISingleton
    fun provideShadeDialogContextInteractor(
        impl: ShadeDialogContextInteractorImpl
    ): ShadeDialogContextInteractor = impl

    @Provides
    @IntoMap
    @ClassKey(ShadeDialogContextInteractor::class)
    fun provideShadeDialogContextInteractorCoreStartable(
        impl: Provider<ShadeDialogContextInteractorImpl>
    ): CoreStartable {
        return if (ShadeWindowGoesAround.isEnabled) {
            impl.get()
        } else {
            CoreStartable.NOP
        }
    }

    @Provides
    @IntoMap
    @ClassKey(ShadePrimaryDisplayCommand::class)
+23 −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.domain.interactor

import android.content.Context

/** Fake context repository that always returns the same context. */
class FakeShadeDialogContextInteractor(override val context: Context) :
    ShadeDialogContextInteractor
+97 −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.domain.interactor

import android.content.Context
import android.util.Log
import android.view.Display
import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL
import com.android.app.tracing.coroutines.launchTraced
import com.android.app.tracing.traceSection
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter

/** Provides the correct context to show dialogs on the shade window, whenever it moves. */
interface ShadeDialogContextInteractor {
    /** Context usable to create dialogs on the notification shade display. */
    val context: Context
}

@SysUISingleton
class ShadeDialogContextInteractorImpl
@Inject
constructor(
    @Main private val defaultContext: Context,
    private val displayWindowPropertyRepository: Provider<DisplayWindowPropertiesRepository>,
    private val shadeDisplaysRepository: ShadeDisplaysRepository,
    @Background private val bgScope: CoroutineScope,
) : CoreStartable, ShadeDialogContextInteractor {

    override fun start() {
        if (ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()) return
        bgScope.launchTraced(TAG) {
            shadeDisplaysRepository.displayId
                // No need for default display pre-warming.
                .filter { it != Display.DEFAULT_DISPLAY }
                .collectLatest { displayId ->
                    // Prewarms the context in the background every time the display changes.
                    // In this way, there will be no main thread delays when a dialog is shown.
                    getContextOrDefault(displayId)
                }
        }
    }

    override val context: Context
        get() {
            if (!ShadeWindowGoesAround.isEnabled) {
                return defaultContext
            }
            val displayId = shadeDisplaysRepository.displayId.value
            return getContextOrDefault(displayId)
        }

    private fun getContextOrDefault(displayId: Int): Context {
        return try {
            traceSection({ "Getting dialog context for displayId=$displayId" }) {
                displayWindowPropertyRepository.get().get(displayId, DIALOG_WINDOW_TYPE).context
            }
        } catch (e: Exception) {
            // This can happen if the display was disconnected in the meantime.
            Log.e(
                TAG,
                "Couldn't get dialog context for displayId=$displayId. Returning default one",
                e,
            )
            defaultContext
        }
    }

    private companion object {
        const val TAG = "ShadeDialogContextRepo"
        const val DIALOG_WINDOW_TYPE = TYPE_STATUS_BAR_SUB_PANEL
    }
}
Loading