Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f5c2a526 authored by Yasin Kilicdere's avatar Yasin Kilicdere
Browse files

Make UserTrackerImpl use UserSwitchObserver instead of broadcast.

UserTrackerImp was listening to ACTION_USER_SWITCHED broadcast
to do stuff that makes changes on the screen for a user switch.
But since the broadcast is async (there is no way to tell back the
broadcaster that the reciver has finished it's work), many times those
UI changes were happening "after" the switch, whereas they should be
done "while" the switch is happening, i.e. when the screen is frozen
and UserSwithingDialog is visible.

In order to achive this, UserSwitchObserver's onUserSwitching method
should be used to do things while the screen is frozen for the user
switch.

This CL makes UserTrackerImpl use UserSwitchObserver instead of
ACTION_USER_SWITCHED and provides onUserChanging method (in addition
to already existing onUserChanged method) to it's subscribers. So that
the subscribers can decide when their code should run, during or after
the switch.

This CL also moves work done in UserRepository from onUserChanged to
onUserChanging because user pillar shown on top right of the tablet's
screen was updating after the user switch. Now it updates during the
switch, while the screen is frozen.

Bug: 249527131
Bug: 265068243
Test: Manual visual test by adding a 5s delay to ACTION_USER_SWITCHED
broadcast to make the jank consistent, and verifying the jank is gone
after the fix.
Test: atest UserTrackerImplTest
Test: atest UserTrackerImplReceiveTest
Test: atest UserRepositoryImplTest

Change-Id: Ica2be8cc434cea4bb526a51eeb8171765f461172
parent c1de6541
Loading
Loading
Loading
Loading
+14 −2
Original line number Diff line number Diff line
@@ -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) {}
+48 −16
Original line number Diff line number Diff line
@@ -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
@@ -33,6 +36,7 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.util.Assert
import java.io.PrintWriter
import java.lang.ref.WeakReference
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executor
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
@@ -55,6 +59,7 @@ import kotlin.reflect.KProperty
open 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() {
@@ -106,7 +111,6 @@ open 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)
@@ -117,14 +121,13 @@ open 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,
@@ -156,22 +159,43 @@ open 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
    protected open fun handleSwitchUser(newUser: Int) {
    protected open fun handleUserSwitching(newUserId: Int) {
        Assert.isNotMainThread()
        if (newUser == UserHandle.USER_NULL) {
            Log.w(TAG, "handleSwitchUser - Couldn't get new id from intent")
            return
        }
        Log.i(TAG, "Switching to user $newUserId")

        if (newUser == userId) return
        Log.i(TAG, "Switching to user $newUser")
        setUserIdInternal(newUserId)
        notifySubscribers {
            onUserChanging(newUserId, userContext)
        }.await()
    }

        val (ctx, profiles) = setUserIdInternal(newUser)
    @WorkerThread
    protected open fun handleUserSwitchComplete(newUserId: Int) {
        Assert.isNotMainThread()
        Log.i(TAG, "Switched to user $newUserId")

        setUserIdInternal(newUserId)
        notifySubscribers {
            onUserChanged(newUser, ctx)
            onProfilesChanged(profiles)
            onUserChanged(newUserId, userContext)
            onProfilesChanged(userProfiles)
        }
    }

@@ -200,17 +224,25 @@ open 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<out String>) {
+4 −1
Original line number Diff line number Diff line
@@ -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;
    }
+1 −1
Original line number Diff line number Diff line
@@ -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()
                        }

+3 −1
Original line number Diff line number Diff line
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
Loading