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

Commit df5d651c authored by Daniel Akinola's avatar Daniel Akinola
Browse files

Listen to lock mode change for connection dialog

Instead of only checking lock mode once, we instead listen for changes
and use the up to date value to generate the appropriate state of the
dialog

Bug: 429288575
Test: manual testing
Flag: com.android.window.flags.enable_updated_display_connection_dialog
Change-Id: Ic0969ed5d85a443339af31a7408ced296ec5bd92
parent e9c57ef7
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -40,6 +40,8 @@ import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepos
import com.android.systemui.display.data.repository.DisplaysWithDecorationsRepositoryImpl
import com.android.systemui.display.data.repository.FocusedDisplayRepository
import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl
import com.android.systemui.display.data.repository.KioskModeRepository
import com.android.systemui.display.data.repository.KioskModeRepositoryImpl
import com.android.systemui.display.data.repository.PerDisplayRepoDumpHelper
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
@@ -77,6 +79,9 @@ interface DisplayModule {
        deviceStateRepository: DeviceStateRepositoryImpl
    ): DeviceStateRepository

    @Binds
    fun bindsKioskModeRepository(kioskModeRepository: KioskModeRepositoryImpl): KioskModeRepository

    @Binds
    fun bindsFocusedDisplayRepository(
        focusedDisplayRepository: FocusedDisplayRepositoryImpl
+71 −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.display.data.repository

import android.app.ActivityManager
import android.app.ActivityManager.LOCK_TASK_MODE_LOCKED
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.kairos.awaitClose
import com.android.systemui.shared.system.TaskStackChangeListener
import com.android.systemui.shared.system.TaskStackChangeListeners
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn

/** Repository for the current kiosk mode (lock task mode) state. */
interface KioskModeRepository {
    /** A flow that emits `true` when the device enters kiosk mode, and `false` when it exits. */
    val isInKioskMode: StateFlow<Boolean>
}

@SysUISingleton
class KioskModeRepositoryImpl
@Inject
constructor(
    @Application private val scope: CoroutineScope,
    @Background private val bgContext: CoroutineContext,
    private val activityManager: ActivityManager,
    private val taskStackChangeListeners: TaskStackChangeListeners,
) : KioskModeRepository {

    override val isInKioskMode: StateFlow<Boolean> =
        conflatedCallbackFlow {
                val listener =
                    object : TaskStackChangeListener {
                        override fun onLockTaskModeChanged(mode: Int) {
                            trySend(mode == LOCK_TASK_MODE_LOCKED)
                        }
                    }

                taskStackChangeListeners.registerTaskStackListener(listener)

                // When the flow is cancelled (e.g., the scope is destroyed),
                // unregister the listener to prevent memory leaks.
                awaitClose { taskStackChangeListeners.unregisterTaskStackListener(listener) }
            }
            .onStart { emit(activityManager.lockTaskModeState == LOCK_TASK_MODE_LOCKED) }
            .flowOn(bgContext)
            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
}
+20 −14
Original line number Diff line number Diff line
@@ -15,8 +15,6 @@
 */
package com.android.systemui.display.ui.viewmodel

import android.app.ActivityManager
import android.app.ActivityManager.LOCK_TASK_MODE_LOCKED
import android.app.Dialog
import android.content.Context
import android.provider.Settings.Secure.MIRROR_BUILT_IN_DISPLAY
@@ -39,6 +37,7 @@ import com.android.systemui.biometrics.Utils.getInsetsOf
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.KioskModeRepository
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.ui.view.ExternalDisplayConnectionDialogDelegate
@@ -75,7 +74,7 @@ constructor(
    private val context: Context,
    private val desktopState: DesktopState,
    private val secureSettings: SecureSettings,
    private val activityManager: ActivityManager,
    private val kioskModeRepository: KioskModeRepository,
    private val connectedDisplayInteractor: ConnectedDisplayInteractor,
    @Application private val scope: CoroutineScope,
    @Background private val bgDispatcher: CoroutineDispatcher,
@@ -91,27 +90,33 @@ constructor(
    @OptIn(FlowPreview::class)
    override fun start() {
        val pendingDisplayFlow = connectedDisplayInteractor.pendingDisplay
        val kioskModeFlow = kioskModeRepository.isInKioskMode
        val concurrentDisplaysInProgressFlow =
            if (Flags.enableDualDisplayBlocking()) {
                connectedDisplayInteractor.concurrentDisplaysInProgress
            } else {
                flow { emit(false) }
            }
        pendingDisplayFlow

        // Let's debounce for 2 reasons:
            // - prevent fast dialog flashes in case pending displays are available for just a few
            // millis
            // - Prevent jumps related to inset changes: when in 3 buttons navigation, device
            // unlock triggers a change in insets that might result in a jump of the dialog (if a
            // display was connected while on the lockscreen).
            .debounce(200.milliseconds)
            .combine(concurrentDisplaysInProgressFlow) {
        // - prevent fast dialog flashes where pending displays are available for just a few millis
        // - prevent jumps related to inset changes: when in 3 buttons navigation, device unlock
        //   triggers a change in insets that might result in a jump of the dialog (if a display was
        //   connected while on the lockscreen).
        val debouncedPendingDisplayFlow = pendingDisplayFlow.debounce(200.milliseconds)

        combine(debouncedPendingDisplayFlow, kioskModeFlow, concurrentDisplaysInProgressFlow) {
                pendingDisplay,
                isInKioskMode,
                concurrentDisplaysInProgress ->
                if (pendingDisplay == null) {
                    dismissDialog()
                } else {
                    handleNewPendingDisplay(pendingDisplay, concurrentDisplaysInProgress)
                    handleNewPendingDisplay(
                        pendingDisplay,
                        isInKioskMode,
                        concurrentDisplaysInProgress,
                    )
                }
            }
            .launchIn(scope)
@@ -170,6 +175,7 @@ constructor(

    private suspend fun handleNewPendingDisplay(
        pendingDisplay: PendingDisplay,
        isInKioskMode: Boolean,
        concurrentDisplaysInProgress: Boolean,
    ) {
        val useNewDialog =
@@ -180,10 +186,10 @@ constructor(
            return
        }

        val isInKioskMode = activityManager.lockTaskModeState == LOCK_TASK_MODE_LOCKED
        val isInExtendedMode = desktopState.isDesktopModeSupportedOnDisplay(DEFAULT_DISPLAY)

        if (isInKioskMode) {
            dismissDialog()
            pendingDisplay.showNewDialog(concurrentDisplaysInProgress, isInKioskMode)
        } else if (isInExtendedMode) {
            pendingDisplay.enableForDesktop()