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

Commit 03384957 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Implement persistence of assistant configuration for Floaty" into main

parents 33fe2afc a01f3185
Loading
Loading
Loading
Loading
+179 −14
Original line number Diff line number Diff line
@@ -16,9 +16,13 @@

package com.android.systemui.topwindoweffects.data.repository

import android.app.role.OnRoleHoldersChangedListener
import android.app.role.RoleManager
import android.content.pm.UserInfo
import android.hardware.input.InputManager
import android.os.Bundle
import android.os.Handler
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.provider.Settings.Global.POWER_BUTTON_LONG_PRESS
@@ -33,15 +37,22 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.shared.Flags
import com.android.systemui.testKosmos
import com.android.systemui.topwindoweffects.data.repository.SqueezeEffectRepositoryImpl.Companion.IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_DEFAULT_VALUE
import com.android.systemui.topwindoweffects.data.repository.SqueezeEffectRepositoryImpl.Companion.IS_INVOCATION_EFFECT_ENABLED_KEY
import com.android.systemui.topwindoweffects.data.repository.SqueezeEffectRepositoryImpl.Companion.SET_INVOCATION_EFFECT_PARAMETERS_ACTION
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.settings.FakeGlobalSettings
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
import kotlinx.coroutines.test.StandardTestDispatcher
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.eq
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations

private fun createAssistantSettingBundle(enableAssistantSetting: Boolean) =
@@ -56,60 +67,66 @@ class SqueezeEffectRepositoryTest : SysuiTestCase() {

    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val globalSettings = FakeGlobalSettings(StandardTestDispatcher())
    private val mainExecutor = Executor(Runnable::run)
    private val userRepository = FakeUserRepository()

    @Mock private lateinit var bgHandler: Handler
    @Mock private lateinit var handler: Handler
    @Mock private lateinit var inputManager: InputManager
    @Mock private lateinit var roleManager: RoleManager

    private val onRoleHoldersChangedListener =
        ArgumentCaptor.forClass(OnRoleHoldersChangedListener::class.java)

    private val Kosmos.underTest by
        Kosmos.Fixture {
            SqueezeEffectRepositoryImpl(
                context = mContext,
                handler = bgHandler,
                coroutineContext = testScope.testScheduler,
                executor = Runnable::run,
                inputManager = inputManager,
                context = context,
                coroutineScope = testScope.backgroundScope,
                globalSettings = globalSettings,
                userRepository = userRepository,
                inputManager = inputManager,
                handler = handler,
                coroutineContext = testScope.testScheduler,
                roleManager = roleManager,
                executor = mainExecutor,
            )
        }

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        MockitoAnnotations.openMocks(this)
    }

    @DisableFlags(Flags.FLAG_ENABLE_LPP_ASSIST_INVOCATION_EFFECT)
    @Test
    fun testSqueezeEffectDisabled_WhenOtherwiseEnabled_FlagDisabled() =
    fun testSqueezeEffectDisabled_FlagDisabled() =
        kosmos.runTest {
            globalSettings.putInt(POWER_BUTTON_LONG_PRESS, 5)
            underTest.tryHandleSetUiHints(createAssistantSettingBundle(true))

            val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)

            assertThat(isSqueezeEffectEnabled).isFalse()
        }

    @EnableFlags(Flags.FLAG_ENABLE_LPP_ASSIST_INVOCATION_EFFECT)
    @Test
    fun testSqueezeEffectDisabled_WhenOtherwiseEnabled_GlobalSettingDisabled() =
    fun testSqueezeEffectDisabled_GlobalSettingDisabled() =
        kosmos.runTest {
            underTest.tryHandleSetUiHints(createAssistantSettingBundle(true))
            globalSettings.putInt(POWER_BUTTON_LONG_PRESS, 0)

            val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)

            assertThat(isSqueezeEffectEnabled).isFalse()
        }

    @EnableFlags(Flags.FLAG_ENABLE_LPP_ASSIST_INVOCATION_EFFECT)
    @Test
    fun testSqueezeEffectDisabled_WhenOtherwiseEnabled_AssistantSettingDisabled() =
    fun testSqueezeEffectDisabled_AssistantSettingDisabled() =
        kosmos.runTest {
            globalSettings.putInt(POWER_BUTTON_LONG_PRESS, 5)
            underTest.tryHandleSetUiHints(createAssistantSettingBundle(false))

            val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)

            assertThat(isSqueezeEffectEnabled).isFalse()
        }

