From 43fe454f23a95ec11f25620975340bf392ddb161 Mon Sep 17 00:00:00 2001 From: mrenouf Date: Tue, 14 Nov 2023 14:03:27 -0500 Subject: Rename UserDataSource to UserRepository Adds requestState to allow modification of user profile state, including availability (quiet mode). Test: UserRepositoryImplTest Change-Id: Ic38f24475c73390841ee599c48d965117981faa0 --- .../v2/data/UserDataSourceImplTest.kt | 194 ------------------ .../v2/data/repository/UserRepositoryImplTest.kt | 222 +++++++++++++++++++++ .../intentresolver/v2/platform/FakeUserManager.kt | 39 +++- 3 files changed, 258 insertions(+), 197 deletions(-) delete mode 100644 java/tests/src/com/android/intentresolver/v2/data/UserDataSourceImplTest.kt create mode 100644 java/tests/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt (limited to 'java/tests') diff --git a/java/tests/src/com/android/intentresolver/v2/data/UserDataSourceImplTest.kt b/java/tests/src/com/android/intentresolver/v2/data/UserDataSourceImplTest.kt deleted file mode 100644 index 56d5de35..00000000 --- a/java/tests/src/com/android/intentresolver/v2/data/UserDataSourceImplTest.kt +++ /dev/null @@ -1,194 +0,0 @@ -@file:OptIn(ExperimentalCoroutinesApi::class) - -package com.android.intentresolver.v2.data - -import android.content.Intent.ACTION_PROFILE_ADDED -import android.content.Intent.ACTION_PROFILE_AVAILABLE -import android.content.Intent.ACTION_PROFILE_REMOVED -import android.content.pm.UserInfo -import android.os.UserHandle -import android.os.UserHandle.USER_NULL -import android.os.UserManager -import com.android.intentresolver.mock -import com.android.intentresolver.v2.coroutines.collectLastValue -import com.android.intentresolver.v2.data.User.Role -import com.android.intentresolver.v2.data.UserDataSourceImpl.UserEvent -import com.android.intentresolver.v2.platform.FakeUserManager -import com.android.intentresolver.v2.platform.FakeUserManager.ProfileType -import com.android.intentresolver.whenever -import com.google.common.truth.Truth.assertThat -import com.google.common.truth.Truth.assertWithMessage -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runTest -import org.junit.Test -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.doReturn -import org.mockito.Mockito.eq - -internal class UserDataSourceImplTest { - private val userManager = FakeUserManager() - private val userState = userManager.state - - @Test - fun initialization() = runTest { - val dataSource = createUserDataSource(userManager) - val users by collectLastValue(dataSource.users) - - assertWithMessage("collectLast(dataSource.users)").that(users).isNotNull() - assertThat(users) - .containsExactly( - userState.primaryUserHandle, - User(userState.primaryUserHandle.identifier, Role.PERSONAL) - ) - } - - @Test - fun createProfile() = runTest { - val dataSource = createUserDataSource(userManager) - val users by collectLastValue(dataSource.users) - - assertWithMessage("collectLast(dataSource.users)").that(users).isNotNull() - assertThat(users!!.values.filter { it.role.type == User.Type.PROFILE }).isEmpty() - - val profile = userState.createProfile(ProfileType.WORK) - assertThat(users).containsEntry(profile, User(profile.identifier, Role.WORK)) - } - - @Test - fun removeProfile() = runTest { - val dataSource = createUserDataSource(userManager) - val users by collectLastValue(dataSource.users) - - assertWithMessage("collectLast(dataSource.users)").that(users).isNotNull() - val work = userState.createProfile(ProfileType.WORK) - assertThat(users).containsEntry(work, User(work.identifier, Role.WORK)) - - userState.removeProfile(work) - assertThat(users).doesNotContainEntry(work, User(work.identifier, Role.WORK)) - } - - @Test - fun isAvailable() = runTest { - val dataSource = createUserDataSource(userManager) - val work = userState.createProfile(ProfileType.WORK) - - val available by collectLastValue(dataSource.isAvailable(work)) - assertThat(available).isTrue() - - userState.setQuietMode(work, true) - assertThat(available).isFalse() - - userState.setQuietMode(work, false) - assertThat(available).isTrue() - } - - /** - * This and all the 'recovers_from_*' tests below all configure a static event flow instead of - * using [FakeUserManager]. These tests verify that a invalid broadcast causes the flow to - * reinitialize with the user profile group. - */ - @Test - fun recovers_from_invalid_profile_added_event() = runTest { - val userManager = - mockUserManager(validUser = UserHandle.USER_SYSTEM, invalidUser = USER_NULL) - val events = flowOf(UserEvent(ACTION_PROFILE_ADDED, UserHandle.of(USER_NULL))) - val dataSource = - UserDataSourceImpl( - profileParent = UserHandle.SYSTEM, - userManager = userManager, - userEvents = events, - scope = backgroundScope, - backgroundDispatcher = Dispatchers.Unconfined - ) - val users by collectLastValue(dataSource.users) - - assertWithMessage("collectLast(dataSource.users)").that(users).isNotNull() - assertThat(users) - .containsExactly(UserHandle.SYSTEM, User(UserHandle.USER_SYSTEM, Role.PERSONAL)) - } - - @Test - fun recovers_from_invalid_profile_removed_event() = runTest { - val userManager = - mockUserManager(validUser = UserHandle.USER_SYSTEM, invalidUser = USER_NULL) - val events = flowOf(UserEvent(ACTION_PROFILE_REMOVED, UserHandle.of(USER_NULL))) - val dataSource = - UserDataSourceImpl( - profileParent = UserHandle.SYSTEM, - userManager = userManager, - userEvents = events, - scope = backgroundScope, - backgroundDispatcher = Dispatchers.Unconfined - ) - val users by collectLastValue(dataSource.users) - - assertWithMessage("collectLast(dataSource.users)").that(users).isNotNull() - assertThat(users) - .containsExactly(UserHandle.SYSTEM, User(UserHandle.USER_SYSTEM, Role.PERSONAL)) - } - - @Test - fun recovers_from_invalid_profile_available_event() = runTest { - val userManager = - mockUserManager(validUser = UserHandle.USER_SYSTEM, invalidUser = USER_NULL) - val events = flowOf(UserEvent(ACTION_PROFILE_AVAILABLE, UserHandle.of(USER_NULL))) - val dataSource = - UserDataSourceImpl( - UserHandle.SYSTEM, - userManager, - events, - backgroundScope, - Dispatchers.Unconfined - ) - val users by collectLastValue(dataSource.users) - - assertWithMessage("collectLast(dataSource.users)").that(users).isNotNull() - assertThat(users) - .containsExactly(UserHandle.SYSTEM, User(UserHandle.USER_SYSTEM, Role.PERSONAL)) - } - - @Test - fun recovers_from_unknown_event() = runTest { - val userManager = - mockUserManager(validUser = UserHandle.USER_SYSTEM, invalidUser = USER_NULL) - val events = flowOf(UserEvent("UNKNOWN_EVENT", UserHandle.of(USER_NULL))) - val dataSource = - UserDataSourceImpl( - profileParent = UserHandle.SYSTEM, - userManager = userManager, - userEvents = events, - scope = backgroundScope, - backgroundDispatcher = Dispatchers.Unconfined - ) - val users by collectLastValue(dataSource.users) - - assertWithMessage("collectLast(dataSource.users)").that(users).isNotNull() - assertThat(users) - .containsExactly(UserHandle.SYSTEM, User(UserHandle.USER_SYSTEM, Role.PERSONAL)) - } -} - -@Suppress("SameParameterValue", "DEPRECATION") -private fun mockUserManager(validUser: Int, invalidUser: Int) = - mock { - val info = UserInfo(validUser, "", "", UserInfo.FLAG_FULL) - doReturn(listOf(info)).whenever(this).getEnabledProfiles(anyInt()) - - doReturn(info).whenever(this).getUserInfo(eq(validUser)) - - doReturn(listOf()).whenever(this).getEnabledProfiles(eq(invalidUser)) - - doReturn(null).whenever(this).getUserInfo(eq(invalidUser)) - } - -private fun TestScope.createUserDataSource(userManager: FakeUserManager) = - UserDataSourceImpl( - profileParent = userManager.state.primaryUserHandle, - userManager = userManager, - userEvents = userManager.state.userEvents, - scope = backgroundScope, - backgroundDispatcher = Dispatchers.Unconfined - ) diff --git a/java/tests/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt b/java/tests/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt new file mode 100644 index 00000000..4f514db5 --- /dev/null +++ b/java/tests/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt @@ -0,0 +1,222 @@ +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 +import android.os.UserHandle.USER_SYSTEM +import android.os.UserManager +import com.android.intentresolver.mock +import com.android.intentresolver.v2.coroutines.collectLastValue +import com.android.intentresolver.v2.data.model.User +import com.android.intentresolver.v2.data.model.User.Role +import com.android.intentresolver.v2.platform.FakeUserManager +import com.android.intentresolver.v2.platform.FakeUserManager.ProfileType +import com.android.intentresolver.whenever +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.mockito.Mockito +import org.mockito.Mockito.doReturn + +internal class UserRepositoryImplTest { + private val userManager = FakeUserManager() + private val userState = userManager.state + + @Test + fun initialization() = runTest { + val repo = createUserRepository(userManager) + val users by collectLastValue(repo.users) + + assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull() + assertThat(users) + .containsExactly( + userState.primaryUserHandle, + User(userState.primaryUserHandle.identifier, Role.PERSONAL) + ) + } + + @Test + fun createProfile() = runTest { + val repo = createUserRepository(userManager) + val users by collectLastValue(repo.users) + + assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull() + assertThat(users!!.values.filter { it.role.type == User.Type.PROFILE }).isEmpty() + + val profile = userState.createProfile(ProfileType.WORK) + assertThat(users).containsEntry(profile, User(profile.identifier, Role.WORK)) + } + + @Test + fun removeProfile() = runTest { + val repo = createUserRepository(userManager) + val users by collectLastValue(repo.users) + + assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull() + val work = userState.createProfile(ProfileType.WORK) + assertThat(users).containsEntry(work, User(work.identifier, Role.WORK)) + + userState.removeProfile(work) + assertThat(users).doesNotContainEntry(work, User(work.identifier, Role.WORK)) + } + + @Test + fun isAvailable() = runTest { + val repo = createUserRepository(userManager) + val work = userState.createProfile(ProfileType.WORK) + + val available by collectLastValue(repo.isAvailable(work)) + assertThat(available).isTrue() + + userState.setQuietMode(work, true) + assertThat(available).isFalse() + + userState.setQuietMode(work, false) + assertThat(available).isTrue() + } + + @Test + fun requestState() = runTest { + val repo = createUserRepository(userManager) + val work = userState.createProfile(ProfileType.WORK) + + val available by collectLastValue(repo.isAvailable(work)) + assertThat(available).isTrue() + + repo.requestState(work, false) + assertThat(available).isFalse() + + repo.requestState(work, true) + assertThat(available).isTrue() + } + + @Test(expected = IllegalArgumentException::class) + fun requestState_invalidForFullUser() = runTest { + val repo = createUserRepository(userManager) + val primaryUser = User(userState.primaryUserHandle.identifier, Role.PERSONAL) + repo.requestState(primaryUser, available = false) + } + + /** + * This and all the 'recovers_from_*' tests below all configure a static event flow instead of + * using [FakeUserManager]. These tests verify that a invalid broadcast causes the flow to + * reinitialize with the user profile group. + */ + @Test + 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 repo = + UserRepositoryImpl( + profileParent = SYSTEM, + userManager = userManager, + userEvents = events, + scope = backgroundScope, + backgroundDispatcher = Dispatchers.Unconfined + ) + val users by collectLastValue(repo.users) + + assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull() + assertThat(users).containsExactly(SYSTEM, User(USER_SYSTEM, Role.PERSONAL)) + } + + @Test + 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 repo = + UserRepositoryImpl( + profileParent = SYSTEM, + userManager = userManager, + userEvents = events, + scope = backgroundScope, + backgroundDispatcher = Dispatchers.Unconfined + ) + val users by collectLastValue(repo.users) + + assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull() + assertThat(users).containsExactly(SYSTEM, User(USER_SYSTEM, Role.PERSONAL)) + } + + @Test + 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 repo = + UserRepositoryImpl(SYSTEM, userManager, events, backgroundScope, Dispatchers.Unconfined) + val users by collectLastValue(repo.users) + + assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull() + assertThat(users).containsExactly(SYSTEM, User(USER_SYSTEM, Role.PERSONAL)) + } + + @Test + 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 repo = + UserRepositoryImpl( + profileParent = SYSTEM, + userManager = userManager, + userEvents = events, + scope = backgroundScope, + backgroundDispatcher = Dispatchers.Unconfined + ) + val users by collectLastValue(repo.users) + + assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull() + assertThat(users).containsExactly(SYSTEM, User(USER_SYSTEM, Role.PERSONAL)) + } +} + +@Suppress("SameParameterValue", "DEPRECATION") +private fun mockUserManager(validUser: Int, invalidUser: Int) = + mock { + val info = UserInfo(validUser, "", "", UserInfo.FLAG_FULL) + doReturn(listOf(info)).whenever(this).getEnabledProfiles(Mockito.anyInt()) + + doReturn(info).whenever(this).getUserInfo(Mockito.eq(validUser)) + + doReturn(listOf()).whenever(this).getEnabledProfiles(Mockito.eq(invalidUser)) + + doReturn(null).whenever(this).getUserInfo(Mockito.eq(invalidUser)) + } + +private fun TestScope.createUserRepository(userManager: FakeUserManager) = + UserRepositoryImpl( + profileParent = userManager.state.primaryUserHandle, + userManager = userManager, + userEvents = userManager.state.userEvents, + scope = backgroundScope, + backgroundDispatcher = Dispatchers.Unconfined + ) diff --git a/java/tests/src/com/android/intentresolver/v2/platform/FakeUserManager.kt b/java/tests/src/com/android/intentresolver/v2/platform/FakeUserManager.kt index ef1e5917..370e5a00 100644 --- a/java/tests/src/com/android/intentresolver/v2/platform/FakeUserManager.kt +++ b/java/tests/src/com/android/intentresolver/v2/platform/FakeUserManager.kt @@ -1,7 +1,10 @@ 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 @@ -12,9 +15,10 @@ import android.content.pm.UserInfo.NO_PROFILE_GROUP_ID import android.os.IUserManager import android.os.UserHandle 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.UserDataSourceImpl.UserEvent +import com.android.intentresolver.v2.data.repository.UserRepositoryImpl.UserEvent import com.android.intentresolver.v2.platform.FakeUserManager.State import com.android.intentresolver.whenever import kotlin.random.Random @@ -77,6 +81,14 @@ class FakeUserManager(val state: State = State()) : } } + override fun requestQuietModeEnabled( + enableQuietMode: Boolean, + @NonNull userHandle: UserHandle + ): Boolean { + state.setQuietMode(userHandle, enableQuietMode) + return true + } + override fun isQuietModeEnabled(userHandle: UserHandle): Boolean { return state.getUser(userHandle).isQuietModeEnabled } @@ -136,8 +148,29 @@ class FakeUserManager(val state: State = State()) : } fun setQuietMode(user: UserHandle, quietMode: Boolean) { - userInfoMap[user]?.also { it.flags = it.flags or UserInfo.FLAG_QUIET_MODE } - eventChannel.trySend(UserEvent(ACTION_PROFILE_UNAVAILABLE, user, quietMode)) + userInfoMap[user]?.also { + it.flags = + if (quietMode) { + it.flags or UserInfo.FLAG_QUIET_MODE + } else { + it.flags and UserInfo.FLAG_QUIET_MODE.inv() + } + val actions = mutableListOf() + 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)) + } + } } fun createProfile(type: ProfileType, parent: UserHandle = primaryUserHandle): UserHandle { -- cgit v1.2.3-59-g8ed1b