Merge "Add remove app flow to the home controls panel" into tm-qpr-dev am: 686f8e9d54
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21408653 Change-Id: I8562b767f0eb87a0446993505748acab5218a634 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -2262,6 +2262,10 @@
|
|||||||
panel (embedded activity) instead of controls rendered by SystemUI [CHAR LIMIT=NONE] -->
|
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>
|
<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] -->
|
<!-- a11y state description for a control that is currently favorited [CHAR LIMIT=NONE] -->
|
||||||
<string name="accessibility_control_favorite">Favorited</string>
|
<string name="accessibility_control_favorite">Favorited</string>
|
||||||
<!-- a11y state description for a control that is currently favorited with its position [CHAR LIMIT=NONE] -->
|
<!-- 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>
|
<string name="controls_dialog_title">Add to device controls</string>
|
||||||
<!-- Controls dialog add to favorites [CHAR LIMIT=40] -->
|
<!-- Controls dialog add to favorites [CHAR LIMIT=40] -->
|
||||||
<string name="controls_dialog_ok">Add</string>
|
<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] -->
|
<!-- 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>
|
<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] -->
|
<!-- 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>
|
<string name="controls_menu_edit">Edit controls</string>
|
||||||
<!-- Controls menu, add another app [CHAR LIMIT=30] -->
|
<!-- Controls menu, add another app [CHAR LIMIT=30] -->
|
||||||
<string name="controls_menu_add_another_app">Add app</string>
|
<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] -->
|
<!-- Title for the media output dialog with media related devices [CHAR LIMIT=50] -->
|
||||||
<string name="media_output_dialog_add_output">Add outputs</string>
|
<string name="media_output_dialog_add_output">Add outputs</string>
|
||||||
|
|||||||
@@ -165,6 +165,19 @@ interface ControlsController : UserAwareController {
|
|||||||
controlInfo: ControlInfo
|
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.
|
* 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) {
|
override fun replaceFavoritesForStructure(structureInfo: StructureInfo) {
|
||||||
if (!confirmAvailability()) return
|
if (!confirmAvailability()) return
|
||||||
executor.execute {
|
executor.execute {
|
||||||
@@ -655,10 +670,11 @@ private object Favorites {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeStructures(componentName: ComponentName) {
|
fun removeStructures(componentName: ComponentName): Boolean {
|
||||||
val newFavMap = favMap.toMutableMap()
|
val newFavMap = favMap.toMutableMap()
|
||||||
newFavMap.remove(componentName)
|
val removed = newFavMap.remove(componentName) != null
|
||||||
favMap = newFavMap
|
favMap = newFavMap
|
||||||
|
return removed
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addFavorite(
|
fun addFavorite(
|
||||||
|
|||||||
@@ -26,6 +26,14 @@ interface AuthorizedPanelsRepository {
|
|||||||
/** A set of package names that the user has previously authorized to show panels. */
|
/** A set of package names that the user has previously authorized to show panels. */
|
||||||
fun getAuthorizedPanels(): Set<String>
|
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. */
|
/** Adds [packageNames] to the set of packages that the user has authorized to show panels. */
|
||||||
fun addAuthorizedPanels(packageNames: Set<String>)
|
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())
|
return getAuthorizedPanelsInternal(instantiateSharedPrefs())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getPreferredPackages(): Set<String> =
|
||||||
|
context.resources.getStringArray(R.array.config_controlsPreferredPackages).toSet()
|
||||||
|
|
||||||
override fun addAuthorizedPanels(packageNames: Set<String>) {
|
override fun addAuthorizedPanels(packageNames: Set<String>) {
|
||||||
addAuthorizedPanelsInternal(instantiateSharedPrefs(), packageNames)
|
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> {
|
private fun getAuthorizedPanelsInternal(sharedPreferences: SharedPreferences): Set<String> {
|
||||||
return sharedPreferences.getStringSet(KEY, emptySet())!!
|
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 we've never run this (i.e., the key doesn't exist), add the default packages
|
||||||
if (sharedPref.getStringSet(KEY, null) == null) {
|
if (sharedPref.getStringSet(KEY, null) == null) {
|
||||||
sharedPref
|
sharedPref.edit().putStringSet(KEY, getPreferredPackages()).apply()
|
||||||
.edit()
|
|
||||||
.putStringSet(
|
|
||||||
KEY,
|
|
||||||
context.resources
|
|
||||||
.getStringArray(R.array.config_controlsPreferredPackages)
|
|
||||||
.toSet()
|
|
||||||
)
|
|
||||||
.apply()
|
|
||||||
}
|
}
|
||||||
return sharedPref
|
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.animation.ObjectAnimator
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.ActivityOptions
|
import android.app.ActivityOptions
|
||||||
|
import android.app.Dialog
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -52,7 +53,6 @@ import com.android.systemui.Dumpable
|
|||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.controls.ControlsMetricsLogger
|
import com.android.systemui.controls.ControlsMetricsLogger
|
||||||
import com.android.systemui.controls.ControlsServiceInfo
|
import com.android.systemui.controls.ControlsServiceInfo
|
||||||
import com.android.systemui.controls.settings.ControlsSettingsRepository
|
|
||||||
import com.android.systemui.controls.CustomIconCache
|
import com.android.systemui.controls.CustomIconCache
|
||||||
import com.android.systemui.controls.controller.ControlsController
|
import com.android.systemui.controls.controller.ControlsController
|
||||||
import com.android.systemui.controls.controller.StructureInfo
|
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.ControlsListingController
|
||||||
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
|
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
|
||||||
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
|
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.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Background
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
@@ -83,7 +84,7 @@ import com.android.wm.shell.TaskViewFactory
|
|||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
import java.util.Optional
|
import java.util.*
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -108,6 +109,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
private val controlsSettingsRepository: ControlsSettingsRepository,
|
private val controlsSettingsRepository: ControlsSettingsRepository,
|
||||||
private val authorizedPanelsRepository: AuthorizedPanelsRepository,
|
private val authorizedPanelsRepository: AuthorizedPanelsRepository,
|
||||||
private val featureFlags: FeatureFlags,
|
private val featureFlags: FeatureFlags,
|
||||||
|
private val dialogsFactory: ControlsDialogsFactory,
|
||||||
dumpManager: DumpManager
|
dumpManager: DumpManager
|
||||||
) : ControlsUiController, Dumpable {
|
) : ControlsUiController, Dumpable {
|
||||||
|
|
||||||
@@ -122,6 +124,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
private const val ADD_CONTROLS_ID = 1L
|
private const val ADD_CONTROLS_ID = 1L
|
||||||
private const val ADD_APP_ID = 2L
|
private const val ADD_APP_ID = 2L
|
||||||
private const val EDIT_CONTROLS_ID = 3L
|
private const val EDIT_CONTROLS_ID = 3L
|
||||||
|
private const val REMOVE_APP_ID = 4L
|
||||||
}
|
}
|
||||||
|
|
||||||
private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION
|
private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION
|
||||||
@@ -151,6 +154,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
|
|
||||||
private var openAppIntent: Intent? = null
|
private var openAppIntent: Intent? = null
|
||||||
private var overflowMenuAdapter: BaseAdapter? = null
|
private var overflowMenuAdapter: BaseAdapter? = null
|
||||||
|
private var removeAppDialog: Dialog? = null
|
||||||
|
|
||||||
private val onSeedingComplete = Consumer<Boolean> {
|
private val onSeedingComplete = Consumer<Boolean> {
|
||||||
accepted ->
|
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<*>) {
|
private fun startTargetedActivity(si: StructureInfo, klazz: Class<*>) {
|
||||||
val i = Intent(activityContext, klazz)
|
val i = Intent(activityContext, klazz)
|
||||||
putIntentExtras(i, si)
|
putIntentExtras(i, si)
|
||||||
@@ -433,7 +462,10 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
val currentApps = panelsAndStructures.map { it.componentName }.toSet()
|
val currentApps = panelsAndStructures.map { it.componentName }.toSet()
|
||||||
val allApps = controlsListingController.get()
|
val allApps = controlsListingController.get()
|
||||||
.getCurrentServices().map { it.componentName }.toSet()
|
.getCurrentServices().map { it.componentName }.toSet()
|
||||||
createMenu(extraApps = (allApps - currentApps).isNotEmpty())
|
createMenu(
|
||||||
|
selectionItem = selectionItem,
|
||||||
|
extraApps = (allApps - currentApps).isNotEmpty(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createPanelView(componentName: ComponentName) {
|
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 isPanel = selectedItem is SelectedItem.PanelItem
|
||||||
val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure
|
val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure
|
||||||
?: EMPTY_STRUCTURE
|
?: EMPTY_STRUCTURE
|
||||||
@@ -490,6 +522,13 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
ADD_APP_ID
|
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 {
|
} else {
|
||||||
add(OverflowMenuAdapter.MenuItem(
|
add(OverflowMenuAdapter.MenuItem(
|
||||||
context.getText(R.string.controls_menu_add),
|
context.getText(R.string.controls_menu_add),
|
||||||
@@ -529,6 +568,9 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
ADD_APP_ID -> startProviderSelectorActivity()
|
ADD_APP_ID -> startProviderSelectorActivity()
|
||||||
ADD_CONTROLS_ID -> startFavoritingActivity(selectedStructure)
|
ADD_CONTROLS_ID -> startFavoritingActivity(selectedStructure)
|
||||||
EDIT_CONTROLS_ID -> startEditingActivity(selectedStructure)
|
EDIT_CONTROLS_ID -> startEditingActivity(selectedStructure)
|
||||||
|
REMOVE_APP_ID -> startRemovingApp(
|
||||||
|
selectedStructure.componentName, selectionItem.appName
|
||||||
|
)
|
||||||
}
|
}
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
@@ -732,6 +774,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
it.value.dismiss()
|
it.value.dismiss()
|
||||||
}
|
}
|
||||||
controlActionCoordinator.closeDialogs()
|
controlActionCoordinator.closeDialogs()
|
||||||
|
removeAppDialog?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hide(parent: ViewGroup) {
|
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.UserFileManager
|
||||||
import com.android.systemui.settings.UserTracker
|
import com.android.systemui.settings.UserTracker
|
||||||
import com.android.systemui.util.concurrency.FakeExecutor
|
import com.android.systemui.util.concurrency.FakeExecutor
|
||||||
|
import com.android.systemui.util.mockito.whenever
|
||||||
import com.android.systemui.util.time.FakeSystemClock
|
import com.android.systemui.util.time.FakeSystemClock
|
||||||
import com.google.common.truth.Truth.assertThat
|
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.After
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertFalse
|
import org.junit.Assert.assertFalse
|
||||||
@@ -58,7 +56,9 @@ import org.mockito.ArgumentMatchers.anyString
|
|||||||
import org.mockito.Captor
|
import org.mockito.Captor
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
|
import org.mockito.Mockito.`when`
|
||||||
import org.mockito.Mockito.anyInt
|
import org.mockito.Mockito.anyInt
|
||||||
|
import org.mockito.Mockito.clearInvocations
|
||||||
import org.mockito.Mockito.inOrder
|
import org.mockito.Mockito.inOrder
|
||||||
import org.mockito.Mockito.mock
|
import org.mockito.Mockito.mock
|
||||||
import org.mockito.Mockito.never
|
import org.mockito.Mockito.never
|
||||||
@@ -66,9 +66,10 @@ import org.mockito.Mockito.reset
|
|||||||
import org.mockito.Mockito.times
|
import org.mockito.Mockito.times
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.Mockito.verifyNoMoreInteractions
|
import org.mockito.Mockito.verifyNoMoreInteractions
|
||||||
import org.mockito.Mockito.`when`
|
|
||||||
import org.mockito.Mockito.clearInvocations
|
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(AndroidTestingRunner::class)
|
@RunWith(AndroidTestingRunner::class)
|
||||||
@@ -146,6 +147,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
|||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
|
whenever(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf())
|
||||||
`when`(userTracker.userHandle).thenReturn(UserHandle.of(user))
|
`when`(userTracker.userHandle).thenReturn(UserHandle.of(user))
|
||||||
|
|
||||||
delayableExecutor = FakeExecutor(FakeSystemClock())
|
delayableExecutor = FakeExecutor(FakeSystemClock())
|
||||||
@@ -945,6 +947,28 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
|||||||
controller.bindComponentForPanel(TEST_COMPONENT)
|
controller.bindComponentForPanel(TEST_COMPONENT)
|
||||||
verify(bindingController).bindServiceForPanel(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 {
|
private class DidRunRunnable() : Runnable {
|
||||||
|
|||||||
@@ -115,6 +115,18 @@ class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() {
|
|||||||
assertThat(sharedPrefs.getStringSet(KEY, null)).containsExactly(TEST_PACKAGE)
|
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 {
|
private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl {
|
||||||
return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker)
|
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.DeviceControlsControllerImpl
|
||||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||||
import com.android.systemui.util.FakeSharedPreferences
|
import com.android.systemui.util.FakeSharedPreferences
|
||||||
|
import com.android.systemui.util.FakeSystemUIDialogController
|
||||||
import com.android.systemui.util.concurrency.FakeExecutor
|
import com.android.systemui.util.concurrency.FakeExecutor
|
||||||
import com.android.systemui.util.mockito.any
|
import com.android.systemui.util.mockito.any
|
||||||
import com.android.systemui.util.mockito.argumentCaptor
|
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.TaskView
|
||||||
import com.android.wm.shell.TaskViewFactory
|
import com.android.wm.shell.TaskViewFactory
|
||||||
import com.google.common.truth.Truth.assertThat
|
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.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.`when`
|
||||||
import org.mockito.Mockito.anyInt
|
import org.mockito.Mockito.anyInt
|
||||||
import org.mockito.Mockito.anyString
|
import org.mockito.Mockito.anyString
|
||||||
import org.mockito.Mockito.clearInvocations
|
import org.mockito.Mockito.clearInvocations
|
||||||
import org.mockito.Mockito.never
|
import org.mockito.Mockito.never
|
||||||
import org.mockito.Mockito.spy
|
import org.mockito.Mockito.spy
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.Mockito.`when`
|
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
import java.util.Optional
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(AndroidTestingRunner::class)
|
@RunWith(AndroidTestingRunner::class)
|
||||||
@@ -98,13 +98,15 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
|||||||
@Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
|
@Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
|
||||||
@Mock lateinit var featureFlags: FeatureFlags
|
@Mock lateinit var featureFlags: FeatureFlags
|
||||||
@Mock lateinit var packageManager: PackageManager
|
@Mock lateinit var packageManager: PackageManager
|
||||||
val sharedPreferences = FakeSharedPreferences()
|
|
||||||
lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
|
|
||||||
|
|
||||||
var uiExecutor = FakeExecutor(FakeSystemClock())
|
private val sharedPreferences = FakeSharedPreferences()
|
||||||
var bgExecutor = FakeExecutor(FakeSystemClock())
|
private val fakeDialogController = FakeSystemUIDialogController()
|
||||||
lateinit var underTest: ControlsUiControllerImpl
|
private val uiExecutor = FakeExecutor(FakeSystemClock())
|
||||||
lateinit var parent: FrameLayout
|
private val bgExecutor = FakeExecutor(FakeSystemClock())
|
||||||
|
|
||||||
|
private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
|
||||||
|
private lateinit var parent: FrameLayout
|
||||||
|
private lateinit var underTest: ControlsUiControllerImpl
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
@@ -125,12 +127,12 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
|||||||
|
|
||||||
underTest =
|
underTest =
|
||||||
ControlsUiControllerImpl(
|
ControlsUiControllerImpl(
|
||||||
Lazy { controlsController },
|
{ controlsController },
|
||||||
context,
|
context,
|
||||||
packageManager,
|
packageManager,
|
||||||
uiExecutor,
|
uiExecutor,
|
||||||
bgExecutor,
|
bgExecutor,
|
||||||
Lazy { controlsListingController },
|
{ controlsListingController },
|
||||||
controlActionCoordinator,
|
controlActionCoordinator,
|
||||||
activityStarter,
|
activityStarter,
|
||||||
iconCache,
|
iconCache,
|
||||||
@@ -142,7 +144,8 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
|||||||
controlsSettingsRepository,
|
controlsSettingsRepository,
|
||||||
authorizedPanelsRepository,
|
authorizedPanelsRepository,
|
||||||
featureFlags,
|
featureFlags,
|
||||||
dumpManager
|
ControlsDialogsFactory { fakeDialogController.dialog },
|
||||||
|
dumpManager,
|
||||||
)
|
)
|
||||||
`when`(
|
`when`(
|
||||||
userFileManager.getSharedPreferences(
|
userFileManager.getSharedPreferences(
|
||||||
@@ -410,8 +413,45 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
|||||||
verify(controlsListingController, never()).removeCallback(any())
|
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 {
|
private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
|
||||||
val activity = ComponentName("pkg", "activity")
|
val activity = ComponentName(context, "activity")
|
||||||
sharedPreferences
|
sharedPreferences
|
||||||
.edit()
|
.edit()
|
||||||
.putString("controls_component", panel.componentName.flattenToString())
|
.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