Merge changes from topics "265180342", "265979516_cts" into tm-qpr-dev
* changes: Add new add flow for panels Flag for new flows Add a persistence mechanism for panels' packages
This commit is contained in:
committed by
Android (Google) Code Review
commit
a96800911c
@@ -2238,6 +2238,14 @@
|
|||||||
<!-- Removed control in management screen [CHAR LIMIT=20] -->
|
<!-- Removed control in management screen [CHAR LIMIT=20] -->
|
||||||
<string name="controls_removed">Removed</string>
|
<string name="controls_removed">Removed</string>
|
||||||
|
|
||||||
|
<!-- Title for the dialog presented to the user to authorize this app to display a Device
|
||||||
|
controls panel (embedded activity) instead of controls rendered by SystemUI [CHAR LIMIT=30] -->
|
||||||
|
<string name="controls_panel_authorization_title">Add <xliff:g id="appName" example="My app">%s</xliff:g>?</string>
|
||||||
|
|
||||||
|
<!-- Shows in a dialog presented to the user to authorize this app to display a Device controls
|
||||||
|
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>
|
||||||
|
|
||||||
<!-- 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] -->
|
||||||
@@ -2387,6 +2395,8 @@
|
|||||||
<string name="controls_menu_add">Add controls</string>
|
<string name="controls_menu_add">Add controls</string>
|
||||||
<!-- Controls menu, edit [CHAR_LIMIT=30] -->
|
<!-- Controls menu, edit [CHAR_LIMIT=30] -->
|
||||||
<string name="controls_menu_edit">Edit controls</string>
|
<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>
|
||||||
|
|
||||||
<!-- 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>
|
||||||
|
|||||||
@@ -188,6 +188,8 @@ interface ControlsController : UserAwareController {
|
|||||||
/** See [ControlsUiController.getPreferredSelectedItem]. */
|
/** See [ControlsUiController.getPreferredSelectedItem]. */
|
||||||
fun getPreferredSelection(): SelectedItem
|
fun getPreferredSelection(): SelectedItem
|
||||||
|
|
||||||
|
fun setPreferredSelection(selectedItem: SelectedItem)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind to a service that provides a Device Controls panel (embedded activity). This will allow
|
* Bind to a service that provides a Device Controls panel (embedded activity). This will allow
|
||||||
* the app to remain "warm", and reduce latency.
|
* the app to remain "warm", and reduce latency.
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import com.android.systemui.backup.BackupHelper
|
|||||||
import com.android.systemui.controls.ControlStatus
|
import com.android.systemui.controls.ControlStatus
|
||||||
import com.android.systemui.controls.ControlsServiceInfo
|
import com.android.systemui.controls.ControlsServiceInfo
|
||||||
import com.android.systemui.controls.management.ControlsListingController
|
import com.android.systemui.controls.management.ControlsListingController
|
||||||
|
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
|
||||||
import com.android.systemui.controls.ui.ControlsUiController
|
import com.android.systemui.controls.ui.ControlsUiController
|
||||||
import com.android.systemui.controls.ui.SelectedItem
|
import com.android.systemui.controls.ui.SelectedItem
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
@@ -61,6 +62,7 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
private val listingController: ControlsListingController,
|
private val listingController: ControlsListingController,
|
||||||
private val userFileManager: UserFileManager,
|
private val userFileManager: UserFileManager,
|
||||||
private val userTracker: UserTracker,
|
private val userTracker: UserTracker,
|
||||||
|
private val authorizedPanelsRepository: AuthorizedPanelsRepository,
|
||||||
optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
|
optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
|
||||||
dumpManager: DumpManager,
|
dumpManager: DumpManager,
|
||||||
) : Dumpable, ControlsController {
|
) : Dumpable, ControlsController {
|
||||||
@@ -249,6 +251,11 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
private fun resetFavorites() {
|
private fun resetFavorites() {
|
||||||
Favorites.clear()
|
Favorites.clear()
|
||||||
Favorites.load(persistenceWrapper.readFavorites())
|
Favorites.load(persistenceWrapper.readFavorites())
|
||||||
|
// After loading favorites, add the package names of any apps with favorites to the list
|
||||||
|
// of authorized panels. That way, if the user has previously favorited controls for an app,
|
||||||
|
// that panel will be authorized.
|
||||||
|
authorizedPanelsRepository.addAuthorizedPanels(
|
||||||
|
Favorites.getAllStructures().map { it.componentName.packageName }.toSet())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun confirmAvailability(): Boolean {
|
private fun confirmAvailability(): Boolean {
|
||||||
@@ -489,6 +496,7 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
if (!confirmAvailability()) return
|
if (!confirmAvailability()) return
|
||||||
executor.execute {
|
executor.execute {
|
||||||
if (Favorites.addFavorite(componentName, structureName, controlInfo)) {
|
if (Favorites.addFavorite(componentName, structureName, controlInfo)) {
|
||||||
|
authorizedPanelsRepository.addAuthorizedPanels(setOf(componentName.packageName))
|
||||||
persistenceWrapper.storeFavorites(Favorites.getAllStructures())
|
persistenceWrapper.storeFavorites(Favorites.getAllStructures())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -555,6 +563,10 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
return uiController.getPreferredSelectedItem(getFavorites())
|
return uiController.getPreferredSelectedItem(getFavorites())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setPreferredSelection(selectedItem: SelectedItem) {
|
||||||
|
uiController.updatePreferences(selectedItem)
|
||||||
|
}
|
||||||
|
|
||||||
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
||||||
pw.println("ControlsController state:")
|
pw.println("ControlsController state:")
|
||||||
pw.println(" Changing users: $userChanging")
|
pw.println(" Changing users: $userChanging")
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ import com.android.systemui.controls.management.ControlsListingController
|
|||||||
import com.android.systemui.controls.management.ControlsListingControllerImpl
|
import com.android.systemui.controls.management.ControlsListingControllerImpl
|
||||||
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
|
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
|
||||||
import com.android.systemui.controls.management.ControlsRequestDialog
|
import com.android.systemui.controls.management.ControlsRequestDialog
|
||||||
|
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
|
||||||
|
import com.android.systemui.controls.panels.AuthorizedPanelsRepositoryImpl
|
||||||
import com.android.systemui.controls.settings.ControlsSettingsDialogManager
|
import com.android.systemui.controls.settings.ControlsSettingsDialogManager
|
||||||
import com.android.systemui.controls.settings.ControlsSettingsDialogManagerImpl
|
import com.android.systemui.controls.settings.ControlsSettingsDialogManagerImpl
|
||||||
import com.android.systemui.controls.ui.ControlActionCoordinator
|
import com.android.systemui.controls.ui.ControlActionCoordinator
|
||||||
@@ -104,6 +106,11 @@ abstract class ControlsModule {
|
|||||||
coordinator: ControlActionCoordinatorImpl
|
coordinator: ControlActionCoordinatorImpl
|
||||||
): ControlActionCoordinator
|
): ControlActionCoordinator
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun provideAuthorizedPanelsRepository(
|
||||||
|
repository: AuthorizedPanelsRepositoryImpl
|
||||||
|
): AuthorizedPanelsRepository
|
||||||
|
|
||||||
@BindsOptionalOf
|
@BindsOptionalOf
|
||||||
abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper
|
abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper
|
||||||
|
|
||||||
|
|||||||
@@ -45,14 +45,15 @@ import java.util.concurrent.Executor
|
|||||||
* @param onAppSelected a callback to indicate that an app has been selected in the list.
|
* @param onAppSelected a callback to indicate that an app has been selected in the list.
|
||||||
*/
|
*/
|
||||||
class AppAdapter(
|
class AppAdapter(
|
||||||
backgroundExecutor: Executor,
|
backgroundExecutor: Executor,
|
||||||
uiExecutor: Executor,
|
uiExecutor: Executor,
|
||||||
lifecycle: Lifecycle,
|
lifecycle: Lifecycle,
|
||||||
controlsListingController: ControlsListingController,
|
controlsListingController: ControlsListingController,
|
||||||
private val layoutInflater: LayoutInflater,
|
private val layoutInflater: LayoutInflater,
|
||||||
private val onAppSelected: (ComponentName?) -> Unit = {},
|
private val onAppSelected: (ControlsServiceInfo) -> Unit = {},
|
||||||
private val favoritesRenderer: FavoritesRenderer,
|
private val favoritesRenderer: FavoritesRenderer,
|
||||||
private val resources: Resources
|
private val resources: Resources,
|
||||||
|
private val authorizedPanels: Set<String> = emptySet(),
|
||||||
) : RecyclerView.Adapter<AppAdapter.Holder>() {
|
) : RecyclerView.Adapter<AppAdapter.Holder>() {
|
||||||
|
|
||||||
private var listOfServices = emptyList<ControlsServiceInfo>()
|
private var listOfServices = emptyList<ControlsServiceInfo>()
|
||||||
@@ -64,8 +65,10 @@ class AppAdapter(
|
|||||||
val localeComparator = compareBy<ControlsServiceInfo, CharSequence>(collator) {
|
val localeComparator = compareBy<ControlsServiceInfo, CharSequence>(collator) {
|
||||||
it.loadLabel() ?: ""
|
it.loadLabel() ?: ""
|
||||||
}
|
}
|
||||||
listOfServices = serviceInfos.filter { it.panelActivity == null }
|
// No panel or the panel is not authorized
|
||||||
.sortedWith(localeComparator)
|
listOfServices = serviceInfos.filter {
|
||||||
|
it.panelActivity == null || it.panelActivity?.packageName !in authorizedPanels
|
||||||
|
}.sortedWith(localeComparator)
|
||||||
uiExecutor.execute(::notifyDataSetChanged)
|
uiExecutor.execute(::notifyDataSetChanged)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,8 +89,8 @@ class AppAdapter(
|
|||||||
|
|
||||||
override fun onBindViewHolder(holder: Holder, index: Int) {
|
override fun onBindViewHolder(holder: Holder, index: Int) {
|
||||||
holder.bindData(listOfServices[index])
|
holder.bindData(listOfServices[index])
|
||||||
holder.itemView.setOnClickListener {
|
holder.view.setOnClickListener {
|
||||||
onAppSelected(ComponentName.unflattenFromString(listOfServices[index].key))
|
onAppSelected(listOfServices[index])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +98,8 @@ class AppAdapter(
|
|||||||
* Holder for binding views in the [RecyclerView]-
|
* Holder for binding views in the [RecyclerView]-
|
||||||
*/
|
*/
|
||||||
class Holder(view: View, val favRenderer: FavoritesRenderer) : RecyclerView.ViewHolder(view) {
|
class Holder(view: View, val favRenderer: FavoritesRenderer) : RecyclerView.ViewHolder(view) {
|
||||||
|
val view: View = itemView
|
||||||
|
|
||||||
private val icon: ImageView = itemView.requireViewById(com.android.internal.R.id.icon)
|
private val icon: ImageView = itemView.requireViewById(com.android.internal.R.id.icon)
|
||||||
private val title: TextView = itemView.requireViewById(com.android.internal.R.id.title)
|
private val title: TextView = itemView.requireViewById(com.android.internal.R.id.title)
|
||||||
private val favorites: TextView = itemView.requireViewById(R.id.favorites)
|
private val favorites: TextView = itemView.requireViewById(R.id.favorites)
|
||||||
@@ -106,7 +111,11 @@ class AppAdapter(
|
|||||||
fun bindData(data: ControlsServiceInfo) {
|
fun bindData(data: ControlsServiceInfo) {
|
||||||
icon.setImageDrawable(data.loadIcon())
|
icon.setImageDrawable(data.loadIcon())
|
||||||
title.text = data.loadLabel()
|
title.text = data.loadLabel()
|
||||||
val text = favRenderer.renderFavoritesForComponent(data.componentName)
|
val text = if (data.panelActivity == null) {
|
||||||
|
favRenderer.renderFavoritesForComponent(data.componentName)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
favorites.text = text
|
favorites.text = text
|
||||||
favorites.visibility = if (text == null) View.GONE else View.VISIBLE
|
favorites.visibility = if (text == null) View.GONE else View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package com.android.systemui.controls.management
|
package com.android.systemui.controls.management
|
||||||
|
|
||||||
import android.app.ActivityOptions
|
import android.app.ActivityOptions
|
||||||
|
import android.app.Dialog
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -31,12 +32,15 @@ import android.widget.TextView
|
|||||||
import android.window.OnBackInvokedCallback
|
import android.window.OnBackInvokedCallback
|
||||||
import android.window.OnBackInvokedDispatcher
|
import android.window.OnBackInvokedDispatcher
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.controls.ControlsServiceInfo
|
||||||
import com.android.systemui.controls.controller.ControlsController
|
import com.android.systemui.controls.controller.ControlsController
|
||||||
|
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
|
||||||
import com.android.systemui.controls.ui.ControlsActivity
|
import com.android.systemui.controls.ui.ControlsActivity
|
||||||
import com.android.systemui.controls.ui.ControlsUiController
|
import com.android.systemui.controls.ui.SelectedItem
|
||||||
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
|
||||||
import com.android.systemui.settings.UserTracker
|
import com.android.systemui.settings.UserTracker
|
||||||
@@ -52,7 +56,8 @@ open class ControlsProviderSelectorActivity @Inject constructor(
|
|||||||
private val listingController: ControlsListingController,
|
private val listingController: ControlsListingController,
|
||||||
private val controlsController: ControlsController,
|
private val controlsController: ControlsController,
|
||||||
private val userTracker: UserTracker,
|
private val userTracker: UserTracker,
|
||||||
private val uiController: ControlsUiController
|
private val authorizedPanelsRepository: AuthorizedPanelsRepository,
|
||||||
|
private val panelConfirmationDialogFactory: PanelConfirmationDialogFactory
|
||||||
) : ComponentActivity() {
|
) : ComponentActivity() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -72,6 +77,7 @@ open class ControlsProviderSelectorActivity @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private var dialog: Dialog? = null
|
||||||
|
|
||||||
private val mOnBackInvokedCallback = OnBackInvokedCallback {
|
private val mOnBackInvokedCallback = OnBackInvokedCallback {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@@ -138,9 +144,11 @@ open class ControlsProviderSelectorActivity @Inject constructor(
|
|||||||
lifecycle,
|
lifecycle,
|
||||||
listingController,
|
listingController,
|
||||||
LayoutInflater.from(this),
|
LayoutInflater.from(this),
|
||||||
::launchFavoritingActivity,
|
::onAppSelected,
|
||||||
FavoritesRenderer(resources, controlsController::countFavoritesForComponent),
|
FavoritesRenderer(resources, controlsController::countFavoritesForComponent),
|
||||||
resources).apply {
|
resources,
|
||||||
|
authorizedPanelsRepository.getAuthorizedPanels()
|
||||||
|
).apply {
|
||||||
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||||
var hasAnimated = false
|
var hasAnimated = false
|
||||||
override fun onChanged() {
|
override fun onChanged() {
|
||||||
@@ -167,13 +175,35 @@ open class ControlsProviderSelectorActivity @Inject constructor(
|
|||||||
Log.d(TAG, "Unregistered onBackInvokedCallback")
|
Log.d(TAG, "Unregistered onBackInvokedCallback")
|
||||||
}
|
}
|
||||||
onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mOnBackInvokedCallback)
|
onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mOnBackInvokedCallback)
|
||||||
|
dialog?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onAppSelected(serviceInfo: ControlsServiceInfo) {
|
||||||
|
dialog?.cancel()
|
||||||
|
if (serviceInfo.panelActivity == null) {
|
||||||
|
launchFavoritingActivity(serviceInfo.componentName)
|
||||||
|
} else {
|
||||||
|
val appName = serviceInfo.loadLabel() ?: ""
|
||||||
|
dialog = panelConfirmationDialogFactory.createConfirmationDialog(this, appName) { ok ->
|
||||||
|
if (ok) {
|
||||||
|
authorizedPanelsRepository.addAuthorizedPanels(
|
||||||
|
setOf(serviceInfo.componentName.packageName)
|
||||||
|
)
|
||||||
|
animateExitAndFinish()
|
||||||
|
val selected = SelectedItem.PanelItem(appName, componentName)
|
||||||
|
controlsController.setPreferredSelection(selected)
|
||||||
|
openControlsOrigin()
|
||||||
|
}
|
||||||
|
dialog = null
|
||||||
|
}.also { it.show() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch the [ControlsFavoritingActivity] for the specified component.
|
* Launch the [ControlsFavoritingActivity] for the specified component.
|
||||||
* @param component a component name for a [ControlsProviderService]
|
* @param component a component name for a [ControlsProviderService]
|
||||||
*/
|
*/
|
||||||
fun launchFavoritingActivity(component: ComponentName?) {
|
private fun launchFavoritingActivity(component: ComponentName?) {
|
||||||
executor.execute {
|
executor.execute {
|
||||||
component?.let {
|
component?.let {
|
||||||
val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java)
|
val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java)
|
||||||
@@ -194,7 +224,15 @@ open class ControlsProviderSelectorActivity @Inject constructor(
|
|||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun animateExitAndFinish() {
|
private fun openControlsOrigin() {
|
||||||
|
startActivity(
|
||||||
|
Intent(applicationContext, ControlsActivity::class.java),
|
||||||
|
ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
internal open fun animateExitAndFinish() {
|
||||||
val rootView = requireViewById<ViewGroup>(R.id.controls_management_root)
|
val rootView = requireViewById<ViewGroup>(R.id.controls_management_root)
|
||||||
ControlsAnimations.exitAnimation(
|
ControlsAnimations.exitAnimation(
|
||||||
rootView,
|
rootView,
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* 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.management
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory to create dialogs for consenting to show app panels for specific apps.
|
||||||
|
*
|
||||||
|
* [internalDialogFactory] is for facilitating testing.
|
||||||
|
*/
|
||||||
|
class PanelConfirmationDialogFactory(
|
||||||
|
private val internalDialogFactory: (Context) -> SystemUIDialog
|
||||||
|
) {
|
||||||
|
@Inject constructor() : this({ SystemUIDialog(it) })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a dialog to show to the user. [response] will be true if an only if the user responds
|
||||||
|
* affirmatively.
|
||||||
|
*/
|
||||||
|
fun createConfirmationDialog(
|
||||||
|
context: Context,
|
||||||
|
appName: CharSequence,
|
||||||
|
response: Consumer<Boolean>
|
||||||
|
): Dialog {
|
||||||
|
val listener =
|
||||||
|
DialogInterface.OnClickListener { _, which ->
|
||||||
|
response.accept(which == DialogInterface.BUTTON_POSITIVE)
|
||||||
|
}
|
||||||
|
return internalDialogFactory(context).apply {
|
||||||
|
setTitle(this.context.getString(R.string.controls_panel_authorization_title, appName))
|
||||||
|
setMessage(this.context.getString(R.string.controls_panel_authorization, appName))
|
||||||
|
setCanceledOnTouchOutside(true)
|
||||||
|
setOnCancelListener { response.accept(false) }
|
||||||
|
setPositiveButton(R.string.controls_dialog_ok, listener)
|
||||||
|
setNeutralButton(R.string.cancel, listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.controls.panels
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository for keeping track of which packages the panel has authorized to show control panels
|
||||||
|
* (embedded activity).
|
||||||
|
*/
|
||||||
|
interface AuthorizedPanelsRepository {
|
||||||
|
|
||||||
|
/** A set of package names that the user has previously authorized to show panels. */
|
||||||
|
fun getAuthorizedPanels(): Set<String>
|
||||||
|
|
||||||
|
/** Adds [packageNames] to the set of packages that the user has authorized to show panels. */
|
||||||
|
fun addAuthorizedPanels(packageNames: Set<String>)
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.controls.panels
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.settings.UserFileManager
|
||||||
|
import com.android.systemui.settings.UserTracker
|
||||||
|
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AuthorizedPanelsRepositoryImpl
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
private val context: Context,
|
||||||
|
private val userFileManager: UserFileManager,
|
||||||
|
private val userTracker: UserTracker
|
||||||
|
) : AuthorizedPanelsRepository {
|
||||||
|
|
||||||
|
override fun getAuthorizedPanels(): Set<String> {
|
||||||
|
return getAuthorizedPanelsInternal(instantiateSharedPrefs())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addAuthorizedPanels(packageNames: Set<String>) {
|
||||||
|
addAuthorizedPanelsInternal(instantiateSharedPrefs(), packageNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAuthorizedPanelsInternal(sharedPreferences: SharedPreferences): Set<String> {
|
||||||
|
return sharedPreferences.getStringSet(KEY, emptySet())!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addAuthorizedPanelsInternal(
|
||||||
|
sharedPreferences: SharedPreferences,
|
||||||
|
packageNames: Set<String>
|
||||||
|
) {
|
||||||
|
val currentSet = getAuthorizedPanelsInternal(sharedPreferences)
|
||||||
|
sharedPreferences.edit().putStringSet(KEY, currentSet + packageNames).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun instantiateSharedPrefs(): SharedPreferences {
|
||||||
|
val sharedPref =
|
||||||
|
userFileManager.getSharedPreferences(
|
||||||
|
DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
|
||||||
|
Context.MODE_PRIVATE,
|
||||||
|
userTracker.userId,
|
||||||
|
)
|
||||||
|
|
||||||
|
// If we've never run this (i.e., the key doesn't exist), add the default packages
|
||||||
|
if (sharedPref.getStringSet(KEY, null) == null) {
|
||||||
|
sharedPref
|
||||||
|
.edit()
|
||||||
|
.putStringSet(
|
||||||
|
KEY,
|
||||||
|
context.resources
|
||||||
|
.getStringArray(R.array.config_controlsPreferredPackages)
|
||||||
|
.toSet()
|
||||||
|
)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
return sharedPref
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val KEY = "authorized_panels"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,6 +58,8 @@ interface ControlsUiController {
|
|||||||
* This element will be the one that appears when the user first opens the controls activity.
|
* This element will be the one that appears when the user first opens the controls activity.
|
||||||
*/
|
*/
|
||||||
fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem
|
fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem
|
||||||
|
|
||||||
|
fun updatePreferences(selectedItem: SelectedItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class SelectedItem {
|
sealed class SelectedItem {
|
||||||
|
|||||||
@@ -60,10 +60,13 @@ import com.android.systemui.controls.management.ControlsEditingActivity
|
|||||||
import com.android.systemui.controls.management.ControlsFavoritingActivity
|
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.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
|
||||||
import com.android.systemui.dump.DumpManager
|
import com.android.systemui.dump.DumpManager
|
||||||
|
import com.android.systemui.flags.FeatureFlags
|
||||||
|
import com.android.systemui.flags.Flags
|
||||||
import com.android.systemui.globalactions.GlobalActionsPopupMenu
|
import com.android.systemui.globalactions.GlobalActionsPopupMenu
|
||||||
import com.android.systemui.plugins.ActivityStarter
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
import com.android.systemui.settings.UserFileManager
|
import com.android.systemui.settings.UserFileManager
|
||||||
@@ -99,6 +102,8 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
private val userTracker: UserTracker,
|
private val userTracker: UserTracker,
|
||||||
private val taskViewFactory: Optional<TaskViewFactory>,
|
private val taskViewFactory: Optional<TaskViewFactory>,
|
||||||
private val controlsSettingsRepository: ControlsSettingsRepository,
|
private val controlsSettingsRepository: ControlsSettingsRepository,
|
||||||
|
private val authorizedPanelsRepository: AuthorizedPanelsRepository,
|
||||||
|
private val featureFlags: FeatureFlags,
|
||||||
dumpManager: DumpManager
|
dumpManager: DumpManager
|
||||||
) : ControlsUiController, Dumpable {
|
) : ControlsUiController, Dumpable {
|
||||||
|
|
||||||
@@ -160,6 +165,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
): ControlsListingController.ControlsListingCallback {
|
): ControlsListingController.ControlsListingCallback {
|
||||||
return object : ControlsListingController.ControlsListingCallback {
|
return object : ControlsListingController.ControlsListingCallback {
|
||||||
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
|
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
|
||||||
|
val authorizedPanels = authorizedPanelsRepository.getAuthorizedPanels()
|
||||||
val lastItems = serviceInfos.map {
|
val lastItems = serviceInfos.map {
|
||||||
val uid = it.serviceInfo.applicationInfo.uid
|
val uid = it.serviceInfo.applicationInfo.uid
|
||||||
|
|
||||||
@@ -169,7 +175,11 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
it.loadIcon(),
|
it.loadIcon(),
|
||||||
it.componentName,
|
it.componentName,
|
||||||
uid,
|
uid,
|
||||||
it.panelActivity
|
if (it.componentName.packageName in authorizedPanels) {
|
||||||
|
it.panelActivity
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
uiExecutor.execute {
|
uiExecutor.execute {
|
||||||
@@ -417,14 +427,20 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
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
|
||||||
|
val newFlows = featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
|
||||||
|
val addControlsId = if (newFlows || isPanel) {
|
||||||
|
R.string.controls_menu_add_another_app
|
||||||
|
} else {
|
||||||
|
R.string.controls_menu_add
|
||||||
|
}
|
||||||
|
|
||||||
val items = if (isPanel) {
|
val items = if (isPanel) {
|
||||||
arrayOf(
|
arrayOf(
|
||||||
context.resources.getString(R.string.controls_menu_add),
|
context.resources.getString(addControlsId),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
arrayOf(
|
arrayOf(
|
||||||
context.resources.getString(R.string.controls_menu_add),
|
context.resources.getString(addControlsId),
|
||||||
context.resources.getString(R.string.controls_menu_edit)
|
context.resources.getString(R.string.controls_menu_edit)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -449,7 +465,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
when (pos) {
|
when (pos) {
|
||||||
// 0: Add Control
|
// 0: Add Control
|
||||||
0 -> {
|
0 -> {
|
||||||
if (isPanel) {
|
if (isPanel || newFlows) {
|
||||||
startProviderSelectorActivity()
|
startProviderSelectorActivity()
|
||||||
} else {
|
} else {
|
||||||
startFavoritingActivity(selectedStructure)
|
startFavoritingActivity(selectedStructure)
|
||||||
@@ -610,11 +626,11 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePreferences(si: SelectedItem) {
|
override fun updatePreferences(selectedItem: SelectedItem) {
|
||||||
sharedPreferences.edit()
|
sharedPreferences.edit()
|
||||||
.putString(PREF_COMPONENT, si.componentName.flattenToString())
|
.putString(PREF_COMPONENT, selectedItem.componentName.flattenToString())
|
||||||
.putString(PREF_STRUCTURE_OR_APP_NAME, si.name.toString())
|
.putString(PREF_STRUCTURE_OR_APP_NAME, selectedItem.name.toString())
|
||||||
.putBoolean(PREF_IS_PANEL, si is SelectedItem.PanelItem)
|
.putBoolean(PREF_IS_PANEL, selectedItem is SelectedItem.PanelItem)
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -530,6 +530,10 @@ object Flags {
|
|||||||
val APP_PANELS_ALL_APPS_ALLOWED =
|
val APP_PANELS_ALL_APPS_ALLOWED =
|
||||||
unreleasedFlag(2001, "app_panels_all_apps_allowed", teamfood = true)
|
unreleasedFlag(2001, "app_panels_all_apps_allowed", teamfood = true)
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val CONTROLS_MANAGEMENT_NEW_FLOWS =
|
||||||
|
unreleasedFlag(2002, "controls_management_new_flows", teamfood = true)
|
||||||
|
|
||||||
// 2100 - Falsing Manager
|
// 2100 - Falsing Manager
|
||||||
@JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")
|
@JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import com.android.systemui.backup.BackupHelper
|
|||||||
import com.android.systemui.controls.ControlStatus
|
import com.android.systemui.controls.ControlStatus
|
||||||
import com.android.systemui.controls.ControlsServiceInfo
|
import com.android.systemui.controls.ControlsServiceInfo
|
||||||
import com.android.systemui.controls.management.ControlsListingController
|
import com.android.systemui.controls.management.ControlsListingController
|
||||||
|
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
|
||||||
import com.android.systemui.controls.ui.ControlsUiController
|
import com.android.systemui.controls.ui.ControlsUiController
|
||||||
import com.android.systemui.dump.DumpManager
|
import com.android.systemui.dump.DumpManager
|
||||||
import com.android.systemui.settings.UserFileManager
|
import com.android.systemui.settings.UserFileManager
|
||||||
@@ -66,6 +67,7 @@ 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.`when`
|
||||||
|
import org.mockito.Mockito.clearInvocations
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@@ -88,6 +90,8 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
|||||||
private lateinit var userTracker: UserTracker
|
private lateinit var userTracker: UserTracker
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var userFileManager: UserFileManager
|
private lateinit var userFileManager: UserFileManager
|
||||||
|
@Mock
|
||||||
|
private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
|
||||||
|
|
||||||
@Captor
|
@Captor
|
||||||
private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo>
|
private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo>
|
||||||
@@ -168,6 +172,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
|||||||
listingController,
|
listingController,
|
||||||
userFileManager,
|
userFileManager,
|
||||||
userTracker,
|
userTracker,
|
||||||
|
authorizedPanelsRepository,
|
||||||
Optional.of(persistenceWrapper),
|
Optional.of(persistenceWrapper),
|
||||||
mock(DumpManager::class.java)
|
mock(DumpManager::class.java)
|
||||||
)
|
)
|
||||||
@@ -224,12 +229,33 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
|||||||
listingController,
|
listingController,
|
||||||
userFileManager,
|
userFileManager,
|
||||||
userTracker,
|
userTracker,
|
||||||
|
authorizedPanelsRepository,
|
||||||
Optional.of(persistenceWrapper),
|
Optional.of(persistenceWrapper),
|
||||||
mock(DumpManager::class.java)
|
mock(DumpManager::class.java)
|
||||||
)
|
)
|
||||||
assertEquals(listOf(TEST_STRUCTURE_INFO), controller_other.getFavorites())
|
assertEquals(listOf(TEST_STRUCTURE_INFO), controller_other.getFavorites())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAddAuthorizedPackagesFromSavedFavoritesOnStart() {
|
||||||
|
clearInvocations(authorizedPanelsRepository)
|
||||||
|
`when`(persistenceWrapper.readFavorites()).thenReturn(listOf(TEST_STRUCTURE_INFO))
|
||||||
|
ControlsControllerImpl(
|
||||||
|
mContext,
|
||||||
|
delayableExecutor,
|
||||||
|
uiController,
|
||||||
|
bindingController,
|
||||||
|
listingController,
|
||||||
|
userFileManager,
|
||||||
|
userTracker,
|
||||||
|
authorizedPanelsRepository,
|
||||||
|
Optional.of(persistenceWrapper),
|
||||||
|
mock(DumpManager::class.java)
|
||||||
|
)
|
||||||
|
verify(authorizedPanelsRepository)
|
||||||
|
.addAuthorizedPanels(setOf(TEST_STRUCTURE_INFO.componentName.packageName))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testOnActionResponse() {
|
fun testOnActionResponse() {
|
||||||
controller.onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID, ControlAction.RESPONSE_OK)
|
controller.onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID, ControlAction.RESPONSE_OK)
|
||||||
|
|||||||
@@ -21,12 +21,15 @@ import android.content.res.Resources
|
|||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import android.testing.TestableLooper
|
import android.testing.TestableLooper
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.settingslib.core.lifecycle.Lifecycle
|
import com.android.settingslib.core.lifecycle.Lifecycle
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.controls.ControlsServiceInfo
|
import com.android.systemui.controls.ControlsServiceInfo
|
||||||
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.capture
|
||||||
import com.android.systemui.util.mockito.mock
|
import com.android.systemui.util.mockito.mock
|
||||||
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
|
||||||
@@ -36,8 +39,10 @@ import org.junit.runner.RunWith
|
|||||||
import org.mockito.ArgumentCaptor
|
import org.mockito.ArgumentCaptor
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.`when`
|
import org.mockito.Mockito.`when`
|
||||||
|
import org.mockito.Mockito.clearInvocations
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
import java.text.Collator
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(AndroidTestingRunner::class)
|
@RunWith(AndroidTestingRunner::class)
|
||||||
@@ -49,25 +54,18 @@ class AppAdapterTest : SysuiTestCase() {
|
|||||||
@Mock lateinit var lifecycle: Lifecycle
|
@Mock lateinit var lifecycle: Lifecycle
|
||||||
@Mock lateinit var controlsListingController: ControlsListingController
|
@Mock lateinit var controlsListingController: ControlsListingController
|
||||||
@Mock lateinit var layoutInflater: LayoutInflater
|
@Mock lateinit var layoutInflater: LayoutInflater
|
||||||
@Mock lateinit var onAppSelected: (ComponentName?) -> Unit
|
@Mock lateinit var onAppSelected: (ControlsServiceInfo) -> Unit
|
||||||
@Mock lateinit var favoritesRenderer: FavoritesRenderer
|
@Mock lateinit var favoritesRenderer: FavoritesRenderer
|
||||||
val resources: Resources = context.resources
|
val resources: Resources = context.resources
|
||||||
lateinit var adapter: AppAdapter
|
lateinit var adapter: AppAdapter
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
adapter = AppAdapter(backgroundExecutor,
|
|
||||||
uiExecutor,
|
|
||||||
lifecycle,
|
|
||||||
controlsListingController,
|
|
||||||
layoutInflater,
|
|
||||||
onAppSelected,
|
|
||||||
favoritesRenderer,
|
|
||||||
resources)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testOnServicesUpdated_nullLoadLabel() {
|
fun testOnServicesUpdated_nullLoadLabel() {
|
||||||
|
adapter = createAdapterWithAuthorizedPanels(emptySet())
|
||||||
val captor = ArgumentCaptor
|
val captor = ArgumentCaptor
|
||||||
.forClass(ControlsListingController.ControlsListingCallback::class.java)
|
.forClass(ControlsListingController.ControlsListingCallback::class.java)
|
||||||
val controlsServiceInfo = mock<ControlsServiceInfo>()
|
val controlsServiceInfo = mock<ControlsServiceInfo>()
|
||||||
@@ -76,14 +74,14 @@ class AppAdapterTest : SysuiTestCase() {
|
|||||||
verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
|
verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
|
||||||
|
|
||||||
captor.value.onServicesUpdated(serviceInfo)
|
captor.value.onServicesUpdated(serviceInfo)
|
||||||
backgroundExecutor.runAllReady()
|
FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
|
||||||
uiExecutor.runAllReady()
|
|
||||||
|
|
||||||
assertThat(adapter.itemCount).isEqualTo(serviceInfo.size)
|
assertThat(adapter.itemCount).isEqualTo(serviceInfo.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testOnServicesUpdatedDoesntHavePanels() {
|
fun testOnServicesUpdated_showsNotAuthorizedPanels() {
|
||||||
|
adapter = createAdapterWithAuthorizedPanels(emptySet())
|
||||||
val captor = ArgumentCaptor
|
val captor = ArgumentCaptor
|
||||||
.forClass(ControlsListingController.ControlsListingCallback::class.java)
|
.forClass(ControlsListingController.ControlsListingCallback::class.java)
|
||||||
val serviceInfo = listOf(
|
val serviceInfo = listOf(
|
||||||
@@ -93,20 +91,88 @@ class AppAdapterTest : SysuiTestCase() {
|
|||||||
verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
|
verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
|
||||||
|
|
||||||
captor.value.onServicesUpdated(serviceInfo)
|
captor.value.onServicesUpdated(serviceInfo)
|
||||||
backgroundExecutor.runAllReady()
|
FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
|
||||||
uiExecutor.runAllReady()
|
|
||||||
|
assertThat(adapter.itemCount).isEqualTo(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnServicesUpdated_doesntShowAuthorizedPanels() {
|
||||||
|
adapter = createAdapterWithAuthorizedPanels(setOf(TEST_PACKAGE))
|
||||||
|
|
||||||
|
val captor = ArgumentCaptor
|
||||||
|
.forClass(ControlsListingController.ControlsListingCallback::class.java)
|
||||||
|
val serviceInfo = listOf(
|
||||||
|
ControlsServiceInfo("no panel", null),
|
||||||
|
ControlsServiceInfo("panel", ComponentName(TEST_PACKAGE, "cls"))
|
||||||
|
)
|
||||||
|
verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
|
||||||
|
|
||||||
|
captor.value.onServicesUpdated(serviceInfo)
|
||||||
|
FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
|
||||||
|
|
||||||
assertThat(adapter.itemCount).isEqualTo(1)
|
assertThat(adapter.itemCount).isEqualTo(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ControlsServiceInfo(
|
@Test
|
||||||
label: CharSequence,
|
fun testOnBindSetsClickListenerToCallOnAppSelected() {
|
||||||
panelComponentName: ComponentName? = null
|
adapter = createAdapterWithAuthorizedPanels(emptySet())
|
||||||
): ControlsServiceInfo {
|
|
||||||
return mock {
|
val captor = ArgumentCaptor
|
||||||
`when`(this.loadLabel()).thenReturn(label)
|
.forClass(ControlsListingController.ControlsListingCallback::class.java)
|
||||||
`when`(this.panelActivity).thenReturn(panelComponentName)
|
val serviceInfo = listOf(
|
||||||
`when`(this.loadIcon()).thenReturn(mock())
|
ControlsServiceInfo("no panel", null),
|
||||||
|
ControlsServiceInfo("panel", ComponentName(TEST_PACKAGE, "cls"))
|
||||||
|
)
|
||||||
|
verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
|
||||||
|
|
||||||
|
captor.value.onServicesUpdated(serviceInfo)
|
||||||
|
FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
|
||||||
|
|
||||||
|
val sorted = serviceInfo.sortedWith(
|
||||||
|
compareBy(Collator.getInstance(resources.configuration.locales[0])) {
|
||||||
|
it.loadLabel() ?: ""
|
||||||
|
})
|
||||||
|
|
||||||
|
sorted.forEachIndexed { index, info ->
|
||||||
|
val fakeView: View = mock()
|
||||||
|
val fakeHolder: AppAdapter.Holder = mock()
|
||||||
|
`when`(fakeHolder.view).thenReturn(fakeView)
|
||||||
|
|
||||||
|
clearInvocations(onAppSelected)
|
||||||
|
adapter.onBindViewHolder(fakeHolder, index)
|
||||||
|
val listenerCaptor: ArgumentCaptor<View.OnClickListener> = argumentCaptor()
|
||||||
|
verify(fakeView).setOnClickListener(capture(listenerCaptor))
|
||||||
|
listenerCaptor.value.onClick(fakeView)
|
||||||
|
|
||||||
|
verify(onAppSelected).invoke(info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createAdapterWithAuthorizedPanels(packages: Set<String>): AppAdapter {
|
||||||
|
return AppAdapter(backgroundExecutor,
|
||||||
|
uiExecutor,
|
||||||
|
lifecycle,
|
||||||
|
controlsListingController,
|
||||||
|
layoutInflater,
|
||||||
|
onAppSelected,
|
||||||
|
favoritesRenderer,
|
||||||
|
resources,
|
||||||
|
packages)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun ControlsServiceInfo(
|
||||||
|
label: CharSequence,
|
||||||
|
panelComponentName: ComponentName? = null
|
||||||
|
): ControlsServiceInfo {
|
||||||
|
return mock {
|
||||||
|
`when`(loadLabel()).thenReturn(label)
|
||||||
|
`when`(panelActivity).thenReturn(panelComponentName)
|
||||||
|
`when`(loadIcon()).thenReturn(mock())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val TEST_PACKAGE = "package"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,13 @@
|
|||||||
|
|
||||||
package com.android.systemui.controls.management
|
package com.android.systemui.controls.management
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.ComponentName
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.pm.ServiceInfo
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.Bundle
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import android.testing.TestableLooper
|
import android.testing.TestableLooper
|
||||||
import android.window.OnBackInvokedCallback
|
import android.window.OnBackInvokedCallback
|
||||||
@@ -25,14 +31,23 @@ import androidx.test.filters.SmallTest
|
|||||||
import androidx.test.rule.ActivityTestRule
|
import androidx.test.rule.ActivityTestRule
|
||||||
import androidx.test.runner.intercepting.SingleActivityFactory
|
import androidx.test.runner.intercepting.SingleActivityFactory
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.controls.ControlsServiceInfo
|
||||||
import com.android.systemui.controls.controller.ControlsController
|
import com.android.systemui.controls.controller.ControlsController
|
||||||
import com.android.systemui.controls.ui.ControlsUiController
|
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
|
||||||
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
|
||||||
import com.android.systemui.settings.UserTracker
|
import com.android.systemui.settings.UserTracker
|
||||||
|
import com.android.systemui.util.mockito.any
|
||||||
|
import com.android.systemui.util.mockito.argumentCaptor
|
||||||
|
import com.android.systemui.util.mockito.capture
|
||||||
|
import com.android.systemui.util.mockito.eq
|
||||||
|
import com.android.systemui.util.mockito.mock
|
||||||
|
import com.android.systemui.util.mockito.whenever
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
import com.google.common.util.concurrent.MoreExecutors
|
import com.google.common.util.concurrent.MoreExecutors
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
|
import java.util.function.Consumer
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -41,7 +56,11 @@ import org.mockito.ArgumentCaptor
|
|||||||
import org.mockito.ArgumentMatchers
|
import org.mockito.ArgumentMatchers
|
||||||
import org.mockito.Captor
|
import org.mockito.Captor
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.mockito.Mockito.doReturn
|
||||||
|
import org.mockito.Mockito.never
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.Mockito.verifyNoMoreInteractions
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@@ -58,9 +77,10 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Mock lateinit var userTracker: UserTracker
|
@Mock lateinit var userTracker: UserTracker
|
||||||
|
|
||||||
@Mock lateinit var uiController: ControlsUiController
|
@Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
|
||||||
|
|
||||||
|
@Mock lateinit var dialogFactory: PanelConfirmationDialogFactory
|
||||||
|
|
||||||
private lateinit var controlsProviderSelectorActivity: ControlsProviderSelectorActivity_Factory
|
|
||||||
private var latch: CountDownLatch = CountDownLatch(1)
|
private var latch: CountDownLatch = CountDownLatch(1)
|
||||||
|
|
||||||
@Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
|
@Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
|
||||||
@@ -81,7 +101,8 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() {
|
|||||||
listingController,
|
listingController,
|
||||||
controlsController,
|
controlsController,
|
||||||
userTracker,
|
userTracker,
|
||||||
uiController,
|
authorizedPanelsRepository,
|
||||||
|
dialogFactory,
|
||||||
mockDispatcher,
|
mockDispatcher,
|
||||||
latch
|
latch
|
||||||
)
|
)
|
||||||
@@ -113,13 +134,99 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() {
|
|||||||
verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value)
|
verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestableControlsProviderSelectorActivity(
|
@Test
|
||||||
|
fun testOnAppSelectedForNonPanelStartsFavoritingActivity() {
|
||||||
|
val info = ControlsServiceInfo(ComponentName("test_pkg", "service"), "", null)
|
||||||
|
activityRule.activity.onAppSelected(info)
|
||||||
|
|
||||||
|
verifyNoMoreInteractions(dialogFactory)
|
||||||
|
|
||||||
|
assertThat(activityRule.activity.lastStartedActivity?.component?.className)
|
||||||
|
.isEqualTo(ControlsFavoritingActivity::class.java.name)
|
||||||
|
|
||||||
|
assertThat(activityRule.activity.triedToFinish).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnAppSelectedForPanelTriggersDialog() {
|
||||||
|
val label = "label"
|
||||||
|
val info =
|
||||||
|
ControlsServiceInfo(
|
||||||
|
ComponentName("test_pkg", "service"),
|
||||||
|
label,
|
||||||
|
ComponentName("test_pkg", "activity")
|
||||||
|
)
|
||||||
|
|
||||||
|
val dialog: Dialog = mock()
|
||||||
|
whenever(dialogFactory.createConfirmationDialog(any(), any(), any())).thenReturn(dialog)
|
||||||
|
|
||||||
|
activityRule.activity.onAppSelected(info)
|
||||||
|
verify(dialogFactory).createConfirmationDialog(any(), eq(label), any())
|
||||||
|
verify(dialog).show()
|
||||||
|
|
||||||
|
assertThat(activityRule.activity.triedToFinish).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dialogAcceptAddsPackage() {
|
||||||
|
val label = "label"
|
||||||
|
val info =
|
||||||
|
ControlsServiceInfo(
|
||||||
|
ComponentName("test_pkg", "service"),
|
||||||
|
label,
|
||||||
|
ComponentName("test_pkg", "activity")
|
||||||
|
)
|
||||||
|
|
||||||
|
val dialog: Dialog = mock()
|
||||||
|
whenever(dialogFactory.createConfirmationDialog(any(), any(), any())).thenReturn(dialog)
|
||||||
|
|
||||||
|
activityRule.activity.onAppSelected(info)
|
||||||
|
|
||||||
|
val captor: ArgumentCaptor<Consumer<Boolean>> = argumentCaptor()
|
||||||
|
verify(dialogFactory).createConfirmationDialog(any(), any(), capture(captor))
|
||||||
|
|
||||||
|
captor.value.accept(true)
|
||||||
|
|
||||||
|
val setCaptor: ArgumentCaptor<Set<String>> = argumentCaptor()
|
||||||
|
verify(authorizedPanelsRepository).addAuthorizedPanels(capture(setCaptor))
|
||||||
|
assertThat(setCaptor.value).containsExactly(info.componentName.packageName)
|
||||||
|
|
||||||
|
assertThat(activityRule.activity.triedToFinish).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dialogCancelDoesntAddPackage() {
|
||||||
|
val label = "label"
|
||||||
|
val info =
|
||||||
|
ControlsServiceInfo(
|
||||||
|
ComponentName("test_pkg", "service"),
|
||||||
|
label,
|
||||||
|
ComponentName("test_pkg", "activity")
|
||||||
|
)
|
||||||
|
|
||||||
|
val dialog: Dialog = mock()
|
||||||
|
whenever(dialogFactory.createConfirmationDialog(any(), any(), any())).thenReturn(dialog)
|
||||||
|
|
||||||
|
activityRule.activity.onAppSelected(info)
|
||||||
|
|
||||||
|
val captor: ArgumentCaptor<Consumer<Boolean>> = argumentCaptor()
|
||||||
|
verify(dialogFactory).createConfirmationDialog(any(), any(), capture(captor))
|
||||||
|
|
||||||
|
captor.value.accept(false)
|
||||||
|
|
||||||
|
verify(authorizedPanelsRepository, never()).addAuthorizedPanels(any())
|
||||||
|
|
||||||
|
assertThat(activityRule.activity.triedToFinish).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestableControlsProviderSelectorActivity(
|
||||||
executor: Executor,
|
executor: Executor,
|
||||||
backExecutor: Executor,
|
backExecutor: Executor,
|
||||||
listingController: ControlsListingController,
|
listingController: ControlsListingController,
|
||||||
controlsController: ControlsController,
|
controlsController: ControlsController,
|
||||||
userTracker: UserTracker,
|
userTracker: UserTracker,
|
||||||
uiController: ControlsUiController,
|
authorizedPanelsRepository: AuthorizedPanelsRepository,
|
||||||
|
dialogFactory: PanelConfirmationDialogFactory,
|
||||||
private val mockDispatcher: OnBackInvokedDispatcher,
|
private val mockDispatcher: OnBackInvokedDispatcher,
|
||||||
private val latch: CountDownLatch
|
private val latch: CountDownLatch
|
||||||
) :
|
) :
|
||||||
@@ -129,16 +236,50 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() {
|
|||||||
listingController,
|
listingController,
|
||||||
controlsController,
|
controlsController,
|
||||||
userTracker,
|
userTracker,
|
||||||
uiController
|
authorizedPanelsRepository,
|
||||||
|
dialogFactory
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
var lastStartedActivity: Intent? = null
|
||||||
|
var triedToFinish = false
|
||||||
|
|
||||||
override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
|
override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
|
||||||
return mockDispatcher
|
return mockDispatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun startActivity(intent: Intent?, options: Bundle?) {
|
||||||
|
lastStartedActivity = intent
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
super.onStop()
|
super.onStop()
|
||||||
// ensures that test runner thread does not proceed until ui thread is done
|
// ensures that test runner thread does not proceed until ui thread is done
|
||||||
latch.countDown()
|
latch.countDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun animateExitAndFinish() {
|
||||||
|
// Activity should only be finished from the rule.
|
||||||
|
triedToFinish = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun ControlsServiceInfo(
|
||||||
|
componentName: ComponentName,
|
||||||
|
label: CharSequence,
|
||||||
|
panelComponentName: ComponentName? = null
|
||||||
|
): ControlsServiceInfo {
|
||||||
|
val serviceInfo =
|
||||||
|
ServiceInfo().apply {
|
||||||
|
applicationInfo = ApplicationInfo()
|
||||||
|
packageName = componentName.packageName
|
||||||
|
name = componentName.className
|
||||||
|
}
|
||||||
|
return Mockito.spy(ControlsServiceInfo(mock(), serviceInfo)).apply {
|
||||||
|
doReturn(label).`when`(this).loadLabel()
|
||||||
|
doReturn(mock<Drawable>()).`when`(this).loadIcon()
|
||||||
|
doReturn(panelComponentName).`when`(this).panelActivity
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* 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.management
|
||||||
|
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.statusbar.phone.SystemUIDialog
|
||||||
|
import com.android.systemui.util.mockito.argumentCaptor
|
||||||
|
import com.android.systemui.util.mockito.capture
|
||||||
|
import com.android.systemui.util.mockito.eq
|
||||||
|
import com.android.systemui.util.mockito.mock
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.ArgumentCaptor
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.Mockito.`when`
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
class PanelConfirmationDialogFactoryTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDialogHasCorrectInfo() {
|
||||||
|
val mockDialog: SystemUIDialog = mock() { `when`(context).thenReturn(mContext) }
|
||||||
|
val factory = PanelConfirmationDialogFactory { mockDialog }
|
||||||
|
val appName = "appName"
|
||||||
|
|
||||||
|
factory.createConfirmationDialog(context, appName) {}
|
||||||
|
|
||||||
|
verify(mockDialog).setCanceledOnTouchOutside(true)
|
||||||
|
verify(mockDialog)
|
||||||
|
.setTitle(context.getString(R.string.controls_panel_authorization_title, appName))
|
||||||
|
verify(mockDialog)
|
||||||
|
.setMessage(context.getString(R.string.controls_panel_authorization, appName))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDialogPositiveButton() {
|
||||||
|
val mockDialog: SystemUIDialog = mock() { `when`(context).thenReturn(mContext) }
|
||||||
|
val factory = PanelConfirmationDialogFactory { mockDialog }
|
||||||
|
|
||||||
|
var response: Boolean? = null
|
||||||
|
|
||||||
|
factory.createConfirmationDialog(context, "") { response = it }
|
||||||
|
|
||||||
|
val captor: ArgumentCaptor<DialogInterface.OnClickListener> = argumentCaptor()
|
||||||
|
verify(mockDialog).setPositiveButton(eq(R.string.controls_dialog_ok), capture(captor))
|
||||||
|
|
||||||
|
captor.value.onClick(mockDialog, DialogInterface.BUTTON_POSITIVE)
|
||||||
|
|
||||||
|
assertThat(response).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDialogNeutralButton() {
|
||||||
|
val mockDialog: SystemUIDialog = mock() { `when`(context).thenReturn(mContext) }
|
||||||
|
val factory = PanelConfirmationDialogFactory { mockDialog }
|
||||||
|
|
||||||
|
var response: Boolean? = null
|
||||||
|
|
||||||
|
factory.createConfirmationDialog(context, "") { response = it }
|
||||||
|
|
||||||
|
val captor: ArgumentCaptor<DialogInterface.OnClickListener> = argumentCaptor()
|
||||||
|
verify(mockDialog).setNeutralButton(eq(R.string.cancel), capture(captor))
|
||||||
|
|
||||||
|
captor.value.onClick(mockDialog, DialogInterface.BUTTON_NEUTRAL)
|
||||||
|
|
||||||
|
assertThat(response).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDialogCancel() {
|
||||||
|
val mockDialog: SystemUIDialog = mock() { `when`(context).thenReturn(mContext) }
|
||||||
|
val factory = PanelConfirmationDialogFactory { mockDialog }
|
||||||
|
|
||||||
|
var response: Boolean? = null
|
||||||
|
|
||||||
|
factory.createConfirmationDialog(context, "") { response = it }
|
||||||
|
|
||||||
|
val captor: ArgumentCaptor<DialogInterface.OnCancelListener> = argumentCaptor()
|
||||||
|
verify(mockDialog).setOnCancelListener(capture(captor))
|
||||||
|
|
||||||
|
captor.value.onCancel(mockDialog)
|
||||||
|
|
||||||
|
assertThat(response).isFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.controls.panels
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.settings.UserFileManager
|
||||||
|
import com.android.systemui.settings.UserTracker
|
||||||
|
import com.android.systemui.util.FakeSharedPreferences
|
||||||
|
import com.android.systemui.util.mockito.whenever
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import java.io.File
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
@SmallTest
|
||||||
|
class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
@Mock private lateinit var userTracker: UserTracker
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
mContext.orCreateTestableResources.addOverride(
|
||||||
|
R.array.config_controlsPreferredPackages,
|
||||||
|
arrayOf<String>()
|
||||||
|
)
|
||||||
|
whenever(userTracker.userId).thenReturn(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPreApprovedPackagesAreSeededIfNoSavedPreferences() {
|
||||||
|
mContext.orCreateTestableResources.addOverride(
|
||||||
|
R.array.config_controlsPreferredPackages,
|
||||||
|
arrayOf(TEST_PACKAGE)
|
||||||
|
)
|
||||||
|
val sharedPrefs = FakeSharedPreferences()
|
||||||
|
val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
|
||||||
|
val repository = createRepository(fileManager)
|
||||||
|
|
||||||
|
assertThat(repository.getAuthorizedPanels()).containsExactly(TEST_PACKAGE)
|
||||||
|
assertThat(sharedPrefs.getStringSet(KEY, null)).containsExactly(TEST_PACKAGE)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPreApprovedPackagesNotSeededIfEmptySavedPreferences() {
|
||||||
|
mContext.orCreateTestableResources.addOverride(
|
||||||
|
R.array.config_controlsPreferredPackages,
|
||||||
|
arrayOf(TEST_PACKAGE)
|
||||||
|
)
|
||||||
|
val sharedPrefs = FakeSharedPreferences()
|
||||||
|
sharedPrefs.edit().putStringSet(KEY, emptySet()).apply()
|
||||||
|
val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
|
||||||
|
createRepository(fileManager)
|
||||||
|
|
||||||
|
assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPreApprovedPackagesOnlySetForUserThatDoesntHaveThem() {
|
||||||
|
mContext.orCreateTestableResources.addOverride(
|
||||||
|
R.array.config_controlsPreferredPackages,
|
||||||
|
arrayOf(TEST_PACKAGE)
|
||||||
|
)
|
||||||
|
val sharedPrefs_0 = FakeSharedPreferences()
|
||||||
|
val sharedPrefs_1 = FakeSharedPreferences()
|
||||||
|
sharedPrefs_1.edit().putStringSet(KEY, emptySet()).apply()
|
||||||
|
val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs_0, 1 to sharedPrefs_1))
|
||||||
|
val repository = createRepository(fileManager)
|
||||||
|
|
||||||
|
assertThat(repository.getAuthorizedPanels()).containsExactly(TEST_PACKAGE)
|
||||||
|
whenever(userTracker.userId).thenReturn(1)
|
||||||
|
assertThat(repository.getAuthorizedPanels()).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGetAuthorizedPackages() {
|
||||||
|
val sharedPrefs = FakeSharedPreferences()
|
||||||
|
sharedPrefs.edit().putStringSet(KEY, mutableSetOf(TEST_PACKAGE)).apply()
|
||||||
|
val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
|
||||||
|
|
||||||
|
val repository = createRepository(fileManager)
|
||||||
|
assertThat(repository.getAuthorizedPanels()).containsExactly(TEST_PACKAGE)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSetAuthorizedPackage() {
|
||||||
|
val sharedPrefs = FakeSharedPreferences()
|
||||||
|
val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
|
||||||
|
|
||||||
|
val repository = createRepository(fileManager)
|
||||||
|
repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
|
||||||
|
assertThat(sharedPrefs.getStringSet(KEY, null)).containsExactly(TEST_PACKAGE)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl {
|
||||||
|
return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
|
||||||
|
UserFileManager {
|
||||||
|
override fun getFile(fileName: String, userId: Int): File {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSharedPreferences(
|
||||||
|
fileName: String,
|
||||||
|
mode: Int,
|
||||||
|
userId: Int
|
||||||
|
): SharedPreferences {
|
||||||
|
if (fileName != FILE_NAME) {
|
||||||
|
throw IllegalArgumentException("Preference files must be $FILE_NAME")
|
||||||
|
}
|
||||||
|
return sharedPrefs.getValue(userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val FILE_NAME = "controls_prefs"
|
||||||
|
private const val KEY = "authorized_panels"
|
||||||
|
private const val TEST_PACKAGE = "package"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,8 +39,10 @@ import com.android.systemui.controls.controller.ControlsController
|
|||||||
import com.android.systemui.controls.controller.StructureInfo
|
import com.android.systemui.controls.controller.StructureInfo
|
||||||
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.settings.FakeControlsSettingsRepository
|
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
|
||||||
import com.android.systemui.dump.DumpManager
|
import com.android.systemui.dump.DumpManager
|
||||||
|
import com.android.systemui.flags.FeatureFlags
|
||||||
import com.android.systemui.plugins.ActivityStarter
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
import com.android.systemui.settings.UserFileManager
|
import com.android.systemui.settings.UserFileManager
|
||||||
import com.android.systemui.settings.UserTracker
|
import com.android.systemui.settings.UserTracker
|
||||||
@@ -91,6 +93,8 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
|||||||
@Mock lateinit var userTracker: UserTracker
|
@Mock lateinit var userTracker: UserTracker
|
||||||
@Mock lateinit var taskViewFactory: TaskViewFactory
|
@Mock lateinit var taskViewFactory: TaskViewFactory
|
||||||
@Mock lateinit var dumpManager: DumpManager
|
@Mock lateinit var dumpManager: DumpManager
|
||||||
|
@Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
|
||||||
|
@Mock lateinit var featureFlags: FeatureFlags
|
||||||
val sharedPreferences = FakeSharedPreferences()
|
val sharedPreferences = FakeSharedPreferences()
|
||||||
lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
|
lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
|
||||||
|
|
||||||
@@ -132,6 +136,8 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
|||||||
userTracker,
|
userTracker,
|
||||||
Optional.of(taskViewFactory),
|
Optional.of(taskViewFactory),
|
||||||
controlsSettingsRepository,
|
controlsSettingsRepository,
|
||||||
|
authorizedPanelsRepository,
|
||||||
|
featureFlags,
|
||||||
dumpManager
|
dumpManager
|
||||||
)
|
)
|
||||||
`when`(
|
`when`(
|
||||||
@@ -240,7 +246,9 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testPanelCallsTaskViewFactoryCreate() {
|
fun testPanelCallsTaskViewFactoryCreate() {
|
||||||
mockLayoutInflater()
|
mockLayoutInflater()
|
||||||
val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
|
val packageName = "pkg"
|
||||||
|
`when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
|
||||||
|
val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls"))
|
||||||
val serviceInfo = setUpPanel(panel)
|
val serviceInfo = setUpPanel(panel)
|
||||||
|
|
||||||
underTest.show(parent, {}, context)
|
underTest.show(parent, {}, context)
|
||||||
@@ -258,9 +266,11 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testPanelControllerStartActivityWithCorrectArguments() {
|
fun testPanelControllerStartActivityWithCorrectArguments() {
|
||||||
mockLayoutInflater()
|
mockLayoutInflater()
|
||||||
|
val packageName = "pkg"
|
||||||
|
`when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
|
||||||
controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
|
controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
|
||||||
|
|
||||||
val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
|
val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls"))
|
||||||
val serviceInfo = setUpPanel(panel)
|
val serviceInfo = setUpPanel(panel)
|
||||||
|
|
||||||
underTest.show(parent, {}, context)
|
underTest.show(parent, {}, context)
|
||||||
@@ -290,9 +300,11 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testPendingIntentExtrasAreModified() {
|
fun testPendingIntentExtrasAreModified() {
|
||||||
mockLayoutInflater()
|
mockLayoutInflater()
|
||||||
|
val packageName = "pkg"
|
||||||
|
`when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
|
||||||
controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
|
controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
|
||||||
|
|
||||||
val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
|
val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls"))
|
||||||
val serviceInfo = setUpPanel(panel)
|
val serviceInfo = setUpPanel(panel)
|
||||||
|
|
||||||
underTest.show(parent, {}, context)
|
underTest.show(parent, {}, context)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class FakeSharedPreferences : SharedPreferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getStringSet(key: String, defValues: MutableSet<String>?): MutableSet<String>? {
|
override fun getStringSet(key: String, defValues: MutableSet<String>?): MutableSet<String>? {
|
||||||
return data.getOrDefault(key, defValues) as? MutableSet<String>?
|
return (data.getOrDefault(key, defValues) as? Set<String>?)?.toMutableSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getInt(key: String, defValue: Int): Int {
|
override fun getInt(key: String, defValue: Int): Int {
|
||||||
|
|||||||
Reference in New Issue
Block a user