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

Commit cf5fd08f authored by Caitlin Shkuratov's avatar Caitlin Shkuratov Committed by Android (Google) Code Review
Browse files

Merge changes from topics "caitlinshk-main-backgesture",...

Merge changes from topics "caitlinshk-main-backgesture", "caitlinshk-main-cs-notiflogger", "caitlinshk-main-statusbarservice" into main

* changes:
  [CS] Have NotificationGutsManager listen for shade/LS vis directly.
  [CS] 4/4: Update IStatusBarService from WindowRootViewVisIntr/Repo.
  [CS] 3/4: Move back gesture callback registration to back interactor.
  [CS] 2/4: Have NotificationLogger listen to shade/LS visibility directly
  [CS] 1/4: Add WindowRootViewVisibilityInteractor visibility flows
parents c56011bb f882c667
Loading
Loading
Loading
Loading
+80 −2
Original line number Diff line number Diff line
@@ -16,24 +16,69 @@

package com.android.systemui.back.domain.interactor

import android.window.BackEvent
import android.window.OnBackAnimationCallback
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import android.window.WindowOnBackInvokedDispatcher
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.shade.QuickSettingsController
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

/** Handles requests to go back either from a button or gesture. */
@SysUISingleton
class BackActionInteractor
@Inject
constructor(
    @Application private val scope: CoroutineScope,
    private val statusBarStateController: StatusBarStateController,
    private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
    private val shadeController: ShadeController
) {
    private val shadeController: ShadeController,
    private val notificationShadeWindowController: NotificationShadeWindowController,
    private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor,
    featureFlags: FeatureFlags,
) : CoreStartable {

    private var isCallbackRegistered = false

    private val callback =
        if (featureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE)) {
            /**
             * New callback that handles back gesture invoked, cancel, progress and provides
             * feedback via Shade animation.
             */
            object : OnBackAnimationCallback {
                override fun onBackInvoked() {
                    onBackRequested()
                }

                override fun onBackProgressed(backEvent: BackEvent) {
                    if (shouldBackBeHandled() && shadeViewController.canBeCollapsed()) {
                        shadeViewController.onBackProgressed(backEvent.progress)
                    }
                }
            }
        } else {
            OnBackInvokedCallback { onBackRequested() }
        }

    private val onBackInvokedDispatcher: WindowOnBackInvokedDispatcher?
        get() =
            notificationShadeWindowController.windowRootView?.viewRootImpl?.onBackInvokedDispatcher

    private lateinit var shadeViewController: ShadeViewController
    private lateinit var qsController: QuickSettingsController