@@ -121,7 +138,155 @@ class SqueezeEffectRepositoryTest : SysuiTestCase() {
            underTest.tryHandleSetUiHints(createAssistantSettingBundle(true))

            val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)

            assertThat(isSqueezeEffectEnabled).isTrue()
        }

    private suspend fun Kosmos.initUserAndAssistant(
        userInfos: List<UserInfo>,
        userIndex: Int,
        assistantName: String,
    ) {
        underTest // "poke" class to ensure it's initialized
        userRepository.setUserInfos(userInfos)
        userRepository.setSelectedUserInfo(userInfos[userIndex])
        verify(roleManager)
            .addOnRoleHoldersChangedListenerAsUser(
                eq(mainExecutor),
                onRoleHoldersChangedListener.capture(),
                eq(UserHandle.ALL),
            )
        `when`(
                roleManager.getRoleHoldersAsUser(
                    eq(RoleManager.ROLE_ASSISTANT),
                    eq(userInfos[userIndex].userHandle),
                )
            )
            .thenReturn(listOf(assistantName))
        onRoleHoldersChangedListener.value.onRoleHoldersChanged(
            RoleManager.ROLE_ASSISTANT,
            userInfos[userIndex].userHandle,
        )
    }

    @EnableFlags(Flags.FLAG_ENABLE_LPP_ASSIST_INVOCATION_EFFECT)
    @Test
    fun testAssistantEnabledStatusIsDefault_AssistantSwitched() =
        kosmos.runTest {
            initUserAndAssistant(userInfos, 0, "a")
            globalSettings.putInt(POWER_BUTTON_LONG_PRESS, 5)
            underTest.tryHandleSetUiHints(
                createAssistantSettingBundle(
                    !IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_DEFAULT_VALUE
                )
            )

            `when`(
                    roleManager.getRoleHoldersAsUser(
                        eq(RoleManager.ROLE_ASSISTANT),
                        eq(userInfos[0].userHandle),
                    )
                )
                .thenReturn(listOf("b"))
            onRoleHoldersChangedListener.value.onRoleHoldersChanged(
                RoleManager.ROLE_ASSISTANT,
                userInfos[0].userHandle,
            )

            val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)
            assertThat(isSqueezeEffectEnabled)
                .isEqualTo(IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_DEFAULT_VALUE)
        }

    @EnableFlags(Flags.FLAG_ENABLE_LPP_ASSIST_INVOCATION_EFFECT)
    @Test
    fun testAssistantEnabledStatusIsDefault_UserSwitched() =
        kosmos.runTest {
            initUserAndAssistant(userInfos, 0, "a")
            globalSettings.putInt(POWER_BUTTON_LONG_PRESS, 5)
            underTest.tryHandleSetUiHints(
                createAssistantSettingBundle(
                    !IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_DEFAULT_VALUE
                )
            )

            userRepository.setSelectedUserInfo(userInfos[1])

            val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)
            assertThat(isSqueezeEffectEnabled)
                .isEqualTo(IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_DEFAULT_VALUE)
        }

    @EnableFlags(Flags.FLAG_ENABLE_LPP_ASSIST_INVOCATION_EFFECT)
    @Test
    fun testAssistantEnabledStatusIsRetained_AssistantSwitchedBackAndForth() =
        kosmos.runTest {
            initUserAndAssistant(userInfos, 0, "a")
            globalSettings.putInt(POWER_BUTTON_LONG_PRESS, 5)
            underTest.tryHandleSetUiHints(
                createAssistantSettingBundle(
                    !IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_DEFAULT_VALUE
                )
            )

            `when`(
                    roleManager.getRoleHoldersAsUser(
                        eq(RoleManager.ROLE_ASSISTANT),
                        eq(UserHandle.CURRENT),
                    )
                )
                .thenReturn(listOf("b"))
            onRoleHoldersChangedListener.value.onRoleHoldersChanged(
                RoleManager.ROLE_ASSISTANT,
                UserHandle.CURRENT,
            )
            `when`(
                    roleManager.getRoleHoldersAsUser(
                        eq(RoleManager.ROLE_ASSISTANT),
                        eq(UserHandle.CURRENT),
                    )
                )
                .thenReturn(listOf("a"))
            onRoleHoldersChangedListener.value.onRoleHoldersChanged(
                RoleManager.ROLE_ASSISTANT,
                UserHandle.CURRENT,
            )

            val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)
            assertThat(isSqueezeEffectEnabled)
                .isEqualTo(!IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_DEFAULT_VALUE)
        }

    @EnableFlags(Flags.FLAG_ENABLE_LPP_ASSIST_INVOCATION_EFFECT)
    @Test
    fun testAssistantEnabledStatusIsRetained_UserSwitchedBackAndForth() =
        kosmos.runTest {
            initUserAndAssistant(userInfos, 0, "a")
            globalSettings.putInt(POWER_BUTTON_LONG_PRESS, 5)
            underTest.tryHandleSetUiHints(
                createAssistantSettingBundle(
                    !IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_DEFAULT_VALUE
                )
            )

            userRepository.setSelectedUserInfo(userInfos[1])
            userRepository.setSelectedUserInfo(userInfos[0])

            val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled)
            assertThat(isSqueezeEffectEnabled)
                .isEqualTo(!IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_DEFAULT_VALUE)
        }

    companion object {
        private val userInfos =
            listOf(
                UserInfo().apply {
                    id = 0
                    name = "User 0"
                },
                UserInfo().apply {
                    id = 1
                    name = "User 1"
                },
            )
    }
}
+133 −7
Original line number Diff line number Diff line
@@ -17,12 +17,15 @@
package com.android.systemui.topwindoweffects.data.repository

