diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt index 1558ac533137c..287e8101f86d0 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt @@ -62,12 +62,24 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { fun removeCallback(callback: Callback) /** - * Ćallback for notifying of changes. + * Callback for notifying of changes. */ interface Callback { + /** + * Notifies that the current user is being changed. + * 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. + */ + @JvmDefault + fun onUserChanging(newUser: Int, userContext: Context) {} + /** * Notifies that the current user has changed. + * Override this method to run things after the screen is unfrozen for the user switch. + * Please see {@link #onUserChanging} if you need to hide jank. */ @JvmDefault fun onUserChanged(newUser: Int, userContext: Context) {} @@ -78,4 +90,4 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { @JvmDefault fun onProfilesChanged(profiles: List<@JvmSuppressWildcards UserInfo>) {} } -} \ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index 61390c582fd60..9f551c6ebd345 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -16,6 +16,8 @@ package com.android.systemui.settings +import android.app.IActivityManager +import android.app.UserSwitchObserver import android.content.BroadcastReceiver import android.content.ContentResolver import android.content.Context @@ -23,6 +25,7 @@ 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 android.util.Log @@ -34,6 +37,7 @@ import com.android.systemui.util.Assert import java.io.PrintWriter import java.lang.IllegalStateException import java.lang.ref.WeakReference +import java.util.concurrent.CountDownLatch import java.util.concurrent.Executor import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @@ -56,6 +60,7 @@ import kotlin.reflect.KProperty class UserTrackerImpl internal constructor( private val context: Context, private val userManager: UserManager, + private val iActivityManager: IActivityManager, private val dumpManager: DumpManager, private val backgroundHandler: Handler ) : UserTracker, Dumpable, BroadcastReceiver() { @@ -107,7 +112,6 @@ class UserTrackerImpl internal constructor( setUserIdInternal(startingUser) val filter = IntentFilter().apply { - addAction(Intent.ACTION_USER_SWITCHED) addAction(Intent.ACTION_USER_INFO_CHANGED) // These get called when a managed profile goes in or out of quiet mode. addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) @@ -118,14 +122,13 @@ class UserTrackerImpl internal constructor( } context.registerReceiverForAllUsers(this, filter, null /* permission */, backgroundHandler) + registerUserSwitchObserver() + dumpManager.registerDumpable(TAG, this) } override fun onReceive(context: Context, intent: Intent) { when (intent.action) { - Intent.ACTION_USER_SWITCHED -> { - handleSwitchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL)) - } Intent.ACTION_USER_INFO_CHANGED, Intent.ACTION_MANAGED_PROFILE_AVAILABLE, Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE, @@ -157,22 +160,43 @@ class UserTrackerImpl internal constructor( return ctx to profiles } + private fun registerUserSwitchObserver() { + iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() { + override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) { + backgroundHandler.run { + handleUserSwitching(newUserId) + reply?.sendResult(null) + } + } + + override fun onUserSwitchComplete(newUserId: Int) { + backgroundHandler.run { + handleUserSwitchComplete(newUserId) + } + } + }, TAG) + } + @WorkerThread - private fun handleSwitchUser(newUser: Int) { + private fun handleUserSwitching(newUserId: Int) { Assert.isNotMainThread() - if (newUser == UserHandle.USER_NULL) { - Log.w(TAG, "handleSwitchUser - Couldn't get new id from intent") - return - } - - if (newUser == userId) return - Log.i(TAG, "Switching to user $newUser") - - val (ctx, profiles) = setUserIdInternal(newUser) + Log.i(TAG, "Switching to user $newUserId") + setUserIdInternal(newUserId) notifySubscribers { - onUserChanged(newUser, ctx) - onProfilesChanged(profiles) + onUserChanging(newUserId, userContext) + }.await() + } + + @WorkerThread + private fun handleUserSwitchComplete(newUserId: Int) { + Assert.isNotMainThread() + Log.i(TAG, "Switched to user $newUserId") + + setUserIdInternal(newUserId) + notifySubscribers { + onUserChanged(newUserId, userContext) + onProfilesChanged(userProfiles) } } @@ -201,17 +225,25 @@ class UserTrackerImpl internal constructor( } } - private inline fun notifySubscribers(crossinline action: UserTracker.Callback.() -> Unit) { + private inline fun notifySubscribers( + crossinline action: UserTracker.Callback.() -> Unit + ): CountDownLatch { val list = synchronized(callbacks) { callbacks.toList() } + val latch = CountDownLatch(list.size) + list.forEach { if (it.callback.get() != null) { it.executor.execute { it.callback.get()?.action() + latch.countDown() } + } else { + latch.countDown() } } + return latch } override fun dump(pw: PrintWriter, args: Array) { @@ -258,4 +290,4 @@ private data class DataItem( fun sameOrEmpty(other: UserTracker.Callback): Boolean { return callback.get()?.equals(other) ?: true } -} \ No newline at end of file +} 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 2f62e44ba4c4a..809fa29769110 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java +++ b/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java @@ -17,6 +17,7 @@ package com.android.systemui.settings.dagger; import android.app.ActivityManager; +import android.app.IActivityManager; import android.content.Context; import android.os.Handler; import android.os.UserManager; @@ -57,11 +58,13 @@ public abstract class MultiUserUtilsModule { static UserTracker provideUserTracker( Context context, UserManager userManager, + IActivityManager iActivityManager, DumpManager dumpManager, @Background Handler handler ) { int startingUser = ActivityManager.getCurrentUser(); - UserTrackerImpl tracker = new UserTrackerImpl(context, userManager, dumpManager, handler); + UserTrackerImpl tracker = new UserTrackerImpl(context, userManager, iActivityManager, + dumpManager, handler); tracker.initialize(startingUser); return tracker; } diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index c0f03902202a2..8cb4deb4882c5 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -174,7 +174,7 @@ constructor( val callback = object : UserTracker.Callback { - override fun onUserChanged(newUser: Int, userContext: Context) { + override fun onUserChanging(newUser: Int, userContext: Context) { send() } 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 3710281499b30..57b6b2bd6fde6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt @@ -1,5 +1,6 @@ package com.android.systemui.settings +import android.app.IActivityManager import android.content.Context import android.content.Intent import android.content.pm.UserInfo @@ -51,6 +52,7 @@ class UserTrackerImplReceiveTest : SysuiTestCase() { @Mock private lateinit var context: Context @Mock private lateinit var userManager: UserManager + @Mock private lateinit var iActivityManager: IActivityManager @Mock(stubOnly = true) private lateinit var dumpManager: DumpManager @Mock(stubOnly = true) private lateinit var handler: Handler @@ -67,7 +69,7 @@ class UserTrackerImplReceiveTest : SysuiTestCase() { `when`(context.user).thenReturn(UserHandle.SYSTEM) `when`(context.createContextAsUser(ArgumentMatchers.any(), anyInt())).thenReturn(context) - tracker = UserTrackerImpl(context, userManager, dumpManager, handler) + tracker = UserTrackerImpl(context, userManager, iActivityManager, dumpManager, handler) } @Test 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 e65bbb1bea086..71ba21538a8e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt @@ -16,11 +16,14 @@ 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 android.testing.AndroidTestingRunner @@ -29,19 +32,20 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.util.mockito.capture import com.google.common.truth.Truth.assertThat +import java.util.concurrent.Executor import org.junit.Before import org.junit.Test import org.junit.runner.RunWith 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.`when` import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -import java.util.concurrent.Executor @SmallTest @RunWith(AndroidTestingRunner::class) @@ -51,6 +55,10 @@ class UserTrackerImplTest : SysuiTestCase() { 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) @@ -76,7 +84,7 @@ class UserTrackerImplTest : SysuiTestCase() { listOf(info) } - tracker = UserTrackerImpl(context, userManager, dumpManager, handler) + tracker = UserTrackerImpl(context, userManager, iActivityManager, dumpManager, handler) } @Test @@ -125,8 +133,7 @@ class UserTrackerImplTest : SysuiTestCase() { verify(context).registerReceiverForAllUsers( eq(tracker), capture(captor), isNull(), eq(handler)) with(captor.value) { - assertThat(countActions()).isEqualTo(7) - assertThat(hasAction(Intent.ACTION_USER_SWITCHED)).isTrue() + assertThat(countActions()).isEqualTo(6) assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue() assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue() assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)).isTrue() @@ -158,8 +165,10 @@ class UserTrackerImplTest : SysuiTestCase() { tracker.initialize(0) val newID = 5 - val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, newID) - tracker.onReceive(context, intent) + val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) + verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) + captor.value.onUserSwitching(newID, userSwitchingReply) + verify(userSwitchingReply).sendResult(any()) verify(userManager).getProfiles(newID) @@ -271,6 +280,24 @@ class UserTrackerImplTest : SysuiTestCase() { assertThat(callback.calledOnUserChanged).isEqualTo(0) } + @Test + fun testCallbackCalledOnUserChanging() { + 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.onUserSwitching(newID, userSwitchingReply) + verify(userSwitchingReply).sendResult(any()) + + assertThat(callback.calledOnUserChanging).isEqualTo(1) + assertThat(callback.lastUser).isEqualTo(newID) + assertThat(callback.lastUserContext?.userId).isEqualTo(newID) + } + @Test fun testCallbackCalledOnUserChanged() { tracker.initialize(0) @@ -279,8 +306,9 @@ class UserTrackerImplTest : SysuiTestCase() { val newID = 5 - val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, newID) - tracker.onReceive(context, intent) + val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) + verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) + captor.value.onUserSwitchComplete(newID) assertThat(callback.calledOnUserChanged).isEqualTo(1) assertThat(callback.lastUser).isEqualTo(newID) @@ -330,25 +358,36 @@ class UserTrackerImplTest : SysuiTestCase() { tracker.addCallback(callback, executor) tracker.removeCallback(callback) - val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, 5) - tracker.onReceive(context, intent) + val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) + verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) + captor.value.onUserSwitching(newID, userSwitchingReply) + 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 calledOnUserChanging = 0 var calledOnUserChanged = 0 var calledOnProfilesChanged = 0 var lastUser: Int? = null var lastUserContext: Context? = null var lastUserProfiles = emptyList() + override fun onUserChanging(newUser: Int, userContext: Context) { + calledOnUserChanging++ + lastUser = newUser + lastUserContext = userContext + } + override fun onUserChanged(newUser: Int, userContext: Context) { calledOnUserChanged++ lastUser = newUser @@ -360,4 +399,4 @@ class UserTrackerImplTest : SysuiTestCase() { lastUserProfiles = profiles } } -} \ No newline at end of file +} 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 0dd1fc758a274..251014fc50b38 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 @@ -67,7 +67,10 @@ class FakeUserTracker( _userHandle = UserHandle.of(_userId) val copy = callbacks.toList() - copy.forEach { it.onUserChanged(_userId, userContext) } + copy.forEach { + it.onUserChanging(_userId, userContext) + it.onUserChanged(_userId, userContext) + } } fun onProfileChanged() {