diff options
| author | 2024-01-23 12:21:43 -0500 | |
|---|---|---|
| committer | 2024-01-25 19:56:19 -0500 | |
| commit | f30cb97a784ba508a82863ef74ea0135355aad0c (patch) | |
| tree | fee5089bc3b3bf2b33e5caa9851303e7b0c52b07 /java | |
| parent | c5d725eec15d2cff2aab08437948d6d0f2d01a63 (diff) | |
UserInteractor and Profile
A domain component which applies business logic to User data.
This component maps Users to Profiles, the set of which is
defined by Profile.Type
Bug: 309960444
Test: atest IntentResolver-tests-unit:UserInteractorTest \
FakeUserRepositoryTest
Change-Id: I9832836ae019ba1b0ae45366f9fc0e26bc9b23ce
Diffstat (limited to 'java')
| -rw-r--r-- | java/src/com/android/intentresolver/v2/data/repository/UserInfoExt.kt | 4 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/v2/data/repository/UserRepository.kt | 24 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/v2/data/repository/UserRepositoryModule.kt | 7 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/v2/data/repository/UserScopedService.kt | 2 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/v2/domain/interactor/UserInteractor.kt | 92 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/v2/domain/model/Profile.kt | 53 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/v2/shared/model/User.kt (renamed from java/src/com/android/intentresolver/v2/data/model/User.kt) | 23 |
7 files changed, 177 insertions, 28 deletions
diff --git a/java/src/com/android/intentresolver/v2/data/repository/UserInfoExt.kt b/java/src/com/android/intentresolver/v2/data/repository/UserInfoExt.kt index fc82efee..a0b2d1ef 100644 --- a/java/src/com/android/intentresolver/v2/data/repository/UserInfoExt.kt +++ b/java/src/com/android/intentresolver/v2/data/repository/UserInfoExt.kt @@ -1,8 +1,8 @@ package com.android.intentresolver.v2.data.repository import android.content.pm.UserInfo -import com.android.intentresolver.v2.data.model.User -import com.android.intentresolver.v2.data.model.User.Role +import com.android.intentresolver.v2.shared.model.User +import com.android.intentresolver.v2.shared.model.User.Role /** Maps the UserInfo to one of the defined [Roles][User.Role], if possible. */ fun UserInfo.getSupportedUserRole(): Role? = diff --git a/java/src/com/android/intentresolver/v2/data/repository/UserRepository.kt b/java/src/com/android/intentresolver/v2/data/repository/UserRepository.kt index d2011aed..91ad6409 100644 --- a/java/src/com/android/intentresolver/v2/data/repository/UserRepository.kt +++ b/java/src/com/android/intentresolver/v2/data/repository/UserRepository.kt @@ -20,8 +20,8 @@ import com.android.intentresolver.inject.Background import com.android.intentresolver.inject.Main import com.android.intentresolver.inject.ProfileParent import com.android.intentresolver.v2.data.broadcastFlow -import com.android.intentresolver.v2.data.model.User import com.android.intentresolver.v2.data.repository.UserRepositoryImpl.UserEvent +import com.android.intentresolver.v2.shared.model.User import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -49,7 +49,7 @@ interface UserRepository { * * Availability is currently defined as not being in [quietMode][UserInfo.isQuietModeEnabled]. */ - fun isAvailable(user: User): Flow<Boolean> + val availability: Flow<Map<User, Boolean>> /** * Request that availability be updated to the requested state. This currently includes toggling @@ -145,30 +145,16 @@ constructor( override val users: Flow<List<User>> = usersWithState.map { userStateMap -> userStateMap.map { it.user } }.distinctUntilChanged() - private val availability: Flow<Map<UserHandle, Boolean>> = + override val availability: Flow<Map<User, Boolean>> = usersWithState - .map { list -> list.associateBy { it.user.handle }.mapValues { it.value.available } } + .map { list -> list.associate { it.user to it.available } } .distinctUntilChanged() - override fun isAvailable(user: User): Flow<Boolean> { - return isAvailable(user.handle) - } - - @VisibleForTesting - fun isAvailable(handle: UserHandle): Flow<Boolean> { - return availability.map { it[handle] ?: false } - } - override suspend fun requestState(user: User, available: Boolean) { require(user.type == User.Type.PROFILE) { "Only profile users are supported" } - return requestState(user.handle, available) - } - - @VisibleForTesting - suspend fun requestState(user: UserHandle, available: Boolean) { return withContext(backgroundDispatcher) { Log.i(TAG, "requestQuietModeEnabled: ${!available} for user $user") - userManager.requestQuietModeEnabled(/* enableQuietMode = */ !available, user) + userManager.requestQuietModeEnabled(/* enableQuietMode = */ !available, user.handle) } } diff --git a/java/src/com/android/intentresolver/v2/data/repository/UserRepositoryModule.kt b/java/src/com/android/intentresolver/v2/data/repository/UserRepositoryModule.kt index 94f985e7..a84342f4 100644 --- a/java/src/com/android/intentresolver/v2/data/repository/UserRepositoryModule.kt +++ b/java/src/com/android/intentresolver/v2/data/repository/UserRepositoryModule.kt @@ -25,8 +25,11 @@ interface UserRepositoryModule { @Provides @Singleton @ProfileParent - fun profileParent(@ApplicationUser user: UserHandle, userManager: UserManager): UserHandle { - return userManager.getProfileParent(user) ?: user + fun profileParent( + @ApplicationContext context: Context, + userManager: UserManager + ): UserHandle { + return userManager.getProfileParent(context.user) ?: context.user } } diff --git a/java/src/com/android/intentresolver/v2/data/repository/UserScopedService.kt b/java/src/com/android/intentresolver/v2/data/repository/UserScopedService.kt index 7ee78d91..3553744a 100644 --- a/java/src/com/android/intentresolver/v2/data/repository/UserScopedService.kt +++ b/java/src/com/android/intentresolver/v2/data/repository/UserScopedService.kt @@ -2,7 +2,7 @@ package com.android.intentresolver.v2.data.repository import android.content.Context import androidx.core.content.getSystemService -import com.android.intentresolver.v2.data.model.User +import com.android.intentresolver.v2.shared.model.User /** * Provides cached instances of a [system service][Context.getSystemService] created with diff --git a/java/src/com/android/intentresolver/v2/domain/interactor/UserInteractor.kt b/java/src/com/android/intentresolver/v2/domain/interactor/UserInteractor.kt new file mode 100644 index 00000000..e1b3fb36 --- /dev/null +++ b/java/src/com/android/intentresolver/v2/domain/interactor/UserInteractor.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2024 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.intentresolver.v2.domain.interactor + +import android.os.UserHandle +import com.android.intentresolver.inject.ApplicationUser +import com.android.intentresolver.v2.data.repository.UserRepository +import com.android.intentresolver.v2.domain.model.Profile +import com.android.intentresolver.v2.domain.model.Profile.Type +import com.android.intentresolver.v2.shared.model.User +import com.android.intentresolver.v2.shared.model.User.Role +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull + +/** The high level User interface. */ +class UserInteractor +@Inject +constructor( + private val userRepository: UserRepository, + /** The specific [User] of the application which started this one. */ + @ApplicationUser val launchedAs: UserHandle, +) { + /** The profile group associated with the launching app user. */ + val profiles: Flow<List<Profile>> = + userRepository.users.map { users -> + users.mapNotNull { user -> + when (user.role) { + // PERSONAL includes CLONE + Role.PERSONAL -> { + Profile(Type.PERSONAL, user, users.firstOrNull { it.role == Role.CLONE }) + } + Role.CLONE -> { + /* ignore, included above */ + null + } + // others map 1:1 + else -> Profile(profileFromRole(user.role), user) + } + } + } + + /** The [Profile] of the application which started this one. */ + val launchedAsProfile: Flow<Profile> = + profiles.map { profiles -> + // The launching user profile is the one with a primary id or clone id + // matching the application user id. By definition there must always be exactly + // one matching profile for the current user. + profiles.single { + it.primary.id == launchedAs.identifier || it.clone?.id == launchedAs.identifier + } + } + + /** + * Provides a flow to report on the availability of the profile. An unavailable profile may be + * hidden or appear disabled within the app. + */ + fun isAvailable(type: Type): Flow<Boolean> { + val profileFlow = profiles.map { list -> list.firstOrNull { it.type == type } } + return combine(profileFlow, userRepository.availability) { profile, availability -> + when (profile) { + null -> false + else -> availability.getOrDefault(profile.primary, false) + } + } + } + + private fun profileFromRole(role: Role): Type = + when (role) { + Role.PERSONAL -> Type.PERSONAL + Role.CLONE -> Type.PERSONAL /* CLONE maps to PERSONAL */ + Role.PRIVATE -> Type.PRIVATE + Role.WORK -> Type.WORK + } +} diff --git a/java/src/com/android/intentresolver/v2/domain/model/Profile.kt b/java/src/com/android/intentresolver/v2/domain/model/Profile.kt new file mode 100644 index 00000000..46015c7a --- /dev/null +++ b/java/src/com/android/intentresolver/v2/domain/model/Profile.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 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.intentresolver.v2.domain.model + +import com.android.intentresolver.v2.domain.model.Profile.Type +import com.android.intentresolver.v2.shared.model.User + +/** + * A domain layer model which associates [users][User] into a [Type] instance. + * + * This is a simple abstraction which combines a primary [user][User] with an optional + * [cloned apps][User.Role.CLONE] user. This encapsulates the cloned app user id, while still being + * available where needed. + */ +data class Profile( + val type: Type, + val primary: User, + /** + * An optional [User] of which contains second instances of some applications installed for the + * personal user. This value may only be supplied when creating the PERSONAL profile. + */ + val clone: User? = null +) { + + init { + clone?.apply { + require(primary.role == User.Role.PERSONAL) { + "clone is not supported for profile=${this@Profile.type} / primary=$primary" + } + require(role == User.Role.CLONE) { "clone is not a clone user ($this)" } + } + } + + enum class Type { + PERSONAL, + WORK, + PRIVATE + } +} diff --git a/java/src/com/android/intentresolver/v2/data/model/User.kt b/java/src/com/android/intentresolver/v2/shared/model/User.kt index 504b04c8..97db3280 100644 --- a/java/src/com/android/intentresolver/v2/data/model/User.kt +++ b/java/src/com/android/intentresolver/v2/shared/model/User.kt @@ -1,10 +1,25 @@ -package com.android.intentresolver.v2.data.model +/* + * Copyright (C) 2024 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.intentresolver.v2.shared.model import android.annotation.UserIdInt import android.os.UserHandle -import com.android.intentresolver.v2.data.model.User.Type -import com.android.intentresolver.v2.data.model.User.Type.FULL -import com.android.intentresolver.v2.data.model.User.Type.PROFILE +import com.android.intentresolver.v2.shared.model.User.Type.FULL +import com.android.intentresolver.v2.shared.model.User.Type.PROFILE /** * A User represents the owner of a distinct set of content. |