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

Commit 111f02ac authored by Bharat Singh's avatar Bharat Singh
Browse files

[SysUI][Floaty] Fix logic for saving invocation effect preferences

* Set all preferences in a single "SharedPreferences.edit" call to
  preserve atomicity
* Change data in SharedPreference only if it's different from currently
  saved data in the preference to prevent callback calls on key change
* Allow assistant to change any parameter for invocation effect config
  if the active and saved user as well as assistant is same
* If the saved assistant and user differs from active user and
  assistant, set all params related to invocation effect (set default if
  not param value not shared in setUiHints API call)

Bug: 412616963
Bug: 418685731
Test: Manual, changes work as expected with test assistant
Test: atest InvocationEffectPreferencesTest
Flag: com.android.systemui.shared.enable_lpp_assist_invocation_effect

Change-Id: I7eb20442ab1e5383ce9d368ea727e2d171ba39ef
parent 57758ae0
Loading
Loading
Loading
Loading
+101 −15
Original line number Diff line number Diff line
@@ -32,6 +32,9 @@ 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.InvocationEffectPreferencesImpl.Companion.DEFAULT_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.DEFAULT_INWARD_EFFECT_PADDING_DURATION_MS
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.DEFAULT_OUTWARD_EFFECT_DURATION_MS
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.INVOCATION_EFFECT_ANIMATION_IN_DURATION_PADDING_MS
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.INVOCATION_EFFECT_ANIMATION_OUT_DURATION_MS
import com.android.systemui.topwindoweffects.data.repository.SqueezeEffectRepositoryImpl.Companion.IS_INVOCATION_EFFECT_ENABLED_KEY
@@ -162,7 +165,14 @@ class SqueezeEffectRepositoryTest : SysuiTestCase() {
    @Test
    fun testInvocationEffectInwardsAnimationDelay() =
        kosmos.runTest {
            fakeInvocationEffectPreferences.setInwardAnimationPaddingDurationMillis(450)
            fakeInvocationEffectPreferences.setInvocationEffectConfig(
                InvocationEffectPreferences.Config(
                    isEnabled = true,
                    inwardsEffectDurationPadding = 450,
                    outwardsEffectDuration = 400,
                ),
                true,
            )

            assertThat(underTest.getInvocationEffectInAnimationDurationMillis()).isEqualTo(800)
        }
@@ -171,44 +181,120 @@ class SqueezeEffectRepositoryTest : SysuiTestCase() {
    @Test
    fun testInvocationEffectOutwardsAnimationDelay() =
        kosmos.runTest {
            fakeInvocationEffectPreferences.setOutwardAnimationDurationMillis(400)
            fakeInvocationEffectPreferences.setInvocationEffectConfig(
                InvocationEffectPreferences.Config(
                    isEnabled = true,
                    inwardsEffectDurationPadding = 450,
                    outwardsEffectDuration = 400,
                ),
                true,
            )

            assertThat(underTest.getInvocationEffectOutAnimationDurationMillis()).isEqualTo(400)
        }

    @EnableFlags(Flags.FLAG_ENABLE_LPP_ASSIST_INVOCATION_EFFECT)
    @Test
    fun testSetUiHintsShouldUpdatePreferences() =
    fun testSetUiHints_whenSuppliedAllConfigs_allUpdatedInPreferences() =
        kosmos.runTest {
            fakeInvocationEffectPreferences.activeUserId = 1
            fakeInvocationEffectPreferences.activeAssistant = "A"

            assertThat(fakeInvocationEffectPreferences.isActiveUserAndAssistantPersisted())
            assertThat(fakeInvocationEffectPreferences.isCurrentUserAndAssistantPersisted())
                .isFalse()

            val hints = createAssistantSettingBundle(false, 0, 1000)
            val hints =
                createAssistantSettingBundle(
                    enableAssistantSetting = false,
                    inwardsPaddingDuration = 0,
                    outwardsAnimationDuration = 1000,
                )
            underTest.tryHandleSetUiHints(hints)

            val isEffectEnabledAndPowerButtonPressed by
                collectLastValue(underTest.isEffectEnabledAndPowerButtonPressedAsSingleGesture)
            assertThat(fakeInvocationEffectPreferences.getInwardAnimationPaddingDurationMillis())
                .isEqualTo(0)
            assertThat(fakeInvocationEffectPreferences.getOutwardAnimationDurationMillis())
                .isEqualTo(1000)
            assertThat(fakeInvocationEffectPreferences.isInvocationEffectEnabledInPreferences())
                .isFalse()
            assertThat(fakeInvocationEffectPreferences.isCurrentUserAndAssistantPersisted())
                .isTrue()
        }

    @EnableFlags(Flags.FLAG_ENABLE_LPP_ASSIST_INVOCATION_EFFECT)
    @Test
    fun testSetUiHints_whenSuppliedPartialConfig() =
        kosmos.runTest {
            fakeInvocationEffectPreferences.activeUserId = 1
            fakeInvocationEffectPreferences.activeAssistant = "A"

            assertThat(fakeInvocationEffectPreferences.isCurrentUserAndAssistantPersisted())
                .isFalse()

            underTest.tryHandleSetUiHints(
                createAssistantSettingBundle(
                    enableAssistantSetting = false,
                    inwardsPaddingDuration = 0,
                    outwardsAnimationDuration = 1000,
                )
            )

            assertThat(isEffectEnabledAndPowerButtonPressed).isFalse()
            assertThat(fakeInvocationEffectPreferences.getInwardAnimationPaddingDurationMillis())
                .isEqualTo(0)
            assertThat(fakeInvocationEffectPreferences.getOutwardAnimationDurationMillis())
                .isEqualTo(1000)
            assertThat(fakeInvocationEffectPreferences.isActiveUserAndAssistantPersisted()).isTrue()
            assertThat(fakeInvocationEffectPreferences.isInvocationEffectEnabledInPreferences())
                .isFalse()
            assertThat(fakeInvocationEffectPreferences.isCurrentUserAndAssistantPersisted())
                .isTrue()

            underTest.tryHandleSetUiHints(
                createAssistantSettingBundle(enableAssistantSetting = true)
            )

            assertThat(fakeInvocationEffectPreferences.getInwardAnimationPaddingDurationMillis())
                .isEqualTo(0)
            assertThat(fakeInvocationEffectPreferences.getOutwardAnimationDurationMillis())
                .isEqualTo(1000)
            assertThat(fakeInvocationEffectPreferences.isInvocationEffectEnabledInPreferences())
                .isTrue()
        }

    @EnableFlags(Flags.FLAG_ENABLE_LPP_ASSIST_INVOCATION_EFFECT)
    @Test
    fun testSetUiHints_whenSuppliedNoConfig_shouldSetDefaults() =
        kosmos.runTest {
            fakeInvocationEffectPreferences.activeUserId = 1
            fakeInvocationEffectPreferences.activeAssistant = "A"

            assertThat(fakeInvocationEffectPreferences.isCurrentUserAndAssistantPersisted())
                .isFalse()

            underTest.tryHandleSetUiHints(createAssistantSettingBundle())

            assertThat(fakeInvocationEffectPreferences.getInwardAnimationPaddingDurationMillis())
                .isEqualTo(DEFAULT_INWARD_EFFECT_PADDING_DURATION_MS)
            assertThat(fakeInvocationEffectPreferences.getOutwardAnimationDurationMillis())
                .isEqualTo(DEFAULT_OUTWARD_EFFECT_DURATION_MS)
            assertThat(fakeInvocationEffectPreferences.isInvocationEffectEnabledInPreferences())
                .isEqualTo(DEFAULT_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE)
            assertThat(fakeInvocationEffectPreferences.isCurrentUserAndAssistantPersisted())
                .isTrue()
        }

    private fun createAssistantSettingBundle(
        enableAssistantSetting: Boolean,
        inwardsPaddingDuration: Long,
        outwardsAnimationDuration: Long,
        enableAssistantSetting: Boolean? = null,
        inwardsPaddingDuration: Long? = null,
        outwardsAnimationDuration: Long? = null,
    ) =
        Bundle().apply {
            putString(AssistManager.ACTION_KEY, SET_INVOCATION_EFFECT_PARAMETERS_ACTION)
            putBoolean(IS_INVOCATION_EFFECT_ENABLED_KEY, enableAssistantSetting)
            putLong(INVOCATION_EFFECT_ANIMATION_IN_DURATION_PADDING_MS, inwardsPaddingDuration)
            putLong(INVOCATION_EFFECT_ANIMATION_OUT_DURATION_MS, outwardsAnimationDuration)
            enableAssistantSetting?.let { putBoolean(IS_INVOCATION_EFFECT_ENABLED_KEY, it) }
            inwardsPaddingDuration?.let {
                putLong(INVOCATION_EFFECT_ANIMATION_IN_DURATION_PADDING_MS, it)
            }
            outwardsAnimationDuration?.let {
                putLong(INVOCATION_EFFECT_ANIMATION_OUT_DURATION_MS, it)
            }
        }
}
+66 −60
Original line number Diff line number Diff line
@@ -51,25 +51,27 @@ interface InvocationEffectPreferences {

    val isInvocationEffectEnabledByAssistant: StateFlow<Boolean>

    fun saveCurrentAssistant()

    fun saveCurrentUserId()

    fun setInvocationEffectEnabledByAssistant(enabled: Boolean)

    fun setInwardAnimationPaddingDurationMillis(duration: Long)
    fun isInvocationEffectEnabledInPreferences(): Boolean

    fun getInwardAnimationPaddingDurationMillis(): Long

    fun setOutwardAnimationDurationMillis(duration: Long)

    fun getOutwardAnimationDurationMillis(): Long

    fun isCurrentUserAndAssistantPersisted(): Boolean

    fun setInvocationEffectConfig(config: Config, saveActiveUserAndAssistant: Boolean)

    fun registerOnChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener)

    fun unregisterOnChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener)

    fun dump(pw: PrintWriter, args: Array<out String>)

    data class Config(
        val isEnabled: Boolean,
        val inwardsEffectDurationPadding: Long,
        val outwardsEffectDuration: Long,
    )
}

