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 ef49b9c3b6adb..0000000000000
--- a/packages/SystemUI/res/layout-sw600dp/global_actions_controls_list_view.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
\ 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 11a566588738a..e08e63b39e59a 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.
-->
-
-
-
-
+
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index 9d011482d0114..9efad22694634 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -18,7 +18,7 @@
@@ -71,5 +71,27 @@
android:background="?android:attr/selectableItemBackgroundBorderless" />
-
+
+
+
+
+
+
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 2389ad134c55e..753d5addeb114 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 @@ class AppAdapter(
val localeComparator = compareBy(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 d3b5d0edd2221..bd704c1ff0861 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.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)
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 180e09e5aaf02..f5c5905779a14 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 @@ 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<*>
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 905636f563d13..4c8e1ac968f9a 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.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,12 +37,14 @@ 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
@@ -49,6 +52,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.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
@@ -57,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
@@ -81,13 +90,14 @@ class ControlsUiControllerImpl @Inject constructor (
val controlsListingController: Lazy,
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,
+ dumpManager: DumpManager
+) : ControlsUiController, Dumpable {
companion object {
private const val PREF_COMPONENT = "controls_component"
@@ -107,6 +117,7 @@ class ControlsUiControllerImpl @Inject constructor (
private lateinit var onDismiss: Runnable
private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow)
private var retainCache = false
+ private var lastSelections = emptyList()
private val sharedPreferences
get() = userFileManager.getSharedPreferences(
fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
@@ -114,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(collator) {
it.getTitle()
@@ -135,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) -> Unit
): ControlsListingController.ControlsListingCallback {
@@ -165,10 +182,12 @@ class ControlsUiControllerImpl @Inject constructor (
override fun resolveActivity(): Class<*> {
val allStructures = controlsController.get().getFavorites()
val selected = getPreferredSelectedItem(allStructures)
+ val anyPanels = controlsListingController.get().getCurrentServices()
+ .none { it.panelActivity != null }
return if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
ControlsActivity::class.java
- } else if (!selected.hasControls && allStructures.size <= 1) {
+ } else if (!selected.hasControls && allStructures.size <= 1 && !anyPanels) {
ControlsProviderSelectorActivity::class.java
} else {
ControlsActivity::class.java
@@ -194,9 +213,13 @@ class ControlsUiControllerImpl @Inject constructor (
if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
listingCallback = createCallback(::showSeedingView)
- } else if (!selectedItem.hasControls && 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 {
val selected = selectedItem
if (selected is SelectedItem.StructureItem) {
@@ -213,11 +236,22 @@ class ControlsUiControllerImpl @Inject constructor (
controlsListingController.get().addCallback(listingCallback)
}
+ private fun initialView(items: List) {
+ 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))
@@ -297,29 +331,90 @@ class ControlsUiControllerImpl @Inject constructor (
private fun showControlsView(items: List) {
controlViewsById.clear()
- val itemsByComponent = items.associateBy { it.componentName }
- val itemsWithStructure = mutableListOf()
- 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()
+ allStructures.mapNotNullTo(panelsAndStructures) {
itemsByComponent.get(it.componentName)?.copy(structure = it.structure)
}
- itemsWithStructure.sortWith(localeComparator)
+ panelsAndStructures.addAll(panels)
- val selectionItem = findSelectionItem(selectedItem, 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() {
- if (selectedItem !is SelectedItem.StructureItem) return
- val selectedStructure = (selectedItem as SelectedItem.StructureItem).structure
- 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(R.id.controls_scroll_view).visibility = View.GONE
+ val container = parent.requireViewById(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(context, R.layout.controls_more_item, items)
val anchor = parent.requireViewById(R.id.controls_more)
@@ -340,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)
}
@@ -362,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
@@ -372,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(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(
@@ -407,16 +518,20 @@ class ControlsUiControllerImpl @Inject constructor (
})
}
- private fun createListView(selected: SelectionItem) {
- if (selectedItem !is SelectedItem.StructureItem) return
- val selectedStructure = (selectedItem as SelectedItem.StructureItem).structure
- val inflater = LayoutInflater.from(context)
+ private fun createControlsSpaceFrame() {
+ val inflater = LayoutInflater.from(activityContext)
inflater.inflate(R.layout.controls_with_favorites, parent, true)
parent.requireViewById(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)
@@ -490,18 +605,25 @@ class ControlsUiControllerImpl @Inject constructor (
.commit()
}
- private fun switchAppOrStructure(item: SelectionItem) {
+ private fun maybeUpdateSelectedItem(item: SelectionItem): Boolean {
val newSelection = if (item.isPanel) {
SelectedItem.PanelItem(item.appName, item.componentName)
} else {
- SelectedItem.StructureItem(allStructures.first {
+ SelectedItem.StructureItem(allStructures.firstOrNull {
it.structure == item.structure && it.componentName == item.componentName
- })
+ } ?: EMPTY_STRUCTURE)
}
-
- if (newSelection != selectedItem) {
+ return if (newSelection != selectedItem ) {
selectedItem = newSelection
updatePreferences(selectedItem)
+ true
+ } else {
+ false
+ }
+ }
+
+ private fun switchAppOrStructure(item: SelectionItem) {
+ if (maybeUpdateSelectedItem(item)) {
reload(parent)
}
}
@@ -525,6 +647,8 @@ class ControlsUiControllerImpl @Inject constructor (
closeDialogs(true)
controlsController.get().unsubscribe()
+ taskViewController?.dismiss()
+ taskViewController = null
parent.removeAllViews()
controlsById.clear()
@@ -567,6 +691,15 @@ class ControlsUiControllerImpl @Inject constructor (
private fun findSelectionItem(si: SelectedItem, items: List): SelectionItem? =
items.firstOrNull { it.matches(si) }
+
+ override fun dump(pw: PrintWriter, args: Array) {
+ pw.println("ControlsUiControllerImpl:")
+ pw.asIndenting().indentIfPossible {
+ println("hidden: $hidden")
+ println("selectedItem: $selectedItem")
+ println("lastSelections: $lastSelections")
+ }
+ }
}
@VisibleForTesting
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 0000000000000..7143be298a9db
--- /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 622fa658f1b0e..0000000000000
--- 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/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
index e3266115f7779..6c66f0bb1e475 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 @@ 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)
}
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 1e4a9e42cce13..765c4c0ac0f07 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.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()
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())
+ }
+ }
}
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 a86cb00b3a818..e679b1391c770 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.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,6 +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.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -62,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 },
@@ -81,12 +94,13 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
Lazy { controlsListingController },
controlActionCoordinator,
activityStarter,
- shadeController,
iconCache,
controlsMetricsLogger,
keyguardStateController,
userFileManager,
- userTracker
+ userTracker,
+ Optional.of(taskViewFactory),
+ dumpManager
)
`when`(
userFileManager.getSharedPreferences(
@@ -171,4 +185,18 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
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 0000000000000..5cd2ace4604a4
--- /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
+
+ 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()
+ val optionsCaptor = argumentCaptor()
+
+ 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()
+ }
+}