Merge changes from topic "258442632" into tm-qpr-dev
* changes:
Add panel using TaskView
Distinguish between selected panel or structure
diff --git a/packages/SystemUI/res/layout-sw600dp/global_actions_controls_list_view.xml b/packages/SystemUI/res/layout-sw600dp/global_actions_controls_list_view.xml
deleted file mode 100644
index ef49b9c..0000000
--- a/packages/SystemUI/res/layout-sw600dp/global_actions_controls_list_view.xml
+++ /dev/null
@@ -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>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_fullscreen.xml b/packages/SystemUI/res/layout/controls_fullscreen.xml
index 11a5665..e08e63b 100644
--- a/packages/SystemUI/res/layout/controls_fullscreen.xml
+++ b/packages/SystemUI/res/layout/controls_fullscreen.xml
@@ -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>
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index 9d01148..9efad22 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -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>
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index 31fadb1..2f49c3f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -24,6 +24,7 @@
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 @@
*/
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].
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 50ce9d4..bdfe1fb 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -38,6 +38,7 @@
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 @@
)
}
- 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>) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/StructureInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/StructureInfo.kt
index 34bfa13..c8090bf 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/StructureInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/StructureInfo.kt
@@ -31,4 +31,9 @@
val componentName: ComponentName,
val structure: CharSequence,
val controls: List<ControlInfo>
-)
+) {
+ companion object {
+ val EMPTY_COMPONENT = ComponentName("", "")
+ val EMPTY_STRUCTURE = StructureInfo(EMPTY_COMPONENT, "", mutableListOf())
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
index 2389ad1..753d5ad 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
@@ -64,7 +64,8 @@
val localeComparator = compareBy<ControlsServiceInfo, CharSequence>(collator) {
it.loadLabel() ?: ""
}
- listOfServices = serviceInfos.sortedWith(localeComparator)
+ listOfServices = serviceInfos.filter { it.panelActivity == null }
+ .sortedWith(localeComparator)
uiExecutor.execute(::notifyDataSetChanged)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index d3b5d0e..bd704c1 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -27,10 +27,13 @@
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 @@
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 @@
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)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index c1cfbcb..f5c5905 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -35,7 +35,7 @@
/**
* 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 @@
)
/**
- * 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)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 6cb0e8b..4c8e1ac 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -21,6 +21,7 @@
import android.animation.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.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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
) {
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 @@
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 @@
// 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 @@
})
}
- 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 @@
}
}
- 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 @@
closeDialogs(true)
controlsController.get().unsubscribe()
+ taskViewController?.dismiss()
+ taskViewController = null
parent.removeAllViews()
controlsById.clear()
@@ -545,20 +689,46 @@
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(
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
new file mode 100644
index 0000000..7143be2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
@@ -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)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java b/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java
deleted file mode 100644
index 622fa65..0000000
--- a/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java
+++ /dev/null
@@ -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);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index c65bd9b..41d8549 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -32,6 +32,7 @@
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 @@
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
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
index e326611..6c66f0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -140,6 +140,9 @@
// 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)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
index 1e4a9e4..765c4c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
@@ -27,6 +27,7 @@
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.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 @@
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 @@
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())
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 49c7442..e679b13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -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.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.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.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 @@
@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 @@
Lazy { controlsListingController },
controlActionCoordinator,
activityStarter,
- shadeController,
iconCache,
controlsMetricsLogger,
keyguardStateController,
userFileManager,
- userTracker
+ userTracker,
+ Optional.of(taskViewFactory),
+ dumpManager
)
`when`(
userFileManager.getSharedPreferences(
@@ -105,8 +118,8 @@
@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 @@
@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 @@
.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())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
new file mode 100644
index 0000000..5cd2ace
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
@@ -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()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/SelectionItemTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/SelectionItemTest.kt
new file mode 100644
index 0000000..57176f0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/SelectionItemTest.kt
@@ -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()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index f7b9438e..e0b3125 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -40,6 +40,7 @@
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 @@
`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 @@
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 @@
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()
@@ -257,6 +260,22 @@
}
@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(
any(LifecycleOwner::class.java),
@@ -303,12 +322,12 @@
)
`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 @@
`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))