summaryrefslogtreecommitdiff
path: root/java/src
diff options
context:
space:
mode:
author Mark Renouf <mrenouf@google.com> 2024-01-23 12:21:43 -0500
committer Mark Renouf <mrenouf@google.com> 2024-01-25 19:56:19 -0500
commitf30cb97a784ba508a82863ef74ea0135355aad0c (patch)
treefee5089bc3b3bf2b33e5caa9851303e7b0c52b07 /java/src
parentc5d725eec15d2cff2aab08437948d6d0f2d01a63 (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/src')
-rw-r--r--java/src/com/android/intentresolver/v2/data/repository/UserInfoExt.kt4
-rw-r--r--java/src/com/android/intentresolver/v2/data/repository/UserRepository.kt24
-rw-r--r--java/src/com/android/intentresolver/v2/data/repository/UserRepositoryModule.kt7
-rw-r--r--java/src/com/android/intentresolver/v2/data/repository/UserScopedService.kt2
-rw-r--r--java/src/com/android/intentresolver/v2/domain/interactor/UserInteractor.kt92
-rw-r--r--java/src/com/android/intentresolver/v2/domain/model/Profile.kt53
-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.