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

Commit b0dc42d2 authored by Prince Donkor's avatar Prince Donkor Committed by Android (Google) Code Review
Browse files

Merge "Creating a flow to get preferred home control panel component name" into main

parents 361033ff b6c570b2
Loading
Loading
Loading
Loading
+18 −5
Original line number Original line Diff line number Diff line
@@ -17,23 +17,36 @@
package com.android.systemui.controls.panels
package com.android.systemui.controls.panels


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


/** Stores user-selected preferred component. */
/** Stores user-selected preferred component. */
interface SelectedComponentRepository {
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
     * Returns the current set preferred component for the specified user, or null when nothing is
     * [ControlsUiController.getPreferredSelectedItem] to get domain specific data
     * 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)
    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()
    fun removeSelectedComponent()


    /**
    /**
+55 −13
Original line number Original line Diff line number Diff line
@@ -19,13 +19,24 @@ package com.android.systemui.controls.panels
import android.content.ComponentName
import android.content.ComponentName
import android.content.Context
import android.content.Context
import android.content.SharedPreferences
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.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.flags.FeatureFlags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import javax.inject.Inject
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
@SysUISingleton
class SelectedComponentRepositoryImpl
class SelectedComponentRepositoryImpl
@Inject
@Inject
@@ -33,6 +44,8 @@ constructor(
    private val userFileManager: UserFileManager,
    private val userFileManager: UserFileManager,
    private val userTracker: UserTracker,
    private val userTracker: UserTracker,
    private val featureFlags: FeatureFlags,
    private val featureFlags: FeatureFlags,
    @Background private val bgDispatcher: CoroutineDispatcher,
    @Application private val applicationScope: CoroutineScope
) : SelectedComponentRepository {
) : SelectedComponentRepository {


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


    private val sharedPreferences: SharedPreferences
    private fun getSharedPreferencesForUser(userId: Int): SharedPreferences {
        get() =
        return userFileManager.getSharedPreferences(
            userFileManager.getSharedPreferences(
            fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
            fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
            mode = Context.MODE_PRIVATE,
            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? {
    override fun getSelectedComponent(
        with(sharedPreferences) {
        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
            val componentString = getString(PREF_COMPONENT, null) ?: return null
            return SelectedComponentRepository.SelectedComponent(
            return SelectedComponentRepository.SelectedComponent(
                name = getString(PREF_STRUCTURE_OR_APP_NAME, "")!!,
                name = getString(PREF_STRUCTURE_OR_APP_NAME, "")!!,
@@ -64,7 +103,7 @@ constructor(
    override fun setSelectedComponent(
    override fun setSelectedComponent(
        selectedComponent: SelectedComponentRepository.SelectedComponent
        selectedComponent: SelectedComponentRepository.SelectedComponent
    ) {
    ) {
        sharedPreferences
        getSharedPreferencesForUser(userTracker.userId)
            .edit()
            .edit()
            .putString(PREF_COMPONENT, selectedComponent.componentName?.flattenToString())
            .putString(PREF_COMPONENT, selectedComponent.componentName?.flattenToString())
            .putString(PREF_STRUCTURE_OR_APP_NAME, selectedComponent.name)
            .putString(PREF_STRUCTURE_OR_APP_NAME, selectedComponent.name)
@@ -73,7 +112,7 @@ constructor(
    }
    }


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


    override fun shouldAddDefaultComponent(): Boolean =
    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) {
    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 Original line Diff line number Diff line
@@ -16,27 +16,55 @@


package com.android.systemui.controls.panels
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 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? =
    override fun getSelectedComponent(
        selectedComponent
        userHandle: UserHandle
    ): SelectedComponentRepository.SelectedComponent? {
        return _selectedComponentFlows[getUserHandle(userHandle)]?.value
    }


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


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

    override fun shouldAddDefaultComponent(): Boolean = shouldAddDefaultPanel
    override fun shouldAddDefaultComponent(): Boolean = shouldAddDefaultPanel


    override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
    override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
        shouldAddDefaultPanel = shouldAdd
        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 Original line Diff line number Diff line
@@ -18,28 +18,43 @@ package com.android.systemui.controls.panels


import android.content.ComponentName
import android.content.ComponentName
import android.content.SharedPreferences
import android.content.SharedPreferences
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
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.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
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.Before
import org.junit.Test
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.MockitoAnnotations


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


    private companion object {
    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 =
        val COMPONENT_A =
            SelectedComponentRepository.SelectedComponent(
            SelectedComponentRepository.SelectedComponent(
                name = "a",
                name = "a",
@@ -53,23 +68,39 @@ class SelectedComponentRepositoryTest : SysuiTestCase() {
                isPanel = false,
                isPanel = false,
            )
            )
    }
    }
    private lateinit var primaryUserSharedPref: FakeSharedPreferences
    private lateinit var secondaryUserSharedPref: FakeSharedPreferences


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


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

    // under test
    // under test
    private lateinit var repository: SelectedComponentRepository
    private lateinit var repository: SelectedComponentRepository


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


        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
    @Test
@@ -115,18 +146,10 @@ class SelectedComponentRepositoryTest : SysuiTestCase() {


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

        val previousPreferredStructure = repository.getSelectedComponent()
        val previousPreferredStructure = repository.getSelectedComponent()
        whenever(userTracker.userId).thenReturn(1)
        whenever(userTracker.userId).thenReturn(SECONDARY_USER.identifier)
        val currentPreferredStructure = repository.getSelectedComponent()
        val currentPreferredStructure = repository.getSelectedComponent()


        assertThat(previousPreferredStructure).isEqualTo(COMPONENT_A)
        assertThat(previousPreferredStructure).isEqualTo(COMPONENT_A)
@@ -134,11 +157,90 @@ class SelectedComponentRepositoryTest : SysuiTestCase() {
        assertThat(currentPreferredStructure).isEqualTo(COMPONENT_B)
        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) {
    private fun SharedPreferences.savePanel(panel: SelectedComponentRepository.SelectedComponent) {
        edit()
        edit()
            .putString("controls_component", panel.componentName?.flattenToString())
            .putString(PREF_COMPONENT, panel.componentName?.flattenToString())
            .putString("controls_structure", panel.name)
            .putString(PREF_STRUCTURE_OR_APP_NAME, panel.name)
            .putBoolean("controls_is_panel", panel.isPanel)
            .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()
            .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)
        }
    }
}
}