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

Commit b6c570b2 authored by Prince's avatar Prince
Browse files

Creating a flow to get preferred home control panel component name

Test: atest SelectedComponentRepositoryTest
Fixes: 311741950
Flag: ACONFIG FLAG_HOME_PANEL_DREAM DEVELOPMENT
Change-Id: I2afe3bb1311b290fa9155252576a16785fc6f7c0
parent 511030f6
Loading
Loading
Loading
Loading
+18 −5
Original line number Diff line number Diff line
@@ -17,23 +17,36 @@
package com.android.systemui.controls.panels

import android.content.ComponentName
import android.os.UserHandle
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.flags.Flags
import kotlinx.coroutines.flow.Flow

/** Stores user-selected preferred component. */
interface SelectedComponentRepository {

    /** Returns current set preferred component for the specified user. */
    fun selectedComponentFlow(userHandle: UserHandle): Flow<SelectedComponent?>

    /**
     * Returns currently set preferred component, or null when nothing is set. Consider using
     * [ControlsUiController.getPreferredSelectedItem] to get domain specific data
     * Returns the current set preferred component for the specified user, or null when nothing is
     * set. If no user is specified, the current user's preference is used. This method by default
     * operates in the context of the current user unless another user is explicitly specified.
     * Consider using [ControlsUiController.getPreferredSelectedItem] to get domain specific data.
     */
    fun getSelectedComponent(): SelectedComponent?
    fun getSelectedComponent(userHandle: UserHandle = UserHandle.CURRENT): SelectedComponent?

    /** Sets preferred component. Use [getSelectedComponent] to get current one */
    /**
     * Sets the preferred component for the current user. Use [getSelectedComponent] to retrieve the
     * currently set preferred component. This method applies to the current user's settings.
     */
    fun setSelectedComponent(selectedComponent: SelectedComponent)

    /** Clears current preferred component. [getSelectedComponent] will return null afterwards */
    /**
     * Clears the current user's preferred component. After this operation, [getSelectedComponent]
     * will return null for the current user.
     */
    fun removeSelectedComponent()

