summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Mark Renouf <mrenouf@google.com> 2024-03-14 14:08:56 -0400
committer Mark Renouf <mrenouf@google.com> 2024-03-14 21:35:57 -0400
commitd64715f0b5f72c4a6f73a113c55638bbb6f05c05 (patch)
treee7e2e32dde3732bebcb2cc9e790b7f9d49776e6c
parent5835e1aabaa9dafa7c5f3c90a782e8ef4ec953e3 (diff)
Readability clean up for UserRepository
Use a sealed interface for events to eliminate unused properties. Extract the event branching from the flow statement. Improve logging and extract to a simple method. Bug: NA Test: atest IntentResolver-tests-unit Flag: NA Change-Id: I348558b3ca1506b7ece1c19295263e8824ac2575
-rw-r--r--java/src/com/android/intentresolver/v2/data/repository/UserRepository.kt134
-rw-r--r--tests/shared/src/com/android/intentresolver/v2/platform/FakeUserManager.kt31
-rw-r--r--tests/unit/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt30
3 files changed, 99 insertions, 96 deletions
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 4c42e2cd..40672249 100644
--- a/java/src/com/android/intentresolver/v2/data/repository/UserRepository.kt
+++ b/java/src/com/android/intentresolver/v2/data/repository/UserRepository.kt
@@ -28,6 +28,7 @@ import android.content.Intent.EXTRA_QUIET_MODE
import android.content.Intent.EXTRA_USER
import android.content.IntentFilter
import android.content.pm.UserInfo
+import android.os.Build
import android.os.UserHandle
import android.os.UserManager
import android.util.Log
@@ -36,7 +37,6 @@ 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.repository.UserRepositoryImpl.UserEvent
import com.android.intentresolver.v2.shared.model.User
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
@@ -73,7 +73,7 @@ interface UserRepository {
* stopping a profile user (along with their many associated processes).
*
* If successful, the change will be applied after the call returns and can be observed using
- * [UserRepository.isAvailable] for the given user.
+ * [UserRepository.availability] for the given user.
*
* No actions are taken if the user is already in requested state.
*
@@ -84,9 +84,9 @@ interface UserRepository {
private const val TAG = "UserRepository"
-private data class UserWithState(val user: User, val available: Boolean)
+internal data class UserWithState(val user: User, val available: Boolean)
-private typealias UserStates = List<UserWithState>
+internal typealias UserStates = List<UserWithState>
/** Tracks and publishes state for the parent user and associated profiles. */
class UserRepositoryImpl
@@ -114,13 +114,21 @@ constructor(
background
)
- data class UserEvent(val action: String, val user: UserHandle, val quietMode: Boolean = false)
+ private fun debugLog(msg: () -> String) {
+ if (Build.IS_USERDEBUG || Build.IS_ENG) {
+ Log.d(TAG, msg())
+ }
+ }
+
+ private fun errorLog(msg: String, caught: Throwable? = null) {
+ Log.e(TAG, msg, caught)
+ }
/**
* An exception which indicates that an inconsistency exists between the user state map and the
* rest of the system.
*/
- internal class UserStateException(
+ private class UserStateException(
override val message: String,
val event: UserEvent,
override val cause: Throwable? = null
@@ -129,35 +137,34 @@ constructor(
private val sharingScope = CoroutineScope(scope.coroutineContext + backgroundDispatcher)
private val usersWithState: Flow<UserStates> =
userEvents
- .onStart { emit(UserEvent(INITIALIZE, profileParent)) }
- .onEach { Log.i(TAG, "userEvent: $it") }
- .runningFold<UserEvent, UserStates>(emptyList()) { users, event ->
- try {
- // Handle an action by performing some operation, then returning a new map
- when (event.action) {
- INITIALIZE -> createNewUserStates(profileParent)
- ACTION_PROFILE_ADDED -> handleProfileAdded(event, users)
- ACTION_PROFILE_REMOVED -> handleProfileRemoved(event, users)
- ACTION_MANAGED_PROFILE_UNAVAILABLE,
- ACTION_MANAGED_PROFILE_AVAILABLE,
- ACTION_PROFILE_AVAILABLE,
- ACTION_PROFILE_UNAVAILABLE -> handleAvailability(event, users)
- else -> {
- Log.w(TAG, "Unhandled event: $event)")
- users
- }
- }
- } catch (e: UserStateException) {
- Log.e(TAG, "An error occurred handling an event: ${e.event}", e)
- Log.e(TAG, "Attempting to recover...")
- createNewUserStates(profileParent)
- }
- }
+ .onStart { emit(Initialize) }
+ .onEach { debugLog { "userEvent: $it" } }
+ .runningFold(emptyList(), ::handleEvent)
.distinctUntilChanged()
- .onEach { Log.i(TAG, "userStateList: $it") }
+ .onEach { debugLog { "userStateList: $it" } }
.stateIn(sharingScope, SharingStarted.Eagerly, emptyList())
.filterNot { it.isEmpty() }
+ private suspend fun handleEvent(users: UserStates, event: UserEvent): UserStates {
+ return try {
+ // Handle an action by performing some operation, then returning a new map
+ when (event) {
+ is Initialize -> createNewUserStates(profileParent)
+ is ProfileAdded -> handleProfileAdded(event, users)
+ is ProfileRemoved -> handleProfileRemoved(event, users)
+ is AvailabilityChange -> handleAvailability(event, users)
+ is UnknownEvent -> {
+ debugLog { "Unhandled event: $event)" }
+ users
+ }
+ }
+ } catch (e: UserStateException) {
+ errorLog("An error occurred handling an event: ${e.event}")
+ errorLog("Attempting to recover...", e)
+ createNewUserStates(profileParent)
+ }
+ }
+
override val users: Flow<List<User>> =
usersWithState.map { userStateMap -> userStateMap.map { it.user } }.distinctUntilChanged()
@@ -168,7 +175,7 @@ constructor(
override suspend fun requestState(user: User, available: Boolean) {
return withContext(backgroundDispatcher) {
- Log.i(TAG, "requestQuietModeEnabled: ${!available} for user $user")
+ debugLog { "requestQuietModeEnabled: ${!available} for user $user" }
userManager.requestQuietModeEnabled(/* enableQuietMode = */ !available, user.handle)
}
}
@@ -176,28 +183,28 @@ constructor(
private fun List<UserWithState>.update(handle: UserHandle, user: UserWithState) =
filter { it.user.id != handle.identifier } + user
- private fun handleAvailability(event: UserEvent, current: UserStates): UserStates {
+ private fun handleAvailability(event: AvailabilityChange, current: UserStates): UserStates {
val userEntry =
current.firstOrNull { it.user.id == event.user.identifier }
?: throw UserStateException("User was not present in the map", event)
return current.update(event.user, userEntry.copy(available = !event.quietMode))
}
- private fun handleProfileRemoved(event: UserEvent, current: UserStates): UserStates {
+ private fun handleProfileRemoved(event: ProfileRemoved, current: UserStates): UserStates {
if (!current.any { it.user.id == event.user.identifier }) {
throw UserStateException("User was not present in the map", event)
}
return current.filter { it.user.id != event.user.identifier }
}
- private suspend fun handleProfileAdded(event: UserEvent, current: UserStates): UserStates {
+ private suspend fun handleProfileAdded(event: ProfileAdded, current: UserStates): UserStates {
val user =
try {
requireNotNull(readUser(event.user))
} catch (e: Exception) {
throw UserStateException("Failed to read user from UserManager", event, e)
}
- return current + UserWithState(user, !event.quietMode)
+ return current + UserWithState(user, true)
}
private suspend fun createNewUserStates(user: UserHandle): UserStates {
@@ -224,29 +231,64 @@ constructor(
}
}
+/** A Model representing changes to profiles and availability */
+sealed interface UserEvent
+
+/** Used as a an initial value to trigger a fetch of all profile data. */
+data object Initialize : UserEvent
+
+/** A profile was added to the profile group. */
+data class ProfileAdded(
+ /** The handle for the added profile. */
+ val user: UserHandle,
+) : UserEvent
+
+/** A profile was removed from the profile group. */
+data class ProfileRemoved(
+ /** The handle for the removed profile. */
+ val user: UserHandle,
+) : UserEvent
+
+/** A profile has changed availability. */
+data class AvailabilityChange(
+ /** THe handle for the profile with availability change. */
+ val user: UserHandle,
+ /** The new quietMode state. */
+ val quietMode: Boolean = false,
+) : UserEvent
+
+/** An unhandled event, logged and ignored. */
+data class UnknownEvent(
+ /** The broadcast intent action received */
+ val action: String?,
+) : UserEvent
+
/** Used with [broadcastFlow] to transform a UserManager broadcast action into a [UserEvent]. */
-private fun Intent.toUserEvent(): UserEvent? {
+private fun Intent.toUserEvent(): UserEvent {
val action = action
val user = extras?.getParcelable(EXTRA_USER, UserHandle::class.java)
- val quietMode = extras?.getBoolean(EXTRA_QUIET_MODE, false) ?: false
- return if (user == null || action == null) {
- null
- } else {
- UserEvent(action, user, quietMode)
+ val quietMode = extras?.getBoolean(EXTRA_QUIET_MODE, false)
+ return when (action) {
+ ACTION_PROFILE_ADDED -> ProfileAdded(requireNotNull(user))
+ ACTION_PROFILE_REMOVED -> ProfileRemoved(requireNotNull(user))
+ ACTION_MANAGED_PROFILE_UNAVAILABLE,
+ ACTION_MANAGED_PROFILE_AVAILABLE,
+ ACTION_PROFILE_AVAILABLE,
+ ACTION_PROFILE_UNAVAILABLE ->
+ AvailabilityChange(requireNotNull(user), requireNotNull(quietMode))
+ else -> UnknownEvent(action)
}
}
-const val INITIALIZE = "INITIALIZE"
-
private fun createFilter(actions: Iterable<String>): IntentFilter {
return IntentFilter().apply { actions.forEach(::addAction) }
}
-private fun UserInfo?.isAvailable(): Boolean {
+internal fun UserInfo?.isAvailable(): Boolean {
return this?.isQuietModeEnabled != true
}
-private fun userBroadcastFlow(context: Context, profileParent: UserHandle): Flow<UserEvent> {
+internal fun userBroadcastFlow(context: Context, profileParent: UserHandle): Flow<UserEvent> {
val userActions =
setOf(
ACTION_PROFILE_ADDED,
diff --git a/tests/shared/src/com/android/intentresolver/v2/platform/FakeUserManager.kt b/tests/shared/src/com/android/intentresolver/v2/platform/FakeUserManager.kt
index 370e5a00..d1b56d5f 100644
--- a/tests/shared/src/com/android/intentresolver/v2/platform/FakeUserManager.kt
+++ b/tests/shared/src/com/android/intentresolver/v2/platform/FakeUserManager.kt
@@ -1,12 +1,6 @@
package com.android.intentresolver.v2.platform
import android.content.Context
-import android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE
-import android.content.Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE
-import android.content.Intent.ACTION_PROFILE_ADDED
-import android.content.Intent.ACTION_PROFILE_AVAILABLE
-import android.content.Intent.ACTION_PROFILE_REMOVED
-import android.content.Intent.ACTION_PROFILE_UNAVAILABLE
import android.content.pm.UserInfo
import android.content.pm.UserInfo.FLAG_FULL
import android.content.pm.UserInfo.FLAG_INITIALIZED
@@ -18,7 +12,10 @@ import android.os.UserManager
import androidx.annotation.NonNull
import com.android.intentresolver.THROWS_EXCEPTION
import com.android.intentresolver.mock
-import com.android.intentresolver.v2.data.repository.UserRepositoryImpl.UserEvent
+import com.android.intentresolver.v2.data.repository.AvailabilityChange
+import com.android.intentresolver.v2.data.repository.ProfileAdded
+import com.android.intentresolver.v2.data.repository.ProfileRemoved
+import com.android.intentresolver.v2.data.repository.UserEvent
import com.android.intentresolver.v2.platform.FakeUserManager.State
import com.android.intentresolver.whenever
import kotlin.random.Random
@@ -155,21 +152,7 @@ class FakeUserManager(val state: State = State()) :
} else {
it.flags and UserInfo.FLAG_QUIET_MODE.inv()
}
- val actions = mutableListOf<String>()
- if (quietMode) {
- actions += ACTION_PROFILE_UNAVAILABLE
- if (it.isManagedProfile) {
- actions += ACTION_MANAGED_PROFILE_UNAVAILABLE
- }
- } else {
- actions += ACTION_PROFILE_AVAILABLE
- if (it.isManagedProfile) {
- actions += ACTION_MANAGED_PROFILE_AVAILABLE
- }
- }
- actions.forEach { action ->
- eventChannel.trySend(UserEvent(action, user, quietMode))
- }
+ eventChannel.trySend(AvailabilityChange(user, quietMode))
}
}
@@ -187,7 +170,7 @@ class FakeUserManager(val state: State = State()) :
profileGroupId = parentUser.profileGroupId
}
userInfoMap[userInfo.userHandle] = userInfo
- eventChannel.trySend(UserEvent(ACTION_PROFILE_ADDED, userInfo.userHandle))
+ eventChannel.trySend(ProfileAdded(userInfo.userHandle))
return userInfo.userHandle
}
@@ -195,7 +178,7 @@ class FakeUserManager(val state: State = State()) :
return userInfoMap[handle]?.let { user ->
require(user.isProfile) { "Only profiles can be removed" }
userInfoMap.remove(user.userHandle)
- eventChannel.trySend(UserEvent(ACTION_PROFILE_REMOVED, user.userHandle))
+ eventChannel.trySend(ProfileRemoved(user.userHandle))
return true
}
?: false
diff --git a/tests/unit/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt b/tests/unit/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt
index 8628bd14..3fcc4c84 100644
--- a/tests/unit/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt
+++ b/tests/unit/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt
@@ -1,6 +1,5 @@
package com.android.intentresolver.v2.data.repository
-import android.content.Intent
import android.content.pm.UserInfo
import android.os.UserHandle
import android.os.UserHandle.SYSTEM
@@ -122,13 +121,7 @@ internal class UserRepositoryImplTest {
fun recovers_from_invalid_profile_added_event() = runTest {
val userManager =
mockUserManager(validUser = USER_SYSTEM, invalidUser = UserHandle.USER_NULL)
- val events =
- flowOf(
- UserRepositoryImpl.UserEvent(
- Intent.ACTION_PROFILE_ADDED,
- UserHandle.of(UserHandle.USER_NULL)
- )
- )
+ val events = flowOf(ProfileAdded(UserHandle.of(UserHandle.USER_NULL)))
val repo =
UserRepositoryImpl(
profileParent = SYSTEM,
@@ -147,13 +140,7 @@ internal class UserRepositoryImplTest {
fun recovers_from_invalid_profile_removed_event() = runTest {
val userManager =
mockUserManager(validUser = USER_SYSTEM, invalidUser = UserHandle.USER_NULL)
- val events =
- flowOf(
- UserRepositoryImpl.UserEvent(
- Intent.ACTION_PROFILE_REMOVED,
- UserHandle.of(UserHandle.USER_NULL)
- )
- )
+ val events = flowOf(ProfileRemoved(UserHandle.of(UserHandle.USER_NULL)))
val repo =
UserRepositoryImpl(
profileParent = SYSTEM,
@@ -172,13 +159,7 @@ internal class UserRepositoryImplTest {
fun recovers_from_invalid_profile_available_event() = runTest {
val userManager =
mockUserManager(validUser = USER_SYSTEM, invalidUser = UserHandle.USER_NULL)
- val events =
- flowOf(
- UserRepositoryImpl.UserEvent(
- Intent.ACTION_PROFILE_AVAILABLE,
- UserHandle.of(UserHandle.USER_NULL)
- )
- )
+ val events = flowOf(AvailabilityChange(UserHandle.of(UserHandle.USER_NULL)))
val repo =
UserRepositoryImpl(SYSTEM, userManager, events, backgroundScope, Dispatchers.Unconfined)
val users by collectLastValue(repo.users)
@@ -191,10 +172,7 @@ internal class UserRepositoryImplTest {
fun recovers_from_unknown_event() = runTest {
val userManager =
mockUserManager(validUser = USER_SYSTEM, invalidUser = UserHandle.USER_NULL)
- val events =
- flowOf(
- UserRepositoryImpl.UserEvent("UNKNOWN_EVENT", UserHandle.of(UserHandle.USER_NULL))
- )
+ val events = flowOf(UnknownEvent("UNKNOWN_EVENT"))
val repo =
UserRepositoryImpl(
profileParent = SYSTEM,