Merge changes from topic "258442632" into tm-qpr-dev

* changes:
  Add panel using TaskView
  Distinguish between selected panel or structure
This commit is contained in:
Fabian Kozynski
2022-11-22 14:34:54 +00:00
committed by Android (Google) Code Review
19 changed files with 835 additions and 209 deletions

View File

@@ -1,30 +0,0 @@
<!--
~ Copyright (C) 2020 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.
-->
<com.android.systemui.globalactions.MinHeightScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:scrollbars="none">
<LinearLayout
android:id="@+id/global_actions_controls_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="@dimen/global_actions_side_margin"
android:layout_marginRight="@dimen/global_actions_side_margin" />
</com.android.systemui.globalactions.MinHeightScrollView>

View File

@@ -15,28 +15,19 @@
limitations under the License.
-->
<LinearLayout
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/control_detail_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.android.systemui.globalactions.MinHeightScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:scrollbars="none">
<LinearLayout
android:id="@+id/global_actions_controls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:layout_height="match_parent"
android:orientation="vertical"
android:clipToPadding="false"
android:paddingHorizontal="@dimen/controls_padding_horizontal" />
</com.android.systemui.globalactions.MinHeightScrollView>
</LinearLayout>
</FrameLayout>

View File

@@ -18,7 +18,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="@dimen/controls_top_margin"
android:layout_marginBottom="@dimen/controls_header_bottom_margin">
@@ -71,5 +71,27 @@
android:background="?android:attr/selectableItemBackgroundBorderless" />
</LinearLayout>
<include layout="@layout/global_actions_controls_list_view" />
<ScrollView
android:id="@+id/controls_scroll_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:clipChildren="true"
android:scrollbars="none">
<include layout="@layout/global_actions_controls_list_view" />
</ScrollView>
<FrameLayout
android:id="@+id/controls_panel"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginLeft="@dimen/global_actions_side_margin"
android:layout_marginRight="@dimen/global_actions_side_margin"
android:background="#ff0000"
android:padding="@dimen/global_actions_side_margin"
android:visibility="gone"
/>
</merge>

View File

@@ -24,6 +24,7 @@ import com.android.systemui.controls.ControlStatus
import com.android.systemui.util.UserAwareController
import com.android.systemui.controls.management.ControlsFavoritingActivity
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.controls.ui.SelectedItem
import java.util.function.Consumer
/**
@@ -184,8 +185,8 @@ interface ControlsController : UserAwareController {
*/
fun countFavoritesForComponent(componentName: ComponentName): Int
/** See [ControlsUiController.getPreferredStructure]. */
fun getPreferredStructure(): StructureInfo
/** See [ControlsUiController.getPreferredSelectedItem]. */
fun getPreferredSelection(): SelectedItem
/**
* Interface for structure to pass data to [ControlsFavoritingActivity].

View File

@@ -38,6 +38,7 @@ import com.android.systemui.controls.ControlStatus
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
@@ -556,8 +557,8 @@ class ControlsControllerImpl @Inject constructor (
)
}
override fun getPreferredStructure(): StructureInfo {
return uiController.getPreferredStructure(getFavorites())
override fun getPreferredSelection(): SelectedItem {
return uiController.getPreferredSelectedItem(getFavorites())
}
override fun dump(pw: PrintWriter, args: Array<out String>) {

View File

@@ -31,4 +31,9 @@ data class StructureInfo(
val componentName: ComponentName,
val structure: CharSequence,
val controls: List<ControlInfo>
)
) {
companion object {
val EMPTY_COMPONENT = ComponentName("", "")
val EMPTY_STRUCTURE = StructureInfo(EMPTY_COMPONENT, "", mutableListOf())
}
}

View File

@@ -64,7 +64,8 @@ class AppAdapter(
val localeComparator = compareBy<ControlsServiceInfo, CharSequence>(collator) {
it.loadLabel() ?: ""
}
listOfServices = serviceInfos.sortedWith(localeComparator)
listOfServices = serviceInfos.filter { it.panelActivity == null }
.sortedWith(localeComparator)
uiExecutor.execute(::notifyDataSetChanged)
}
}

View File

@@ -27,10 +27,13 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import android.view.WindowInsets.Type
import android.view.WindowManager
import androidx.activity.ComponentActivity
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.management.ControlsAnimations
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import javax.inject.Inject
/**
@@ -44,6 +47,7 @@ class ControlsActivity @Inject constructor(
private val uiController: ControlsUiController,
private val broadcastDispatcher: BroadcastDispatcher,
private val dreamManager: IDreamManager,
private val featureFlags: FeatureFlags
) : ComponentActivity() {
private lateinit var parent: ViewGroup
@@ -52,6 +56,9 @@ class ControlsActivity @Inject constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
}
setContentView(R.layout.controls_fullscreen)

View File

@@ -35,7 +35,7 @@ interface ControlsUiController {
/**
* Returns the preferred activity to start, depending on if the user has favorited any
* controls.
* controls or whether there are any app providing panels.
*/
fun resolveActivity(): Class<*>
@@ -53,9 +53,43 @@ interface ControlsUiController {
)
/**
* Returns the structure that is currently preferred by the user.
* Returns the element that is currently preferred by the user.
*
* This structure 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 getPreferredStructure(structures: List<StructureInfo>): StructureInfo
fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem
}
sealed class SelectedItem {
abstract val name: CharSequence
abstract val hasControls: Boolean
abstract val componentName: ComponentName
/**
* Represents the currently selected item for a structure.
*/
data class StructureItem(val structure: StructureInfo) : SelectedItem() {
override val name: CharSequence = structure.structure
override val hasControls: Boolean = structure.controls.isNotEmpty()
override val componentName: ComponentName = structure.componentName
}
/**
* Represents the currently selected item for a service that provides a panel activity.
*
* The [componentName] is that of the service, as that is the expected identifier that should
* not change (to always provide proper migration).
*/
data class PanelItem(
val appName: CharSequence,
override val componentName:
ComponentName
) : SelectedItem() {
override val name: CharSequence = appName
override val hasControls: Boolean = true
}
companion object {
val EMPTY_SELECTION: SelectedItem = StructureItem(StructureInfo.EMPTY_STRUCTURE)
}
}

