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

Commit 2ee73627 authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

User domain layer.

UserSwitcherActivity refactor: CL 5/7

This CL adds the domain layer, which is just the UserInteractor really.

Bug: 243844359
Test: Unit tests included. Also verified manually as part of the
testing of CL 7/7 in the chain.

Change-Id: I83e45a0060ab746874107c79bc8a1e2aeb07e409
parent 2aca8b4d
Loading
Loading
Loading
Loading
+110 −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.domain.interactor

import android.content.Intent
import android.provider.Settings
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.shared.model.UserModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map

/** Encapsulates business logic to interact with user data and systems. */
@SysUISingleton
class UserInteractor
@Inject
constructor(
    repository: UserRepository,
    private val controller: UserSwitcherController,
    private val activityStarter: ActivityStarter,
    keyguardInteractor: KeyguardInteractor,
) {
    /** List of current on-device users to select from. */
    val users: Flow<List<UserModel>> = repository.users

    /** The currently-selected user. */
    val selectedUser: Flow<UserModel> = repository.selectedUser

    /** List of user-switcher related actions that are available. */
    val actions: Flow<List<UserActionModel>> =
        combine(
                repository.isActionableWhenLocked,
                keyguardInteractor.isKeyguardShowing,
            ) { isActionableWhenLocked, isLocked ->
                isActionableWhenLocked || !isLocked
            }
            .flatMapLatest { isActionable ->
                if (isActionable) {
                    repository.actions.map { actions ->
                        actions +
                            if (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.
                                listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
                            } else {
                                // If no actions, don't add the navigate action.
                                emptyList()
                            }
                    }
                } else {
                    // If not actionable it means that we're not allowed to show actions when locked
                    // and we
                    // are locked. Therefore, we should show no actions.
                    flowOf(emptyList())
                }
            }

    /** Whether the device is configured to always have a guest user available. */
    val isGuestUserAutoCreated: Boolean = repository.isGuestUserAutoCreated

    /** Whether the guest user is currently being reset. */
    val isGuestUserResetting: Boolean = repository.isGuestUserResetting

    /** Switches to the user with the given user ID. */
    fun selectUser(
        userId: Int,
    ) {
        controller.onUserSelected(userId, /* dialogShower= */ null)
    }

    /** Executes the given action. */
    fun executeAction(action: UserActionModel) {
        when (action) {
            UserActionModel.ENTER_GUEST_MODE -> controller.createAndSwitchToGuestUser(null)
            UserActionModel.ADD_USER -> controller.showAddUserDialog(null)
            UserActionModel.ADD_SUPERVISED_USER -> controller.startSupervisedUserActivity()
            UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
                activityStarter.startActivity(
                    Intent(Settings.ACTION_USER_SETTINGS),
                    /* dismissShade= */ false,
                )
        }
    }
}
+213 −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.domain.interactor

import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations

@SmallTest
@RunWith(JUnit4::class)
class UserInteractorTest : SysuiTestCase() {

    @Mock private lateinit var controller: UserSwitcherController
    @Mock private lateinit var activityStarter: ActivityStarter

    private lateinit var underTest: UserInteractor

    private lateinit var userRepository: FakeUserRepository
    private lateinit var keyguardRepository: FakeKeyguardRepository

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        userRepository = FakeUserRepository()
        keyguardRepository = FakeKeyguardRepository()
        underTest =
            UserInteractor(
                repository = userRepository,
                controller = controller,
                activityStarter = activityStarter,
                keyguardInteractor =
                    KeyguardInteractor(
                        repository = keyguardRepository,
                    ),
            )
    }

    @Test
    fun `actions - not actionable when locked and locked - no actions`() =
        runBlocking(IMMEDIATE) {
            userRepository.setActions(UserActionModel.values().toList())
            userRepository.setActionableWhenLocked(false)
            keyguardRepository.setKeyguardShowing(true)

            var actions: List<UserActionModel>? = null
            val job = underTest.actions.onEach { actions = it }.launchIn(this)

            assertThat(actions).isEmpty()
            job.cancel()
        }

    @Test
    fun `actions - not actionable when locked and not locked`() =
        runBlocking(IMMEDIATE) {
            userRepository.setActions(
                listOf(
                    UserActionModel.ENTER_GUEST_MODE,
                    UserActionModel.ADD_USER,
                    UserActionModel.ADD_SUPERVISED_USER,
                )
            )
            userRepository.setActionableWhenLocked(false)
            keyguardRepository.setKeyguardShowing(false)

            var actions: List<UserActionModel>? = null
            val job = underTest.actions.onEach { actions = it }.launchIn(this)

            assertThat(actions)
                .isEqualTo(
                    listOf(
                        UserActionModel.ENTER_GUEST_MODE,
                        UserActionModel.ADD_USER,
                        UserActionModel.ADD_SUPERVISED_USER,
                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
                    )
                )
            job.cancel()
        }

    @Test
    fun `actions - actionable when locked and not locked`() =
        runBlocking(IMMEDIATE) {
            userRepository.setActions(
                listOf(
                    UserActionModel.ENTER_GUEST_MODE,
                    UserActionModel.ADD_USER,
                    UserActionModel.ADD_SUPERVISED_USER,
                )
            )
            userRepository.setActionableWhenLocked(true)
            keyguardRepository.setKeyguardShowing(false)

            var actions: List<UserActionModel>? = null
            val job = underTest.actions.onEach { actions = it }.launchIn(this)

            assertThat(actions)
                .isEqualTo(
                    listOf(
                        UserActionModel.ENTER_GUEST_MODE,
                        UserActionModel.ADD_USER,
                        UserActionModel.ADD_SUPERVISED_USER,
                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
                    )
                )
            job.cancel()
        }

    @Test
    fun `actions - actionable when locked and locked`() =
        runBlocking(IMMEDIATE) {
            userRepository.setActions(
                listOf(
                    UserActionModel.ENTER_GUEST_MODE,
                    UserActionModel.ADD_USER,
                    UserActionModel.ADD_SUPERVISED_USER,
                )
            )
            userRepository.setActionableWhenLocked(true)
            keyguardRepository.setKeyguardShowing(true)

            var actions: List<UserActionModel>? = null
            val job = underTest.actions.onEach { actions = it }.launchIn(this)

            assertThat(actions)
                .isEqualTo(
                    listOf(
                        UserActionModel.ENTER_GUEST_MODE,
                        UserActionModel.ADD_USER,
                        UserActionModel.ADD_SUPERVISED_USER,
                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
                    )
                )
            job.cancel()
        }

    @Test
    fun selectUser() {
        val userId = 3

        underTest.selectUser(userId)

        verify(controller).onUserSelected(eq(userId), nullable())
    }

    @Test
    fun `executeAction - guest`() {
        underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)

        verify(controller).createAndSwitchToGuestUser(nullable())
    }

    @Test
    fun `executeAction - add user`() {
        underTest.executeAction(UserActionModel.ADD_USER)

        verify(controller).showAddUserDialog(nullable())
    }

    @Test
    fun `executeAction - add supervised user`() {
        underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)

        verify(controller).startSupervisedUserActivity()
    }

    @Test
    fun `executeAction - manage users`() {
        underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)

        verify(activityStarter).startActivity(any(), anyBoolean())
    }

    companion object {
        private val IMMEDIATE = Dispatchers.Main.immediate
    }
}