diff options
| author | 2023-09-21 12:05:39 +0000 | |
|---|---|---|
| committer | 2023-09-21 12:05:39 +0000 | |
| commit | 3061f0b61dc8aaec2c38e3f0c964f0572447cdcc (patch) | |
| tree | 27ed7c065fc2ff0db8fc3ab7223b78536f8157f5 | |
| parent | 1c083f6971dead2407866b5df60acf61c639bf6b (diff) | |
| parent | f726ce4fe200c160b47e8e4c0134f91dffc1ab11 (diff) | |
Merge "Post UserTracker callbacks to background" into main
9 files changed, 238 insertions, 68 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 0f733c6e1f24..422b2e1a1d28 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -203,7 +203,6 @@ import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.TimeZone; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.stream.Collectors; @@ -2440,7 +2439,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab handleDevicePolicyManagerStateChanged(msg.arg1); break; case MSG_USER_SWITCHING: - handleUserSwitching(msg.arg1, (CountDownLatch) msg.obj); + handleUserSwitching(msg.arg1, (Runnable) msg.obj); break; case MSG_USER_SWITCH_COMPLETE: handleUserSwitchComplete(msg.arg1); @@ -2741,10 +2740,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } private final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() { + @Override - public void onUserChanging(int newUser, Context userContext, CountDownLatch latch) { + public void onUserChanging(int newUser, @NonNull Context userContext, + @NonNull Runnable resultCallback) { mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHING, - newUser, 0, latch)); + newUser, 0, resultCallback)); } @Override @@ -3575,7 +3576,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * Handle {@link #MSG_USER_SWITCHING} */ @VisibleForTesting - void handleUserSwitching(int userId, CountDownLatch latch) { + void handleUserSwitching(int userId, Runnable resultCallback) { mLogger.logUserSwitching(userId, "from UserTracker"); Assert.isMainThread(); clearBiometricRecognized(); @@ -3589,7 +3590,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onUserSwitching(userId); } } - latch.countDown(); + resultCallback.run(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index cef52e73d711..d0ceaf6075fa 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -117,7 +117,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.function.Supplier; @@ -492,9 +491,9 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis notifySystemUiStateFlags(mSysUiState.getFlags()); notifyConnectionChanged(); - if (mLatchForOnUserChanging != null) { - mLatchForOnUserChanging.countDown(); - mLatchForOnUserChanging = null; + if (mDoneUserChanging != null) { + mDoneUserChanging.run(); + mDoneUserChanging = null; } } @@ -550,14 +549,14 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } }; - private CountDownLatch mLatchForOnUserChanging; + private Runnable mDoneUserChanging; private final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() { @Override public void onUserChanging(int newUser, @NonNull Context userContext, - CountDownLatch latch) { + @NonNull Runnable resultCallback) { mConnectionBackoffAttempts = 0; - mLatchForOnUserChanging = latch; + mDoneUserChanging = resultCallback; internalConnectToCurrentUser("User changed"); } }; diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt index 93a3e905f0e0..bd592c9daff6 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt @@ -19,7 +19,6 @@ package com.android.systemui.settings import android.content.Context import android.content.pm.UserInfo import android.os.UserHandle -import java.util.concurrent.CountDownLatch import java.util.concurrent.Executor /** @@ -68,8 +67,8 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { interface Callback { /** - * Same as {@link onUserChanging(Int, Context, CountDownLatch)} but the latch will be - * auto-decremented after the completion of this method. + * Same as {@link onUserChanging(Int, Context, Runnable)} but the callback will be + * called automatically after the completion of this method. */ fun onUserChanging(newUser: Int, userContext: Context) {} @@ -78,12 +77,12 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { * Override this method to run things while the screen is frozen for the user switch. * Please use {@link #onUserChanged} if the task doesn't need to push the unfreezing of the * screen further. Please be aware that code executed in this callback will lengthen the - * user switch duration. When overriding this method, countDown() MUST be called on the - * latch once execution is complete. + * user switch duration. When overriding this method, resultCallback#run() MUST be called + * once the execution is complete. */ - fun onUserChanging(newUser: Int, userContext: Context, latch: CountDownLatch) { + fun onUserChanging(newUser: Int, userContext: Context, resultCallback: Runnable) { onUserChanging(newUser, userContext) - latch.countDown() + resultCallback.run() } /** diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index 5fb3c01bbc1d..2f873019349c 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -33,11 +33,22 @@ import androidx.annotation.GuardedBy import androidx.annotation.WorkerThread import com.android.systemui.Dumpable import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.util.Assert +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex import java.io.PrintWriter import java.lang.ref.WeakReference import java.util.concurrent.CountDownLatch import java.util.concurrent.Executor +import javax.inject.Provider import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @@ -58,20 +69,26 @@ import kotlin.reflect.KProperty */ open class UserTrackerImpl internal constructor( private val context: Context, + private val featureFlagsProvider: Provider<FeatureFlagsClassic>, private val userManager: UserManager, private val iActivityManager: IActivityManager, private val dumpManager: DumpManager, - private val backgroundHandler: Handler + private val appScope: CoroutineScope, + private val backgroundContext: CoroutineDispatcher, + private val backgroundHandler: Handler, ) : UserTracker, Dumpable, BroadcastReceiver() { companion object { private const val TAG = "UserTrackerImpl" + private const val USER_CHANGE_THRESHOLD = 5L * 1000 // 5 sec } var initialized = false private set private val mutex = Any() + private val isBackgroundUserSwitchEnabled: Boolean get() = + featureFlagsProvider.get().isEnabled(Flags.USER_TRACKER_BACKGROUND_CALLBACKS) override var userId: Int by SynchronizedDelegate(context.userId) protected set @@ -103,6 +120,10 @@ open class UserTrackerImpl internal constructor( @GuardedBy("callbacks") private val callbacks: MutableList<DataItem> = ArrayList() + private var beforeUserSwitchingJob: Job? = null + private var userSwitchingJob: Job? = null + private var afterUserSwitchingJob: Job? = null + open fun initialize(startingUser: Int) { if (initialized) { return @@ -119,7 +140,7 @@ open class UserTrackerImpl internal constructor( addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED) addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED) } - context.registerReceiverForAllUsers(this, filter, null /* permission */, backgroundHandler) + context.registerReceiverForAllUsers(this, filter, null, backgroundHandler) registerUserSwitchObserver() @@ -162,16 +183,39 @@ open class UserTrackerImpl internal constructor( private fun registerUserSwitchObserver() { iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() { override fun onBeforeUserSwitching(newUserId: Int) { - handleBeforeUserSwitching(newUserId) + if (isBackgroundUserSwitchEnabled) { + beforeUserSwitchingJob?.cancel() + beforeUserSwitchingJob = appScope.launch(backgroundContext) { + handleBeforeUserSwitching(newUserId) + } + } else { + handleBeforeUserSwitching(newUserId) + } } override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) { - handleUserSwitching(newUserId) - reply?.sendResult(null) + if (isBackgroundUserSwitchEnabled) { + userSwitchingJob?.cancel() + userSwitchingJob = appScope.launch(backgroundContext) { + handleUserSwitchingCoroutines(newUserId) { + reply?.sendResult(null) + } + } + } else { + handleUserSwitching(newUserId) + reply?.sendResult(null) + } } override fun onUserSwitchComplete(newUserId: Int) { - handleUserSwitchComplete(newUserId) + if (isBackgroundUserSwitchEnabled) { + afterUserSwitchingJob?.cancel() + afterUserSwitchingJob = appScope.launch(backgroundContext) { + handleUserSwitchComplete(newUserId) + } + } else { + handleUserSwitchComplete(newUserId) + } } }, TAG) } @@ -195,7 +239,7 @@ open class UserTrackerImpl internal constructor( val callback = it.callback.get() if (callback != null) { it.executor.execute { - callback.onUserChanging(userId, userContext, latch) + callback.onUserChanging(userId, userContext) { latch.countDown() } } } else { latch.countDown() @@ -205,6 +249,28 @@ open class UserTrackerImpl internal constructor( } @WorkerThread + protected open suspend fun handleUserSwitchingCoroutines(newUserId: Int, onDone: () -> Unit) = + coroutineScope { + Assert.isNotMainThread() + Log.i(TAG, "Switching to user $newUserId") + + for (callbackDataItem in synchronized(callbacks) { callbacks.toList() }) { + val callback: UserTracker.Callback = callbackDataItem.callback.get() ?: continue + launch(callbackDataItem.executor.asCoroutineDispatcher()) { + val mutex = Mutex(true) + val thresholdLogJob = launch(backgroundContext) { + delay(USER_CHANGE_THRESHOLD) + Log.e(TAG, "Failed to finish $callback in time") + } + callback.onUserChanging(userId, userContext) { mutex.unlock() } + mutex.lock() + thresholdLogJob.cancel() + }.join() + } + onDone() + } + + @WorkerThread protected open fun handleUserSwitchComplete(newUserId: Int) { Assert.isNotMainThread() Log.i(TAG, "Switched to user $newUserId") diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java b/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java index e9a1dd7e6ecb..a0dd924b3959 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java +++ b/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java @@ -25,8 +25,10 @@ import android.os.UserManager; import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlagsClassic; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.DisplayTrackerImpl; import com.android.systemui.settings.UserContentResolverProvider; @@ -42,6 +44,11 @@ import dagger.Provides; import dagger.multibindings.ClassKey; import dagger.multibindings.IntoMap; +import javax.inject.Provider; + +import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.CoroutineScope; + /** * Dagger Module for classes found within the com.android.systemui.settings package. */ @@ -60,14 +67,17 @@ public abstract class MultiUserUtilsModule { @Provides static UserTracker provideUserTracker( Context context, + Provider<FeatureFlagsClassic> featureFlagsProvider, UserManager userManager, IActivityManager iActivityManager, DumpManager dumpManager, + @Application CoroutineScope appScope, + @Background CoroutineDispatcher backgroundDispatcher, @Background Handler handler ) { int startingUser = ActivityManager.getCurrentUser(); - UserTrackerImpl tracker = new UserTrackerImpl(context, userManager, iActivityManager, - dumpManager, handler); + UserTrackerImpl tracker = new UserTrackerImpl(context, featureFlagsProvider, userManager, + iActivityManager, dumpManager, appScope, backgroundDispatcher, handler); tracker.initialize(startingUser); return tracker; } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 562d3a557ea2..29511329b630 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -172,7 +172,6 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.Random; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -1363,7 +1362,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(1); assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(1); - mKeyguardUpdateMonitor.handleUserSwitching(10 /* user */, new CountDownLatch(0)); + mKeyguardUpdateMonitor.handleUserSwitching(10 /* user */, () -> {}); assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(0); assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(0); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt index beb981d89735..1bb2ff825115 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt @@ -11,10 +11,13 @@ import androidx.concurrent.futures.DirectExecutor 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.util.mockito.any import com.android.systemui.util.mockito.capture import com.google.common.truth.Truth.assertThat import java.util.concurrent.Executor +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -60,6 +63,9 @@ class UserTrackerImplReceiveTest : SysuiTestCase() { @Mock private lateinit var callback: UserTracker.Callback @Captor private lateinit var captor: ArgumentCaptor<List<UserInfo>> + private val fakeFeatures = FakeFeatureFlagsClassic() + private val testDispatcher = StandardTestDispatcher() + private lateinit var tracker: UserTrackerImpl @Before @@ -68,12 +74,21 @@ class UserTrackerImplReceiveTest : SysuiTestCase() { `when`(context.user).thenReturn(UserHandle.SYSTEM) `when`(context.createContextAsUser(ArgumentMatchers.any(), anyInt())).thenReturn(context) - - tracker = UserTrackerImpl(context, userManager, iActivityManager, dumpManager, handler) } @Test - fun callsCallbackAndUpdatesProfilesWhenAnIntentReceived() { + fun callsCallbackAndUpdatesProfilesWhenAnIntentReceived() = runTest { + tracker = + UserTrackerImpl( + context, + { fakeFeatures }, + userManager, + iActivityManager, + dumpManager, + this, + testDispatcher, + handler + ) tracker.initialize(0) tracker.addCallback(callback, executor) val profileID = tracker.userId + 10 diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt index aa98f08e9015..52b25f85b870 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt @@ -26,16 +26,25 @@ import android.os.Handler import android.os.IRemoteCallback import android.os.UserHandle import android.os.UserManager -import android.testing.AndroidTestingRunner 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 java.util.concurrent.Executor +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 @@ -43,28 +52,52 @@ 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(AndroidTestingRunner::class) +@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 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 @@ -84,54 +117,65 @@ class UserTrackerImplTest : SysuiTestCase() { listOf(info) } - tracker = UserTrackerImpl(context, userManager, iActivityManager, dumpManager, handler) + featureFlags.set(Flags.USER_TRACKER_BACKGROUND_CALLBACKS, isBackgroundUserTrackerEnabled) + tracker = + UserTrackerImpl( + context, + { featureFlags }, + userManager, + iActivityManager, + dumpManager, + testScope.backgroundScope, + testDispatcher, + handler, + ) } @Test - fun testNotInitialized() { + fun testNotInitialized() = testScope.runTest { assertThat(tracker.initialized).isFalse() } @Test(expected = IllegalStateException::class) - fun testGetUserIdBeforeInitThrowsException() { + fun testGetUserIdBeforeInitThrowsException() = testScope.runTest { tracker.userId } @Test(expected = IllegalStateException::class) - fun testGetUserHandleBeforeInitThrowsException() { + fun testGetUserHandleBeforeInitThrowsException() = testScope.runTest { tracker.userHandle } @Test(expected = IllegalStateException::class) - fun testGetUserContextBeforeInitThrowsException() { + fun testGetUserContextBeforeInitThrowsException() = testScope.runTest { tracker.userContext } @Test(expected = IllegalStateException::class) - fun testGetUserContentResolverBeforeInitThrowsException() { + fun testGetUserContentResolverBeforeInitThrowsException() = testScope.runTest { tracker.userContentResolver } @Test(expected = IllegalStateException::class) - fun testGetUserProfilesBeforeInitThrowsException() { + fun testGetUserProfilesBeforeInitThrowsException() = testScope.runTest { tracker.userProfiles } @Test - fun testInitialize() { + fun testInitialize() = testScope.runTest { tracker.initialize(0) assertThat(tracker.initialized).isTrue() } @Test - fun testReceiverRegisteredOnInitialize() { + fun testReceiverRegisteredOnInitialize() = testScope.runTest { tracker.initialize(0) val captor = ArgumentCaptor.forClass(IntentFilter::class.java) - verify(context).registerReceiverForAllUsers( - eq(tracker), capture(captor), isNull(), eq(handler)) + verify(context) + .registerReceiverForAllUsers(eq(tracker), capture(captor), isNull(), eq(handler)) with(captor.value) { assertThat(countActions()).isEqualTo(6) assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue() @@ -144,7 +188,7 @@ class UserTrackerImplTest : SysuiTestCase() { } @Test - fun testInitialValuesSet() { + fun testInitialValuesSet() = testScope.runTest { val testID = 4 tracker.initialize(testID) @@ -161,7 +205,7 @@ class UserTrackerImplTest : SysuiTestCase() { } @Test - fun testUserSwitch() { + fun testUserSwitch() = testScope.runTest { tracker.initialize(0) val newID = 5 @@ -169,6 +213,7 @@ class UserTrackerImplTest : SysuiTestCase() { verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) captor.value.onBeforeUserSwitching(newID) captor.value.onUserSwitching(newID, userSwitchingReply) + runCurrent() verify(userSwitchingReply).sendResult(any()) verify(userManager).getProfiles(newID) @@ -184,7 +229,7 @@ class UserTrackerImplTest : SysuiTestCase() { } @Test - fun testManagedProfileAvailable() { + fun testManagedProfileAvailable() = testScope.runTest { tracker.initialize(0) val profileID = tracker.userId + 10 @@ -209,7 +254,8 @@ class UserTrackerImplTest : SysuiTestCase() { assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId, profileID) } - fun testManagedProfileUnavailable() { + @Test + fun testManagedProfileUnavailable() = testScope.runTest { tracker.initialize(0) val profileID = tracker.userId + 10 @@ -234,7 +280,8 @@ class UserTrackerImplTest : SysuiTestCase() { assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId, profileID) } - fun testManagedProfileStartedAndRemoved() { + @Test + fun testManagedProfileStartedAndRemoved() = testScope.runTest { tracker.initialize(0) val profileID = tracker.userId + 10 @@ -271,7 +318,7 @@ class UserTrackerImplTest : SysuiTestCase() { } @Test - fun testCallbackNotCalledOnAdd() { + fun testCallbackNotCalledOnAdd() = testScope.runTest { tracker.initialize(0) val callback = TestCallback() @@ -282,7 +329,7 @@ class UserTrackerImplTest : SysuiTestCase() { } @Test - fun testCallbackCalledOnUserChanging() { + fun testCallbackCalledOnUserChanging() = testScope.runTest { tracker.initialize(0) val callback = TestCallback() tracker.addCallback(callback, executor) @@ -293,15 +340,49 @@ class UserTrackerImplTest : SysuiTestCase() { verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) captor.value.onBeforeUserSwitching(newID) captor.value.onUserSwitching(newID, userSwitchingReply) - verify(userSwitchingReply).sendResult(any()) + runCurrent() + verify(userSwitchingReply).sendResult(any()) assertThat(callback.calledOnUserChanging).isEqualTo(1) assertThat(callback.lastUser).isEqualTo(newID) assertThat(callback.lastUserContext?.userId).isEqualTo(newID) } @Test - fun testCallbackCalledOnUserChanged() { + 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.onBeforeUserSwitching(newID) + 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) @@ -312,6 +393,7 @@ class UserTrackerImplTest : SysuiTestCase() { verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) captor.value.onBeforeUserSwitching(newID) captor.value.onUserSwitchComplete(newID) + runCurrent() assertThat(callback.calledOnUserChanged).isEqualTo(1) assertThat(callback.lastUser).isEqualTo(newID) @@ -321,7 +403,7 @@ class UserTrackerImplTest : SysuiTestCase() { } @Test - fun testCallbackCalledOnUserInfoChanged() { + fun testCallbackCalledOnUserInfoChanged() = testScope.runTest { tracker.initialize(0) val callback = TestCallback() tracker.addCallback(callback, executor) @@ -331,18 +413,18 @@ class UserTrackerImplTest : SysuiTestCase() { 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 + 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)) + .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID)) tracker.onReceive(context, intent) @@ -352,7 +434,7 @@ class UserTrackerImplTest : SysuiTestCase() { } @Test - fun testCallbackRemoved() { + fun testCallbackRemoved() = testScope.runTest { tracker.initialize(0) val newID = 5 val profileID = newID + 10 @@ -364,6 +446,7 @@ class UserTrackerImplTest : SysuiTestCase() { val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) captor.value.onUserSwitching(newID, userSwitchingReply) + runCurrent() verify(userSwitchingReply).sendResult(any()) captor.value.onUserSwitchComplete(newID) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt index 4242c1635468..f5f924dfce4b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt @@ -22,7 +22,6 @@ import android.content.pm.UserInfo import android.os.UserHandle import android.test.mock.MockContentResolver import com.android.systemui.util.mockito.mock -import java.util.concurrent.CountDownLatch import java.util.concurrent.Executor /** A fake [UserTracker] to be used in tests. */ @@ -73,8 +72,7 @@ class FakeUserTracker( fun onUserChanging(userId: Int = _userId) { val copy = callbacks.toList() - val latch = CountDownLatch(copy.size) - copy.forEach { it.onUserChanging(userId, userContext, latch) } + copy.forEach { it.onUserChanging(userId, userContext) {} } } fun onUserChanged(userId: Int = _userId) { |