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

Commit 1a738d74 authored by Marcello Galhardo's avatar Marcello Galhardo Committed by Automerger Merge Worker
Browse files

Merge "Handle multi-user in note task initialization" into udc-qpr-dev am: 87c0d2c6

parents b864abb9 87c0d2c6
Loading
Loading
Loading
Loading
+41 −11
Original line number Diff line number Diff line
@@ -287,16 +287,56 @@ constructor(
        debugLog { "setNoteTaskShortcutEnabled - completed: $isEnabled" }
    }

    /**
     * Like [updateNoteTaskAsUser] but automatically apply to the current user and all its work
     * profiles.
     *
     * @see updateNoteTaskAsUser
     * @see UserTracker.userHandle
     * @see UserTracker.userProfiles
     */
    fun updateNoteTaskForCurrentUserAndManagedProfiles() {
        updateNoteTaskAsUser(userTracker.userHandle)
        for (profile in userTracker.userProfiles) {
            if (userManager.isManagedProfile(profile.id)) {
                updateNoteTaskAsUser(profile.userHandle)
            }
        }
    }

    /**
     * Updates all [NoteTaskController] related information, including but not exclusively the
     * widget shortcut created by the [user] - by default it will use the current user.
     *
     * If the user is not current user, the update will be dispatched to run in that user's process.
     *
     * Keep in mind the shortcut API has a
     * [rate limiting](https://developer.android.com/develop/ui/views/launch/shortcuts/managing-shortcuts#rate-limiting)
     * and may not be updated in real-time. To reduce the chance of stale shortcuts, we run the
     * function during System UI initialization.
     */
    fun updateNoteTaskAsUser(user: UserHandle) {
        if (!userManager.isUserUnlocked(user)) {
            debugLog { "updateNoteTaskAsUser call but user locked: user=$user" }
            return
        }

        if (user == userTracker.userHandle) {
            updateNoteTaskAsUserInternal(user)
        } else {
            // TODO(b/278729185): Replace fire and forget service with a bounded service.
            val intent = NoteTaskControllerUpdateService.createIntent(context)
            context.startServiceAsUser(intent, user)
        }
    }

    @InternalNoteTaskApi
    fun updateNoteTaskAsUserInternal(user: UserHandle) {
        if (!userManager.isUserUnlocked(user)) {
            debugLog { "updateNoteTaskAsUserInternal call but user locked: user=$user" }
            return
        }

        val packageName = roleManager.getDefaultRoleHolderAsUser(ROLE_NOTES, user)
        val hasNotesRoleHolder = isEnabled && !packageName.isNullOrEmpty()

@@ -314,18 +354,8 @@ constructor(
    /** @see OnRoleHoldersChangedListener */
    fun onRoleHoldersChanged(roleName: String, user: UserHandle) {
        if (roleName != ROLE_NOTES) return
        if (!userManager.isUserUnlocked(user)) {
            debugLog { "onRoleHoldersChanged call but user locked: role=$roleName, user=$user" }
            return
        }

        if (user == userTracker.userHandle) {
        updateNoteTaskAsUser(user)
        } else {
            // TODO(b/278729185): Replace fire and forget service with a bounded service.
            val intent = NoteTaskControllerUpdateService.createIntent(context)
            context.startServiceAsUser(intent, user)
        }
    }

    private val SecureSettings.preferredUser: UserHandle
+1 −1
Original line number Diff line number Diff line
@@ -44,7 +44,7 @@ constructor(
    override fun onCreate() {
        super.onCreate()
        // TODO(b/278729185): Replace fire and forget service with a bounded service.
        controller.updateNoteTaskAsUser(user)
        controller.updateNoteTaskAsUserInternal(user)
        stopSelf()
    }

+36 −9
Original line number Diff line number Diff line
@@ -15,7 +15,10 @@
 */
package com.android.systemui.notetask

import android.app.role.OnRoleHoldersChangedListener
import android.app.role.RoleManager
import android.content.Context
import android.content.pm.UserInfo
import android.os.UserHandle
import android.view.KeyEvent
import android.view.KeyEvent.KEYCODE_N
@@ -54,6 +57,7 @@ constructor(
        initializeHandleSystemKey()
        initializeOnRoleHoldersChanged()
        initializeOnUserUnlocked()
        initializeUserTracker()
    }

    /**
@@ -79,7 +83,7 @@ constructor(
    private fun initializeOnRoleHoldersChanged() {
        roleManager.addOnRoleHoldersChangedListenerAsUser(
            backgroundExecutor,
            controller::onRoleHoldersChanged,
            callbacks,
            UserHandle.ALL,
        )
    }
@@ -93,18 +97,41 @@ constructor(
     */
    private fun initializeOnUserUnlocked() {
        if (keyguardUpdateMonitor.isUserUnlocked(userTracker.userId)) {
            controller.setNoteTaskShortcutEnabled(true, userTracker.userHandle)
        } else {
            keyguardUpdateMonitor.registerCallback(onUserUnlockedCallback)
            controller.updateNoteTaskForCurrentUserAndManagedProfiles()
        }
        keyguardUpdateMonitor.registerCallback(callbacks)
    }

    private fun initializeUserTracker() {
        userTracker.addCallback(callbacks, backgroundExecutor)
    }

    // Some callbacks use a weak reference, so we play safe and keep a hard reference to them all.
    private val callbacks =
        object :
            KeyguardUpdateMonitorCallback(),
            CommandQueue.Callbacks,
            UserTracker.Callback,
            OnRoleHoldersChangedListener {

            override fun handleSystemKey(key: KeyEvent) {
                key.toNoteTaskEntryPointOrNull()?.let(controller::showNoteTask)
            }

            override fun onRoleHoldersChanged(roleName: String, user: UserHandle) {
                controller.onRoleHoldersChanged(roleName, user)
            }

    // KeyguardUpdateMonitor.registerCallback uses a weak reference, so we need a hard reference.
    private val onUserUnlockedCallback =
        object : KeyguardUpdateMonitorCallback() {
            override fun onUserUnlocked() {
                controller.setNoteTaskShortcutEnabled(true, userTracker.userHandle)
                keyguardUpdateMonitor.removeCallback(this)
                controller.updateNoteTaskForCurrentUserAndManagedProfiles()
            }

            override fun onUserChanged(newUser: Int, userContext: Context) {
                controller.updateNoteTaskForCurrentUserAndManagedProfiles()
            }

            override fun onProfilesChanged(profiles: List<UserInfo>) {
                controller.updateNoteTaskForCurrentUserAndManagedProfiles()
            }
        }
}
+47 −11
Original line number Diff line number Diff line
@@ -63,6 +63,7 @@ import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.settings.SecureSettings
import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.Bubbles
@@ -649,7 +650,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
    }

    @Test
    fun onRoleHoldersChanged_notesRole_sameUser_shouldUpdateShortcuts() {
    fun onRoleHoldersChanged_notesRole_shouldUpdateShortcuts() {
        val user = userTracker.userHandle
        val controller = spy(createNoteTaskController())
        doNothing().whenever(controller).updateNoteTaskAsUser(any())
@@ -658,22 +659,41 @@ internal class NoteTaskControllerTest : SysuiTestCase() {

        verify(controller).updateNoteTaskAsUser(user)
    }
    // endregion

    // region updateNoteTaskAsUser
    @Test
    fun updateNoteTaskAsUser_sameUser_shouldUpdateShortcuts() {
        val user = userTracker.userHandle
        val controller = spy(createNoteTaskController())
        doNothing().whenever(controller).updateNoteTaskAsUserInternal(any())

        controller.updateNoteTaskAsUser(user)

        verify(controller).updateNoteTaskAsUserInternal(user)
        verify(context, never()).startServiceAsUser(any(), any())
    }

    @Test
    fun onRoleHoldersChanged_notesRole_differentUser_shouldUpdateShortcutsInUserProcess() {
    fun updateNoteTaskAsUser_differentUser_shouldUpdateShortcutsInUserProcess() {
        // FakeUserTracker will default to UserHandle.SYSTEM.
        val user = UserHandle.CURRENT
        val controller = spy(createNoteTaskController(isEnabled = true))
        doNothing().whenever(controller).updateNoteTaskAsUserInternal(any())

        createNoteTaskController(isEnabled = true).onRoleHoldersChanged(ROLE_NOTES, user)
        controller.updateNoteTaskAsUser(user)

        verify(context).startServiceAsUser(any(), eq(user))
        verify(controller, never()).updateNoteTaskAsUserInternal(any())
        val intent = withArgCaptor { verify(context).startServiceAsUser(capture(), eq(user)) }
        assertThat(intent).hasComponentClass(NoteTaskControllerUpdateService::class.java)
    }
    // endregion

    // region updateNoteTaskAsUser
    // region internalUpdateNoteTaskAsUser
    @Test
    fun updateNoteTaskAsUser_withNotesRole_withShortcuts_shouldUpdateShortcuts() {
        createNoteTaskController(isEnabled = true).updateNoteTaskAsUser(userTracker.userHandle)
    fun updateNoteTaskAsUserInternal_withNotesRole_withShortcuts_shouldUpdateShortcuts() {
        createNoteTaskController(isEnabled = true)
            .updateNoteTaskAsUserInternal(userTracker.userHandle)

        val actualComponent = argumentCaptor<ComponentName>()
        verify(context.packageManager)
@@ -702,11 +722,12 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
    }

    @Test
    fun updateNoteTaskAsUser_noNotesRole_shouldDisableShortcuts() {
    fun updateNoteTaskAsUserInternal_noNotesRole_shouldDisableShortcuts() {
        whenever(roleManager.getRoleHoldersAsUser(ROLE_NOTES, userTracker.userHandle))
            .thenReturn(emptyList())

        createNoteTaskController(isEnabled = true).updateNoteTaskAsUser(userTracker.userHandle)
        createNoteTaskController(isEnabled = true)
            .updateNoteTaskAsUserInternal(userTracker.userHandle)

        val argument = argumentCaptor<ComponentName>()
        verify(context.packageManager)
@@ -723,8 +744,9 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
    }

    @Test
    fun updateNoteTaskAsUser_flagDisabled_shouldDisableShortcuts() {
        createNoteTaskController(isEnabled = false).updateNoteTaskAsUser(userTracker.userHandle)
    fun updateNoteTaskAsUserInternal_flagDisabled_shouldDisableShortcuts() {
        createNoteTaskController(isEnabled = false)
            .updateNoteTaskAsUserInternal(userTracker.userHandle)

        val argument = argumentCaptor<ComponentName>()
        verify(context.packageManager)
@@ -741,6 +763,20 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
    }
    // endregion

    // startregion updateNoteTaskForAllUsers
    @Test
    fun updateNoteTaskForAllUsers_shouldRunUpdateForCurrentUserAndProfiles() {
        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
        val controller = spy(createNoteTaskController())
        doNothing().whenever(controller).updateNoteTaskAsUser(any())

        controller.updateNoteTaskForCurrentUserAndManagedProfiles()

        verify(controller).updateNoteTaskAsUser(mainUserInfo.userHandle)
        verify(controller).updateNoteTaskAsUser(workUserInfo.userHandle)
    }
    // endregion

    // region getUserForHandlingNotesTaking
    @Test
    fun getUserForHandlingNotesTaking_cope_quickAffordance_shouldReturnWorkProfileUser() {
+58 −36
Original line number Diff line number Diff line
@@ -32,9 +32,12 @@ import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.time.FakeSystemClock
import com.android.wm.shell.bubbles.Bubbles
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before
@@ -93,8 +96,8 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {

        verify(commandQueue).addCallback(any())
        verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
        verify(controller).setNoteTaskShortcutEnabled(any(), any())
        verify(keyguardMonitor, never()).registerCallback(any())
        verify(controller).updateNoteTaskForCurrentUserAndManagedProfiles()
        verify(keyguardMonitor).registerCallback(any())
    }

    @Test
@@ -107,6 +110,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
        verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
        verify(controller, never()).setNoteTaskShortcutEnabled(any(), any())
        verify(keyguardMonitor).registerCallback(any())
        assertThat(userTracker.callbacks).isNotEmpty()
    }

    @Test
@@ -146,7 +150,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
        val expectedKeyEvent = KeyEvent(ACTION_DOWN, KEYCODE_STYLUS_BUTTON_TAIL)
        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
        underTest.initialize()
        val callback = captureArgument { verify(commandQueue).addCallback(capture()) }
        val callback = withArgCaptor { verify(commandQueue).addCallback(capture()) }

        callback.handleSystemKey(expectedKeyEvent)

@@ -154,22 +158,23 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
    }

    @Test
    fun initialize_userUnlocked() {
    fun initialize_userUnlocked_shouldUpdateNoteTask() {
        whenever(keyguardMonitor.isUserUnlocked(userTracker.userId)).thenReturn(false)
        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
        underTest.initialize()
        val callback = captureArgument { verify(keyguardMonitor).registerCallback(capture()) }
        val callback = withArgCaptor { verify(keyguardMonitor).registerCallback(capture()) }
        whenever(keyguardMonitor.isUserUnlocked(userTracker.userId)).thenReturn(true)

        callback.onUserUnlocked()
        verify(controller).setNoteTaskShortcutEnabled(any(), any())

        verify(controller).updateNoteTaskForCurrentUserAndManagedProfiles()
    }

    @Test
    fun initialize_onRoleHoldersChanged() {
    fun initialize_onRoleHoldersChanged_shouldRunOnRoleHoldersChanged() {
        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
        underTest.initialize()
        val callback = captureArgument {
        val callback = withArgCaptor {
            verify(roleManager)
                    .addOnRoleHoldersChangedListenerAsUser(any(), capture(), eq(UserHandle.ALL))
        }
@@ -178,7 +183,24 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {

        verify(controller).onRoleHoldersChanged(ROLE_NOTES, userTracker.userHandle)
    }

    @Test
    fun initialize_onProfilesChanged_shouldUpdateNoteTask() {
        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
        underTest.initialize()

        userTracker.callbacks.first().onProfilesChanged(emptyList())

        verify(controller, times(2)).updateNoteTaskForCurrentUserAndManagedProfiles()
    }

private inline fun <reified T : Any> captureArgument(block: ArgumentCaptor<T>.() -> Unit) =
    argumentCaptor<T>().apply(block).value
    @Test
    fun initialize_onUserChanged_shouldUpdateNoteTask() {
        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
        underTest.initialize()

        userTracker.callbacks.first().onUserChanged(0, mock())

        verify(controller, times(2)).updateNoteTaskForCurrentUserAndManagedProfiles()
    }
}