@SysUISingleton
@@ -81,7 +83,7 @@ constructor(
    private val userRepository: UserRepository,
    roleManager: RoleManager,
    @Background executor: Executor,
    @Background coroutineContext: CoroutineContext,
    @Background private val coroutineContext: CoroutineContext,
) : InvocationEffectPreferences {

    private val sharedPreferences by lazy {
@@ -120,7 +122,8 @@ constructor(
                val changed = activeUser != userId || activeAssistant != assistant
                if (changed) {
                    activeUser = userId
                    activeAssistant = assistant
                    activeAssistant =
                        roleManager.getCurrentAssistantFor(userRepository.selectedUserHandle)
                }
                changed
            }
@@ -152,10 +155,6 @@ constructor(
                initialValue = isInvocationEffectEnabledByAssistant(),
            )

    override fun saveCurrentAssistant() {
        setInPreferences { putString(PERSISTED_FOR_ASSISTANT_PREFERENCE, activeAssistant) }
    }

    private fun getSavedAssistant(): String =
        getOrDefault<String>(
            key = PERSISTED_FOR_ASSISTANT_PREFERENCE,
@@ -163,10 +162,6 @@ constructor(
            checkUserAndAssistant = false,
        )

    override fun saveCurrentUserId() {
        setInPreferences { putInt(PERSISTED_FOR_USER_PREFERENCE, activeUser) }
    }

    private fun getSavedUserId(): Int =
        getOrDefault<Int>(
            key = PERSISTED_FOR_USER_PREFERENCE,
@@ -174,31 +169,8 @@ constructor(
            checkUserAndAssistant = false,
        )

    override fun setInvocationEffectEnabledByAssistant(enabled: Boolean) {
        setInPreferences {
            putBoolean(IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE, enabled)
        }
    }

    private fun isInvocationEffectEnabledByAssistant(): Boolean =
        getOrDefault<Boolean>(
            key = IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE,
            default = true,
            checkUserAndAssistant = true,
        ) && activeAssistant.isNotEmpty()

    override fun setInwardAnimationPaddingDurationMillis(duration: Long) {
        setInPreferences {
            if (duration in 0..1000) {
                putLong(INVOCATION_EFFECT_ANIMATION_IN_DURATION_PADDING_MS, duration)
            } else {
                putLong(
                    INVOCATION_EFFECT_ANIMATION_IN_DURATION_PADDING_MS,
                    DEFAULT_INWARD_EFFECT_PADDING_DURATION_MS,
                )
            }
        }
    }
        isInvocationEffectEnabledInPreferences() && activeAssistant.isNotEmpty()

    override fun getInwardAnimationPaddingDurationMillis(): Long =
        getOrDefault<Long>(
@@ -207,20 +179,6 @@ constructor(
            checkUserAndAssistant = true,
        )

    // TODO(b/418685731): Should we have a positive non-zero min value for out effect duration?
    override fun setOutwardAnimationDurationMillis(duration: Long) {
        setInPreferences {
            if (duration in 0..1000) {
                putLong(INVOCATION_EFFECT_ANIMATION_OUT_DURATION_MS, duration)
            } else {
                putLong(
                    INVOCATION_EFFECT_ANIMATION_OUT_DURATION_MS,
                    DEFAULT_OUTWARD_EFFECT_DURATION_MS,
                )
            }
        }
    }

    override fun getOutwardAnimationDurationMillis(): Long =
        getOrDefault<Long>(
            key = INVOCATION_EFFECT_ANIMATION_OUT_DURATION_MS,
@@ -228,11 +186,59 @@ constructor(
            checkUserAndAssistant = true,
        )

    private fun isCurrentUserAndAssistantPersisted(): Boolean =
    override fun isCurrentUserAndAssistantPersisted(): Boolean =
        activeUser == getSavedUserId() && activeAssistant == getSavedAssistant()

    private fun setInPreferences(block: SharedPreferences.Editor.() -> Unit) {
        bgScope.launch { sharedPreferences.edit { block() } }
    override fun isInvocationEffectEnabledInPreferences(): Boolean =
        getOrDefault<Boolean>(
            key = IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE,
            default = DEFAULT_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE,
            checkUserAndAssistant = true,
        )

    override fun setInvocationEffectConfig(
        config: InvocationEffectPreferences.Config,
        saveActiveUserAndAssistant: Boolean,
    ) {
        bgScope.launch(context = coroutineContext) {
            sharedPreferences.edit {
                if (saveActiveUserAndAssistant) {
                    putString(PERSISTED_FOR_ASSISTANT_PREFERENCE, activeAssistant)
                    putInt(PERSISTED_FOR_USER_PREFERENCE, activeUser)
                }

                if (config.isEnabled != isInvocationEffectEnabledInPreferences()) {
                    putBoolean(
                        IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE,
                        config.isEnabled,
                    )
                }

                if (
                    config.inwardsEffectDurationPadding != getInwardAnimationPaddingDurationMillis()
                ) {
                    putLong(
                        INVOCATION_EFFECT_ANIMATION_IN_DURATION_PADDING_MS,
                        if (config.inwardsEffectDurationPadding in 0..1000) {
                            config.inwardsEffectDurationPadding
                        } else {
                            DEFAULT_INWARD_EFFECT_PADDING_DURATION_MS
                        },
                    )
                }

                if (config.outwardsEffectDuration != getOutwardAnimationDurationMillis()) {
                    putLong(
                        INVOCATION_EFFECT_ANIMATION_OUT_DURATION_MS,
                        if (config.outwardsEffectDuration in 100..1000) {
                            config.outwardsEffectDuration
                        } else {
                            DEFAULT_OUTWARD_EFFECT_DURATION_MS
                        },
                    )
                }
            }
        }
    }

    private inline fun <reified T> getOrDefault(
+43 −32
Original line number Diff line number Diff line
@@ -29,9 +29,6 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.shared.Flags
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.DEFAULT_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.DEFAULT_INWARD_EFFECT_PADDING_DURATION_MS
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.DEFAULT_OUTWARD_EFFECT_DURATION_MS
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.INVOCATION_EFFECT_ANIMATION_IN_DURATION_PADDING_MS
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.INVOCATION_EFFECT_ANIMATION_OUT_DURATION_MS
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE
@@ -91,43 +88,57 @@ constructor(
        return preferences.getOutwardAnimationDurationMillis()
    }

    private fun setInvocationEffectPreferences(
        isEnabled: Boolean? = null,
        inwardsEffectDurationPadding: Long? = null,
        outwardsEffectDuration: Long? = null,
    ) {

        preferences.setInvocationEffectConfig(
            config =
                InvocationEffectPreferences.Config(
                    isEnabled = isEnabled ?: preferences.isInvocationEffectEnabledInPreferences(),
                    inwardsEffectDurationPadding =
                        inwardsEffectDurationPadding
                            ?: preferences.getInwardAnimationPaddingDurationMillis(),
                    outwardsEffectDuration =
                        outwardsEffectDuration ?: preferences.getOutwardAnimationDurationMillis(),
                ),
            saveActiveUserAndAssistant = !preferences.isCurrentUserAndAssistantPersisted(),
        )
    }

    override fun tryHandleSetUiHints(hints: Bundle): Boolean {
        return when (hints.getString(AssistManager.ACTION_KEY)) {
            SET_INVOCATION_EFFECT_PARAMETERS_ACTION -> {

                preferences.saveCurrentUserId()
                preferences.saveCurrentAssistant()

                val isEnabled: Boolean? =
                    if (hints.containsKey(IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE)) {
                    preferences.setInvocationEffectEnabledByAssistant(
                        hints.getBoolean(IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE)
                    )
                    } else {
                    preferences.setInvocationEffectEnabledByAssistant(
                        DEFAULT_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE
                    )
                        null
                    }

                val inwardsEffectDurationPadding: Long? =
                    if (hints.containsKey(INVOCATION_EFFECT_ANIMATION_IN_DURATION_PADDING_MS)) {
                    preferences.setInwardAnimationPaddingDurationMillis(
                        hints.getLong(INVOCATION_EFFECT_ANIMATION_IN_DURATION_PADDING_MS)
                    )
                    } else {
                    preferences.setInwardAnimationPaddingDurationMillis(
                        DEFAULT_INWARD_EFFECT_PADDING_DURATION_MS
                    )
                        null
                    }

                val outwardsEffectDuration: Long? =
                    if (hints.containsKey(INVOCATION_EFFECT_ANIMATION_OUT_DURATION_MS)) {
                    preferences.setOutwardAnimationDurationMillis(
                        hints.getLong(INVOCATION_EFFECT_ANIMATION_OUT_DURATION_MS)
                    )
                    } else {
                    preferences.setOutwardAnimationDurationMillis(
                        DEFAULT_OUTWARD_EFFECT_DURATION_MS
                    )
                        null
                    }

                setInvocationEffectPreferences(
                    isEnabled = isEnabled,
                    inwardsEffectDurationPadding = inwardsEffectDurationPadding,
                    outwardsEffectDuration = outwardsEffectDuration,
                )

                true
            }
            else -> false
+38 −24
Original line number Diff line number Diff line
@@ -19,10 +19,12 @@ package com.android.systemui.topwindoweffects.data.repository
import android.content.SharedPreferences
import androidx.core.content.edit
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.DEFAULT_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.DEFAULT_INWARD_EFFECT_PADDING_DURATION_MS
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.DEFAULT_OUTWARD_EFFECT_DURATION_MS
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.INVOCATION_EFFECT_ANIMATION_IN_DURATION_PADDING_MS
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.INVOCATION_EFFECT_ANIMATION_OUT_DURATION_MS
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.PERSISTED_FOR_ASSISTANT_PREFERENCE
import com.android.systemui.topwindoweffects.data.repository.InvocationEffectPreferencesImpl.Companion.PERSISTED_FOR_USER_PREFERENCE
import java.io.PrintWriter
@@ -42,35 +44,23 @@ class FakeInvocationEffectPreferences : InvocationEffectPreferences {
        fakeSharedPreferences.edit { f() }
    }

    fun clear() {
        fakeSharedPreferences = FakeSharedPreferences()
    override fun isInvocationEffectEnabledInPreferences(): Boolean {
        return fakeSharedPreferences.getBoolean(
            IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE,
            DEFAULT_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE,
        )
    }

    override fun saveCurrentAssistant() {
        addToPref { putString(PERSISTED_FOR_ASSISTANT_PREFERENCE, activeAssistant) }
    fun clear() {
        fakeSharedPreferences = FakeSharedPreferences()
    }

    fun getSavedAssistant(): String {
        return fakeSharedPreferences.getString(PERSISTED_FOR_ASSISTANT_PREFERENCE, "") ?: ""
    }

    override fun saveCurrentUserId() {
        addToPref { putInt(PERSISTED_FOR_USER_PREFERENCE, activeUserId) }
    }

    fun getSavedUserId(): Int {
        return fakeSharedPreferences.getInt(PERSISTED_FOR_USER_PREFERENCE, -1)
    }

    fun isActiveUserAndAssistantPersisted() =
        activeUserId == getSavedUserId() && activeAssistant == getSavedAssistant()

    override fun setInvocationEffectEnabledByAssistant(enabled: Boolean) {
        isInvocationEffectEnabledByAssistant.value = enabled
    }

    override fun setInwardAnimationPaddingDurationMillis(duration: Long) {
        addToPref { putLong(INVOCATION_EFFECT_ANIMATION_IN_DURATION_PADDING_MS, duration) }
        return fakeSharedPreferences.getInt(PERSISTED_FOR_USER_PREFERENCE, Int.MIN_VALUE)
    }

    override fun getInwardAnimationPaddingDurationMillis(): Long {
@@ -80,10 +70,6 @@ class FakeInvocationEffectPreferences : InvocationEffectPreferences {
        )
    }

    override fun setOutwardAnimationDurationMillis(duration: Long) {
        addToPref { putLong(INVOCATION_EFFECT_ANIMATION_OUT_DURATION_MS, duration) }
    }

    override fun getOutwardAnimationDurationMillis(): Long {
        return fakeSharedPreferences.getLong(
            INVOCATION_EFFECT_ANIMATION_OUT_DURATION_MS,
@@ -91,6 +77,30 @@ class FakeInvocationEffectPreferences : InvocationEffectPreferences {
        )
    }

    override fun isCurrentUserAndAssistantPersisted(): Boolean {
        return activeUserId == getSavedUserId() && activeAssistant == getSavedAssistant()
    }

    override fun setInvocationEffectConfig(
        config: InvocationEffectPreferences.Config,
        saveActiveUserAndAssistant: Boolean,
    ) {
        if (saveActiveUserAndAssistant) {
            addToPref {
                putString(PERSISTED_FOR_ASSISTANT_PREFERENCE, activeAssistant)
                putInt(PERSISTED_FOR_USER_PREFERENCE, activeUserId)
            }
        }
        addToPref {
            putLong(INVOCATION_EFFECT_ANIMATION_OUT_DURATION_MS, config.outwardsEffectDuration)
            putLong(
                INVOCATION_EFFECT_ANIMATION_IN_DURATION_PADDING_MS,
                config.inwardsEffectDurationPadding,
            )
            putBoolean(IS_INVOCATION_EFFECT_ENABLED_BY_ASSISTANT_PREFERENCE, config.isEnabled)
        }
    }

    override fun registerOnChangeListener(
        listener: SharedPreferences.OnSharedPreferenceChangeListener
    ) {
@@ -106,6 +116,10 @@ class FakeInvocationEffectPreferences : InvocationEffectPreferences {
    override fun dump(pw: PrintWriter, args: Array<out String>) {
        // empty
    }

    fun setInvocationEffectEnabledByAssistant(enabled: Boolean) {
        isInvocationEffectEnabledByAssistant.value = enabled
    }
}

private class FakeSharedPreferences : SharedPreferences {