Merge "Add remove app flow to the home controls panel" into tm-qpr-dev

This commit is contained in:
Anton Potapov
2023-02-27 11:36:03 +00:00
committed by Android (Google) Code Review
12 changed files with 424 additions and 34 deletions

View File

@@ -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>

View File

@@ -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.
*

View File

@@ -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(

View File

@@ -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>)
}

View File

@@ -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
}

View File

@@ -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)
}
}
}

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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()
}
}

View File

@@ -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())

View File

@@ -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
}
}