Merge "Allow removing the last app from home controls" into tm-qpr-dev

This commit is contained in:
Anton Potapov
2023-03-07 10:40:38 +00:00
committed by Android (Google) Code Review
15 changed files with 540 additions and 223 deletions

View File

@@ -172,12 +172,6 @@ interface ControlsController : UserAwareController {
*/
fun removeFavorites(componentName: ComponentName): Boolean
/**
* Checks if the favorites can be removed. You can't remove components from the preferred list.
* @param componentName the name of the service that provides the [Control]
*/
fun canRemoveFavorites(componentName: ComponentName): Boolean
/**
* Replaces the favorites for the given structure.
*

View File

@@ -37,6 +37,7 @@ import com.android.systemui.controls.ControlStatus
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.dagger.SysUISingleton
@@ -55,16 +56,17 @@ import javax.inject.Inject
@SysUISingleton
class ControlsControllerImpl @Inject constructor (
private val context: Context,
@Background private val executor: DelayableExecutor,
private val uiController: ControlsUiController,
private val bindingController: ControlsBindingController,
private val listingController: ControlsListingController,
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
private val authorizedPanelsRepository: AuthorizedPanelsRepository,
optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
dumpManager: DumpManager,
private val context: Context,
@Background private val executor: DelayableExecutor,
private val uiController: ControlsUiController,
private val selectedComponentRepository: SelectedComponentRepository,
private val bindingController: ControlsBindingController,
private val listingController: ControlsListingController,
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
private val authorizedPanelsRepository: AuthorizedPanelsRepository,
optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
dumpManager: DumpManager,
) : Dumpable, ControlsController {
companion object {
@@ -497,17 +499,14 @@ class ControlsControllerImpl @Inject constructor (
}
}
override fun canRemoveFavorites(componentName: ComponentName): Boolean =
!authorizedPanelsRepository.getPreferredPackages().contains(componentName.packageName)
override fun removeFavorites(componentName: ComponentName): Boolean {
if (!confirmAvailability()) return false
if (!canRemoveFavorites(componentName)) return false
executor.execute {
Favorites.removeStructures(componentName)
if (Favorites.removeStructures(componentName)) {
persistenceWrapper.storeFavorites(Favorites.getAllStructures())
}
authorizedPanelsRepository.removeAuthorizedPanels(setOf(componentName.packageName))
persistenceWrapper.storeFavorites(Favorites.getAllStructures())
}
return true
}
@@ -574,7 +573,9 @@ class ControlsControllerImpl @Inject constructor (
}
override fun setPreferredSelection(selectedItem: SelectedItem) {
uiController.updatePreferences(selectedItem)
selectedComponentRepository.setSelectedComponent(
SelectedComponentRepository.SelectedComponent(selectedItem)
)
}
override fun dump(pw: PrintWriter, args: Array<out String>) {

View File

@@ -36,6 +36,8 @@ import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.management.ControlsRequestDialog
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.controls.panels.AuthorizedPanelsRepositoryImpl
import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.panels.SelectedComponentRepositoryImpl
import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.controls.settings.ControlsSettingsDialogManagerImpl
import com.android.systemui.controls.ui.ControlActionCoordinator
@@ -114,6 +116,11 @@ abstract class ControlsModule {
repository: AuthorizedPanelsRepositoryImpl
): AuthorizedPanelsRepository
@Binds
abstract fun providePreferredPanelRepository(
repository: SelectedComponentRepositoryImpl
): SelectedComponentRepository
@BindsOptionalOf
abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper

View File

@@ -20,6 +20,8 @@ package com.android.systemui.controls.panels
import android.content.Context
import android.content.SharedPreferences
import com.android.systemui.R
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
@@ -30,7 +32,8 @@ class AuthorizedPanelsRepositoryImpl
constructor(
private val context: Context,
private val userFileManager: UserFileManager,
private val userTracker: UserTracker
private val userTracker: UserTracker,
private val featureFlags: FeatureFlags,
) : AuthorizedPanelsRepository {
override fun getAuthorizedPanels(): Set<String> {
@@ -71,8 +74,18 @@ constructor(
userTracker.userId,
)
// If we've never run this (i.e., the key doesn't exist), add the default packages
if (sharedPref.getStringSet(KEY, null) == null) {
// We should add default packages in two cases:
// 1) We've never run this
// 2) APP_PANELS_REMOVE_APPS_ALLOWED got disabled after user removed all apps
val needToSetup =
if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
sharedPref.getStringSet(KEY, null) == null
} else {
// There might be an empty set that need to be overridden after the feature has been
// turned off after being turned on
sharedPref.getStringSet(KEY, null).isNullOrEmpty()
}
if (needToSetup) {
sharedPref.edit().putStringSet(KEY, getPreferredPackages()).apply()
}
return sharedPref

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.controls.panels
import android.content.ComponentName
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.flags.Flags
/** Stores user-selected preferred component. */
interface SelectedComponentRepository {
/**
* Returns currently set preferred component, or null when nothing is set. Consider using
* [ControlsUiController.getPreferredSelectedItem] to get domain specific data
*/
fun getSelectedComponent(): SelectedComponent?
/** Sets preferred component. Use [getSelectedComponent] to get current one */
fun setSelectedComponent(selectedComponent: SelectedComponent)
/** Clears current preferred component. [getSelectedComponent] will return null afterwards */
fun removeSelectedComponent()
/**
* Return true when default preferred component should be set up and false the otherwise. This
* is always true when [Flags.APP_PANELS_REMOVE_APPS_ALLOWED] is disabled
*/
fun shouldAddDefaultComponent(): Boolean
/**
* Sets if default component should be added. This is ignored when
* [Flags.APP_PANELS_REMOVE_APPS_ALLOWED] is disabled
*/
fun setShouldAddDefaultComponent(shouldAdd: Boolean)
data class SelectedComponent(
val name: String,
val componentName: ComponentName?,
val isPanel: Boolean,
) {
constructor(
selectedItem: SelectedItem
) : this(
name = selectedItem.name.toString(),
componentName = selectedItem.componentName,
isPanel = selectedItem is SelectedItem.PanelItem,
)
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.controls.panels
import android.content.ComponentName
import android.content.Context
import android.content.SharedPreferences
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import javax.inject.Inject
@SysUISingleton
class SelectedComponentRepositoryImpl
@Inject
constructor(
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
private val featureFlags: FeatureFlags,
) : SelectedComponentRepository {
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"
const val SHOULD_ADD_DEFAULT_PANEL = "should_add_default_panel"
}
private val sharedPreferences: SharedPreferences
get() =
userFileManager.getSharedPreferences(
fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
mode = Context.MODE_PRIVATE,
userId = userTracker.userId
)
override fun getSelectedComponent(): SelectedComponentRepository.SelectedComponent? {
with(sharedPreferences) {
val componentString = getString(PREF_COMPONENT, null) ?: return null
return SelectedComponentRepository.SelectedComponent(
name = getString(PREF_STRUCTURE_OR_APP_NAME, "")!!,
componentName = ComponentName.unflattenFromString(componentString),
isPanel = getBoolean(PREF_IS_PANEL, false)
)
}
}
override fun setSelectedComponent(
selectedComponent: SelectedComponentRepository.SelectedComponent
) {
sharedPreferences
.edit()
.putString(PREF_COMPONENT, selectedComponent.componentName?.flattenToString())
.putString(PREF_STRUCTURE_OR_APP_NAME, selectedComponent.name)
.putBoolean(PREF_IS_PANEL, selectedComponent.isPanel)
.apply()
}
override fun removeSelectedComponent() {
sharedPreferences
.edit()
.remove(PREF_COMPONENT)
.remove(PREF_STRUCTURE_OR_APP_NAME)
.remove(PREF_IS_PANEL)
.apply()
}
override fun shouldAddDefaultComponent(): Boolean =
if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
sharedPreferences.getBoolean(SHOULD_ADD_DEFAULT_PANEL, true)
} else {
true
}
override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
sharedPreferences.edit().putBoolean(SHOULD_ADD_DEFAULT_PANEL, shouldAdd).apply()
}
}

