summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/res/values/strings.xml8
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialogsFactory.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt89
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt68
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSystemUIDialogController.kt86
12 files changed, 424 insertions, 34 deletions
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 464ce0333fd1..48f257a7f4c9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -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>
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index 822190f21da1..3555d0a7e7fb 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -166,6 +166,19 @@ interface ControlsController : UserAwareController {
)
/**
+ * 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.
*
* Calling this method will eliminate the previous selection of favorites and replace it with a
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 278ee7079720..854790360f6a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -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(
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
index 3e672f391e81..ae9c37aa0e7b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
@@ -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>)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
index f7e43a77b573..e51e8326c0a5 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
@@ -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
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialogsFactory.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialogsFactory.kt
new file mode 100644
index 000000000000..d6cfb79101d7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialogsFactory.kt
@@ -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)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 860601bfa290..c61dad6fc075 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -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) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index e35b2a384bd0..28e80057a672 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -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 {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
index b91a3fd4b28c..7ac1953ee495 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
@@ -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)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt
new file mode 100644
index 000000000000..1e8cd4117688
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt
@@ -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()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index aa90e2a45f10..23faa99c0b9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -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())
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSystemUIDialogController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSystemUIDialogController.kt
new file mode 100644
index 000000000000..0c9ce0f145f1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSystemUIDialogController.kt
@@ -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
+ }
+}