Merge changes from topic "am-731c4ffd5541452fbd06d90db1babbb5"

* changes:
  Merge changes from topics "265180342", "265979516_cts" into tm-qpr-dev am: a96800911c am: e48ab2d2e8
  Flag for new flows am: 694d2d5711 am: 470c6f6728
  Add a persistence mechanism for panels' packages am: e192a439af am: 479f4f61fb
This commit is contained in:
Automerger Merge Worker
2023-01-24 18:50:25 +00:00
committed by Android (Google) Code Review
19 changed files with 829 additions and 60 deletions

View File

@@ -2342,6 +2342,14 @@
<!-- Removed control in management screen [CHAR LIMIT=20] -->
<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] -->
<string name="accessibility_control_favorite">Favorited</string>
<!-- a11y state description for a control that is currently favorited with its position [CHAR LIMIT=NONE] -->
@@ -2491,6 +2499,8 @@
<string name="controls_menu_add">Add controls</string>
<!-- Controls menu, edit [CHAR_LIMIT=30] -->
<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] -->
<string name="media_output_dialog_add_output">Add outputs</string>

View File

@@ -188,6 +188,8 @@ interface ControlsController : UserAwareController {
/** See [ControlsUiController.getPreferredSelectedItem]. */
fun getPreferredSelection(): SelectedItem
fun setPreferredSelection(selectedItem: SelectedItem)
/**
* Bind to a service that provides a Device Controls panel (embedded activity). This will allow
* the app to remain "warm", and reduce latency.

View File

@@ -37,6 +37,7 @@ import com.android.systemui.backup.BackupHelper
import com.android.systemui.controls.ControlStatus
import com.android.systemui.controls.ControlsServiceInfo
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.SelectedItem
import com.android.systemui.dagger.SysUISingleton
@@ -63,6 +64,7 @@ class ControlsControllerImpl @Inject constructor (
private val listingController: ControlsListingController,
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
private val authorizedPanelsRepository: AuthorizedPanelsRepository,
optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
dumpManager: DumpManager,
) : Dumpable, ControlsController {
@@ -251,6 +253,11 @@ class ControlsControllerImpl @Inject constructor (
private fun resetFavorites() {
Favorites.clear()
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 {
@@ -491,6 +498,7 @@ class ControlsControllerImpl @Inject constructor (
if (!confirmAvailability()) return
executor.execute {
if (Favorites.addFavorite(componentName, structureName, controlInfo)) {
authorizedPanelsRepository.addAuthorizedPanels(setOf(componentName.packageName))
persistenceWrapper.storeFavorites(Favorites.getAllStructures())
}
}
@@ -557,6 +565,10 @@ class ControlsControllerImpl @Inject constructor (
return uiController.getPreferredSelectedItem(getFavorites())
}
override fun setPreferredSelection(selectedItem: SelectedItem) {
uiController.updatePreferences(selectedItem)
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.println("ControlsController state:")
pw.println(" Changing users: $userChanging")

View File

@@ -34,6 +34,8 @@ import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsListingControllerImpl
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
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.ControlsSettingsDialogManagerImpl
import com.android.systemui.controls.ui.ControlActionCoordinator
@@ -104,6 +106,11 @@ abstract class ControlsModule {
coordinator: ControlActionCoordinatorImpl
): ControlActionCoordinator
@Binds
abstract fun provideAuthorizedPanelsRepository(
repository: AuthorizedPanelsRepositoryImpl
): AuthorizedPanelsRepository
@BindsOptionalOf
abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper

View File

@@ -45,14 +45,15 @@ import java.util.concurrent.Executor
* @param onAppSelected a callback to indicate that an app has been selected in the list.
*/
class AppAdapter(
backgroundExecutor: Executor,
uiExecutor: Executor,
lifecycle: Lifecycle,
controlsListingController: ControlsListingController,
private val layoutInflater: LayoutInflater,
private val onAppSelected: (ComponentName?) -> Unit = {},
private val favoritesRenderer: FavoritesRenderer,
private val resources: Resources
backgroundExecutor: Executor,
uiExecutor: Executor,
lifecycle: Lifecycle,
controlsListingController: ControlsListingController,
private val layoutInflater: LayoutInflater,
private val onAppSelected: (ControlsServiceInfo) -> Unit = {},
private val favoritesRenderer: FavoritesRenderer,
private val resources: Resources,
private val authorizedPanels: Set<String> = emptySet(),
) : RecyclerView.Adapter<AppAdapter.Holder>() {
private var listOfServices = emptyList<ControlsServiceInfo>()
@@ -64,8 +65,10 @@ class AppAdapter(
val localeComparator = compareBy<ControlsServiceInfo, CharSequence>(collator) {
it.loadLabel() ?: ""
}
listOfServices = serviceInfos.filter { it.panelActivity == null }
.sortedWith(localeComparator)
// No panel or the panel is not authorized
listOfServices = serviceInfos.filter {
it.panelActivity == null || it.panelActivity?.packageName !in authorizedPanels
}.sortedWith(localeComparator)
uiExecutor.execute(::notifyDataSetChanged)
}
}
@@ -86,8 +89,8 @@ class AppAdapter(
override fun onBindViewHolder(holder: Holder, index: Int) {
holder.bindData(listOfServices[index])
holder.itemView.setOnClickListener {
onAppSelected(ComponentName.unflattenFromString(listOfServices[index].key))
holder.view.setOnClickListener {
onAppSelected(listOfServices[index])
}
}
@@ -95,6 +98,8 @@ class AppAdapter(
* Holder for binding views in the [RecyclerView]-
*/
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 title: TextView = itemView.requireViewById(com.android.internal.R.id.title)
private val favorites: TextView = itemView.requireViewById(R.id.favorites)
@@ -106,7 +111,11 @@ class AppAdapter(
fun bindData(data: ControlsServiceInfo) {
icon.setImageDrawable(data.loadIcon())
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.visibility = if (text == null) View.GONE else View.VISIBLE
}

View File

@@ -17,6 +17,7 @@
package com.android.systemui.controls.management
import android.app.ActivityOptions
import android.app.Dialog
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -31,12 +32,15 @@ import android.widget.TextView
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import androidx.activity.ComponentActivity
import androidx.annotation.VisibleForTesting
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
import com.android.systemui.controls.ControlsServiceInfo
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.ControlsUiController
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.UserTracker
@@ -52,7 +56,8 @@ open class ControlsProviderSelectorActivity @Inject constructor(
private val listingController: ControlsListingController,
private val controlsController: ControlsController,
private val userTracker: UserTracker,
private val uiController: ControlsUiController
private val authorizedPanelsRepository: AuthorizedPanelsRepository,
private val panelConfirmationDialogFactory: PanelConfirmationDialogFactory
) : ComponentActivity() {
companion object {
@@ -72,6 +77,7 @@ open class ControlsProviderSelectorActivity @Inject constructor(
}
}
}
private var dialog: Dialog? = null
private val mOnBackInvokedCallback = OnBackInvokedCallback {
if (DEBUG) {
@@ -138,9 +144,11 @@ open class ControlsProviderSelectorActivity @Inject constructor(
lifecycle,
listingController,
LayoutInflater.from(this),
::launchFavoritingActivity,
::onAppSelected,
FavoritesRenderer(resources, controlsController::countFavoritesForComponent),
resources).apply {
resources,
authorizedPanelsRepository.getAuthorizedPanels()
).apply {
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
var hasAnimated = false
override fun onChanged() {
@@ -167,13 +175,35 @@ open class ControlsProviderSelectorActivity @Inject constructor(
Log.d(TAG, "Unregistered onBackInvokedCallback")
}
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.
* @param component a component name for a [ControlsProviderService]
*/
fun launchFavoritingActivity(component: ComponentName?) {
private fun launchFavoritingActivity(component: ComponentName?) {
executor.execute {
component?.let {
val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java)
@@ -194,7 +224,15 @@ open class ControlsProviderSelectorActivity @Inject constructor(
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)
ControlsAnimations.exitAnimation(
rootView,

View File

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

View File

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

View File

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

View File

@@ -58,6 +58,8 @@ interface ControlsUiController {
* This element will be the one that appears when the user first opens the controls activity.
*/
fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem
fun updatePreferences(selectedItem: SelectedItem)
}
sealed class SelectedItem {

View File

@@ -60,10 +60,13 @@ import com.android.systemui.controls.management.ControlsEditingActivity
import com.android.systemui.controls.management.ControlsFavoritingActivity
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
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.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
@@ -99,6 +102,8 @@ class ControlsUiControllerImpl @Inject constructor (
private val userTracker: UserTracker,
private val taskViewFactory: Optional<TaskViewFactory>,
private val controlsSettingsRepository: ControlsSettingsRepository,
private val authorizedPanelsRepository: AuthorizedPanelsRepository,
private val featureFlags: FeatureFlags,
dumpManager: DumpManager
) : ControlsUiController, Dumpable {
@@ -160,6 +165,7 @@ class ControlsUiControllerImpl @Inject constructor (
): ControlsListingController.ControlsListingCallback {
return object : ControlsListingController.ControlsListingCallback {
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
val authorizedPanels = authorizedPanelsRepository.getAuthorizedPanels()
val lastItems = serviceInfos.map {
val uid = it.serviceInfo.applicationInfo.uid
@@ -169,7 +175,11 @@ class ControlsUiControllerImpl @Inject constructor (
it.loadIcon(),
it.componentName,
uid,
it.panelActivity
if (it.componentName.packageName in authorizedPanels) {
it.panelActivity
} else {
null
}
)
}
uiExecutor.execute {
@@ -417,14 +427,20 @@ class ControlsUiControllerImpl @Inject constructor (
val isPanel = selectedItem is SelectedItem.PanelItem
val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.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) {
arrayOf(
context.resources.getString(R.string.controls_menu_add),
context.resources.getString(addControlsId),
)
} else {
arrayOf(
context.resources.getString(R.string.controls_menu_add),
context.resources.getString(addControlsId),
context.resources.getString(R.string.controls_menu_edit)
)
}
@@ -449,7 +465,7 @@ class ControlsUiControllerImpl @Inject constructor (
when (pos) {
// 0: Add Control
0 -> {
if (isPanel) {
if (isPanel || newFlows) {
startProviderSelectorActivity()
} else {
startFavoritingActivity(selectedStructure)
@@ -610,11 +626,11 @@ class ControlsUiControllerImpl @Inject constructor (
}
}
private fun updatePreferences(si: SelectedItem) {
override fun updatePreferences(selectedItem: SelectedItem) {
sharedPreferences.edit()
.putString(PREF_COMPONENT, si.componentName.flattenToString())
.putString(PREF_STRUCTURE_OR_APP_NAME, si.name.toString())
.putBoolean(PREF_IS_PANEL, si is SelectedItem.PanelItem)
.putString(PREF_COMPONENT, selectedItem.componentName.flattenToString())
.putString(PREF_STRUCTURE_OR_APP_NAME, selectedItem.name.toString())
.putBoolean(PREF_IS_PANEL, selectedItem is SelectedItem.PanelItem)
.commit()
}

View File

@@ -530,6 +530,10 @@ object Flags {
val APP_PANELS_ALL_APPS_ALLOWED =
releasedFlag(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
@JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")

View File

@@ -33,6 +33,7 @@ import com.android.systemui.backup.BackupHelper
import com.android.systemui.controls.ControlStatus
import com.android.systemui.controls.ControlsServiceInfo
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.dump.DumpManager
import com.android.systemui.settings.UserFileManager
@@ -66,6 +67,7 @@ import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when`
import org.mockito.Mockito.clearInvocations
import org.mockito.MockitoAnnotations
@SmallTest
@@ -88,6 +90,8 @@ class ControlsControllerImplTest : SysuiTestCase() {
private lateinit var userTracker: UserTracker
@Mock
private lateinit var userFileManager: UserFileManager
@Mock
private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
@Captor
private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo>
@@ -168,6 +172,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
listingController,
userFileManager,
userTracker,
authorizedPanelsRepository,
Optional.of(persistenceWrapper),
mock(DumpManager::class.java)
)
@@ -224,12 +229,33 @@ class ControlsControllerImplTest : SysuiTestCase() {
listingController,
userFileManager,
userTracker,
authorizedPanelsRepository,
Optional.of(persistenceWrapper),
mock(DumpManager::class.java)
)
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
fun testOnActionResponse() {
controller.onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID, ControlAction.RESPONSE_OK)

View File

@@ -21,12 +21,15 @@ import android.content.res.Resources
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.LayoutInflater
import android.view.View
import androidx.test.filters.SmallTest
import com.android.settingslib.core.lifecycle.Lifecycle
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.util.concurrency.FakeExecutor
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.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -36,8 +39,10 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import java.text.Collator
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -49,25 +54,18 @@ class AppAdapterTest : SysuiTestCase() {
@Mock lateinit var lifecycle: Lifecycle
@Mock lateinit var controlsListingController: ControlsListingController
@Mock lateinit var layoutInflater: LayoutInflater
@Mock lateinit var onAppSelected: (ComponentName?) -> Unit
@Mock lateinit var onAppSelected: (ControlsServiceInfo) -> Unit
@Mock lateinit var favoritesRenderer: FavoritesRenderer
val resources: Resources = context.resources
lateinit var adapter: AppAdapter
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
adapter = AppAdapter(backgroundExecutor,
uiExecutor,
lifecycle,
controlsListingController,
layoutInflater,
onAppSelected,
favoritesRenderer,
resources)
}
@Test
fun testOnServicesUpdated_nullLoadLabel() {
adapter = createAdapterWithAuthorizedPanels(emptySet())
val captor = ArgumentCaptor
.forClass(ControlsListingController.ControlsListingCallback::class.java)
val controlsServiceInfo = mock<ControlsServiceInfo>()
@@ -76,14 +74,14 @@ class AppAdapterTest : SysuiTestCase() {
verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
captor.value.onServicesUpdated(serviceInfo)
backgroundExecutor.runAllReady()
uiExecutor.runAllReady()
FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
assertThat(adapter.itemCount).isEqualTo(serviceInfo.size)
}
@Test
fun testOnServicesUpdatedDoesntHavePanels() {
fun testOnServicesUpdated_showsNotAuthorizedPanels() {
adapter = createAdapterWithAuthorizedPanels(emptySet())
val captor = ArgumentCaptor
.forClass(ControlsListingController.ControlsListingCallback::class.java)
val serviceInfo = listOf(
@@ -93,20 +91,88 @@ class AppAdapterTest : SysuiTestCase() {
verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
captor.value.onServicesUpdated(serviceInfo)
backgroundExecutor.runAllReady()
uiExecutor.runAllReady()
FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
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)
}
fun ControlsServiceInfo(
label: CharSequence,
panelComponentName: ComponentName? = null
): ControlsServiceInfo {
return mock {
`when`(this.loadLabel()).thenReturn(label)
`when`(this.panelActivity).thenReturn(panelComponentName)
`when`(this.loadIcon()).thenReturn(mock())
@Test
fun testOnBindSetsClickListenerToCallOnAppSelected() {
adapter = createAdapterWithAuthorizedPanels(emptySet())
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)
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"
}
}

View File

@@ -16,7 +16,13 @@
package com.android.systemui.controls.management
import android.app.Dialog
import android.content.ComponentName
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.TestableLooper
import android.window.OnBackInvokedCallback
@@ -25,14 +31,23 @@ import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsServiceInfo
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.Main
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 java.util.concurrent.CountDownLatch
import java.util.concurrent.Executor
import java.util.function.Consumer
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -41,7 +56,11 @@ import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
import org.mockito.Captor
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.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
@SmallTest
@@ -58,9 +77,10 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() {
@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)
@Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
@@ -81,7 +101,8 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() {
listingController,
controlsController,
userTracker,
uiController,
authorizedPanelsRepository,
dialogFactory,
mockDispatcher,
latch
)
@@ -113,13 +134,99 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() {
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,
backExecutor: Executor,
listingController: ControlsListingController,
controlsController: ControlsController,
userTracker: UserTracker,
uiController: ControlsUiController,
authorizedPanelsRepository: AuthorizedPanelsRepository,
dialogFactory: PanelConfirmationDialogFactory,
private val mockDispatcher: OnBackInvokedDispatcher,
private val latch: CountDownLatch
) :
@@ -129,16 +236,50 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() {
listingController,
controlsController,
userTracker,
uiController
authorizedPanelsRepository,
dialogFactory
) {
var lastStartedActivity: Intent? = null
var triedToFinish = false
override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
return mockDispatcher
}
override fun startActivity(intent: Intent?, options: Bundle?) {
lastStartedActivity = intent
}
override fun onStop() {
super.onStop()
// ensures that test runner thread does not proceed until ui thread is done
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
}
}
}
}

View File

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

View File

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

View File

@@ -39,8 +39,10 @@ import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
@@ -91,6 +93,8 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
@Mock lateinit var userTracker: UserTracker
@Mock lateinit var taskViewFactory: TaskViewFactory
@Mock lateinit var dumpManager: DumpManager
@Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
@Mock lateinit var featureFlags: FeatureFlags
val sharedPreferences = FakeSharedPreferences()
lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
@@ -132,6 +136,8 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
userTracker,
Optional.of(taskViewFactory),
controlsSettingsRepository,
authorizedPanelsRepository,
featureFlags,
dumpManager
)
`when`(
@@ -240,7 +246,9 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
@Test
fun testPanelCallsTaskViewFactoryCreate() {
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)
underTest.show(parent, {}, context)
@@ -258,9 +266,11 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
@Test
fun testPanelControllerStartActivityWithCorrectArguments() {
mockLayoutInflater()
val packageName = "pkg"
`when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
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)
underTest.show(parent, {}, context)
@@ -290,9 +300,11 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
@Test
fun testPendingIntentExtrasAreModified() {
mockLayoutInflater()
val packageName = "pkg"
`when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
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)
underTest.show(parent, {}, context)

View File

@@ -41,7 +41,7 @@ class FakeSharedPreferences : SharedPreferences {
}
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 {