View File

@@ -18,17 +18,16 @@
package com.android.systemui.controls.start
import android.content.Context
import android.content.res.Resources
import android.os.UserHandle
import com.android.systemui.CoreStartable
import com.android.systemui.R
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.UserTracker
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -37,7 +36,7 @@ import javax.inject.Inject
* Started with SystemUI to perform early operations for device controls subsystem (only if enabled)
*
* In particular, it will perform the following:
* * If there is no preferred selection for provider and at least one of the preferred packages
* * If there is no preferred selection for provider and at least one of the preferred packages
* provides a panel, it will select the first one that does.
* * If the preferred selection provides a panel, it will bind to that service (to reduce latency on
* displaying the panel).
@@ -48,10 +47,11 @@ import javax.inject.Inject
class ControlsStartable
@Inject
constructor(
@Main private val resources: Resources,
@Background private val executor: Executor,
private val controlsComponent: ControlsComponent,
private val userTracker: UserTracker
@Background private val executor: Executor,
private val controlsComponent: ControlsComponent,
private val userTracker: UserTracker,
private val authorizedPanelsRepository: AuthorizedPanelsRepository,
private val selectedComponentRepository: SelectedComponentRepository,
) : CoreStartable {
// These two controllers can only be accessed after `start` method once we've checked if the
@@ -85,12 +85,15 @@ constructor(
}
private fun selectDefaultPanelIfNecessary() {
if (!selectedComponentRepository.shouldAddDefaultComponent()) {
return
}
val currentSelection = controlsController.getPreferredSelection()
if (currentSelection == SelectedItem.EMPTY_SELECTION) {
val availableServices = controlsListingController.getCurrentServices()
val panels = availableServices.filter { it.panelActivity != null }
resources
.getStringArray(R.array.config_controlsPreferredPackages)
authorizedPanelsRepository
.getPreferredPackages()
// Looking for the first element in the string array such that there is one package
// that has a panel. It will return null if there are no packages in the array,
// or if no packages in the array have a panel associated with it.

View File

@@ -64,8 +64,6 @@ interface ControlsUiController {
* This element will be the one that appears when the user first opens the controls activity.
*/
fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem
fun updatePreferences(selectedItem: SelectedItem)
}
sealed class SelectedItem {

View File

@@ -64,6 +64,7 @@ import com.android.systemui.controls.management.ControlsFavoritingActivity
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -73,9 +74,7 @@ import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsPopupMenu
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.asIndenting
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -84,7 +83,7 @@ import com.android.wm.shell.TaskViewFactory
import dagger.Lazy
import java.io.PrintWriter
import java.text.Collator
import java.util.*
import java.util.Optional
import java.util.function.Consumer
import javax.inject.Inject
@@ -98,25 +97,22 @@ class ControlsUiControllerImpl @Inject constructor (
@Main val uiExecutor: DelayableExecutor,
@Background val bgExecutor: DelayableExecutor,
val controlsListingController: Lazy<ControlsListingController>,
val controlActionCoordinator: ControlActionCoordinator,
private val controlActionCoordinator: ControlActionCoordinator,
private val activityStarter: ActivityStarter,
private val iconCache: CustomIconCache,
private val controlsMetricsLogger: ControlsMetricsLogger,
private val keyguardStateController: KeyguardStateController,
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
private val taskViewFactory: Optional<TaskViewFactory>,
private val controlsSettingsRepository: ControlsSettingsRepository,
private val authorizedPanelsRepository: AuthorizedPanelsRepository,
private val selectedComponentRepository: SelectedComponentRepository,
private val featureFlags: FeatureFlags,
private val dialogsFactory: ControlsDialogsFactory,
dumpManager: DumpManager
) : ControlsUiController, Dumpable {
companion object {
private const val PREF_COMPONENT = "controls_component"
private const val PREF_STRUCTURE_OR_APP_NAME = "controls_structure"
private const val PREF_IS_PANEL = "controls_is_panel"
private const val FADE_IN_MILLIS = 200L
@@ -138,12 +134,6 @@ class ControlsUiControllerImpl @Inject constructor (
private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow)
private var retainCache = false
private var lastSelections = emptyList<SelectionItem>()
private val sharedPreferences
get() = userFileManager.getSharedPreferences(
fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
mode = 0,
userId = userTracker.userId
)
private var taskViewController: PanelTaskViewController? = null
@@ -341,20 +331,18 @@ class ControlsUiControllerImpl @Inject constructor (
if (!controlsController.get().removeFavorites(componentName)) {
return@createRemoveAppDialog
}
if (
sharedPreferences.getString(PREF_COMPONENT, "") ==
componentName.flattenToString()
) {
sharedPreferences
.edit()
.remove(PREF_COMPONENT)
.remove(PREF_STRUCTURE_OR_APP_NAME)
.remove(PREF_IS_PANEL)
.commit()
if (selectedComponentRepository.getSelectedComponent()?.componentName ==
componentName) {
selectedComponentRepository.removeSelectedComponent()
}
allStructures = controlsController.get().getFavorites()
selectedItem = getPreferredSelectedItem(allStructures)
val selectedItem = getPreferredSelectedItem(controlsController.get().getFavorites())
if (selectedItem == SelectedItem.EMPTY_SELECTION) {
// User removed the last panel. In this case we start app selection flow and don't
// want to auto-add it again
selectedComponentRepository.setShouldAddDefaultComponent(false)
}
reload(parent)
}.apply { show() }
}
@@ -522,8 +510,7 @@ class ControlsUiControllerImpl @Inject constructor (
ADD_APP_ID
))
}
if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED) &&
controlsController.get().canRemoveFavorites(selectedItem.componentName)) {
if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
add(OverflowMenuAdapter.MenuItem(
context.getText(R.string.controls_menu_remove),
REMOVE_APP_ID,
@@ -569,7 +556,7 @@ class ControlsUiControllerImpl @Inject constructor (
ADD_CONTROLS_ID -> startFavoritingActivity(selectedStructure)
EDIT_CONTROLS_ID -> startEditingActivity(selectedStructure)
REMOVE_APP_ID -> startRemovingApp(
selectedStructure.componentName, selectionItem.appName
selectionItem.componentName, selectionItem.appName
)
}
dismiss()
@@ -714,29 +701,22 @@ class ControlsUiControllerImpl @Inject constructor (
}
override fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem {
val sp = sharedPreferences
val component = sp.getString(PREF_COMPONENT, null)?.let {
ComponentName.unflattenFromString(it)
} ?: EMPTY_COMPONENT
val name = sp.getString(PREF_STRUCTURE_OR_APP_NAME, "")!!
val isPanel = sp.getBoolean(PREF_IS_PANEL, false)
return if (isPanel) {
SelectedItem.PanelItem(name, component)
val preferredPanel = selectedComponentRepository.getSelectedComponent()
val component = preferredPanel?.componentName ?: EMPTY_COMPONENT
return if (preferredPanel?.isPanel == true) {
SelectedItem.PanelItem(preferredPanel.name, component)
} else {
if (structures.isEmpty()) return SelectedItem.EMPTY_SELECTION
SelectedItem.StructureItem(structures.firstOrNull {
component == it.componentName && name == it.structure
} ?: structures.get(0))
component == it.componentName && preferredPanel?.name == it.structure
} ?: structures[0])
}
}
override fun updatePreferences(selectedItem: SelectedItem) {
sharedPreferences.edit()
.putString(PREF_COMPONENT, selectedItem.componentName.flattenToString())
.putString(PREF_STRUCTURE_OR_APP_NAME, selectedItem.name.toString())
.putBoolean(PREF_IS_PANEL, selectedItem is SelectedItem.PanelItem)
.apply()
private fun updatePreferences(selectedItem: SelectedItem) {
selectedComponentRepository.setSelectedComponent(
SelectedComponentRepository.SelectedComponent(selectedItem)
)
}
private fun maybeUpdateSelectedItem(item: SelectionItem): Boolean {

View File

@@ -34,6 +34,7 @@ import com.android.systemui.controls.ControlStatus
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.controls.panels.FakeSelectedComponentRepository
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dump.DumpManager
import com.android.systemui.settings.UserFileManager
@@ -56,7 +57,6 @@ import org.mockito.ArgumentMatchers.anyString
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.inOrder
@@ -66,6 +66,7 @@ import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import java.io.File
import java.util.*
@@ -108,6 +109,8 @@ class ControlsControllerImplTest : SysuiTestCase() {
private lateinit var listingCallbackCaptor:
ArgumentCaptor<ControlsListingController.ControlsListingCallback>
private val preferredPanelRepository = FakeSelectedComponentRepository()
private lateinit var delayableExecutor: FakeExecutor
private lateinit var controller: ControlsControllerImpl
private lateinit var canceller: DidRunRunnable
@@ -168,6 +171,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
wrapper,
delayableExecutor,
uiController,
preferredPanelRepository,
bindingController,
listingController,
userFileManager,
@@ -221,6 +225,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
mContext,
delayableExecutor,
uiController,
preferredPanelRepository,
bindingController,
listingController,
userFileManager,
@@ -240,6 +245,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
mContext,
delayableExecutor,
uiController,
preferredPanelRepository,
bindingController,
listingController,
userFileManager,

View File

@@ -22,6 +22,8 @@ import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.FakeSharedPreferences
@@ -40,6 +42,8 @@ class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var userTracker: UserTracker
private val featureFlags = FakeFeatureFlags()
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -48,6 +52,7 @@ class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() {
arrayOf<String>()
)
whenever(userTracker.userId).thenReturn(0)
featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
}
@Test
@@ -127,8 +132,25 @@ class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() {
assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty()
}
@Test
fun testSetAuthorizedPackageAfterFeatureDisabled() {
mContext.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf(TEST_PACKAGE)
)
val sharedPrefs = FakeSharedPreferences()
val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
val repository = createRepository(fileManager)
repository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
assertThat(repository.getAuthorizedPanels()).isEqualTo(setOf(TEST_PACKAGE))
}
private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl {
return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker)
return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker, featureFlags)
}
private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.controls.panels
class FakeSelectedComponentRepository : SelectedComponentRepository {
private var selectedComponent: SelectedComponentRepository.SelectedComponent? = null
private var shouldAddDefaultPanel: Boolean = true
override fun getSelectedComponent(): SelectedComponentRepository.SelectedComponent? =
selectedComponent
override fun setSelectedComponent(
selectedComponent: SelectedComponentRepository.SelectedComponent
) {
this.selectedComponent = selectedComponent
}
override fun removeSelectedComponent() {
selectedComponent = null
}
override fun shouldAddDefaultComponent(): Boolean = shouldAddDefaultPanel
override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
shouldAddDefaultPanel = shouldAdd
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.controls.panels
import android.content.ComponentName
import android.content.SharedPreferences
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
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 org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class SelectedComponentRepositoryTest : SysuiTestCase() {
private companion object {
val COMPONENT_A =
SelectedComponentRepository.SelectedComponent(
name = "a",
componentName = ComponentName.unflattenFromString("pkg/.cls_a"),
isPanel = false,
)
val COMPONENT_B =
SelectedComponentRepository.SelectedComponent(
name = "b",
componentName = ComponentName.unflattenFromString("pkg/.cls_b"),
isPanel = false,
)
}
@Mock private lateinit var userTracker: UserTracker
@Mock 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)
repository = SelectedComponentRepositoryImpl(userFileManager, userTracker, featureFlags)
}
@Test
fun testUnsetIsNull() {
assertThat(repository.getSelectedComponent()).isNull()
}
@Test
fun testGetReturnsSet() {
repository.setSelectedComponent(COMPONENT_A)
assertThat(repository.getSelectedComponent()).isEqualTo(COMPONENT_A)
}
@Test
fun testSetOverrides() {
repository.setSelectedComponent(COMPONENT_A)
repository.setSelectedComponent(COMPONENT_B)
assertThat(repository.getSelectedComponent()).isEqualTo(COMPONENT_B)
}
@Test
fun testRemove() {
repository.setSelectedComponent(COMPONENT_A)
repository.removeSelectedComponent()
assertThat(repository.getSelectedComponent()).isNull()
}
@Test
fun testFeatureEnabled_shouldAddDefaultPanelDefaultsToTrue() {
featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
assertThat(repository.shouldAddDefaultComponent()).isTrue()
}
@Test
fun testFeatureDisabled_shouldAddDefaultPanelDefaultsToTrue() {
featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
assertThat(repository.shouldAddDefaultComponent()).isTrue()
}
@Test
fun testFeatureEnabled_shouldAddDefaultPanelChecked() {
featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
repository.setShouldAddDefaultComponent(false)
assertThat(repository.shouldAddDefaultComponent()).isFalse()
}
@Test
fun testFeatureDisabled_shouldAlwaysAddDefaultPanelAlwaysTrue() {
featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
repository.setShouldAddDefaultComponent(false)
assertThat(repository.shouldAddDefaultComponent()).isTrue()
}
@Test
fun testGetPreferredStructure_differentUserId() {
sharedPreferences.savePanel(COMPONENT_A)
whenever(
userFileManager.getSharedPreferences(
DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
0,
1,
)
)
.thenReturn(FakeSharedPreferences().also { it.savePanel(COMPONENT_B) })
val previousPreferredStructure = repository.getSelectedComponent()
whenever(userTracker.userId).thenReturn(1)
val currentPreferredStructure = repository.getSelectedComponent()
assertThat(previousPreferredStructure).isEqualTo(COMPONENT_A)
assertThat(currentPreferredStructure).isNotEqualTo(previousPreferredStructure)
assertThat(currentPreferredStructure).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)
.commit()
}
}

View File

@@ -23,17 +23,19 @@ import android.content.pm.ApplicationInfo
import android.content.pm.ServiceInfo
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.controls.panels.FakeSelectedComponentRepository
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import java.util.Optional
import org.junit.Before
@@ -53,16 +55,16 @@ class ControlsStartableTest : SysuiTestCase() {
@Mock private lateinit var controlsController: ControlsController
@Mock private lateinit var controlsListingController: ControlsListingController
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
private val preferredPanelsRepository = FakeSelectedComponentRepository()
private lateinit var fakeExecutor: FakeExecutor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
context.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf<String>()
)
whenever(authorizedPanelsRepository.getPreferredPackages()).thenReturn(setOf())
fakeExecutor = FakeExecutor(FakeSystemClock())
}
@@ -87,10 +89,8 @@ class ControlsStartableTest : SysuiTestCase() {
@Test
fun testPreferredPackagesNotInstalled_noNewSelection() {
context.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf(TEST_PACKAGE_PANEL)
)
whenever(authorizedPanelsRepository.getPreferredPackages())
.thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
`when`(controlsListingController.getCurrentServices()).thenReturn(emptyList())
@@ -101,10 +101,8 @@ class ControlsStartableTest : SysuiTestCase() {
@Test
fun testPreferredPackageNotPanel_noNewSelection() {
context.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf(TEST_PACKAGE_PANEL)
)
whenever(authorizedPanelsRepository.getPreferredPackages())
.thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "not panel", hasPanel = false))
`when`(controlsListingController.getCurrentServices()).thenReturn(listings)
@@ -116,10 +114,8 @@ class ControlsStartableTest : SysuiTestCase() {
@Test
fun testExistingSelection_noNewSelection() {
context.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf(TEST_PACKAGE_PANEL)
)
whenever(authorizedPanelsRepository.getPreferredPackages())
.thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection())
.thenReturn(mock<SelectedItem.PanelItem>())
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
@@ -132,10 +128,8 @@ class ControlsStartableTest : SysuiTestCase() {
@Test
fun testPanelAdded() {
context.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf(TEST_PACKAGE_PANEL)
)
whenever(authorizedPanelsRepository.getPreferredPackages())
.thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
`when`(controlsListingController.getCurrentServices()).thenReturn(listings)
@@ -147,10 +141,8 @@ class ControlsStartableTest : SysuiTestCase() {
@Test
fun testMultiplePreferredOnlyOnePanel_panelAdded() {
context.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf("other_package", TEST_PACKAGE_PANEL)
)
whenever(authorizedPanelsRepository.getPreferredPackages())
.thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings =
listOf(
@@ -166,10 +158,8 @@ class ControlsStartableTest : SysuiTestCase() {
@Test
fun testMultiplePreferredMultiplePanels_firstPreferredAdded() {
context.orCreateTestableResources.addOverride(
R.array.config_controlsPreferredPackages,
arrayOf(TEST_PACKAGE_PANEL, "other_package")
)
whenever(authorizedPanelsRepository.getPreferredPackages())
.thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings =
listOf(
@@ -217,6 +207,20 @@ class ControlsStartableTest : SysuiTestCase() {
verify(controlsController, never()).bindComponentForPanel(any())
}
@Test
fun testAlreadyAddedPanel_noNewSelection() {
preferredPanelsRepository.setShouldAddDefaultComponent(false)
whenever(authorizedPanelsRepository.getPreferredPackages())
.thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
`when`(controlsListingController.getCurrentServices()).thenReturn(listings)
createStartable(enabled = true).start()
verify(controlsController, never()).setPreferredSelection(any())
}
private fun createStartable(enabled: Boolean): ControlsStartable {
val component: ControlsComponent =
mock() {
@@ -230,7 +234,13 @@ class ControlsStartableTest : SysuiTestCase() {
`when`(getControlsListingController()).thenReturn(Optional.empty())
}
}
return ControlsStartable(context.resources, fakeExecutor, component, userTracker)
return ControlsStartable(
fakeExecutor,
component,
userTracker,
authorizedPanelsRepository,
preferredPanelsRepository,
)
}
private fun ControlsServiceInfo(

View File

@@ -42,16 +42,14 @@ import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.controls.panels.FakeSelectedComponentRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeController
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.FakeSystemUIDialogController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -64,20 +62,18 @@ import com.android.systemui.util.time.FakeSystemClock
import com.android.wm.shell.TaskView
import com.android.wm.shell.TaskViewFactory
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import java.util.function.Consumer
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyString
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import java.util.Optional
import java.util.function.Consumer
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -87,11 +83,9 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
@Mock lateinit var controlsListingController: ControlsListingController
@Mock lateinit var controlActionCoordinator: ControlActionCoordinator
@Mock lateinit var activityStarter: ActivityStarter
@Mock lateinit var shadeController: ShadeController
@Mock lateinit var iconCache: CustomIconCache
@Mock lateinit var controlsMetricsLogger: ControlsMetricsLogger
@Mock lateinit var keyguardStateController: KeyguardStateController
@Mock lateinit var userFileManager: UserFileManager
@Mock lateinit var userTracker: UserTracker
@Mock lateinit var taskViewFactory: TaskViewFactory
@Mock lateinit var dumpManager: DumpManager
@@ -99,7 +93,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
@Mock lateinit var featureFlags: FeatureFlags
@Mock lateinit var packageManager: PackageManager
private val sharedPreferences = FakeSharedPreferences()
private val preferredPanelRepository = FakeSelectedComponentRepository()
private val fakeDialogController = FakeSystemUIDialogController()
private val uiExecutor = FakeExecutor(FakeSystemClock())
private val bgExecutor = FakeExecutor(FakeSystemClock())
@@ -138,94 +132,30 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
iconCache,
controlsMetricsLogger,
keyguardStateController,
userFileManager,
userTracker,
Optional.of(taskViewFactory),
controlsSettingsRepository,
authorizedPanelsRepository,
preferredPanelRepository,
featureFlags,
ControlsDialogsFactory { fakeDialogController.dialog },
dumpManager,
)
`when`(
userFileManager.getSharedPreferences(
DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
0,
0
)
)
.thenReturn(sharedPreferences)
`when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
.thenReturn(sharedPreferences)
`when`(userTracker.userId).thenReturn(0)
`when`(userTracker.userHandle).thenReturn(UserHandle.of(0))
}
@Test
fun testGetPreferredStructure() {
val structureInfo = mock<StructureInfo>()
underTest.getPreferredSelectedItem(listOf(structureInfo))
verify(userFileManager)
.getSharedPreferences(
fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
mode = 0,
userId = 0
)
}
@Test
fun testGetPreferredStructure_differentUserId() {
val selectedItems =
listOf(
SelectedItem.StructureItem(
StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
),
SelectedItem.StructureItem(
StructureInfo(ComponentName.unflattenFromString("pkg/.cls2"), "b", ArrayList())
),
)
val structures = selectedItems.map { it.structure }
sharedPreferences
.edit()
.putString("controls_component", selectedItems[0].componentName.flattenToString())
.putString("controls_structure", selectedItems[0].name.toString())
.commit()
val differentSharedPreferences = FakeSharedPreferences()
differentSharedPreferences
.edit()
.putString("controls_component", selectedItems[1].componentName.flattenToString())
.putString("controls_structure", selectedItems[1].name.toString())
.commit()
val previousPreferredStructure = underTest.getPreferredSelectedItem(structures)
`when`(
userFileManager.getSharedPreferences(
DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
0,
1
)
)
.thenReturn(differentSharedPreferences)
`when`(userTracker.userId).thenReturn(1)
val currentPreferredStructure = underTest.getPreferredSelectedItem(structures)
assertThat(previousPreferredStructure).isEqualTo(selectedItems[0])
assertThat(currentPreferredStructure).isEqualTo(selectedItems[1])
assertThat(currentPreferredStructure).isNotEqualTo(previousPreferredStructure)
}
@Test
fun testGetPreferredPanel() {
val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
sharedPreferences
.edit()
.putString("controls_component", panel.componentName.flattenToString())
.putString("controls_structure", panel.appName.toString())
.putBoolean("controls_is_panel", true)
.commit()
preferredPanelRepository.setSelectedComponent(
SelectedComponentRepository.SelectedComponent(
name = panel.appName.toString(),
componentName = panel.componentName,
isPanel = true,
)
)
val selected = underTest.getPreferredSelectedItem(emptyList())
@@ -369,11 +299,9 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
),
)
sharedPreferences
.edit()
.putString("controls_component", selectedItems[0].componentName.flattenToString())
.putString("controls_structure", selectedItems[0].name.toString())
.commit()
preferredPanelRepository.setSelectedComponent(
SelectedComponentRepository.SelectedComponent(selectedItems[0])
)
assertThat(underTest.resolveActivity())
.isEqualTo(ControlsProviderSelectorActivity::class.java)
@@ -418,12 +346,9 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
val componentName = ComponentName(context, "cls")
whenever(controlsController.removeFavorites(eq(componentName))).thenReturn(true)
val panel = SelectedItem.PanelItem("App name", componentName)
sharedPreferences
.edit()
.putString("controls_component", panel.componentName.flattenToString())
.putString("controls_structure", panel.appName.toString())
.putBoolean("controls_is_panel", true)
.commit()
preferredPanelRepository.setSelectedComponent(
SelectedComponentRepository.SelectedComponent(panel)
)
underTest.show(parent, {}, context)
underTest.startRemovingApp(componentName, "Test App")
@@ -432,11 +357,8 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
verify(controlsController).removeFavorites(eq(componentName))
assertThat(underTest.getPreferredSelectedItem(emptyList()))
.isEqualTo(SelectedItem.EMPTY_SELECTION)
with(sharedPreferences) {
assertThat(contains("controls_component")).isFalse()
assertThat(contains("controls_structure")).isFalse()
assertThat(contains("controls_is_panel")).isFalse()
}
assertThat(preferredPanelRepository.shouldAddDefaultComponent()).isFalse()
assertThat(preferredPanelRepository.getSelectedComponent()).isNull()
}
@Test
@@ -452,12 +374,9 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
val activity = ComponentName(context, "activity")
sharedPreferences
.edit()
.putString("controls_component", panel.componentName.flattenToString())
.putString("controls_structure", panel.appName.toString())
.putBoolean("controls_is_panel", true)
.commit()
preferredPanelRepository.setSelectedComponent(
SelectedComponentRepository.SelectedComponent(panel)
)
return ControlsServiceInfo(panel.componentName, panel.appName, activity)
}