import android.annotation.SuppressLint
import android.app.role.OnRoleHoldersChangedListener
import android.app.role.RoleManager
import android.content.Context
import android.database.ContentObserver
import android.hardware.input.InputManager
import android.hardware.input.KeyGestureEvent
import android.os.Bundle
import android.os.Handler
import android.os.UserHandle
import android.provider.Settings.Global.POWER_BUTTON_LONG_PRESS
import android.provider.Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS
import android.util.DisplayUtils
@@ -30,6 +33,7 @@ import android.view.DisplayInfo
import android.view.KeyEvent
import androidx.annotation.ArrayRes
import androidx.annotation.DrawableRes
import androidx.core.content.edit
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.assist.AssistManager
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -39,30 +43,97 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.res.R
import com.android.systemui.shared.Flags
import com.android.systemui.topwindoweffects.data.entity.SqueezeEffectCornersInfo
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

@SysUISingleton
class SqueezeEffectRepositoryImpl
@Inject
constructor(
    @Application private val context: Context,
    @Background private val handler: Handler?,
    @Background private val coroutineContext: CoroutineContext,
    @Background executor: Executor,
    @Background private val coroutineScope: CoroutineScope,
    private val globalSettings: GlobalSettings,
    private val userRepository: UserRepository,
    private val inputManager: InputManager,
    @Background handler: Handler?,
    @Background coroutineContext: CoroutineContext,
    roleManager: RoleManager,
    @Background executor: Executor,
) : SqueezeEffectRepository, InvocationEffectSetUiHintsHandler {

    private val sharedPreferences by lazy {
        context.getSharedPreferences(SHARED_PREFERENCES_FILE_NAME, Context.MODE_PRIVATE)
    }
    private val isInvocationEffectEnabledByAssistantFlow = MutableStateFlow<Boolean?>(null)

    private val selectedAssistantName: StateFlow<String> =
        conflatedCallbackFlow {
                val listener = OnRoleHoldersChangedListener { roleName, _ ->
                    if (roleName == RoleManager.ROLE_ASSISTANT) {
                        trySendWithFailureLogging(
                            roleManager.getCurrentAssistantFor(userRepository.selectedUserHandle),
                            TAG,
                            "updated currentlyActiveAssistantName due to role change",
                        )
                    }
                }
                roleManager.addOnRoleHoldersChangedListenerAsUser(
                    executor,
                    listener,
                    UserHandle.ALL,
                )

                launch {
                    userRepository.selectedUser.collect {
                        trySendWithFailureLogging(
                            roleManager.getCurrentAssistantFor(userRepository.selectedUserHandle),
                            TAG,
                            "updated currentlyActiveAssistantName due to user change",
                        )
                    }
                }

                awaitClose {
                    roleManager.removeOnRoleHoldersChangedListenerAsUser(listener, UserHandle.ALL)
                }
            }
            .flowOn(coroutineContext)
            .stateIn(
                scope = coroutineScope,
                started = SharingStarted.WhileSubscribed(),
                initialValue = roleManager.getCurrentAssistantFor(userRepository.selectedUserHandle),
            )

    private val selectedAssistantNameAndUserFlow =
        selectedAssistantName
            .combine(userRepository.selectedUser) { a, b -> Pair(a, b) }
            .distinctUntilChanged()

    init {
        coroutineScope.launch {
            selectedAssistantNameAndUserFlow.collect {
                // Assistant or user changed, reload enabled state
                isInvocationEffectEnabledByAssistantFlow.value =
                    loadIsInvocationEffectEnabledByAssistant()
            }
        }
    }

    private val isPowerButtonLongPressConfiguredToLaunchAssistantFlow: Flow<Boolean> =
        conflatedCallbackFlow {
                val observer =
@@ -175,14 +246,38 @@ constructor(
        return drawableResource
    }

    private val isInvocationEffectEnabledForCurrentAssistantFlow = MutableStateFlow(true)
    private fun loadIsInvocationEffectEnabledByAssistant(): Boolean {
        val persistedForUser =
            sharedPreferences.getInt(
                PERSISTED_FOR_USER_PREFERENCE,
                PERSISTED_FOR_USER_DEFAULT_VALUE,
            )

        val persistedForAssistant =
            sharedPreferences.getString(
                PERSISTED_FOR_ASSISTANT_PREFERENCE,
                PERSISTED_FOR_ASSISTANT_DEFAULT_VALUE,
            )

        return if (
            persistedForUser == userRepository.selectedUserHandle.identifier &&
                persistedForAssistant == selectedAssistantName.value
        ) {
            sharedPreferences.getBoolean(
                IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE,
                IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_DEFAULT_VALUE,
            )
        } else {
            IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_DEFAULT_VALUE
        }
    }

    override val isSqueezeEffectEnabled: Flow<Boolean> =
        combine(
            isPowerButtonLongPressConfiguredToLaunchAssistantFlow,
            isInvocationEffectEnabledForCurrentAssistantFlow,
            isInvocationEffectEnabledByAssistantFlow,
        ) { prerequisites ->
            prerequisites.all { it } && Flags.enableLppAssistInvocationEffect()
            prerequisites.all { it ?: false } && Flags.enableLppAssistInvocationEffect()
        }

    private fun getIsPowerButtonLongPressConfiguredToLaunchAssistant() =
@@ -207,8 +302,9 @@ constructor(
        return when (hints.getString(AssistManager.ACTION_KEY)) {
            SET_INVOCATION_EFFECT_PARAMETERS_ACTION -> {
                if (hints.containsKey(IS_INVOCATION_EFFECT_ENABLED_KEY)) {
                    isInvocationEffectEnabledForCurrentAssistantFlow.value =
                    setIsInvocationEffectEnabledByAssistant(
                        hints.getBoolean(IS_INVOCATION_EFFECT_ENABLED_KEY)
                    )
                }
                true
            }
@@ -216,6 +312,17 @@ constructor(
        }
    }

    private fun setIsInvocationEffectEnabledByAssistant(enabled: Boolean) {
        coroutineScope.launch {
            isInvocationEffectEnabledByAssistantFlow.value = enabled
            sharedPreferences.edit {
                putBoolean(IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE, enabled)
                putString(PERSISTED_FOR_ASSISTANT_PREFERENCE, selectedAssistantName.value)
                putInt(PERSISTED_FOR_USER_PREFERENCE, userRepository.selectedUserHandle.identifier)
            }
        }
    }

    companion object {
        private const val TAG = "SqueezeEffectRepository"

@@ -229,9 +336,28 @@ constructor(
         */
        @VisibleForTesting const val DEFAULT_INITIAL_DELAY_MILLIS = 150L
        @VisibleForTesting const val DEFAULT_LONG_PRESS_POWER_DURATION_MILLIS = 500L

        @VisibleForTesting
        const val SET_INVOCATION_EFFECT_PARAMETERS_ACTION = "set_invocation_effect_parameters"
        @VisibleForTesting
        const val IS_INVOCATION_EFFECT_ENABLED_KEY = "is_invocation_effect_enabled"

        @VisibleForTesting const val IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_DEFAULT_VALUE = true
        private const val PERSISTED_FOR_USER_DEFAULT_VALUE = Integer.MIN_VALUE
        private const val PERSISTED_FOR_ASSISTANT_DEFAULT_VALUE = ""

        @VisibleForTesting
        const val SHARED_PREFERENCES_FILE_NAME = "assistant_invocation_effect_preferences"
        @VisibleForTesting
        const val IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE =
            "is_invocation_effect_enabled"
        private const val PERSISTED_FOR_ASSISTANT_PREFERENCE = "persisted_for_assistant"
        private const val PERSISTED_FOR_USER_PREFERENCE = "persisted_for_user"
    }
}

private val UserRepository.selectedUserHandle
    get() = selectedUser.value.userInfo.userHandle

private fun RoleManager.getCurrentAssistantFor(userHandle: UserHandle) =
    getRoleHoldersAsUser(RoleManager.ROLE_ASSISTANT, userHandle)?.firstOrNull() ?: ""