View File

@@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter
import android.animation.ObjectAnimator
import android.app.Activity
import android.app.ActivityOptions
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -36,18 +37,22 @@ import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ListPopupWindow
import android.widget.Space
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.controller.StructureInfo.Companion.EMPTY_COMPONENT
import com.android.systemui.controls.controller.StructureInfo.Companion.EMPTY_STRUCTURE
import com.android.systemui.controls.management.ControlAdapter
import com.android.systemui.controls.management.ControlsEditingActivity
import com.android.systemui.controls.management.ControlsFavoritingActivity
@@ -56,16 +61,21 @@ import com.android.systemui.controls.management.ControlsProviderSelectorActivity
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.globalactions.GlobalActionsPopupMenu
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeController
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.asIndenting
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.indentIfPossible
import com.android.wm.shell.TaskViewFactory
import dagger.Lazy
import java.io.PrintWriter
import java.text.Collator
import java.util.Optional
import java.util.function.Consumer
import javax.inject.Inject
@@ -80,39 +90,34 @@ class ControlsUiControllerImpl @Inject constructor (
val controlsListingController: Lazy<ControlsListingController>,
val controlActionCoordinator: ControlActionCoordinator,
private val activityStarter: ActivityStarter,
private val shadeController: ShadeController,
private val iconCache: CustomIconCache,
private val controlsMetricsLogger: ControlsMetricsLogger,
private val keyguardStateController: KeyguardStateController,
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
) : ControlsUiController {
private val taskViewFactory: Optional<TaskViewFactory>,
dumpManager: DumpManager
) : ControlsUiController, Dumpable {
companion object {
private const val PREF_COMPONENT = "controls_component"
private const val PREF_STRUCTURE = "controls_structure"
private const val PREF_STRUCTURE_OR_APP_NAME = "controls_structure"
private const val PREF_IS_PANEL = "controls_is_panel"
private const val FADE_IN_MILLIS = 200L
private val EMPTY_COMPONENT = ComponentName("", "")
private val EMPTY_STRUCTURE = StructureInfo(
EMPTY_COMPONENT,
"",
mutableListOf<ControlInfo>()
)
}
private var selectedStructure: StructureInfo = EMPTY_STRUCTURE
private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION
private lateinit var allStructures: List<StructureInfo>
private val controlsById = mutableMapOf<ControlKey, ControlWithState>()
private val controlViewsById = mutableMapOf<ControlKey, ControlViewHolder>()
private lateinit var parent: ViewGroup
private lateinit var lastItems: List<SelectionItem>
private var popup: ListPopupWindow? = null
private var hidden = true
private lateinit var onDismiss: Runnable
private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow)
private var retainCache = false
private var lastSelections = emptyList<SelectionItem>()
private val sharedPreferences
get() = userFileManager.getSharedPreferences(
fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
@@ -120,6 +125,8 @@ class ControlsUiControllerImpl @Inject constructor (
userId = userTracker.userId
)
private var taskViewController: PanelTaskViewController? = null
private val collator = Collator.getInstance(context.resources.configuration.locales[0])
private val localeComparator = compareBy<SelectionItem, CharSequence>(collator) {
it.getTitle()
@@ -128,10 +135,12 @@ class ControlsUiControllerImpl @Inject constructor (
private val onSeedingComplete = Consumer<Boolean> {
accepted ->
if (accepted) {
selectedStructure = controlsController.get().getFavorites().maxByOrNull {
selectedItem = controlsController.get().getFavorites().maxByOrNull {
it.controls.size
} ?: EMPTY_STRUCTURE
updatePreferences(selectedStructure)
}?.let {
SelectedItem.StructureItem(it)
} ?: SelectedItem.EMPTY_SELECTION
updatePreferences(selectedItem)
}
reload(parent)
}
@@ -139,6 +148,10 @@ class ControlsUiControllerImpl @Inject constructor (
private lateinit var activityContext: Context
private lateinit var listingCallback: ControlsListingController.ControlsListingCallback
init {
dumpManager.registerDumpable(javaClass.name, this)
}
private fun createCallback(
onResult: (List<SelectionItem>) -> Unit
): ControlsListingController.ControlsListingCallback {
@@ -146,7 +159,15 @@ class ControlsUiControllerImpl @Inject constructor (
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
val lastItems = serviceInfos.map {
val uid = it.serviceInfo.applicationInfo.uid
SelectionItem(it.loadLabel(), "", it.loadIcon(), it.componentName, uid)
SelectionItem(
it.loadLabel(),
"",
it.loadIcon(),
it.componentName,
uid,
it.panelActivity
)
}
uiExecutor.execute {
parent.removeAllViews()
@@ -160,11 +181,13 @@ class ControlsUiControllerImpl @Inject constructor (
override fun resolveActivity(): Class<*> {
val allStructures = controlsController.get().getFavorites()
val selectedStructure = getPreferredStructure(allStructures)
val selected = getPreferredSelectedItem(allStructures)
val anyPanels = controlsListingController.get().getCurrentServices()
.none { it.panelActivity != null }
return if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
ControlsActivity::class.java
} else if (selectedStructure.controls.isEmpty() && allStructures.size <= 1) {
} else if (!selected.hasControls && allStructures.size <= 1 && !anyPanels) {
ControlsProviderSelectorActivity::class.java
} else {
ControlsActivity::class.java
@@ -186,31 +209,49 @@ class ControlsUiControllerImpl @Inject constructor (
controlActionCoordinator.activityContext = activityContext
allStructures = controlsController.get().getFavorites()
selectedStructure = getPreferredStructure(allStructures)
selectedItem = getPreferredSelectedItem(allStructures)
if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
listingCallback = createCallback(::showSeedingView)
} else if (selectedStructure.controls.isEmpty() && allStructures.size <= 1) {
} else if (
selectedItem !is SelectedItem.PanelItem &&
!selectedItem.hasControls &&
allStructures.size <= 1
) {
// only show initial view if there are really no favorites across any structure
listingCallback = createCallback(::showInitialSetupView)
listingCallback = createCallback(::initialView)
} else {
selectedStructure.controls.map {
ControlWithState(selectedStructure.componentName, it, null)
}.associateByTo(controlsById) {
ControlKey(selectedStructure.componentName, it.ci.controlId)
val selected = selectedItem
if (selected is SelectedItem.StructureItem) {
selected.structure.controls.map {
ControlWithState(selected.structure.componentName, it, null)
}.associateByTo(controlsById) {
ControlKey(selected.structure.componentName, it.ci.controlId)
}
controlsController.get().subscribeToFavorites(selected.structure)
}
listingCallback = createCallback(::showControlsView)
controlsController.get().subscribeToFavorites(selectedStructure)
}
controlsListingController.get().addCallback(listingCallback)
}
private fun initialView(items: List<SelectionItem>) {
if (items.any { it.isPanel }) {
// We have at least a panel, so we'll end up showing that.
showControlsView(items)
} else {
showInitialSetupView(items)
}
}
private fun reload(parent: ViewGroup) {
if (hidden) return
controlsListingController.get().removeCallback(listingCallback)
controlsController.get().unsubscribe()
taskViewController?.dismiss()
taskViewController = null
val fadeAnim = ObjectAnimator.ofFloat(parent, "alpha", 1.0f, 0.0f)
fadeAnim.setInterpolator(AccelerateInterpolator(1.0f))
@@ -290,27 +331,90 @@ class ControlsUiControllerImpl @Inject constructor (
private fun showControlsView(items: List<SelectionItem>) {
controlViewsById.clear()
val itemsByComponent = items.associateBy { it.componentName }
val itemsWithStructure = mutableListOf<SelectionItem>()
allStructures.mapNotNullTo(itemsWithStructure) {
val (panels, structures) = items.partition { it.isPanel }
val panelComponents = panels.map { it.componentName }.toSet()
val itemsByComponent = structures.associateBy { it.componentName }
.filterNot { it.key in panelComponents }
val panelsAndStructures = mutableListOf<SelectionItem>()
allStructures.mapNotNullTo(panelsAndStructures) {
itemsByComponent.get(it.componentName)?.copy(structure = it.structure)
}
itemsWithStructure.sortWith(localeComparator)
panelsAndStructures.addAll(panels)
val selectionItem = findSelectionItem(selectedStructure, itemsWithStructure) ?: items[0]
panelsAndStructures.sortWith(localeComparator)
controlsMetricsLogger.refreshBegin(selectionItem.uid, !keyguardStateController.isUnlocked())
lastSelections = panelsAndStructures
createListView(selectionItem)
createDropDown(itemsWithStructure, selectionItem)
val selectionItem = findSelectionItem(selectedItem, panelsAndStructures)
?: if (panels.isNotEmpty()) {
// If we couldn't find a good selected item, but there's at least one panel,
// show a panel.
panels[0]
} else {
items[0]
}
maybeUpdateSelectedItem(selectionItem)
createControlsSpaceFrame()
if (taskViewFactory.isPresent && selectionItem.isPanel) {
createPanelView(selectionItem.panelComponentName!!)
} else if (!selectionItem.isPanel) {
controlsMetricsLogger
.refreshBegin(selectionItem.uid, !keyguardStateController.isUnlocked())
createListView(selectionItem)
} else {
Log.w(ControlsUiController.TAG, "Not TaskViewFactory to display panel $selectionItem")
}
createDropDown(panelsAndStructures, selectionItem)
createMenu()
}
private fun createMenu() {
val items = arrayOf(
context.resources.getString(R.string.controls_menu_add),
context.resources.getString(R.string.controls_menu_edit)
private fun createPanelView(componentName: ComponentName) {
val pendingIntent = PendingIntent.getActivity(
context,
0,
Intent().setComponent(componentName),
PendingIntent.FLAG_IMMUTABLE
)
parent.requireViewById<View>(R.id.controls_scroll_view).visibility = View.GONE
val container = parent.requireViewById<FrameLayout>(R.id.controls_panel)
container.visibility = View.VISIBLE
container.post {
taskViewFactory.get().create(activityContext, uiExecutor) { taskView ->
taskViewController = PanelTaskViewController(
activityContext,
uiExecutor,
pendingIntent,
taskView,
onDismiss::run
).also {
container.addView(taskView)
it.launchTaskView()
}
}
}
}
private fun createMenu() {
val isPanel = selectedItem is SelectedItem.PanelItem
val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure
?: EMPTY_STRUCTURE
val items = if (isPanel) {
arrayOf(
context.resources.getString(R.string.controls_menu_add),
)
} else {
arrayOf(
context.resources.getString(R.string.controls_menu_add),
context.resources.getString(R.string.controls_menu_edit)
)
}
var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items)
val anchor = parent.requireViewById<ImageView>(R.id.controls_more)
@@ -331,7 +435,13 @@ class ControlsUiControllerImpl @Inject constructor (
) {
when (pos) {
// 0: Add Control
0 -> startFavoritingActivity(selectedStructure)
0 -> {
if (isPanel) {
startProviderSelectorActivity()
} else {
startFavoritingActivity(selectedStructure)
}
}
// 1: Edit controls
1 -> startEditingActivity(selectedStructure)
}
@@ -353,6 +463,9 @@ class ControlsUiControllerImpl @Inject constructor (
addAll(items)
}
val iconSize = context.resources
.getDimensionPixelSize(R.dimen.controls_header_app_icon_size)
/*
* Default spinner widget does not work with the window type required
* for this dialog. Use a textView with the ListPopupWindow to achieve
@@ -363,14 +476,21 @@ class ControlsUiControllerImpl @Inject constructor (
// override the default color on the dropdown drawable
(getBackground() as LayerDrawable).getDrawable(0)
.setTint(context.resources.getColor(R.color.control_spinner_dropdown, null))
}
if (items.size == 1) {
spinner.setBackground(null)
return
selected.icon.setBounds(0, 0, iconSize, iconSize)
compoundDrawablePadding = (iconSize / 2.4f).toInt()
setCompoundDrawablesRelative(selected.icon, null, null, null)
}
val anchor = parent.requireViewById<ViewGroup>(R.id.controls_header)
if (items.size == 1) {
spinner.setBackground(null)
anchor.setOnClickListener(null)
return
} else {
spinner.background = parent.context.resources
.getDrawable(R.drawable.control_spinner_background)
}
anchor.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View) {
popup = GlobalActionsPopupMenu(
@@ -398,14 +518,20 @@ class ControlsUiControllerImpl @Inject constructor (
})
}
private fun createListView(selected: SelectionItem) {
val inflater = LayoutInflater.from(context)
private fun createControlsSpaceFrame() {
val inflater = LayoutInflater.from(activityContext)
inflater.inflate(R.layout.controls_with_favorites, parent, true)
parent.requireViewById<ImageView>(R.id.controls_close).apply {
setOnClickListener { _: View -> onDismiss.run() }
visibility = View.VISIBLE
}
}
private fun createListView(selected: SelectionItem) {
if (selectedItem !is SelectedItem.StructureItem) return
val selectedStructure = (selectedItem as SelectedItem.StructureItem).structure
val inflater = LayoutInflater.from(activityContext)
val maxColumns = ControlAdapter.findMaxColumns(activityContext.resources)
@@ -453,35 +579,51 @@ class ControlsUiControllerImpl @Inject constructor (
}
}
override fun getPreferredStructure(structures: List<StructureInfo>): StructureInfo {
if (structures.isEmpty()) return EMPTY_STRUCTURE
override fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem {
val sp = sharedPreferences
val component = sharedPreferences.getString(PREF_COMPONENT, null)?.let {
val component = sp.getString(PREF_COMPONENT, null)?.let {
ComponentName.unflattenFromString(it)
} ?: EMPTY_COMPONENT
val structure = sharedPreferences.getString(PREF_STRUCTURE, "")
return structures.firstOrNull {
component == it.componentName && structure == it.structure
} ?: structures.get(0)
val name = sp.getString(PREF_STRUCTURE_OR_APP_NAME, "")!!
val isPanel = sp.getBoolean(PREF_IS_PANEL, false)
return if (isPanel) {
SelectedItem.PanelItem(name, component)
} else {
if (structures.isEmpty()) return SelectedItem.EMPTY_SELECTION
SelectedItem.StructureItem(structures.firstOrNull {
component == it.componentName && name == it.structure
} ?: structures.get(0))
}
}
private fun updatePreferences(si: StructureInfo) {
if (si == EMPTY_STRUCTURE) return
private fun updatePreferences(si: SelectedItem) {
sharedPreferences.edit()
.putString(PREF_COMPONENT, si.componentName.flattenToString())
.putString(PREF_STRUCTURE, si.structure.toString())
.commit()
.putString(PREF_COMPONENT, si.componentName.flattenToString())
.putString(PREF_STRUCTURE_OR_APP_NAME, si.name.toString())
.putBoolean(PREF_IS_PANEL, si is SelectedItem.PanelItem)
.commit()
}
private fun maybeUpdateSelectedItem(item: SelectionItem): Boolean {
val newSelection = if (item.isPanel) {
SelectedItem.PanelItem(item.appName, item.componentName)
} else {
SelectedItem.StructureItem(allStructures.firstOrNull {
it.structure == item.structure && it.componentName == item.componentName
} ?: EMPTY_STRUCTURE)
}
return if (newSelection != selectedItem ) {
selectedItem = newSelection
updatePreferences(selectedItem)
true
} else {
false
}
}
private fun switchAppOrStructure(item: SelectionItem) {
val newSelection = allStructures.first {
it.structure == item.structure && it.componentName == item.componentName
}
if (newSelection != selectedStructure) {
selectedStructure = newSelection
updatePreferences(selectedStructure)
if (maybeUpdateSelectedItem(item)) {
reload(parent)
}
}
@@ -505,6 +647,8 @@ class ControlsUiControllerImpl @Inject constructor (
closeDialogs(true)
controlsController.get().unsubscribe()
taskViewController?.dismiss()
taskViewController = null
parent.removeAllViews()
controlsById.clear()
@@ -545,20 +689,46 @@ class ControlsUiControllerImpl @Inject constructor (
return row
}
private fun findSelectionItem(si: StructureInfo, items: List<SelectionItem>): SelectionItem? =
items.firstOrNull {
it.componentName == si.componentName && it.structure == si.structure
private fun findSelectionItem(si: SelectedItem, items: List<SelectionItem>): SelectionItem? =
items.firstOrNull { it.matches(si) }
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.println("ControlsUiControllerImpl:")
pw.asIndenting().indentIfPossible {
println("hidden: $hidden")
println("selectedItem: $selectedItem")
println("lastSelections: $lastSelections")
}
}
}
private data class SelectionItem(
@VisibleForTesting
internal data class SelectionItem(
val appName: CharSequence,
val structure: CharSequence,
val icon: Drawable,
val componentName: ComponentName,
val uid: Int
val uid: Int,
val panelComponentName: ComponentName?
) {
fun getTitle() = if (structure.isEmpty()) { appName } else { structure }
val isPanel: Boolean = panelComponentName != null
fun matches(selectedItem: SelectedItem): Boolean {
if (componentName != selectedItem.componentName) {
// Not the same component so they are not the same.
return false
}
if (isPanel || selectedItem is SelectedItem.PanelItem) {
// As they have the same component, if [this.isPanel] then we may be migrating from
// device controls API into panel. Want this to match, even if the selectedItem is not
// a panel. We don't want to match on app name because that can change with locale.
return true
}
// Return true if we find a structure with the correct name
return structure == (selectedItem as SelectedItem.StructureItem).structure.structure
}
}
private class ItemAdapter(

View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.android.systemui.controls.ui
import android.app.ActivityOptions
import android.app.ActivityTaskManager
import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import com.android.systemui.util.boundsOnScreen
import com.android.wm.shell.TaskView
import java.util.concurrent.Executor
class PanelTaskViewController(
private val activityContext: Context,
private val uiExecutor: Executor,
private val pendingIntent: PendingIntent,
private val taskView: TaskView,
private val hide: () -> Unit = {}
) {
private var detailTaskId = INVALID_TASK_ID
private val fillInIntent =
Intent().apply {
// Apply flags to make behaviour match documentLaunchMode=always.
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
}
private fun removeDetailTask() {
if (detailTaskId == INVALID_TASK_ID) return
ActivityTaskManager.getInstance().removeTask(detailTaskId)
detailTaskId = INVALID_TASK_ID
}
private val stateCallback =
object : TaskView.Listener {
override fun onInitialized() {
val options =
ActivityOptions.makeCustomAnimation(
activityContext,
0 /* enterResId */,
0 /* exitResId */
)
options.taskAlwaysOnTop = true
taskView.post {
taskView.startActivity(
pendingIntent,
fillInIntent,
options,
taskView.boundsOnScreen
)
}
}
override fun onTaskRemovalStarted(taskId: Int) {
detailTaskId = INVALID_TASK_ID
dismiss()
}
override fun onTaskCreated(taskId: Int, name: ComponentName?) {
detailTaskId = taskId
}
override fun onReleased() {
removeDetailTask()
}
override fun onBackPressedOnTaskRoot(taskId: Int) {
dismiss()
hide()
}
}
fun dismiss() {
taskView.release()
}
fun launchTaskView() {
taskView.setListener(uiExecutor, stateCallback)
}
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright (C) 2020 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.globalactions;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ScrollView;
/**
* When measured, this view sets the minimum height of its first child to be equal to its own
* target height.
*
* This ensures fall-through click handlers can be placed on this view's child component.
*/
public class MinHeightScrollView extends ScrollView {
public MinHeightScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
View firstChild = getChildAt(0);
if (firstChild != null) {
firstChild.setMinimumHeight(MeasureSpec.getSize(heightMeasureSpec));
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

View File

@@ -32,6 +32,7 @@ import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE
import com.android.systemui.controls.management.ControlsListingController
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.plugins.ActivityStarter
@@ -125,14 +126,15 @@ class DeviceControlsTile @Inject constructor(
state.icon = icon
if (controlsComponent.isEnabled() && hasControlsApps.get()) {
if (controlsComponent.getVisibility() == AVAILABLE) {
val structureInfo = controlsComponent
.getControlsController().get().getPreferredStructure()
state.state = if (structureInfo.controls.isEmpty()) {
val selection = controlsComponent
.getControlsController().get().getPreferredSelection()
state.state = if (selection is SelectedItem.StructureItem &&
selection.structure.controls.isEmpty()) {
Tile.STATE_INACTIVE
} else {
Tile.STATE_ACTIVE
}
val label = structureInfo.structure
val label = selection.name
state.secondaryLabel = if (label == tileLabel) null else label
} else {
state.state = Tile.STATE_INACTIVE

View File

@@ -140,6 +140,9 @@ public class DeviceControlsControllerImpl @Inject constructor(
// is out of sync, perhaps through a device restore, and update the
// preference
addPackageToSeededSet(prefs, pkg)
} else if (it.panelActivity != null) {
// Do not seed for packages with panels
addPackageToSeededSet(prefs, pkg)
} else {
componentsToSeed.add(it.componentName)
}

View File

@@ -27,6 +27,7 @@ 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.mock
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -34,9 +35,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@@ -70,7 +70,7 @@ class AppAdapterTest : SysuiTestCase() {
fun testOnServicesUpdated_nullLoadLabel() {
val captor = ArgumentCaptor
.forClass(ControlsListingController.ControlsListingCallback::class.java)
val controlsServiceInfo = mock(ControlsServiceInfo::class.java)
val controlsServiceInfo = mock<ControlsServiceInfo>()
val serviceInfo = listOf(controlsServiceInfo)
`when`(controlsServiceInfo.loadLabel()).thenReturn(null)
verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
@@ -81,4 +81,32 @@ class AppAdapterTest : SysuiTestCase() {
assertThat(adapter.itemCount).isEqualTo(serviceInfo.size)
}
@Test
fun testOnServicesUpdatedDoesntHavePanels() {
val captor = ArgumentCaptor
.forClass(ControlsListingController.ControlsListingCallback::class.java)
val serviceInfo = listOf(
ControlsServiceInfo("no panel", null),
ControlsServiceInfo("panel", mock())
)
verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
captor.value.onServicesUpdated(serviceInfo)
backgroundExecutor.runAllReady()
uiExecutor.runAllReady()
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())
}
}
}

View File

@@ -17,8 +17,10 @@
package com.android.systemui.controls.ui
import android.content.ComponentName
import android.content.Context
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsMetricsLogger
@@ -26,6 +28,7 @@ import com.android.systemui.controls.CustomIconCache
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.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
@@ -34,9 +37,12 @@ import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.time.FakeSystemClock
import com.android.wm.shell.TaskViewFactory
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,7 +50,7 @@ import org.mockito.Mock
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyString
import org.mockito.Mockito.mock
import org.mockito.Mockito.times
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -63,16 +69,22 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
@Mock lateinit var keyguardStateController: KeyguardStateController
@Mock lateinit var userFileManager: UserFileManager
@Mock lateinit var userTracker: UserTracker
@Mock lateinit var taskViewFactory: TaskViewFactory
@Mock lateinit var activityContext: Context
@Mock lateinit var dumpManager: DumpManager
val sharedPreferences = FakeSharedPreferences()
var uiExecutor = FakeExecutor(FakeSystemClock())
var bgExecutor = FakeExecutor(FakeSystemClock())
lateinit var underTest: ControlsUiControllerImpl
lateinit var parent: FrameLayout
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
parent = FrameLayout(mContext)
underTest =
ControlsUiControllerImpl(
Lazy { controlsController },
@@ -82,12 +94,13 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
Lazy { controlsListingController },
controlActionCoordinator,
activityStarter,
shadeController,
iconCache,
controlsMetricsLogger,
keyguardStateController,
userFileManager,
userTracker
userTracker,
Optional.of(taskViewFactory),
dumpManager
)
`when`(
userFileManager.getSharedPreferences(
@@ -105,8 +118,8 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
@Test
fun testGetPreferredStructure() {
val structureInfo = mock(StructureInfo::class.java)
underTest.getPreferredStructure(listOf(structureInfo))
verify(userFileManager, times(2))
underTest.getPreferredSelectedItem(listOf(structureInfo))
verify(userFileManager)
.getSharedPreferences(
fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
mode = 0,
@@ -116,25 +129,30 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
@Test
fun testGetPreferredStructure_differentUserId() {
val structureInfo =
val selectedItems =
listOf(
StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList()),
StructureInfo(ComponentName.unflattenFromString("pkg/.cls2"), "b", ArrayList()),
SelectedItem.StructureItem(
StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
),
SelectedItem.StructureItem(
StructureInfo(ComponentName.unflattenFromString("pkg/.cls2"), "b", ArrayList())
),
)
val structures = selectedItems.map { it.structure }
sharedPreferences
.edit()
.putString("controls_component", structureInfo[0].componentName.flattenToString())
.putString("controls_structure", structureInfo[0].structure.toString())
.putString("controls_component", selectedItems[0].componentName.flattenToString())
.putString("controls_structure", selectedItems[0].name.toString())
.commit()
val differentSharedPreferences = FakeSharedPreferences()
differentSharedPreferences
.edit()
.putString("controls_component", structureInfo[1].componentName.flattenToString())
.putString("controls_structure", structureInfo[1].structure.toString())
.putString("controls_component", selectedItems[1].componentName.flattenToString())
.putString("controls_structure", selectedItems[1].name.toString())
.commit()
val previousPreferredStructure = underTest.getPreferredStructure(structureInfo)
val previousPreferredStructure = underTest.getPreferredSelectedItem(structures)
`when`(
userFileManager.getSharedPreferences(
@@ -146,10 +164,39 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
.thenReturn(differentSharedPreferences)
`when`(userTracker.userId).thenReturn(1)
val currentPreferredStructure = underTest.getPreferredStructure(structureInfo)
val currentPreferredStructure = underTest.getPreferredSelectedItem(structures)
assertThat(previousPreferredStructure).isEqualTo(structureInfo[0])
assertThat(currentPreferredStructure).isEqualTo(structureInfo[1])
assertThat(previousPreferredStructure).isEqualTo(selectedItems[0])
assertThat(currentPreferredStructure).isEqualTo(selectedItems[1])
assertThat(currentPreferredStructure).isNotEqualTo(previousPreferredStructure)
}
@Test
fun testGetPreferredPanel() {
val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
sharedPreferences
.edit()
.putString("controls_component", panel.componentName.flattenToString())
.putString("controls_structure", panel.appName.toString())
.putBoolean("controls_is_panel", true)
.commit()
val selected = underTest.getPreferredSelectedItem(emptyList())
assertThat(selected).isEqualTo(panel)
}
@Test
fun testPanelDoesNotRefreshControls() {
val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
sharedPreferences
.edit()
.putString("controls_component", panel.componentName.flattenToString())
.putString("controls_structure", panel.appName.toString())
.putBoolean("controls_is_panel", true)
.commit()
underTest.show(parent, {}, activityContext)
verify(controlsController, never()).refreshStatus(any(), any())
}
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.android.systemui.controls.ui
import android.app.ActivityOptions
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.boundsOnScreen
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.eq
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.android.wm.shell.TaskView
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class PanelTaskViewControllerTest : SysuiTestCase() {
companion object {
val FAKE_BOUNDS = Rect(10, 20, 30, 40)
}
@Mock private lateinit var activityContext: Context
@Mock private lateinit var taskView: TaskView
@Mock private lateinit var pendingIntent: PendingIntent
@Mock private lateinit var hideRunnable: () -> Unit
@Captor private lateinit var listenerCaptor: ArgumentCaptor<TaskView.Listener>
private lateinit var uiExecutor: FakeExecutor
private lateinit var underTest: PanelTaskViewController
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(taskView.boundsOnScreen).thenAnswer { (it.arguments[0] as Rect).set(FAKE_BOUNDS) }
whenever(taskView.post(any())).thenAnswer {
uiExecutor.execute(it.arguments[0] as Runnable)
true
}
uiExecutor = FakeExecutor(FakeSystemClock())
underTest =
PanelTaskViewController(
activityContext,
uiExecutor,
pendingIntent,
taskView,
hideRunnable
)
}
@Test
fun testLaunchTaskViewAttachedListener() {
underTest.launchTaskView()
verify(taskView).setListener(eq(uiExecutor), any())
}
@Test
fun testTaskViewOnInitializeStartsActivity() {
underTest.launchTaskView()
verify(taskView).setListener(any(), capture(listenerCaptor))
listenerCaptor.value.onInitialized()
uiExecutor.runAllReady()
val intentCaptor = argumentCaptor<Intent>()
val optionsCaptor = argumentCaptor<ActivityOptions>()
verify(taskView)
.startActivity(
eq(pendingIntent),
/* fillInIntent */ capture(intentCaptor),
capture(optionsCaptor),
eq(FAKE_BOUNDS)
)
assertThat(intentCaptor.value.flags)
.isEqualTo(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_MULTIPLE_TASK)
assertThat(optionsCaptor.value.taskAlwaysOnTop).isTrue()
}
@Test
fun testHideRunnableCalledWhenBackOnRoot() {
underTest.launchTaskView()
verify(taskView).setListener(any(), capture(listenerCaptor))
listenerCaptor.value.onBackPressedOnTaskRoot(0)
verify(hideRunnable).invoke()
}
@Test
fun testTaskViewReleasedOnDismiss() {
underTest.dismiss()
verify(taskView).release()
}
@Test
fun testTaskViewReleasedOnBackOnRoot() {
underTest.launchTaskView()
verify(taskView).setListener(any(), capture(listenerCaptor))
listenerCaptor.value.onBackPressedOnTaskRoot(0)
verify(taskView).release()
}
@Test
fun testOnTaskRemovalStarted() {
underTest.launchTaskView()
verify(taskView).setListener(any(), capture(listenerCaptor))
listenerCaptor.value.onTaskRemovalStarted(0)
verify(taskView).release()
}
}

View File

@@ -0,0 +1,112 @@
package com.android.systemui.controls.ui
import android.content.ComponentName
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidTestingRunner::class)
class SelectionItemTest : SysuiTestCase() {
@Test
fun testMatchBadComponentName_false() {
val selectionItem =
SelectionItem(
appName = "app",
structure = "structure",
icon = mock(),
componentName = ComponentName("pkg", "cls"),
uid = 0,
panelComponentName = null
)
assertThat(
selectionItem.matches(
SelectedItem.StructureItem(
StructureInfo(ComponentName("", ""), "s", emptyList())
)
)
)
.isFalse()
assertThat(selectionItem.matches(SelectedItem.PanelItem("name", ComponentName("", ""))))
.isFalse()
}
@Test
fun testMatchSameComponentName_panelSelected_true() {
val componentName = ComponentName("pkg", "cls")
val selectionItem =
SelectionItem(
appName = "app",
structure = "structure",
icon = mock(),
componentName = componentName,
uid = 0,
panelComponentName = null
)
assertThat(selectionItem.matches(SelectedItem.PanelItem("name", componentName))).isTrue()
}
@Test
fun testMatchSameComponentName_panelSelection_true() {
val componentName = ComponentName("pkg", "cls")
val selectionItem =
SelectionItem(
appName = "app",
structure = "structure",
icon = mock(),
componentName = componentName,
uid = 0,
panelComponentName = ComponentName("pkg", "panel")
)
assertThat(selectionItem.matches(SelectedItem.PanelItem("name", componentName))).isTrue()
}
@Test
fun testMatchSameComponentSameStructure_true() {
val componentName = ComponentName("pkg", "cls")
val structureName = "structure"
val structureItem =
SelectedItem.StructureItem(StructureInfo(componentName, structureName, emptyList()))
val selectionItem =
SelectionItem(
appName = "app",
structure = structureName,
icon = mock(),
componentName = componentName,
uid = 0,
panelComponentName = null
)
assertThat(selectionItem.matches(structureItem)).isTrue()
}
@Test
fun testMatchSameComponentDifferentStructure_false() {
val componentName = ComponentName("pkg", "cls")
val structureName = "structure"
val structureItem =
SelectedItem.StructureItem(StructureInfo(componentName, structureName, emptyList()))
val selectionItem =
SelectionItem(
appName = "app",
structure = "other",
icon = mock(),
componentName = componentName,
uid = 0,
panelComponentName = null
)
assertThat(selectionItem.matches(structureItem)).isFalse()
}
}

View File

@@ -40,6 +40,7 @@ import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
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.plugins.ActivityStarter
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
@@ -118,8 +119,9 @@ class DeviceControlsTileTest : SysuiTestCase() {
`when`(qsHost.context).thenReturn(spiedContext)
`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
`when`(controlsComponent.isEnabled()).thenReturn(true)
`when`(controlsController.getPreferredStructure())
.thenReturn(StructureInfo(ComponentName("pkg", "cls"), "structure", listOf()))
`when`(controlsController.getPreferredSelection())
.thenReturn(SelectedItem.StructureItem(
StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())))
secureSettings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 1)
setupControlsComponent()
@@ -226,12 +228,12 @@ class DeviceControlsTileTest : SysuiTestCase() {
capture(listingCallbackCaptor)
)
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
`when`(controlsController.getPreferredStructure()).thenReturn(
StructureInfo(
`when`(controlsController.getPreferredSelection()).thenReturn(
SelectedItem.StructureItem(StructureInfo(
ComponentName("pkg", "cls"),
"structure",
listOf(ControlInfo("id", "title", "subtitle", 1))
)
))
)
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
@@ -247,8 +249,9 @@ class DeviceControlsTileTest : SysuiTestCase() {
capture(listingCallbackCaptor)
)
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
`when`(controlsController.getPreferredStructure())
.thenReturn(StructureInfo(ComponentName("pkg", "cls"), "structure", listOf()))
`when`(controlsController.getPreferredSelection())
.thenReturn(SelectedItem.StructureItem(
StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())))
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
testableLooper.processAllMessages()
@@ -256,6 +259,22 @@ class DeviceControlsTileTest : SysuiTestCase() {
assertThat(tile.state.state).isEqualTo(Tile.STATE_INACTIVE)
}
@Test
fun testStateActiveIfPreferredIsPanel() {
verify(controlsListingController).observe(
any(LifecycleOwner::class.java),
capture(listingCallbackCaptor)
)
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
`when`(controlsController.getPreferredSelection())
.thenReturn(SelectedItem.PanelItem("appName", ComponentName("pkg", "cls")))
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
testableLooper.processAllMessages()
assertThat(tile.state.state).isEqualTo(Tile.STATE_ACTIVE)
}
@Test
fun testStateInactiveIfLocked() {
verify(controlsListingController).observe(
@@ -303,12 +322,12 @@ class DeviceControlsTileTest : SysuiTestCase() {
)
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
`when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
`when`(controlsController.getPreferredStructure()).thenReturn(
StructureInfo(
ComponentName("pkg", "cls"),
"structure",
listOf(ControlInfo("id", "title", "subtitle", 1))
)
`when`(controlsController.getPreferredSelection()).thenReturn(
SelectedItem.StructureItem(StructureInfo(
ComponentName("pkg", "cls"),
"structure",
listOf(ControlInfo("id", "title", "subtitle", 1))
))
)
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
@@ -334,12 +353,12 @@ class DeviceControlsTileTest : SysuiTestCase() {
`when`(controlsComponent.getVisibility())
.thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK)
`when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
`when`(controlsController.getPreferredStructure()).thenReturn(
StructureInfo(
`when`(controlsController.getPreferredSelection()).thenReturn(
SelectedItem.StructureItem(StructureInfo(
ComponentName("pkg", "cls"),
"structure",
listOf(ControlInfo("id", "title", "subtitle", 1))
)
))
)
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))