@@ -42,6 +87,19 @@ constructor(
        this.shadeViewController = svController
    }

    override fun start() {
        scope.launch {
            windowRootViewVisibilityInteractor.isLockscreenOrShadeVisibleAndInteractive.collect {
                visible ->
                if (visible) {
                    registerBackCallback()
                } else {
                    unregisterBackCallback()
                }
            }
        }
    }

    fun shouldBackBeHandled(): Boolean {
        return statusBarStateController.state != StatusBarState.KEYGUARD &&
            statusBarStateController.state != StatusBarState.SHADE_LOCKED &&
@@ -74,4 +132,24 @@ constructor(
        }
        return false
    }

    private fun registerBackCallback() {
        if (isCallbackRegistered) {
            return
        }
        onBackInvokedDispatcher?.let {
            it.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, callback)
            isCallbackRegistered = true
        }
    }

    private fun unregisterBackCallback() {
        if (!isCallbackRegistered) {
            return
        }
        onBackInvokedDispatcher?.let {
            it.unregisterOnBackInvokedCallback(callback)
            isCallbackRegistered = false
        }
    }
}
+6 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import com.android.systemui.ScreenDecorations
import com.android.systemui.SliceBroadcastRelayHandler
import com.android.systemui.accessibility.SystemActions
import com.android.systemui.accessibility.WindowMagnification
import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.BiometricNotificationService
import com.android.systemui.clipboardoverlay.ClipboardListener
@@ -346,4 +347,9 @@ abstract class SystemUICoreStartableModule {
    abstract fun bindStatusBarHeadsUpChangeListener(
        impl: StatusBarHeadsUpChangeListener
    ): CoreStartable

    @Binds
    @IntoMap
    @ClassKey(BackActionInteractor::class)
    abstract fun bindBackActionInteractor(impl: BackActionInteractor): CoreStartable
}
+23 −17
Original line number Diff line number Diff line
@@ -158,7 +158,7 @@ interface KeyguardRepository {
    val lastDozeTapToWakePosition: StateFlow<Point?>

    /** Observable for the [StatusBarState] */
    val statusBarState: Flow<StatusBarState>
    val statusBarState: StateFlow<StatusBarState>

    /** Observable for device wake/sleep state */
    val wakefulness: StateFlow<WakefulnessModel>
@@ -520,23 +520,29 @@ constructor(
        return keyguardBypassController.bypassEnabled
    }

    override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow {
    // TODO(b/297345631): Expose this at the interactor level instead so that it can be powered by
    // [SceneInteractor] when scenes are ready.
    override val statusBarState: StateFlow<StatusBarState> =
        conflatedCallbackFlow {
                val callback =
                    object : StatusBarStateController.StateListener {
                        override fun onStateChanged(state: Int) {
                    trySendWithFailureLogging(statusBarStateIntToObject(state), TAG, "state")
                }
            }

        statusBarStateController.addCallback(callback)
                            trySendWithFailureLogging(
            statusBarStateIntToObject(statusBarStateController.getState()),
                                statusBarStateIntToObject(state),
                                TAG,
            "initial state"
                                "state"
                            )
                        }
                    }

                statusBarStateController.addCallback(callback)
                awaitClose { statusBarStateController.removeCallback(callback) }
            }
            .stateIn(
                scope,
                SharingStarted.Eagerly,
                statusBarStateIntToObject(statusBarStateController.state)
            )

    override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
        fun dispatchUpdate() {
+74 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.scene.data.repository

import android.os.RemoteException
import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.UiBackground
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow

/** Source of truth for the visibility of various parts of the window root view. */
@SysUISingleton
class WindowRootViewVisibilityRepository
@Inject
constructor(
    private val statusBarService: IStatusBarService,
    @UiBackground private val uiBgExecutor: Executor,
) {
    private val _isLockscreenOrShadeVisible = MutableStateFlow(false)
    val isLockscreenOrShadeVisible: StateFlow<Boolean> = _isLockscreenOrShadeVisible.asStateFlow()

    fun setIsLockscreenOrShadeVisible(visible: Boolean) {
        _isLockscreenOrShadeVisible.value = visible
    }

    /**
     * Called when the lockscreen or shade has been shown and can be interacted with so that SysUI
     * can notify external services.
     */
    fun onLockscreenOrShadeInteractive(
        shouldClearNotificationEffects: Boolean,
        notificationCount: Int,
    ) {
        executeServiceCallOnUiBg {
            statusBarService.onPanelRevealed(shouldClearNotificationEffects, notificationCount)
        }
    }

    /**
     * Called when the lockscreen or shade no longer can be interactecd with so that SysUI can
     * notify external services.
     */
    fun onLockscreenOrShadeNotInteractive() {
        executeServiceCallOnUiBg { statusBarService.onPanelHidden() }
    }

    private fun executeServiceCallOnUiBg(runnable: () -> Unit) {
        uiBgExecutor.execute {
            try {
                runnable.invoke()
            } catch (ex: RemoteException) {
                // Won't fail unless the world has ended
            }
        }
    }
}
+121 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.scene.domain.interactor

import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.init.NotificationsController
import com.android.systemui.statusbar.policy.HeadsUpManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

/** Business logic about the visibility of various parts of the window root view. */
@SysUISingleton
class WindowRootViewVisibilityInteractor
@Inject
constructor(
    @Application private val scope: CoroutineScope,
    private val windowRootViewVisibilityRepository: WindowRootViewVisibilityRepository,
    private val keyguardRepository: KeyguardRepository,
    private val headsUpManager: HeadsUpManager,
) : CoreStartable {

    private var notificationPresenter: NotificationPresenter? = null
    private var notificationsController: NotificationsController? = null

    private val isNotifPresenterFullyCollapsed: Boolean
        get() = notificationPresenter?.isPresenterFullyCollapsed ?: true

    /**
     * True if lockscreen (including AOD) or the shade is visible and false otherwise. Notably,
     * false if the bouncer is visible.
     *
     * TODO(b/297080059): Use [SceneInteractor] as the source of truth if the scene flag is on.
     */
    val isLockscreenOrShadeVisible: StateFlow<Boolean> =
        windowRootViewVisibilityRepository.isLockscreenOrShadeVisible

    /**
     * True if lockscreen (including AOD) or the shade is visible **and** the user is currently
     * interacting with the device, false otherwise. Notably, false if the bouncer is visible and
     * false if the device is asleep.
     */
    val isLockscreenOrShadeVisibleAndInteractive: StateFlow<Boolean> =
        combine(
                isLockscreenOrShadeVisible,
                keyguardRepository.wakefulness,
            ) { isKeyguardAodOrShadeVisible, wakefulness ->
                isKeyguardAodOrShadeVisible && wakefulness.isDeviceInteractive()
            }
            .stateIn(scope, SharingStarted.Eagerly, initialValue = false)

    /**
     * Sets classes that aren't easily injectable on this class.
     *
     * TODO(b/277762009): Inject these directly instead.
     */
    fun setUp(
        presenter: NotificationPresenter?,
        notificationsController: NotificationsController?,
    ) {
        this.notificationPresenter = presenter
        this.notificationsController = notificationsController
    }

    override fun start() {
        scope.launch {
            isLockscreenOrShadeVisibleAndInteractive.collect { interactive ->
                if (interactive) {
                    windowRootViewVisibilityRepository.onLockscreenOrShadeInteractive(
                        getShouldClearNotificationEffects(keyguardRepository.statusBarState.value),
                        getNotificationLoad(),
                    )
                } else {
                    windowRootViewVisibilityRepository.onLockscreenOrShadeNotInteractive()
                }
            }
        }
    }

    fun setIsLockscreenOrShadeVisible(visible: Boolean) {
        windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(visible)
    }

    private fun getShouldClearNotificationEffects(statusBarState: StatusBarState): Boolean {
        return !isNotifPresenterFullyCollapsed &&
            (statusBarState == StatusBarState.SHADE ||
                statusBarState == StatusBarState.SHADE_LOCKED)
    }

    private fun getNotificationLoad(): Int {
        return if (headsUpManager.hasPinnedHeadsUp() && isNotifPresenterFullyCollapsed) {
            1
        } else {
            notificationsController?.getActiveNotificationsCount() ?: 0
        }
    }
}
Loading