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

Commit ae0fcebd authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

user UI layer changes to support USC dep removal

Minor changes to the view-model class and the addition of a
UserSwitcherDialogCoordinator to be able to show and dismiss dialogs
based on state exposed by the interactor.

Bug: 246631653
Test: unit tests for new coordinator class included, manually verified
the operations of the full-screen user switcher, the smaller dialog, the
footer in quick settings, and the dropdown switcher on the lock-screen
bouncer.

Change-Id: I058541bb5c31023735121bbad54b93afba73a89a
parent fbbb1a8b
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.app.Activity;

import com.android.settingslib.users.EditUserInfoController;
import com.android.systemui.user.data.repository.UserRepositoryModule;
import com.android.systemui.user.ui.dialog.UserDialogModule;

import dagger.Binds;
import dagger.Module;
@@ -32,6 +33,7 @@ import dagger.multibindings.IntoMap;
 */
@Module(
        includes = {
                UserDialogModule.class,
                UserRepositoryModule.class,
        }
)
+1 −2
Original line number Diff line number Diff line
@@ -33,6 +33,5 @@ data class UserModel(
    /** Whether this use is selectable. A non-selectable user cannot be switched to. */
    val isSelectable: Boolean,
    /** Whether this model represents the guest user. */
    // TODO(b/246631653): remove this default value it was only here to be able to split up CLs
    val isGuest: Boolean = false,
    val isGuest: Boolean,
)
+33 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.user.ui.dialog

import com.android.systemui.CoreStartable
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap

@Module
interface UserDialogModule {

    @Binds
    @IntoMap
    @ClassKey(UserSwitcherDialogCoordinator::class)
    fun bindFeature(impl: UserSwitcherDialogCoordinator): CoreStartable
}
+122 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.user.ui.dialog

import android.app.Dialog
import android.content.Context
import com.android.settingslib.users.UserCreatingDialog
import com.android.systemui.CoreStartable
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastSender
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.FalsingManager
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.domain.model.ShowDialogRequestModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch

