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

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

User data layer.

UserSwitcherActivity refactor: CL 4/7

This is the data layer for the UserSwitcherActivity refactor. It
includes a repository and a couple of shared models that the repository
exposes.

Bug: 243844359
Test: Unit tests included. Also tested as part of the big manual test in
CL 7/7 in this chain

Change-Id: I4cb5b1c4f018def88cda5864de51c644c6d8ce0f
parent 6a449c62
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.user;
import android.app.Activity;

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

import dagger.Binds;
import dagger.Module;
@@ -29,7 +30,11 @@ import dagger.multibindings.IntoMap;
/**
 * Dagger module for User related classes.
 */
@Module
@Module(
        includes = {
                UserRepositoryModule.class,
        }
)
public abstract class UserModule {

    private static final String FILE_PROVIDER_AUTHORITY = "com.android.systemui.fileprovider";
+166 −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.data.repository

import android.content.Context
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.UserManager
import androidx.appcompat.content.res.AppCompatResources
import com.android.internal.util.UserIcons
import com.android.systemui.R
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.user.data.source.UserRecord
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.shared.model.UserModel
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

/**
 * Acts as source of truth for user related data.
 *
 * Abstracts-away data sources and their schemas so the rest of the app doesn't need to worry about
 * upstream changes.
 */
interface UserRepository {
    /** List of all users on the device. */
    val users: Flow<List<UserModel>>

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

    /** List of available user-related actions. */
    val actions: Flow<List<UserActionModel>>

    /** Whether actions are available even when locked. */
    val isActionableWhenLocked: Flow<Boolean>

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

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

@SysUISingleton
class UserRepositoryImpl
@Inject
constructor(
    @Application private val appContext: Context,
    private val manager: UserManager,
    controller: UserSwitcherController,
) : UserRepository {

    private val userRecords: Flow<List<UserRecord>> = conflatedCallbackFlow {
        fun send() {
            trySendWithFailureLogging(
                controller.users,
                TAG,
            )
        }

        val callback = UserSwitcherController.UserSwitchCallback { send() }

        controller.addUserSwitchCallback(callback)
        send()

        awaitClose { controller.removeUserSwitchCallback(callback) }
    }

    override val users: Flow<List<UserModel>> =
        userRecords.map { records -> records.filter { it.isUser() }.map { it.toUserModel() } }

    override val selectedUser: Flow<UserModel> =
        users.map { users -> users.first { user -> user.isSelected } }

    override val actions: Flow<List<UserActionModel>> =
        userRecords.map { records -> records.filter { it.isNotUser() }.map { it.toActionModel() } }

    override val isActionableWhenLocked: Flow<Boolean> = controller.addUsersFromLockScreen

    override val isGuestUserAutoCreated: Boolean = controller.isGuestUserAutoCreated

    override val isGuestUserResetting: Boolean = controller.isGuestUserResetting

    private fun UserRecord.isUser(): Boolean {
        return when {
            isAddUser -> false
            isAddSupervisedUser -> false
            isGuest -> info != null
            else -> true
        }
    }

    private fun UserRecord.isNotUser(): Boolean {
        return !isUser()
    }

    private fun UserRecord.toUserModel(): UserModel {
        return UserModel(
            id = resolveId(),
            name = getUserName(this),
            image = getUserImage(this),
            isSelected = isCurrent,
            isSelectable = isSwitchToEnabled || isGuest,
        )
    }

    private fun UserRecord.toActionModel(): UserActionModel {
        return when {
            isAddUser -> UserActionModel.ADD_USER
            isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER
            isGuest -> UserActionModel.ENTER_GUEST_MODE
            else -> error("Don't know how to convert to UserActionModel: $this")
        }
    }

    private fun getUserName(record: UserRecord): Text {
        val resourceId: Int? = LegacyUserUiHelper.getGuestUserRecordNameResourceId(record)
        return if (resourceId != null) {
            Text.Resource(resourceId)
        } else {
            Text.Loaded(checkNotNull(record.info).name)
        }
    }

    private fun getUserImage(record: UserRecord): Drawable {
        if (record.isGuest) {
            return checkNotNull(
                AppCompatResources.getDrawable(appContext, R.drawable.ic_account_circle)
            )
        }

        val userId = checkNotNull(record.info?.id)
        return manager.getUserIcon(userId)?.let { userSelectedIcon ->
            BitmapDrawable(userSelectedIcon)
        }
            ?: UserIcons.getDefaultUserIcon(appContext.resources, userId, /* light= */ false)
    }

    companion object {
        private const val TAG = "UserRepository"
    }
}
+26 −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.data.repository

import dagger.Binds
import dagger.Module

@Module
interface UserRepositoryModule {
    @Binds fun bindRepository(impl: UserRepositoryImpl): UserRepository
}
+10 −22
Original line number Diff line number Diff line
@@ -20,41 +20,29 @@ import android.content.pm.UserInfo
import android.graphics.Bitmap
import android.os.UserHandle

/**
 * Encapsulates raw data for a user or an option item related to managing users on the device.
 */
/** Encapsulates raw data for a user or an option item related to managing users on the device. */
data class UserRecord(
    /** Relevant user information. If `null`, this record is not a user but an option item. */
    @JvmField
    val info: UserInfo? = null,
    @JvmField val info: UserInfo? = null,
    /** An image representing the user. */
    @JvmField
    val picture: Bitmap? = null,
    @JvmField val picture: Bitmap? = null,
    /** Whether this record represents an option to switch to a guest user. */
    @JvmField
    val isGuest: Boolean = false,
    @JvmField val isGuest: Boolean = false,
    /** Whether this record represents the currently-selected user. */
    @JvmField
    val isCurrent: Boolean = false,
    @JvmField val isCurrent: Boolean = false,
    /** Whether this record represents an option to add another user to the device. */
    @JvmField
    val isAddUser: Boolean = false,
    @JvmField val isAddUser: Boolean = false,
    /**
     * If true, the record is only available if unlocked or if the user has granted permission to
     * access this user action whilst on the device is locked.
     */
    @JvmField
    val isRestricted: Boolean = false,
    @JvmField val isRestricted: Boolean = false,
    /** Whether it is possible to switch to this user. */
    @JvmField
    val isSwitchToEnabled: Boolean = false,
    @JvmField val isSwitchToEnabled: Boolean = false,
    /** Whether this record represents an option to add another supervised user to the device. */
    @JvmField
    val isAddSupervisedUser: Boolean = false,
    @JvmField val isAddSupervisedUser: Boolean = false,
) {
    /**
     * Returns a new instance of [UserRecord] with its [isCurrent] set to the given value.
     */
    /** Returns a new instance of [UserRecord] with its [isCurrent] set to the given value. */
    fun copyWithIsCurrent(isCurrent: Boolean): UserRecord {
        return copy(isCurrent = isCurrent)
    }
+25 −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.shared.model

enum class UserActionModel {
    ENTER_GUEST_MODE,
    ADD_USER,
    ADD_SUPERVISED_USER,
    NAVIGATE_TO_USER_MANAGEMENT,
}
Loading