Loading packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt +18 −5 Original line number Original line Diff line number Diff line Loading @@ -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() /** /** Loading packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt +55 −13 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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 { Loading @@ -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, "")!!, Loading @@ -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) Loading @@ -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) Loading @@ -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() } } } } packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt +35 −7 Original line number Original line Diff line number Diff line Loading @@ -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 } } } } packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt +127 −25 Original line number Original line Diff line number Diff line Loading @@ -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", Loading @@ -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 Loading Loading @@ -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) Loading @@ -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) } } } } Loading
packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt +18 −5 Original line number Original line Diff line number Diff line Loading @@ -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() /** /** Loading
packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt +55 −13 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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 { Loading @@ -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, "")!!, Loading @@ -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) Loading @@ -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) Loading @@ -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() } } } }
packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt +35 −7 Original line number Original line Diff line number Diff line Loading @@ -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 } } } }
packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt +127 −25 Original line number Original line Diff line number Diff line Loading @@ -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", Loading @@ -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 Loading Loading @@ -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) Loading @@ -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) } } } }