/** Coordinates dialogs for user switcher logic. */
@SysUISingleton
class UserSwitcherDialogCoordinator
@Inject
constructor(
    @Application private val context: Context,
    @Application private val applicationScope: CoroutineScope,
    private val falsingManager: FalsingManager,
    private val broadcastSender: BroadcastSender,
    private val dialogLaunchAnimator: DialogLaunchAnimator,
    private val interactor: UserInteractor,
    private val featureFlags: FeatureFlags,
) : CoreStartable(context) {

    private var currentDialog: Dialog? = null

    override fun start() {
        if (!featureFlags.isEnabled(Flags.REFACTORED_USER_SWITCHER_CONTROLLER)) {
            return
        }

        startHandlingDialogShowRequests()
        startHandlingDialogDismissRequests()
    }

    private fun startHandlingDialogShowRequests() {
        applicationScope.launch {
            interactor.dialogShowRequests.filterNotNull().collect { request ->
                currentDialog?.let {
                    if (it.isShowing) {
                        it.cancel()
                    }
                }

                currentDialog =
                    when (request) {
                        is ShowDialogRequestModel.ShowAddUserDialog ->
                            AddUserDialog(
                                context = context,
                                userHandle = request.userHandle,
                                isKeyguardShowing = request.isKeyguardShowing,
                                showEphemeralMessage = request.showEphemeralMessage,
                                falsingManager = falsingManager,
                                broadcastSender = broadcastSender,
                                dialogLaunchAnimator = dialogLaunchAnimator,
                            )
                        is ShowDialogRequestModel.ShowUserCreationDialog ->
                            UserCreatingDialog(
                                context,
                                request.isGuest,
                            )
                        is ShowDialogRequestModel.ShowExitGuestDialog ->
                            ExitGuestDialog(
                                context = context,
                                guestUserId = request.guestUserId,
                                isGuestEphemeral = request.isGuestEphemeral,
                                targetUserId = request.targetUserId,
                                isKeyguardShowing = request.isKeyguardShowing,
                                falsingManager = falsingManager,
                                dialogLaunchAnimator = dialogLaunchAnimator,
                                onExitGuestUserListener = request.onExitGuestUser,
                            )
                    }

                currentDialog?.show()
                interactor.onDialogShown()
            }
        }
    }

    private fun startHandlingDialogDismissRequests() {
        applicationScope.launch {
            interactor.dialogDismissRequests.filterNotNull().collect {
                currentDialog?.let {
                    if (it.isShowing) {
                        it.cancel()
                    }
                }

                interactor.onDialogDismissed()
            }
        }
    }
}
+47 −9
Original line number Diff line number Diff line
@@ -21,7 +21,10 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.android.systemui.R
import com.android.systemui.common.ui.drawable.CircularDrawable
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.user.domain.interactor.GuestUserInteractor
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
import com.android.systemui.user.shared.model.UserActionModel
@@ -36,9 +39,14 @@ import kotlinx.coroutines.flow.map
class UserSwitcherViewModel
private constructor(
    private val userInteractor: UserInteractor,
    private val guestUserInteractor: GuestUserInteractor,
    private val powerInteractor: PowerInteractor,
    private val featureFlags: FeatureFlags,
) : ViewModel() {

    private val isNewImpl: Boolean
        get() = featureFlags.isEnabled(Flags.REFACTORED_USER_SWITCHER_CONTROLLER)

    /** On-device users. */
    val users: Flow<List<UserViewModel>> =
        userInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
@@ -47,9 +55,6 @@ private constructor(
    val maximumUserColumns: Flow<Int> =
        users.map { LegacyUserUiHelper.getMaxUserSwitcherItemColumns(it.size) }

    /** Whether the button to open the user action menu is visible. */
    val isOpenMenuButtonVisible: Flow<Boolean> = userInteractor.actions.map { it.isNotEmpty() }

    private val _isMenuVisible = MutableStateFlow(false)
    /**
     * Whether the user action menu should be shown. Once the action menu is dismissed/closed, the
@@ -58,9 +63,23 @@ private constructor(
    val isMenuVisible: Flow<Boolean> = _isMenuVisible
    /** The user action menu. */
    val menu: Flow<List<UserActionViewModel>> =
        userInteractor.actions.map { actions -> actions.map { action -> toViewModel(action) } }
        userInteractor.actions.map { actions ->
            if (isNewImpl && actions.isNotEmpty()) {
                    // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT because that's a user
                    // switcher specific action that is not known to the our data source or other
                    // features.
                    actions + listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
                } else {
                    actions
                }
                .map { action -> toViewModel(action) }
        }

    /** Whether the button to open the user action menu is visible. */
    val isOpenMenuButtonVisible: Flow<Boolean> = menu.map { it.isNotEmpty() }

    private val hasCancelButtonBeenClicked = MutableStateFlow(false)
    private val isFinishRequiredDueToExecutedAction = MutableStateFlow(false)

    /**
     * Whether the observer should finish the experience. Once consumed, [onFinished] must be called
@@ -81,6 +100,7 @@ private constructor(
     */
    fun onFinished() {
        hasCancelButtonBeenClicked.value = false
        isFinishRequiredDueToExecutedAction.value = false
    }

    /** Notifies that the user has clicked the "open menu" button. */
@@ -120,8 +140,10 @@ private constructor(
            },
            // When the cancel button is clicked, we should finish.
            hasCancelButtonBeenClicked,
        ) { selectedUserChanged, screenTurnedOff, cancelButtonClicked ->
            selectedUserChanged || screenTurnedOff || cancelButtonClicked
            // If an executed action told us to finish, we should finish,
            isFinishRequiredDueToExecutedAction,
        ) { selectedUserChanged, screenTurnedOff, cancelButtonClicked, executedActionFinish ->
            selectedUserChanged || screenTurnedOff || cancelButtonClicked || executedActionFinish
        }
    }

@@ -164,13 +186,25 @@ private constructor(
                } else {
                    LegacyUserUiHelper.getUserSwitcherActionTextResourceId(
                        isGuest = model == UserActionModel.ENTER_GUEST_MODE,
                        isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
                        isGuestUserResetting = userInteractor.isGuestUserResetting,
                        isGuestUserAutoCreated = guestUserInteractor.isGuestUserAutoCreated,
                        isGuestUserResetting = guestUserInteractor.isGuestUserResetting,
                        isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
                        isAddUser = model == UserActionModel.ADD_USER,
                    )
                },
            onClicked = { userInteractor.executeAction(action = model) },
            onClicked = {
                userInteractor.executeAction(action = model)
                // We don't finish because we want to show a dialog over the full-screen UI and
                // that dialog can be dismissed in case the user changes their mind and decides not
                // to add a user.
                //
                // We finish for all other actions because they navigate us away from the
                // full-screen experience or are destructive (like changing to the guest user).
                val shouldFinish = model != UserActionModel.ADD_USER
                if (shouldFinish) {
                    isFinishRequiredDueToExecutedAction.value = true
                }
            },
        )
    }

@@ -186,13 +220,17 @@ private constructor(
    @Inject
    constructor(
        private val userInteractor: UserInteractor,
        private val guestUserInteractor: GuestUserInteractor,
        private val powerInteractor: PowerInteractor,
        private val featureFlags: FeatureFlags,
    ) : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            @Suppress("UNCHECKED_CAST")
            return UserSwitcherViewModel(
                userInteractor = userInteractor,
                guestUserInteractor = guestUserInteractor,
                powerInteractor = powerInteractor,
                featureFlags = featureFlags,
            )
                as T
        }
Loading