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

Commit 23aaf018 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Listen to lock mode change for connection dialog" into main

parents f3ada595 df5d651c
Loading
Loading
Loading
Loading
+5 −0
Original line number Original line 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.DisplaysWithDecorationsRepositoryImpl
import com.android.systemui.display.data.repository.FocusedDisplayRepository
import com.android.systemui.display.data.repository.FocusedDisplayRepository
import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl
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.data.repository.PerDisplayRepoDumpHelper
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
@@ -77,6 +79,9 @@ interface DisplayModule {
        deviceStateRepository: DeviceStateRepositoryImpl
        deviceStateRepository: DeviceStateRepositoryImpl
    ): DeviceStateRepository
    ): DeviceStateRepository


    @Binds
    fun bindsKioskModeRepository(kioskModeRepository: KioskModeRepositoryImpl): KioskModeRepository

    @Binds
    @Binds
    fun bindsFocusedDisplayRepository(
    fun bindsFocusedDisplayRepository(
        focusedDisplayRepository: FocusedDisplayRepositoryImpl
        focusedDisplayRepository: FocusedDisplayRepositoryImpl
+71 −0
Original line number Original line 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 Original line Diff line number Diff line
@@ -15,8 +15,6 @@
 */
 */
package com.android.systemui.display.ui.viewmodel
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.app.Dialog
import android.content.Context
import android.content.Context
import android.provider.Settings.Secure.MIRROR_BUILT_IN_DISPLAY
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.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
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
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.ui.view.ExternalDisplayConnectionDialogDelegate
import com.android.systemui.display.ui.view.ExternalDisplayConnectionDialogDelegate
@@ -75,7 +74,7 @@ constructor(
    private val context: Context,
    private val context: Context,
    private val desktopState: DesktopState,
    private val desktopState: DesktopState,
    private val secureSettings: SecureSettings,
    private val secureSettings: SecureSettings,
    private val activityManager: ActivityManager,
    private val kioskModeRepository: KioskModeRepository,
    private val connectedDisplayInteractor: ConnectedDisplayInteractor,
    private val connectedDisplayInteractor: ConnectedDisplayInteractor,
    @Application private val scope: CoroutineScope,
    @Application private val scope: CoroutineScope,
    @Background private val bgDispatcher: CoroutineDispatcher,
    @Background private val bgDispatcher: CoroutineDispatcher,
@@ -91,27 +90,33 @@ constructor(
    @OptIn(FlowPreview::class)
    @OptIn(FlowPreview::class)
    override fun start() {
    override fun start() {
        val pendingDisplayFlow = connectedDisplayInteractor.pendingDisplay
        val pendingDisplayFlow = connectedDisplayInteractor.pendingDisplay
        val kioskModeFlow = kioskModeRepository.isInKioskMode
        val concurrentDisplaysInProgressFlow =
        val concurrentDisplaysInProgressFlow =
            if (Flags.enableDualDisplayBlocking()) {
            if (Flags.enableDualDisplayBlocking()) {
                connectedDisplayInteractor.concurrentDisplaysInProgress
                connectedDisplayInteractor.concurrentDisplaysInProgress
            } else {
            } else {
                flow { emit(false) }
                flow { emit(false) }
            }
            }
        pendingDisplayFlow

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

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


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


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


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