Merge "Add remove app flow to the home controls panel" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
686f8e9d54
@@ -2262,6 +2262,10 @@
|
||||
panel (embedded activity) instead of controls rendered by SystemUI [CHAR LIMIT=NONE] -->
|
||||
<string name="controls_panel_authorization">When you add <xliff:g id="appName" example="My app">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here.</string>
|
||||
|
||||
<!-- Shows in a dialog presented to the user to authorize this app removal from a Device
|
||||
controls panel [CHAR LIMIT=NONE] -->
|
||||
<string name="controls_panel_remove_app_authorization">Remove controls for <xliff:g example="My app" id="appName">%s</xliff:g>?</string>
|
||||
|
||||
<!-- a11y state description for a control that is currently favorited [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_control_favorite">Favorited</string>
|
||||
<!-- a11y state description for a control that is currently favorited with its position [CHAR LIMIT=NONE] -->
|
||||
@@ -2302,6 +2306,8 @@
|
||||
<string name="controls_dialog_title">Add to device controls</string>
|
||||
<!-- Controls dialog add to favorites [CHAR LIMIT=40] -->
|
||||
<string name="controls_dialog_ok">Add</string>
|
||||
<!-- Controls dialog remove app from a panel [CHAR LIMIT=40] -->
|
||||
<string name="controls_dialog_remove">Remove</string>
|
||||
<!-- Controls dialog message. Indicates app that suggested this control [CHAR LIMIT=NONE] -->
|
||||
<string name="controls_dialog_message">Suggested by <xliff:g id="app" example="System UI">%s</xliff:g></string>
|
||||
<!-- Controls tile secondary label when device is locked and user does not want access to controls from lockscreen [CHAR LIMIT=20] -->
|
||||
@@ -2419,6 +2425,8 @@
|
||||
<string name="controls_menu_edit">Edit controls</string>
|
||||
<!-- Controls menu, add another app [CHAR LIMIT=30] -->
|
||||
<string name="controls_menu_add_another_app">Add app</string>
|
||||
<!-- Controls menu, remove app [CHAR_LIMIT=30] -->
|
||||
<string name="controls_menu_remove">Remove app</string>
|
||||
|
||||
<!-- Title for the media output dialog with media related devices [CHAR LIMIT=50] -->
|
||||
<string name="media_output_dialog_add_output">Add outputs</string>
|
||||
|
||||
@@ -165,6 +165,19 @@ interface ControlsController : UserAwareController {
|
||||
controlInfo: ControlInfo
|
||||
)
|
||||
|
||||
/**
|
||||
* Removes favorites for a given component
|
||||
* @param componentName the name of the service that provides the [Control]
|
||||
* @return true when favorites is scheduled for deletion
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -497,6 +497,21 @@ 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)
|
||||
authorizedPanelsRepository.removeAuthorizedPanels(setOf(componentName.packageName))
|
||||
persistenceWrapper.storeFavorites(Favorites.getAllStructures())
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun replaceFavoritesForStructure(structureInfo: StructureInfo) {
|
||||
if (!confirmAvailability()) return
|
||||
executor.execute {
|
||||
@@ -655,10 +670,11 @@ private object Favorites {
|
||||
return true
|
||||
}
|
||||
|
||||
fun removeStructures(componentName: ComponentName) {
|
||||
fun removeStructures(componentName: ComponentName): Boolean {
|
||||
val newFavMap = favMap.toMutableMap()
|
||||
newFavMap.remove(componentName)
|
||||
val removed = newFavMap.remove(componentName) != null
|
||||
favMap = newFavMap
|
||||
return removed
|
||||
}
|
||||
|
||||
fun addFavorite(
|
||||
|
||||
@@ -26,6 +26,14 @@ interface AuthorizedPanelsRepository {
|
||||
/** A set of package names that the user has previously authorized to show panels. */
|
||||
fun getAuthorizedPanels(): Set<String>
|
||||
|
||||
/** Preferred applications to query controls suggestions from */
|
||||
fun getPreferredPackages(): Set<String>
|
||||
|
||||
/** Adds [packageNames] to the set of packages that the user has authorized to show panels. */
|
||||
fun addAuthorizedPanels(packageNames: Set<String>)
|
||||
|
||||
/**
|
||||
* Removes [packageNames] from the set of packages that the user has authorized to show panels.
|
||||
*/
|
||||
fun removeAuthorizedPanels(packageNames: Set<String>)
|
||||
}
|
||||
|
||||
@@ -37,10 +37,20 @@ constructor(
|
||||
return getAuthorizedPanelsInternal(instantiateSharedPrefs())
|
||||
}
|
||||
|
||||
override fun getPreferredPackages(): Set<String> =
|
||||
context.resources.getStringArray(R.array.config_controlsPreferredPackages).toSet()
|
||||
|
||||
override fun addAuthorizedPanels(packageNames: Set<String>) {
|
||||
addAuthorizedPanelsInternal(instantiateSharedPrefs(), packageNames)
|
||||
}
|
||||
|
||||
override fun removeAuthorizedPanels(packageNames: Set<String>) {
|
||||
with(instantiateSharedPrefs()) {
|
||||
val currentSet = getAuthorizedPanelsInternal(this)
|
||||
edit().putStringSet(KEY, currentSet - packageNames).apply()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAuthorizedPanelsInternal(sharedPreferences: SharedPreferences): Set<String> {
|
||||
return sharedPreferences.getStringSet(KEY, emptySet())!!
|
||||
}
|
||||
@@ -63,15 +73,7 @@ constructor(
|
||||
|
||||
// If we've never run this (i.e., the key doesn't exist), add the default packages
|
||||
if (sharedPref.getStringSet(KEY, null) == null) {
|
||||
sharedPref
|
||||
.edit()
|
||||
.putStringSet(
|
||||
KEY,
|
||||
context.resources
|
||||
.getStringArray(R.array.config_controlsPreferredPackages)
|
||||
.toSet()
|
||||
)
|
||||
.apply()
|
||||
sharedPref.edit().putStringSet(KEY, getPreferredPackages()).apply()
|
||||
}
|
||||
return sharedPref
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.ui
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.statusbar.phone.SystemUIDialog
|
||||
import java.util.function.Consumer
|
||||
import javax.inject.Inject
|
||||
|
||||
class ControlsDialogsFactory(private val internalDialogFactory: (Context) -> SystemUIDialog) {
|
||||
|
||||
@Inject constructor() : this({ SystemUIDialog(it) })
|
||||
|
||||
fun createRemoveAppDialog(
|
||||
context: Context,
|
||||
appName: CharSequence,
|
||||
response: Consumer<Boolean>
|
||||
): Dialog {
|
||||
val listener =
|
||||
DialogInterface.OnClickListener { _, which ->
|
||||
response.accept(which == DialogInterface.BUTTON_POSITIVE)
|
||||
}
|
||||
return internalDialogFactory(context).apply {
|
||||
setTitle(context.getString(R.string.controls_panel_remove_app_authorization, appName))
|
||||
setCanceledOnTouchOutside(true)
|
||||
setOnCancelListener { response.accept(false) }
|
||||
setPositiveButton(R.string.controls_dialog_remove, listener)
|
||||
setNeutralButton(R.string.cancel, listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.ObjectAnimator
|
||||
import android.app.Activity
|
||||
import android.app.ActivityOptions
|
||||
import android.app.Dialog
|
||||
import android.app.PendingIntent
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
@@ -52,7 +53,6 @@ import com.android.systemui.Dumpable
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.controls.ControlsMetricsLogger
|
||||
import com.android.systemui.controls.ControlsServiceInfo
|
||||
import com.android.systemui.controls.settings.ControlsSettingsRepository
|
||||
import com.android.systemui.controls.CustomIconCache
|
||||
import com.android.systemui.controls.controller.ControlsController
|
||||
import com.android.systemui.controls.controller.StructureInfo
|
||||
@@ -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.settings.ControlsSettingsRepository
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
@@ -83,7 +84,7 @@ import com.android.wm.shell.TaskViewFactory
|
||||
import dagger.Lazy
|
||||
import java.io.PrintWriter
|
||||
import java.text.Collator
|
||||
import java.util.Optional
|
||||
import java.util.*
|
||||
import java.util.function.Consumer
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -108,6 +109,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
private val controlsSettingsRepository: ControlsSettingsRepository,
|
||||
private val authorizedPanelsRepository: AuthorizedPanelsRepository,
|
||||
private val featureFlags: FeatureFlags,
|
||||
private val dialogsFactory: ControlsDialogsFactory,
|
||||
dumpManager: DumpManager
|
||||
) : ControlsUiController, Dumpable {
|
||||
|
||||
@@ -122,6 +124,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
private const val ADD_CONTROLS_ID = 1L
|
||||
private const val ADD_APP_ID = 2L
|
||||
private const val EDIT_CONTROLS_ID = 3L
|
||||
private const val REMOVE_APP_ID = 4L
|
||||
}
|
||||
|
||||
private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION
|
||||
@@ -151,6 +154,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
|
||||
private var openAppIntent: Intent? = null
|
||||
private var overflowMenuAdapter: BaseAdapter? = null
|
||||
private var removeAppDialog: Dialog? = null
|
||||
|
||||
private val onSeedingComplete = Consumer<Boolean> {
|
||||
accepted ->
|
||||
@@ -330,6 +334,31 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun startRemovingApp(componentName: ComponentName, appName: CharSequence) {
|
||||
removeAppDialog?.cancel()
|
||||
removeAppDialog = dialogsFactory.createRemoveAppDialog(context, appName) {
|
||||
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()
|
||||
}
|
||||
|
||||
allStructures = controlsController.get().getFavorites()
|
||||
selectedItem = getPreferredSelectedItem(allStructures)
|
||||
reload(parent)
|
||||
}.apply { show() }
|
||||
}
|
||||
|
||||
private fun startTargetedActivity(si: StructureInfo, klazz: Class<*>) {
|
||||
val i = Intent(activityContext, klazz)
|
||||
putIntentExtras(i, si)
|
||||
@@ -433,7 +462,10 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
val currentApps = panelsAndStructures.map { it.componentName }.toSet()
|
||||
val allApps = controlsListingController.get()
|
||||
.getCurrentServices().map { it.componentName }.toSet()
|
||||
createMenu(extraApps = (allApps - currentApps).isNotEmpty())
|
||||
createMenu(
|
||||
selectionItem = selectionItem,
|
||||
extraApps = (allApps - currentApps).isNotEmpty(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createPanelView(componentName: ComponentName) {
|
||||
@@ -472,7 +504,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMenu(extraApps: Boolean) {
|
||||
private fun createMenu(selectionItem: SelectionItem, extraApps: Boolean) {
|
||||
val isPanel = selectedItem is SelectedItem.PanelItem
|
||||
val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure
|
||||
?: EMPTY_STRUCTURE
|
||||
@@ -490,6 +522,13 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
ADD_APP_ID
|
||||
))
|
||||
}
|
||||
if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED) &&
|
||||
controlsController.get().canRemoveFavorites(selectedItem.componentName)) {
|
||||
add(OverflowMenuAdapter.MenuItem(
|
||||
context.getText(R.string.controls_menu_remove),
|
||||
REMOVE_APP_ID,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
add(OverflowMenuAdapter.MenuItem(
|
||||
context.getText(R.string.controls_menu_add),
|
||||
@@ -529,6 +568,9 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
ADD_APP_ID -> startProviderSelectorActivity()
|
||||
ADD_CONTROLS_ID -> startFavoritingActivity(selectedStructure)
|
||||
EDIT_CONTROLS_ID -> startEditingActivity(selectedStructure)
|
||||
REMOVE_APP_ID -> startRemovingApp(
|
||||
selectedStructure.componentName, selectionItem.appName
|
||||
)
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
@@ -732,6 +774,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
it.value.dismiss()
|
||||
}
|
||||
controlActionCoordinator.closeDialogs()
|
||||
removeAppDialog?.cancel()
|
||||
}
|
||||
|
||||
override fun hide(parent: ViewGroup) {
|
||||
|
||||
@@ -39,11 +39,9 @@ import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.settings.UserFileManager
|
||||
import com.android.systemui.settings.UserTracker
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.io.File
|
||||
import java.util.Optional
|
||||
import java.util.function.Consumer
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
@@ -58,7 +56,9 @@ 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
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.never
|
||||
@@ -66,9 +66,10 @@ 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.Mockito.clearInvocations
|
||||
import org.mockito.MockitoAnnotations
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.function.Consumer
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@@ -146,6 +147,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
whenever(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf())
|
||||
`when`(userTracker.userHandle).thenReturn(UserHandle.of(user))
|
||||
|
||||
delayableExecutor = FakeExecutor(FakeSystemClock())
|
||||
@@ -945,6 +947,28 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
controller.bindComponentForPanel(TEST_COMPONENT)
|
||||
verify(bindingController).bindServiceForPanel(TEST_COMPONENT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRemoveFavoriteRemovesFavorite() {
|
||||
val componentName = ComponentName(context, "test.Cls")
|
||||
controller.addFavorite(
|
||||
componentName,
|
||||
"test structure",
|
||||
ControlInfo(
|
||||
controlId = "testId",
|
||||
controlTitle = "Test Control",
|
||||
controlSubtitle = "test control subtitle",
|
||||
deviceType = DeviceTypes.TYPE_LIGHT,
|
||||
),
|
||||
)
|
||||
|
||||
controller.removeFavorites(componentName)
|
||||
delayableExecutor.runAllReady()
|
||||
|
||||
verify(authorizedPanelsRepository)
|
||||
.removeAuthorizedPanels(eq(setOf(componentName.packageName)))
|
||||
assertThat(controller.getFavorites()).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private class DidRunRunnable() : Runnable {
|
||||
|
||||
@@ -115,6 +115,18 @@ class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() {
|
||||
assertThat(sharedPrefs.getStringSet(KEY, null)).containsExactly(TEST_PACKAGE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRemoveAuthorizedPackageRemovesIt() {
|
||||
val sharedPrefs = FakeSharedPreferences()
|
||||
val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
|
||||
val repository = createRepository(fileManager)
|
||||
repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
|
||||
|
||||
repository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
|
||||
|
||||
assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty()
|
||||
}
|
||||
|
||||
private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl {
|
||||
return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.ui
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.util.FakeSystemUIDialogController
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mockito.eq
|
||||
import org.mockito.Mockito.verify
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class ControlsDialogsFactoryTest : SysuiTestCase() {
|
||||
|
||||
private companion object {
|
||||
const val APP_NAME = "Test App"
|
||||
}
|
||||
|
||||
private val fakeDialogController = FakeSystemUIDialogController()
|
||||
|
||||
private lateinit var underTest: ControlsDialogsFactory
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
underTest = ControlsDialogsFactory { fakeDialogController.dialog }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreatesRemoveAppDialog() {
|
||||
val dialog = underTest.createRemoveAppDialog(context, APP_NAME) {}
|
||||
|
||||
verify(dialog)
|
||||
.setTitle(
|
||||
eq(context.getString(R.string.controls_panel_remove_app_authorization, APP_NAME))
|
||||
)
|
||||
verify(dialog).setCanceledOnTouchOutside(eq(true))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPositiveClickRemoveAppDialogWorks() {
|
||||
var dialogResult: Boolean? = null
|
||||
underTest.createRemoveAppDialog(context, APP_NAME) { dialogResult = it }
|
||||
|
||||
fakeDialogController.clickPositive()
|
||||
|
||||
assertThat(dialogResult).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNeutralClickRemoveAppDialogWorks() {
|
||||
var dialogResult: Boolean? = null
|
||||
underTest.createRemoveAppDialog(context, APP_NAME) { dialogResult = it }
|
||||
|
||||
fakeDialogController.clickNeutral()
|
||||
|
||||
assertThat(dialogResult).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCancelRemoveAppDialogWorks() {
|
||||
var dialogResult: Boolean? = null
|
||||
underTest.createRemoveAppDialog(context, APP_NAME) { dialogResult = it }
|
||||
|
||||
fakeDialogController.cancel()
|
||||
|
||||
assertThat(dialogResult).isFalse()
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@ 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
|
||||
import com.android.systemui.util.mockito.argumentCaptor
|
||||
@@ -63,21 +64,20 @@ 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 dagger.Lazy
|
||||
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)
|
||||
@@ -98,13 +98,15 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
||||
@Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
|
||||
@Mock lateinit var featureFlags: FeatureFlags
|
||||
@Mock lateinit var packageManager: PackageManager
|
||||
val sharedPreferences = FakeSharedPreferences()
|
||||
lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
|
||||
|
||||
var uiExecutor = FakeExecutor(FakeSystemClock())
|
||||
var bgExecutor = FakeExecutor(FakeSystemClock())
|
||||
lateinit var underTest: ControlsUiControllerImpl
|
||||
lateinit var parent: FrameLayout
|
||||
private val sharedPreferences = FakeSharedPreferences()
|
||||
private val fakeDialogController = FakeSystemUIDialogController()
|
||||
private val uiExecutor = FakeExecutor(FakeSystemClock())
|
||||
private val bgExecutor = FakeExecutor(FakeSystemClock())
|
||||
|
||||
private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
|
||||
private lateinit var parent: FrameLayout
|
||||
private lateinit var underTest: ControlsUiControllerImpl
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
@@ -125,12 +127,12 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
||||
|
||||
underTest =
|
||||
ControlsUiControllerImpl(
|
||||
Lazy { controlsController },
|
||||
{ controlsController },
|
||||
context,
|
||||
packageManager,
|
||||
uiExecutor,
|
||||
bgExecutor,
|
||||
Lazy { controlsListingController },
|
||||
{ controlsListingController },
|
||||
controlActionCoordinator,
|
||||
activityStarter,
|
||||
iconCache,
|
||||
@@ -142,7 +144,8 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
||||
controlsSettingsRepository,
|
||||
authorizedPanelsRepository,
|
||||
featureFlags,
|
||||
dumpManager
|
||||
ControlsDialogsFactory { fakeDialogController.dialog },
|
||||
dumpManager,
|
||||
)
|
||||
`when`(
|
||||
userFileManager.getSharedPreferences(
|
||||
@@ -410,8 +413,45 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
||||
verify(controlsListingController, never()).removeCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRemovingAppsRemovesFavorite() {
|
||||
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()
|
||||
underTest.show(parent, {}, context)
|
||||
underTest.startRemovingApp(componentName, "Test App")
|
||||
|
||||
fakeDialogController.clickPositive()
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHideCancelsTheRemoveAppDialog() {
|
||||
val componentName = ComponentName(context, "cls")
|
||||
underTest.show(parent, {}, context)
|
||||
underTest.startRemovingApp(componentName, "Test App")
|
||||
|
||||
underTest.hide(parent)
|
||||
|
||||
verify(fakeDialogController.dialog).cancel()
|
||||
}
|
||||
|
||||
private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
|
||||
val activity = ComponentName("pkg", "activity")
|
||||
val activity = ComponentName(context, "activity")
|
||||
sharedPreferences
|
||||
.edit()
|
||||
.putString("controls_component", panel.componentName.flattenToString())
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import android.content.DialogInterface
|
||||
import com.android.systemui.statusbar.phone.SystemUIDialog
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Mockito.doAnswer
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.stubbing.Stubber
|
||||
|
||||
class FakeSystemUIDialogController {
|
||||
|
||||
val dialog: SystemUIDialog = mock()
|
||||
|
||||
private val clickListeners: MutableMap<Int, DialogInterface.OnClickListener> = mutableMapOf()
|
||||
|
||||
init {
|
||||
saveListener(DialogInterface.BUTTON_POSITIVE)
|
||||
.whenever(dialog)
|
||||
.setPositiveButton(any(), any())
|
||||
saveListener(DialogInterface.BUTTON_POSITIVE)
|
||||
.whenever(dialog)
|
||||
.setPositiveButton(any(), any(), any())
|
||||
|
||||
saveListener(DialogInterface.BUTTON_NEGATIVE)
|
||||
.whenever(dialog)
|
||||
.setNegativeButton(any(), any())
|
||||
saveListener(DialogInterface.BUTTON_NEGATIVE)
|
||||
.whenever(dialog)
|
||||
.setNegativeButton(any(), any(), any())
|
||||
|
||||
saveListener(DialogInterface.BUTTON_NEUTRAL).whenever(dialog).setNeutralButton(any(), any())
|
||||
saveListener(DialogInterface.BUTTON_NEUTRAL)
|
||||
.whenever(dialog)
|
||||
.setNeutralButton(any(), any(), any())
|
||||
}
|
||||
|
||||
fun clickNegative() {
|
||||
performClick(DialogInterface.BUTTON_NEGATIVE, "This dialog has no negative button")
|
||||
}
|
||||
|
||||
fun clickPositive() {
|
||||
performClick(DialogInterface.BUTTON_POSITIVE, "This dialog has no positive button")
|
||||
}
|
||||
|
||||
fun clickNeutral() {
|
||||
performClick(DialogInterface.BUTTON_NEUTRAL, "This dialog has no neutral button")
|
||||
}
|
||||
|
||||
fun cancel() {
|
||||
val captor = ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
|
||||
verify(dialog).setOnCancelListener(captor.capture())
|
||||
captor.value.onCancel(dialog)
|
||||
}
|
||||
|
||||
private fun performClick(which: Int, errorMessage: String) {
|
||||
clickListeners
|
||||
.getOrElse(which) { throw IllegalAccessException(errorMessage) }
|
||||
.onClick(dialog, which)
|
||||
}
|
||||
|
||||
private fun saveListener(which: Int): Stubber = doAnswer {
|
||||
val listener = it.getArgument<DialogInterface.OnClickListener>(1)
|
||||
clickListeners[which] = listener
|
||||
Unit
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user