Loading packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt +18 −5 Original line number Diff line number Diff line Loading @@ -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() /** Loading packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt +55 −13 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 { Loading @@ -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, "")!!, Loading @@ -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) Loading @@ -73,7 +112,7 @@ constructor( } override fun removeSelectedComponent() { sharedPreferences getSharedPreferencesForUser(userTracker.userId) .edit() .remove(PREF_COMPONENT) .remove(PREF_STRUCTURE_OR_APP_NAME) Loading @@ -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() } } packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt +35 −7 Original line number Diff line number Diff line Loading @@ -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 } } } packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt +127 −25 Original line number Diff line number Diff line Loading @@ -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", Loading @@ -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 Loading Loading @@ -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) Loading @@ -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) } } } Loading
packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt +18 −5 Original line number Diff line number Diff line Loading @@ -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() /** Loading
packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt +55 −13 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 { Loading @@ -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, "")!!, Loading @@ -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) Loading @@ -73,7 +112,7 @@ constructor( } override fun removeSelectedComponent() { sharedPreferences getSharedPreferencesForUser(userTracker.userId) .edit() .remove(PREF_COMPONENT) .remove(PREF_STRUCTURE_OR_APP_NAME) Loading @@ -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() } }
packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt +35 −7 Original line number Diff line number Diff line Loading @@ -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 } } }
packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt +127 −25 Original line number Diff line number Diff line Loading @@ -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", Loading @@ -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 Loading Loading @@ -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) Loading @@ -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) } } }