blob: 57b82e3393c5e919eca5030d91eca15e94c47371 [file]
/*
* Copyright (C) 2020 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.settings
import android.app.IActivityManager
import android.app.IUserSwitchObserver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.UserInfo
import android.os.Handler
import android.os.IRemoteCallback
import android.os.UserHandle
import android.os.UserManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.TruthJUnit.assume
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import java.util.concurrent.Executor
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(Parameterized::class)
class UserTrackerImplTest : SysuiTestCase() {
companion object {
@JvmStatic
@Parameterized.Parameters
fun isBackgroundUserTrackerEnabled(): Iterable<Boolean> = listOf(true, false)
}
@Mock
private lateinit var context: Context
@Mock
private lateinit var userManager: UserManager
@Mock
private lateinit var iActivityManager: IActivityManager
@Mock
private lateinit var beforeUserSwitchingReply: IRemoteCallback
@Mock
private lateinit var userSwitchingReply: IRemoteCallback
@Mock(stubOnly = true)
private lateinit var dumpManager: DumpManager
@Mock(stubOnly = true)
private lateinit var handler: Handler
@Parameterized.Parameter
@JvmField
var isBackgroundUserTrackerEnabled: Boolean = false
private val testScope = TestScope()
private val testDispatcher = StandardTestDispatcher(testScope.testScheduler)
private val executor = Executor(Runnable::run)
private val featureFlags = FakeFeatureFlagsClassic()
private lateinit var tracker: UserTrackerImpl
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
`when`(context.userId).thenReturn(UserHandle.USER_SYSTEM)
`when`(context.user).thenReturn(UserHandle.SYSTEM)
`when`(context.createContextAsUser(any(), anyInt())).thenAnswer { invocation ->
val user = invocation.getArgument<UserHandle>(0)
`when`(context.user).thenReturn(user)
`when`(context.userId).thenReturn(user.identifier)
context
}
`when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
val info = UserInfo(invocation.getArgument<Int>(0), "", UserInfo.FLAG_FULL)
listOf(info)
}
featureFlags.set(Flags.USER_TRACKER_BACKGROUND_CALLBACKS, isBackgroundUserTrackerEnabled)
tracker =
UserTrackerImpl(
context,
{ featureFlags },
userManager,
iActivityManager,
dumpManager,
testScope.backgroundScope,
testDispatcher,
handler,
)
}
@Test
fun testNotInitialized() = testScope.runTest {
assertThat(tracker.initialized).isFalse()
}
@Test(expected = IllegalStateException::class)
fun testGetUserIdBeforeInitThrowsException() = testScope.runTest {
tracker.userId
}
@Test(expected = IllegalStateException::class)
fun testGetUserHandleBeforeInitThrowsException() = testScope.runTest {
tracker.userHandle
}
@Test(expected = IllegalStateException::class)
fun testGetUserContextBeforeInitThrowsException() = testScope.runTest {
tracker.userContext
}
@Test(expected = IllegalStateException::class)
fun testGetUserContentResolverBeforeInitThrowsException() = testScope.runTest {
tracker.userContentResolver
}
@Test(expected = IllegalStateException::class)
fun testGetUserProfilesBeforeInitThrowsException() = testScope.runTest {
tracker.userProfiles
}
@Test
fun testInitialize() = testScope.runTest {
tracker.initialize(0)
assertThat(tracker.initialized).isTrue()
}
@Test
fun testReceiverRegisteredOnInitialize() = testScope.runTest {
tracker.initialize(0)
val captor = ArgumentCaptor.forClass(IntentFilter::class.java)
verify(context)
.registerReceiverForAllUsers(eq(tracker), capture(captor), isNull(), eq(handler))
with(captor.value) {
assertThat(countActions()).isEqualTo(11)
assertThat(hasAction(Intent.ACTION_LOCALE_CHANGED)).isTrue()
assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue()
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue()
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)).isTrue()
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_ADDED)).isTrue()
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)).isTrue()
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)).isTrue()
assertThat(hasAction(Intent.ACTION_PROFILE_ADDED)).isTrue()
assertThat(hasAction(Intent.ACTION_PROFILE_REMOVED)).isTrue()
assertThat(hasAction(Intent.ACTION_PROFILE_AVAILABLE)).isTrue()
assertThat(hasAction(Intent.ACTION_PROFILE_UNAVAILABLE)).isTrue()
}
}
@Test
fun testInitialValuesSet() = testScope.runTest {
val testID = 4
tracker.initialize(testID)
verify(userManager).getProfiles(testID)
assertThat(tracker.userId).isEqualTo(testID)
assertThat(tracker.userHandle).isEqualTo(UserHandle.of(testID))
assertThat(tracker.userContext.userId).isEqualTo(testID)
assertThat(tracker.userContext.user).isEqualTo(UserHandle.of(testID))
assertThat(tracker.userProfiles).hasSize(1)
val info = tracker.userProfiles[0]
assertThat(info.id).isEqualTo(testID)
}
@Test
fun testUserSwitch() = testScope.runTest {
tracker.initialize(0)
val newID = 5
val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
captor.value.onBeforeUserSwitching(newID, beforeUserSwitchingReply)
captor.value.onUserSwitching(newID, userSwitchingReply)
runCurrent()
verify(beforeUserSwitchingReply).sendResult(any())
verify(userSwitchingReply).sendResult(any())
verify(userManager).getProfiles(newID)
assertThat(tracker.userId).isEqualTo(newID)
assertThat(tracker.userHandle).isEqualTo(UserHandle.of(newID))
assertThat(tracker.userContext.userId).isEqualTo(newID)
assertThat(tracker.userContext.user).isEqualTo(UserHandle.of(newID))
assertThat(tracker.userProfiles).hasSize(1)
val info = tracker.userProfiles[0]
assertThat(info.id).isEqualTo(newID)
}
@Test
fun testManagedProfileAvailable() = testScope.runTest {
tracker.initialize(0)
val profileID = tracker.userId + 10
`when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
val id = invocation.getArgument<Int>(0)
val info = UserInfo(id, "", UserInfo.FLAG_FULL)
val infoProfile = UserInfo(
id + 10,
"",
"",
UserInfo.FLAG_MANAGED_PROFILE,
UserManager.USER_TYPE_PROFILE_MANAGED
)
infoProfile.profileGroupId = id
listOf(info, infoProfile)
}
val intent = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
.putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
tracker.onReceive(context, intent)
assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId, profileID)
}
@Test
fun testManagedProfileUnavailable() = testScope.runTest {
tracker.initialize(0)
val profileID = tracker.userId + 10
`when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
val id = invocation.getArgument<Int>(0)
val info = UserInfo(id, "", UserInfo.FLAG_FULL)
val infoProfile = UserInfo(
id + 10,
"",
"",
UserInfo.FLAG_MANAGED_PROFILE or UserInfo.FLAG_QUIET_MODE,
UserManager.USER_TYPE_PROFILE_MANAGED
)
infoProfile.profileGroupId = id
listOf(info, infoProfile)
}
val intent = Intent(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
.putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
tracker.onReceive(context, intent)
assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId, profileID)
}
@Test
fun testManagedProfileStartedAndRemoved() = testScope.runTest {
tracker.initialize(0)
val profileID = tracker.userId + 10
`when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
val id = invocation.getArgument<Int>(0)
val info = UserInfo(id, "", UserInfo.FLAG_FULL)
val infoProfile = UserInfo(
id + 10,
"",
"",
UserInfo.FLAG_MANAGED_PROFILE,
UserManager.USER_TYPE_PROFILE_MANAGED
)
infoProfile.profileGroupId = id
listOf(info, infoProfile)
}
// Managed profile started
val intent = Intent(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)
.putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
tracker.onReceive(context, intent)
assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId, profileID)
`when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
listOf(UserInfo(invocation.getArgument(0), "", UserInfo.FLAG_FULL))
}
val intent2 = Intent(Intent.ACTION_MANAGED_PROFILE_REMOVED)
.putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
tracker.onReceive(context, intent2)
assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId)
}
@Test
fun testCallbackNotCalledOnAdd() = testScope.runTest {
tracker.initialize(0)
val callback = TestCallback()
tracker.addCallback(callback, executor)
assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
assertThat(callback.calledOnUserChanged).isEqualTo(0)
}
@Test
fun testCallbackCalledOnUserChanging() = testScope.runTest {
tracker.initialize(0)
val callback = TestCallback()
tracker.addCallback(callback, executor)
val newID = 5
val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
captor.value.onBeforeUserSwitching(newID, beforeUserSwitchingReply)
captor.value.onUserSwitching(newID, userSwitchingReply)
runCurrent()
verify(beforeUserSwitchingReply).sendResult(any())
verify(userSwitchingReply).sendResult(any())
assertThat(callback.calledOnUserChanging).isEqualTo(1)
assertThat(callback.lastUser).isEqualTo(newID)
assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
}
@Test
fun testAsyncCallbackWaitsUserToChange() = testScope.runTest {
// Skip this test for CountDownLatch variation. The problem is that there would be a
// deadlock if the callbacks processing runs on the same thread as the callback (which
// is blocked by the latch). Before the change it works because the callbacks are
// processed on a binder thread which is always distinct.
// This is the issue that this feature addresses.
assume().that(isBackgroundUserTrackerEnabled).isTrue()
tracker.initialize(0)
val callback = TestCallback()
val callbackExecutor = FakeExecutor(FakeSystemClock())
tracker.addCallback(callback, callbackExecutor)
val newID = 5
val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
captor.value.onUserSwitching(newID, userSwitchingReply)
assertThat(callback.calledOnUserChanging).isEqualTo(0)
verify(userSwitchingReply, never()).sendResult(any())
FakeExecutor.exhaustExecutors(callbackExecutor)
runCurrent()
FakeExecutor.exhaustExecutors(callbackExecutor)
runCurrent()
assertThat(callback.calledOnUserChanging).isEqualTo(1)
verify(userSwitchingReply).sendResult(any())
}
@Test
fun testCallbackCalledOnUserChanged() = testScope.runTest {
tracker.initialize(0)
val callback = TestCallback()
tracker.addCallback(callback, executor)
val newID = 5
val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
captor.value.onBeforeUserSwitching(newID, any())
captor.value.onUserSwitchComplete(newID)
runCurrent()
assertThat(callback.calledOnUserChanged).isEqualTo(1)
assertThat(callback.lastUser).isEqualTo(newID)
assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(newID)
}
@Test
fun testCallbackCalledOnUserInfoChanged() = testScope.runTest {
tracker.initialize(0)
val callback = TestCallback()
tracker.addCallback(callback, executor)
val profileID = tracker.userId + 10
`when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
val id = invocation.getArgument<Int>(0)
val info = UserInfo(id, "", UserInfo.FLAG_FULL)
val infoProfile = UserInfo(
id + 10,
"",
"",
UserInfo.FLAG_MANAGED_PROFILE,
UserManager.USER_TYPE_PROFILE_MANAGED
)
infoProfile.profileGroupId = id
listOf(info, infoProfile)
}
val intent = Intent(Intent.ACTION_USER_INFO_CHANGED)
.putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
tracker.onReceive(context, intent)
assertThat(callback.calledOnUserChanged).isEqualTo(0)
assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(0, profileID)
}
@Test
fun testCallbackRemoved() = testScope.runTest {
tracker.initialize(0)
val newID = 5
val profileID = newID + 10
val callback = TestCallback()
tracker.addCallback(callback, executor)
tracker.removeCallback(callback)
val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
captor.value.onBeforeUserSwitching(newID, beforeUserSwitchingReply)
captor.value.onUserSwitching(newID, userSwitchingReply)
runCurrent()
verify(beforeUserSwitchingReply).sendResult(any())
verify(userSwitchingReply).sendResult(any())
captor.value.onUserSwitchComplete(newID)
val intentProfiles = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
.putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
tracker.onReceive(context, intentProfiles)
assertThat(callback.calledOnUserChanging).isEqualTo(0)
assertThat(callback.calledOnUserChanged).isEqualTo(0)
assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
}
private class TestCallback : UserTracker.Callback {
var calledOnBeforeUserChanging = 0
var calledOnUserChanging = 0
var calledOnUserChanged = 0
var calledOnProfilesChanged = 0
var lastUser: Int? = null
var lastUserContext: Context? = null
var lastUserProfiles = emptyList<UserInfo>()
override fun onBeforeUserSwitching(newUser: Int) {
calledOnBeforeUserChanging++
lastUser = newUser
}
override fun onUserChanging(newUser: Int, userContext: Context) {
calledOnUserChanging++
lastUser = newUser
lastUserContext = userContext
}
override fun onUserChanged(newUser: Int, userContext: Context) {
calledOnUserChanged++
lastUser = newUser
lastUserContext = userContext
}
override fun onProfilesChanged(profiles: List<UserInfo>) {
calledOnProfilesChanged++
lastUserProfiles = profiles
}
}
}