    /**
+55 −13
Original line number Diff line number Diff line
@@ -19,13 +19,24 @@ package com.android.systemui.controls.panels
import android.content.ComponentName
import android.content.Context
import android.content.SharedPreferences
import android.os.UserHandle
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch

@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
@SysUISingleton
class SelectedComponentRepositoryImpl
@Inject
@@ -33,6 +44,8 @@ constructor(
    private val userFileManager: UserFileManager,
    private val userTracker: UserTracker,
    private val featureFlags: FeatureFlags,
    @Background private val bgDispatcher: CoroutineDispatcher,
    @Application private val applicationScope: CoroutineScope
) : SelectedComponentRepository {

    private companion object {
@@ -42,16 +55,42 @@ constructor(
        const val SHOULD_ADD_DEFAULT_PANEL = "should_add_default_panel"
    }

    private val sharedPreferences: SharedPreferences
        get() =
            userFileManager.getSharedPreferences(
    private fun getSharedPreferencesForUser(userId: Int): SharedPreferences {
        return userFileManager.getSharedPreferences(
            fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
            mode = Context.MODE_PRIVATE,
                userId = userTracker.userId
            userId = userId
        )
    }

    override fun selectedComponentFlow(
        userHandle: UserHandle
    ): Flow<SelectedComponentRepository.SelectedComponent?> {
        return conflatedCallbackFlow {
                val sharedPreferencesByUserId = getSharedPreferencesForUser(userHandle.identifier)
                val listener =
                    SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
                        applicationScope.launch(bgDispatcher) {
                            if (key == PREF_COMPONENT) {
                                trySend(getSelectedComponent(userHandle))
                            }
                        }
                    }
                sharedPreferencesByUserId.registerOnSharedPreferenceChangeListener(listener)
                send(getSelectedComponent(userHandle))
                awaitClose {
                    sharedPreferencesByUserId.unregisterOnSharedPreferenceChangeListener(listener)
                }
            }
            .flowOn(bgDispatcher)
    }

    override fun getSelectedComponent(): SelectedComponentRepository.SelectedComponent? {
        with(sharedPreferences) {
    override fun getSelectedComponent(
        userHandle: UserHandle
    ): SelectedComponentRepository.SelectedComponent? {
        val userId =
            if (userHandle == UserHandle.CURRENT) userTracker.userId else userHandle.identifier
        with(getSharedPreferencesForUser(userId)) {
            val componentString = getString(PREF_COMPONENT, null) ?: return null
            return SelectedComponentRepository.SelectedComponent(
                name = getString(PREF_STRUCTURE_OR_APP_NAME, "")!!,
@@ -64,7 +103,7 @@ constructor(
    override fun setSelectedComponent(
        selectedComponent: SelectedComponentRepository.SelectedComponent
    ) {
        sharedPreferences
        getSharedPreferencesForUser(userTracker.userId)
            .edit()
            .putString(PREF_COMPONENT, selectedComponent.componentName?.flattenToString())
            .putString(PREF_STRUCTURE_OR_APP_NAME, selectedComponent.name)
@@ -73,7 +112,7 @@ constructor(
    }

    override fun removeSelectedComponent() {
        sharedPreferences
        getSharedPreferencesForUser(userTracker.userId)
            .edit()
            .remove(PREF_COMPONENT)
            .remove(PREF_STRUCTURE_OR_APP_NAME)
@@ -82,9 +121,12 @@ constructor(
    }

    override fun shouldAddDefaultComponent(): Boolean =
        sharedPreferences.getBoolean(SHOULD_ADD_DEFAULT_PANEL, true)
        getSharedPreferencesForUser(userTracker.userId).getBoolean(SHOULD_ADD_DEFAULT_PANEL, true)

    override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
        sharedPreferences.edit().putBoolean(SHOULD_ADD_DEFAULT_PANEL, shouldAdd).apply()
        getSharedPreferencesForUser(userTracker.userId)
            .edit()
            .putBoolean(SHOULD_ADD_DEFAULT_PANEL, shouldAdd)
            .apply()
    }
}
+35 −7
Original line number Diff line number Diff line
@@ -16,27 +16,55 @@

package com.android.systemui.controls.panels

class FakeSelectedComponentRepository : SelectedComponentRepository {
import android.os.UserHandle
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow

    private var selectedComponent: SelectedComponentRepository.SelectedComponent? = null
class FakeSelectedComponentRepository : SelectedComponentRepository {
    private var shouldAddDefaultPanel: Boolean = true
    private val _selectedComponentFlows =
        mutableMapOf<UserHandle, MutableStateFlow<SelectedComponentRepository.SelectedComponent?>>()
    private var currentUserHandle: UserHandle = UserHandle.of(0)

    override fun selectedComponentFlow(
        userHandle: UserHandle
    ): Flow<SelectedComponentRepository.SelectedComponent?> {
        // Return an existing flow for the user or create a new one
        return _selectedComponentFlows.getOrPut(getUserHandle(userHandle)) {
            MutableStateFlow(null)
        }
    }

    override fun getSelectedComponent(): SelectedComponentRepository.SelectedComponent? =
        selectedComponent
    override fun getSelectedComponent(
        userHandle: UserHandle
    ): SelectedComponentRepository.SelectedComponent? {
        return _selectedComponentFlows[getUserHandle(userHandle)]?.value
    }

    override fun setSelectedComponent(
        selectedComponent: SelectedComponentRepository.SelectedComponent
    ) {
        this.selectedComponent = selectedComponent
        val flow = _selectedComponentFlows.getOrPut(currentUserHandle) { MutableStateFlow(null) }
        flow.value = selectedComponent
    }

    override fun removeSelectedComponent() {
        selectedComponent = null
        _selectedComponentFlows[currentUserHandle]?.value = null
    }

    override fun shouldAddDefaultComponent(): Boolean = shouldAddDefaultPanel

    override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
        shouldAddDefaultPanel = shouldAdd
    }

    fun setCurrentUserHandle(userHandle: UserHandle) {
        currentUserHandle = userHandle
    }
    private fun getUserHandle(userHandle: UserHandle): UserHandle {
        return if (userHandle == UserHandle.CURRENT) {
            currentUserHandle
        } else {
            userHandle
        }
    }
}
+127 −25
Original line number Diff line number Diff line
@@ -18,28 +18,43 @@ package com.android.systemui.controls.panels

import android.content.ComponentName
import android.content.SharedPreferences
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.io.File
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations

@ExperimentalCoroutinesApi
@RunWith(AndroidTestingRunner::class)
@SmallTest
class SelectedComponentRepositoryTest : SysuiTestCase() {

    private companion object {
        const val PREF_COMPONENT = "controls_component"
        const val PREF_STRUCTURE_OR_APP_NAME = "controls_structure"
        const val PREF_IS_PANEL = "controls_is_panel"
        val PRIMARY_USER: UserHandle = UserHandle.of(0)
        val SECONDARY_USER: UserHandle = UserHandle.of(12)
        val COMPONENT_A =
            SelectedComponentRepository.SelectedComponent(
                name = "a",
@@ -53,23 +68,39 @@ class SelectedComponentRepositoryTest : SysuiTestCase() {
                isPanel = false,
            )
    }
    private lateinit var primaryUserSharedPref: FakeSharedPreferences
    private lateinit var secondaryUserSharedPref: FakeSharedPreferences

    @Mock private lateinit var userTracker: UserTracker
    @Mock private lateinit var userFileManager: UserFileManager
    private lateinit var userFileManager: UserFileManager

    private val featureFlags = FakeFeatureFlags()
    private val sharedPreferences: SharedPreferences = FakeSharedPreferences()

    // under test
    private lateinit var repository: SelectedComponentRepository

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        whenever(userFileManager.getSharedPreferences(any(), any(), any()))
            .thenReturn(sharedPreferences)
    private val kosmos = testKosmos()

        repository = SelectedComponentRepositoryImpl(userFileManager, userTracker, featureFlags)
    @Before
    fun setUp() =
        with(kosmos) {
            primaryUserSharedPref = FakeSharedPreferences()
            secondaryUserSharedPref = FakeSharedPreferences()
            MockitoAnnotations.initMocks(this@SelectedComponentRepositoryTest)
            userFileManager =
                FakeUserFileManager(
                    mapOf(
                        PRIMARY_USER.identifier to primaryUserSharedPref,
                        SECONDARY_USER.identifier to secondaryUserSharedPref
                    )
                )
            repository =
                SelectedComponentRepositoryImpl(
                    userFileManager,
                    userTracker,
                    featureFlags,
                    bgDispatcher = testDispatcher,
                    applicationScope = applicationCoroutineScope
                )
        }

    @Test
@@ -115,18 +146,10 @@ class SelectedComponentRepositoryTest : SysuiTestCase() {

    @Test
    fun testGetPreferredStructure_differentUserId() {
        sharedPreferences.savePanel(COMPONENT_A)
        whenever(
                userFileManager.getSharedPreferences(
                    DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
                    0,
                    1,
                )
            )
            .thenReturn(FakeSharedPreferences().also { it.savePanel(COMPONENT_B) })

        primaryUserSharedPref.savePanel(COMPONENT_A)
        secondaryUserSharedPref.savePanel(COMPONENT_B)
        val previousPreferredStructure = repository.getSelectedComponent()
        whenever(userTracker.userId).thenReturn(1)
        whenever(userTracker.userId).thenReturn(SECONDARY_USER.identifier)
        val currentPreferredStructure = repository.getSelectedComponent()

        assertThat(previousPreferredStructure).isEqualTo(COMPONENT_A)
@@ -134,11 +157,90 @@ class SelectedComponentRepositoryTest : SysuiTestCase() {
        assertThat(currentPreferredStructure).isEqualTo(COMPONENT_B)
    }

    @Test
    fun testEmitValueFromGetSelectedComponent() =
        with(kosmos) {
            testScope.runTest {
                primaryUserSharedPref.savePanel(COMPONENT_A)
                val emittedValue by collectLastValue(repository.selectedComponentFlow(PRIMARY_USER))
                assertThat(emittedValue).isEqualTo(COMPONENT_A)
            }
        }

    @Test
    fun testEmitNullWhenRemoveSelectedComponentIsCalled() =
        with(kosmos) {
            testScope.runTest {
                primaryUserSharedPref.savePanel(COMPONENT_A)
                primaryUserSharedPref.removePanel()
                val emittedValue by collectLastValue(repository.selectedComponentFlow(PRIMARY_USER))
                assertThat(emittedValue).isEqualTo(null)
            }
        }

    @Test
    fun testChangeEmitValueChangeWhenANewComponentIsSelected() =
        with(kosmos) {
            testScope.runTest {
                primaryUserSharedPref.savePanel(COMPONENT_A)
                val emittedValue by collectLastValue(repository.selectedComponentFlow(PRIMARY_USER))
                advanceUntilIdle()
                assertThat(emittedValue).isEqualTo(COMPONENT_A)
                primaryUserSharedPref.savePanel(COMPONENT_B)
                advanceUntilIdle()
                assertThat(emittedValue).isEqualTo(COMPONENT_B)
            }
        }

    @Test
    fun testDifferentUsersWithDifferentComponentSelected() =
        with(kosmos) {
            testScope.runTest {
                primaryUserSharedPref.savePanel(COMPONENT_A)
                secondaryUserSharedPref.savePanel(COMPONENT_B)
                val primaryUserValue by
                    collectLastValue(repository.selectedComponentFlow(PRIMARY_USER))
                val secondaryUserValue by
                    collectLastValue(repository.selectedComponentFlow(SECONDARY_USER))
                assertThat(primaryUserValue).isEqualTo(COMPONENT_A)
                assertThat(secondaryUserValue).isEqualTo(COMPONENT_B)
            }
        }

    private fun SharedPreferences.savePanel(panel: SelectedComponentRepository.SelectedComponent) {
        edit()
            .putString("controls_component", panel.componentName?.flattenToString())
            .putString("controls_structure", panel.name)
            .putBoolean("controls_is_panel", panel.isPanel)
            .putString(PREF_COMPONENT, panel.componentName?.flattenToString())
            .putString(PREF_STRUCTURE_OR_APP_NAME, panel.name)
            .putBoolean(PREF_IS_PANEL, panel.isPanel)
            .commit()
    }

    private fun SharedPreferences.removePanel() {
        edit()
            .remove(PREF_COMPONENT)
            .remove(PREF_STRUCTURE_OR_APP_NAME)
            .remove(PREF_IS_PANEL)
            .commit()
    }

    private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
        UserFileManager {
        override fun getFile(fileName: String, userId: Int): File {
            throw UnsupportedOperationException()
        }

        override fun getSharedPreferences(
            fileName: String,
            mode: Int,
            userId: Int
        ): SharedPreferences {
            if (fileName != DeviceControlsControllerImpl.PREFS_CONTROLS_FILE) {
                throw IllegalArgumentException(
                    "Preference files must be " +
                        "$DeviceControlsControllerImpl.PREFS_CONTROLS_FILE"
                )
            }
            return sharedPrefs.getValue(userId)
        }
    }
}