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

Commit 7db6873b authored by Matt Pietal's avatar Matt Pietal
Browse files

Fix user switching race condition on boot

With HSUM, a switch is done immediately on boot to the main
user. This can occur as SystemUI is being initialized, leading
it to miss the "onUserSwitching" event, which performs critical
setup for the upcoming user.

If a switch is in progress during boot, manually send that event.

Also, fix a crash in the contentprovider during this user switch.

Fixes: 365707550
Test: atest UserTrackerImplTest
Test: manual - setup multiple users, one with security NONE, reboot
Flag: EXEMPT bugfix
Change-Id: Idf1403f7115c07c20c0514beb89af554aeb7bbc8
parent 5f067b4c
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -2489,6 +2489,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
                    this::onTransitionStateChanged
            );
        }

        // start() can be invoked in the middle of user switching, so check for this state and issue
        // the call manually as that important event was missed.
        if (mUserTracker.isUserSwitching()) {
            handleUserSwitching(mUserTracker.getUserId(), () -> {});
        }
    }

    @VisibleForTesting
+16 −40
Original line number Diff line number Diff line
@@ -54,29 +54,25 @@ class CustomizationProvider :
            addURI(
                Contract.AUTHORITY,
                Contract.LockScreenQuickAffordances.qualifiedTablePath(
                    Contract.LockScreenQuickAffordances.SlotTable.TABLE_NAME,
                    Contract.LockScreenQuickAffordances.SlotTable.TABLE_NAME
                ),
                MATCH_CODE_ALL_SLOTS,
            )
            addURI(
                Contract.AUTHORITY,
                Contract.LockScreenQuickAffordances.qualifiedTablePath(
                    Contract.LockScreenQuickAffordances.AffordanceTable.TABLE_NAME,
                    Contract.LockScreenQuickAffordances.AffordanceTable.TABLE_NAME
                ),
                MATCH_CODE_ALL_AFFORDANCES,
            )
            addURI(
                Contract.AUTHORITY,
                Contract.LockScreenQuickAffordances.qualifiedTablePath(
                    Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME,
                    Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME
                ),
                MATCH_CODE_ALL_SELECTIONS,
            )
            addURI(
                Contract.AUTHORITY,
                Contract.FlagsTable.TABLE_NAME,
                MATCH_CODE_ALL_FLAGS,
            )
            addURI(Contract.AUTHORITY, Contract.FlagsTable.TABLE_NAME, MATCH_CODE_ALL_FLAGS)
        }

    override fun onCreate(): Boolean {
@@ -106,15 +102,15 @@ class CustomizationProvider :
            when (uriMatcher.match(uri)) {
                MATCH_CODE_ALL_SLOTS ->
                    Contract.LockScreenQuickAffordances.qualifiedTablePath(
                        Contract.LockScreenQuickAffordances.SlotTable.TABLE_NAME,
                        Contract.LockScreenQuickAffordances.SlotTable.TABLE_NAME
                    )
                MATCH_CODE_ALL_AFFORDANCES ->
                    Contract.LockScreenQuickAffordances.qualifiedTablePath(
                        Contract.LockScreenQuickAffordances.AffordanceTable.TABLE_NAME,
                        Contract.LockScreenQuickAffordances.AffordanceTable.TABLE_NAME
                    )
                MATCH_CODE_ALL_SELECTIONS ->
                    Contract.LockScreenQuickAffordances.qualifiedTablePath(
                        Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME,
                        Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME
                    )
                MATCH_CODE_ALL_FLAGS -> Contract.FlagsTable.TABLE_NAME
                else -> null
@@ -128,6 +124,7 @@ class CustomizationProvider :
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        if (!::mainDispatcher.isInitialized) return null
        if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) {
            throw UnsupportedOperationException()
        }
@@ -142,6 +139,7 @@ class CustomizationProvider :
        selectionArgs: Array<out String>?,
        sortOrder: String?,
    ): Cursor? {
        if (!::mainDispatcher.isInitialized) return null
        return runBlocking("$TAG#query", mainDispatcher) {
            when (uriMatcher.match(uri)) {
                MATCH_CODE_ALL_AFFORDANCES -> queryAffordances()
@@ -163,11 +161,8 @@ class CustomizationProvider :
        return 0
    }

    override fun delete(
        uri: Uri,
        selection: String?,
        selectionArgs: Array<out String>?,
    ): Int {
    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        if (!::mainDispatcher.isInitialized) return 0
        if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) {
            throw UnsupportedOperationException()
        }
@@ -232,11 +227,7 @@ class CustomizationProvider :
            throw IllegalArgumentException("Cannot insert selection, affordance ID was empty!")
        }

        val success =
            interactor.select(
                slotId = slotId,
                affordanceId = affordanceId,
            )
        val success = interactor.select(slotId = slotId, affordanceId = affordanceId)

        return if (success) {
            Log.d(TAG, "Successfully selected $affordanceId for slot $slotId")
@@ -318,22 +309,14 @@ class CustomizationProvider :
            )
            .apply {
                interactor.getSlotPickerRepresentations().forEach { representation ->
                    addRow(
                        arrayOf(
                            representation.id,
                            representation.maxSelectedAffordances,
                        )
                    )
                    addRow(arrayOf(representation.id, representation.maxSelectedAffordances))
                }
            }
    }

    private suspend fun queryFlags(): Cursor {
        return MatrixCursor(
                arrayOf(
                    Contract.FlagsTable.Columns.NAME,
                    Contract.FlagsTable.Columns.VALUE,
                )
                arrayOf(Contract.FlagsTable.Columns.NAME, Contract.FlagsTable.Columns.VALUE)
            )
            .apply {
                interactor.getPickerFlags().forEach { flag ->
@@ -351,10 +334,7 @@ class CustomizationProvider :
            }
    }

    private suspend fun deleteSelection(
        uri: Uri,
        selectionArgs: Array<out String>?,
    ): Int {
    private suspend fun deleteSelection(uri: Uri, selectionArgs: Array<out String>?): Int {
        if (selectionArgs == null) {
            throw IllegalArgumentException(
                "Cannot delete selection, selection arguments not included!"
@@ -372,11 +352,7 @@ class CustomizationProvider :
                    )
            }

        val deleted =
            interactor.unselect(
                slotId = slotId,
                affordanceId = affordanceId,
            )
        val deleted = interactor.unselect(slotId = slotId, affordanceId = affordanceId)

        return if (deleted) {
            Log.d(TAG, "Successfully unselected $affordanceId for slot $slotId")
+6 −0
Original line number Diff line number Diff line
@@ -1689,6 +1689,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
                    });
            mJavaAdapter.alwaysCollectFlow(communalViewModel.getTransitionFromOccludedEnded(),
                    getFinishedCallbackConsumer());

            // System ready can be invoked in the middle of user switching, so check for this state
            // and issue the call manually as that important event was missed.
            if (mUserTracker.isUserSwitching()) {
                mUpdateCallback.onUserSwitching(mUserTracker.getUserId());
            }
        }
        // Most services aren't available until the system reaches the ready state, so we
        // send it here when the device first boots.
+2 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.settings;

import android.app.ActivityManager;
import android.app.IActivityManager;
import android.content.Context;
import android.hardware.display.DisplayManager;
@@ -66,7 +67,7 @@ public abstract class MultiUserUtilsModule {
            @Background CoroutineDispatcher backgroundDispatcher,
            @Background Handler handler
    ) {
        int startingUser = userManager.getBootUser().getIdentifier();
        int startingUser = ActivityManager.getCurrentUser();
        UserTrackerImpl tracker = new UserTrackerImpl(context, featureFlagsProvider, userManager,
                iActivityManager, dumpManager, appScope, backgroundDispatcher, handler);
        tracker.initialize(startingUser);
+22 −37
Original line number Diff line number Diff line
@@ -16,11 +16,10 @@

package com.android.systemui.settings

import com.android.systemui.util.annotations.WeaklyReferencedCallback

import android.content.Context
import android.content.pm.UserInfo
import android.os.UserHandle
import com.android.systemui.util.annotations.WeaklyReferencedCallback
import java.util.concurrent.Executor

/**
@@ -31,19 +30,13 @@ import java.util.concurrent.Executor
 */
interface UserTracker : UserContentResolverProvider, UserContextProvider {

    /**
     * Current user's id.
     */
    /** Current user's id. */
    val userId: Int

    /**
     * [UserHandle] for current user
     */
    /** [UserHandle] for current user */
    val userHandle: UserHandle

    /**
     * [UserInfo] for current user
     */
    /** [UserInfo] for current user */
    val userInfo: UserInfo

    /**
@@ -56,39 +49,33 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider {
     */
    val userProfiles: List<UserInfo>

    /**
     * Add a [Callback] to be notified of chances, on a particular [Executor]
     */
    /** Is the system in the process of switching users? */
    val isUserSwitching: Boolean

    /** Add a [Callback] to be notified of chances, on a particular [Executor] */
    fun addCallback(callback: Callback, executor: Executor)

    /**
     * Remove a [Callback] previously added.
     */
    /** Remove a [Callback] previously added. */
    fun removeCallback(callback: Callback)

    /**
     * Callback for notifying of changes.
     */
    /** Callback for notifying of changes. */
    @WeaklyReferencedCallback
    interface Callback {
        /**
         * Notifies that the current user will be changed.
         */
        /** Notifies that the current user will be changed. */
        fun onBeforeUserSwitching(newUser: Int) {}

        /**
         * Same as {@link onUserChanging(Int, Context, Runnable)} but the callback will be
         * called automatically 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) {}

        /**
         * 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. When overriding this method, resultCallback#run() MUST be called
         * once the  execution is complete.
         * 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. When overriding this
         * method, resultCallback#run() MUST be called once the execution is complete.
         */
        fun onUserChanging(newUser: Int, userContext: Context, resultCallback: Runnable) {
            onUserChanging(newUser, userContext)
@@ -96,15 +83,13 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider {
        }

        /**
         * 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.
         * 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.
         */
        fun onUserChanged(newUser: Int, userContext: Context) {}

        /**
         * Notifies that the current user's profiles have changed.
         */
        /** Notifies that the current user's profiles have changed. */
        fun onProfilesChanged(profiles: List<@JvmSuppressWildcards UserInfo>) {}
    }
}
Loading