Merge changes from topic "258442632" into tm-qpr-dev am: e1833a3a2d

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20469410

Change-Id: I3b93f60da441aa3d1284216dcfd0dd362904da1a
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Fabian Kozynski
2022-11-22 14:57:01 +00:00
committed by Automerger Merge Worker
13 changed files with 525 additions and 129 deletions

View File

@@ -1,30 +0,0 @@
<!--
~ Copyright (C) 2020 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<com.android.systemui.globalactions.MinHeightScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:scrollbars="none">
<LinearLayout
android:id="@+id/global_actions_controls_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="@dimen/global_actions_side_margin"
android:layout_marginRight="@dimen/global_actions_side_margin" />
</com.android.systemui.globalactions.MinHeightScrollView>

View File

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

View File

@@ -18,7 +18,7 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_marginTop="@dimen/controls_top_margin" android:layout_marginTop="@dimen/controls_top_margin"
android:layout_marginBottom="@dimen/controls_header_bottom_margin"> android:layout_marginBottom="@dimen/controls_header_bottom_margin">
@@ -71,5 +71,27 @@
android:background="?android:attr/selectableItemBackgroundBorderless" /> android:background="?android:attr/selectableItemBackgroundBorderless" />
</LinearLayout> </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> </merge>

View File

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

View File

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

View File

@@ -35,7 +35,7 @@ interface ControlsUiController {
/** /**
* Returns the preferred activity to start, depending on if the user has favorited any * 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<*> fun resolveActivity(): Class<*>

View File

@@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.app.Activity import android.app.Activity
import android.app.ActivityOptions import android.app.ActivityOptions
import android.app.PendingIntent
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@@ -36,12 +37,14 @@ import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.ListPopupWindow import android.widget.ListPopupWindow
import android.widget.Space import android.widget.Space
import android.widget.TextView import android.widget.TextView
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsServiceInfo 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.ControlsController
import com.android.systemui.controls.controller.StructureInfo 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_COMPONENT
import com.android.systemui.controls.controller.StructureInfo.Companion.EMPTY_STRUCTURE
import com.android.systemui.controls.management.ControlAdapter import com.android.systemui.controls.management.ControlAdapter
import com.android.systemui.controls.management.ControlsEditingActivity import com.android.systemui.controls.management.ControlsEditingActivity
import com.android.systemui.controls.management.ControlsFavoritingActivity 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.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.globalactions.GlobalActionsPopupMenu import com.android.systemui.globalactions.GlobalActionsPopupMenu
import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker 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.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController 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.concurrency.DelayableExecutor
import com.android.systemui.util.indentIfPossible
import com.android.wm.shell.TaskViewFactory
import dagger.Lazy import dagger.Lazy
import java.io.PrintWriter
import java.text.Collator import java.text.Collator
import java.util.Optional
import java.util.function.Consumer import java.util.function.Consumer
import javax.inject.Inject import javax.inject.Inject
@@ -81,13 +90,14 @@ class ControlsUiControllerImpl @Inject constructor (
val controlsListingController: Lazy<ControlsListingController>, val controlsListingController: Lazy<ControlsListingController>,
val controlActionCoordinator: ControlActionCoordinator, val controlActionCoordinator: ControlActionCoordinator,
private val activityStarter: ActivityStarter, private val activityStarter: ActivityStarter,
private val shadeController: ShadeController,
private val iconCache: CustomIconCache, private val iconCache: CustomIconCache,
private val controlsMetricsLogger: ControlsMetricsLogger, private val controlsMetricsLogger: ControlsMetricsLogger,
private val keyguardStateController: KeyguardStateController, private val keyguardStateController: KeyguardStateController,
private val userFileManager: UserFileManager, private val userFileManager: UserFileManager,
private val userTracker: UserTracker, private val userTracker: UserTracker,
) : ControlsUiController { private val taskViewFactory: Optional<TaskViewFactory>,
dumpManager: DumpManager
) : ControlsUiController, Dumpable {
companion object { companion object {
private const val PREF_COMPONENT = "controls_component" private const val PREF_COMPONENT = "controls_component"
@@ -107,6 +117,7 @@ class ControlsUiControllerImpl @Inject constructor (
private lateinit var onDismiss: Runnable private lateinit var onDismiss: Runnable
private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow) private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow)
private var retainCache = false private var retainCache = false
private var lastSelections = emptyList<SelectionItem>()
private val sharedPreferences private val sharedPreferences
get() = userFileManager.getSharedPreferences( get() = userFileManager.getSharedPreferences(
fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE, fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
@@ -114,6 +125,8 @@ class ControlsUiControllerImpl @Inject constructor (
userId = userTracker.userId userId = userTracker.userId
) )
private var taskViewController: PanelTaskViewController? = null
private val collator = Collator.getInstance(context.resources.configuration.locales[0]) private val collator = Collator.getInstance(context.resources.configuration.locales[0])
private val localeComparator = compareBy<SelectionItem, CharSequence>(collator) { private val localeComparator = compareBy<SelectionItem, CharSequence>(collator) {
it.getTitle() it.getTitle()
@@ -135,6 +148,10 @@ class ControlsUiControllerImpl @Inject constructor (
private lateinit var activityContext: Context private lateinit var activityContext: Context
private lateinit var listingCallback: ControlsListingController.ControlsListingCallback private lateinit var listingCallback: ControlsListingController.ControlsListingCallback
init {
dumpManager.registerDumpable(javaClass.name, this)
}
private fun createCallback( private fun createCallback(
onResult: (List<SelectionItem>) -> Unit onResult: (List<SelectionItem>) -> Unit
): ControlsListingController.ControlsListingCallback { ): ControlsListingController.ControlsListingCallback {
@@ -165,10 +182,12 @@ class ControlsUiControllerImpl @Inject constructor (
override fun resolveActivity(): Class<*> { override fun resolveActivity(): Class<*> {
val allStructures = controlsController.get().getFavorites() val allStructures = controlsController.get().getFavorites()
val selected = getPreferredSelectedItem(allStructures) val selected = getPreferredSelectedItem(allStructures)
val anyPanels = controlsListingController.get().getCurrentServices()
.none { it.panelActivity != null }
return if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) { return if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
ControlsActivity::class.java ControlsActivity::class.java
} else if (!selected.hasControls && allStructures.size <= 1) { } else if (!selected.hasControls && allStructures.size <= 1 && !anyPanels) {
ControlsProviderSelectorActivity::class.java ControlsProviderSelectorActivity::class.java
} else { } else {
ControlsActivity::class.java ControlsActivity::class.java
@@ -194,9 +213,13 @@ class ControlsUiControllerImpl @Inject constructor (
if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) { if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
listingCallback = createCallback(::showSeedingView) 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 // only show initial view if there are really no favorites across any structure
listingCallback = createCallback(::showInitialSetupView) listingCallback = createCallback(::initialView)
} else { } else {
val selected = selectedItem val selected = selectedItem
if (selected is SelectedItem.StructureItem) { if (selected is SelectedItem.StructureItem) {
@@ -213,11 +236,22 @@ class ControlsUiControllerImpl @Inject constructor (
controlsListingController.get().addCallback(listingCallback) 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) { private fun reload(parent: ViewGroup) {
if (hidden) return if (hidden) return
controlsListingController.get().removeCallback(listingCallback) controlsListingController.get().removeCallback(listingCallback)
controlsController.get().unsubscribe() controlsController.get().unsubscribe()
taskViewController?.dismiss()
taskViewController = null
val fadeAnim = ObjectAnimator.ofFloat(parent, "alpha", 1.0f, 0.0f) val fadeAnim = ObjectAnimator.ofFloat(parent, "alpha", 1.0f, 0.0f)
fadeAnim.setInterpolator(AccelerateInterpolator(1.0f)) fadeAnim.setInterpolator(AccelerateInterpolator(1.0f))
@@ -297,29 +331,90 @@ class ControlsUiControllerImpl @Inject constructor (
private fun showControlsView(items: List<SelectionItem>) { private fun showControlsView(items: List<SelectionItem>) {
controlViewsById.clear() controlViewsById.clear()
val itemsByComponent = items.associateBy { it.componentName } val (panels, structures) = items.partition { it.isPanel }
val itemsWithStructure = mutableListOf<SelectionItem>() val panelComponents = panels.map { it.componentName }.toSet()
allStructures.mapNotNullTo(itemsWithStructure) {
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) 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) val selectionItem = findSelectionItem(selectedItem, panelsAndStructures)
createDropDown(itemsWithStructure, selectionItem) ?: 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() createMenu()
} }
private fun createMenu() { private fun createPanelView(componentName: ComponentName) {
if (selectedItem !is SelectedItem.StructureItem) return val pendingIntent = PendingIntent.getActivity(
val selectedStructure = (selectedItem as SelectedItem.StructureItem).structure context,
val items = arrayOf( 0,
context.resources.getString(R.string.controls_menu_add), Intent().setComponent(componentName),
context.resources.getString(R.string.controls_menu_edit) 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) var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items)
val anchor = parent.requireViewById<ImageView>(R.id.controls_more) val anchor = parent.requireViewById<ImageView>(R.id.controls_more)
@@ -340,7 +435,13 @@ class ControlsUiControllerImpl @Inject constructor (
) { ) {
when (pos) { when (pos) {
// 0: Add Control // 0: Add Control
0 -> startFavoritingActivity(selectedStructure) 0 -> {
if (isPanel) {
startProviderSelectorActivity()
} else {
startFavoritingActivity(selectedStructure)
}
}
// 1: Edit controls // 1: Edit controls
1 -> startEditingActivity(selectedStructure) 1 -> startEditingActivity(selectedStructure)
} }
@@ -362,6 +463,9 @@ class ControlsUiControllerImpl @Inject constructor (
addAll(items) 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 * Default spinner widget does not work with the window type required
* for this dialog. Use a textView with the ListPopupWindow to achieve * 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 // override the default color on the dropdown drawable
(getBackground() as LayerDrawable).getDrawable(0) (getBackground() as LayerDrawable).getDrawable(0)
.setTint(context.resources.getColor(R.color.control_spinner_dropdown, null)) .setTint(context.resources.getColor(R.color.control_spinner_dropdown, null))
} selected.icon.setBounds(0, 0, iconSize, iconSize)
compoundDrawablePadding = (iconSize / 2.4f).toInt()
if (items.size == 1) { setCompoundDrawablesRelative(selected.icon, null, null, null)
spinner.setBackground(null)
return
} }
val anchor = parent.requireViewById<ViewGroup>(R.id.controls_header) 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 { anchor.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View) { override fun onClick(v: View) {
popup = GlobalActionsPopupMenu( popup = GlobalActionsPopupMenu(
@@ -407,16 +518,20 @@ class ControlsUiControllerImpl @Inject constructor (
}) })
} }
private fun createListView(selected: SelectionItem) { private fun createControlsSpaceFrame() {
if (selectedItem !is SelectedItem.StructureItem) return val inflater = LayoutInflater.from(activityContext)
val selectedStructure = (selectedItem as SelectedItem.StructureItem).structure
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.controls_with_favorites, parent, true) inflater.inflate(R.layout.controls_with_favorites, parent, true)
parent.requireViewById<ImageView>(R.id.controls_close).apply { parent.requireViewById<ImageView>(R.id.controls_close).apply {
setOnClickListener { _: View -> onDismiss.run() } setOnClickListener { _: View -> onDismiss.run() }
visibility = View.VISIBLE 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) val maxColumns = ControlAdapter.findMaxColumns(activityContext.resources)
@@ -490,18 +605,25 @@ class ControlsUiControllerImpl @Inject constructor (
.commit() .commit()
} }
private fun switchAppOrStructure(item: SelectionItem) { private fun maybeUpdateSelectedItem(item: SelectionItem): Boolean {
val newSelection = if (item.isPanel) { val newSelection = if (item.isPanel) {
SelectedItem.PanelItem(item.appName, item.componentName) SelectedItem.PanelItem(item.appName, item.componentName)
} else { } else {
SelectedItem.StructureItem(allStructures.first { SelectedItem.StructureItem(allStructures.firstOrNull {
it.structure == item.structure && it.componentName == item.componentName it.structure == item.structure && it.componentName == item.componentName
}) } ?: EMPTY_STRUCTURE)
} }
return if (newSelection != selectedItem ) {
if (newSelection != selectedItem) {
selectedItem = newSelection selectedItem = newSelection
updatePreferences(selectedItem) updatePreferences(selectedItem)
true
} else {
false
}
}
private fun switchAppOrStructure(item: SelectionItem) {
if (maybeUpdateSelectedItem(item)) {
reload(parent) reload(parent)
} }
} }
@@ -525,6 +647,8 @@ class ControlsUiControllerImpl @Inject constructor (
closeDialogs(true) closeDialogs(true)
controlsController.get().unsubscribe() controlsController.get().unsubscribe()
taskViewController?.dismiss()
taskViewController = null
parent.removeAllViews() parent.removeAllViews()
controlsById.clear() controlsById.clear()
@@ -567,6 +691,15 @@ class ControlsUiControllerImpl @Inject constructor (
private fun findSelectionItem(si: SelectedItem, items: List<SelectionItem>): SelectionItem? = private fun findSelectionItem(si: SelectedItem, items: List<SelectionItem>): SelectionItem? =
items.firstOrNull { it.matches(si) } 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")
}
}
} }
@VisibleForTesting @VisibleForTesting

View File

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

View File

@@ -1,43 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.globalactions;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ScrollView;
/**
* When measured, this view sets the minimum height of its first child to be equal to its own
* target height.
*
* This ensures fall-through click handlers can be placed on this view's child component.
*/
public class MinHeightScrollView extends ScrollView {
public MinHeightScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
View firstChild = getChildAt(0);
if (firstChild != null) {
firstChild.setMinimumHeight(MeasureSpec.getSize(heightMeasureSpec));
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

View File

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

View File

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

View File

@@ -17,8 +17,10 @@
package com.android.systemui.controls.ui package com.android.systemui.controls.ui
import android.content.ComponentName import android.content.ComponentName
import android.content.Context
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import android.testing.TestableLooper import android.testing.TestableLooper
import android.widget.FrameLayout
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsMetricsLogger 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.ControlsController
import com.android.systemui.controls.controller.StructureInfo import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker import com.android.systemui.settings.UserTracker
@@ -34,9 +37,12 @@ import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.util.time.FakeSystemClock
import com.android.wm.shell.TaskViewFactory
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import dagger.Lazy import dagger.Lazy
import java.util.Optional
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -44,6 +50,7 @@ import org.mockito.Mock
import org.mockito.Mockito.anyInt import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyString import org.mockito.Mockito.anyString
import org.mockito.Mockito.mock import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
@@ -62,16 +69,22 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
@Mock lateinit var keyguardStateController: KeyguardStateController @Mock lateinit var keyguardStateController: KeyguardStateController
@Mock lateinit var userFileManager: UserFileManager @Mock lateinit var userFileManager: UserFileManager
@Mock lateinit var userTracker: UserTracker @Mock lateinit var userTracker: UserTracker
@Mock lateinit var taskViewFactory: TaskViewFactory
@Mock lateinit var activityContext: Context
@Mock lateinit var dumpManager: DumpManager
val sharedPreferences = FakeSharedPreferences() val sharedPreferences = FakeSharedPreferences()
var uiExecutor = FakeExecutor(FakeSystemClock()) var uiExecutor = FakeExecutor(FakeSystemClock())
var bgExecutor = FakeExecutor(FakeSystemClock()) var bgExecutor = FakeExecutor(FakeSystemClock())
lateinit var underTest: ControlsUiControllerImpl lateinit var underTest: ControlsUiControllerImpl
lateinit var parent: FrameLayout
@Before @Before
fun setup() { fun setup() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
parent = FrameLayout(mContext)
underTest = underTest =
ControlsUiControllerImpl( ControlsUiControllerImpl(
Lazy { controlsController }, Lazy { controlsController },
@@ -81,12 +94,13 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
Lazy { controlsListingController }, Lazy { controlsListingController },
controlActionCoordinator, controlActionCoordinator,
activityStarter, activityStarter,
shadeController,
iconCache, iconCache,
controlsMetricsLogger, controlsMetricsLogger,
keyguardStateController, keyguardStateController,
userFileManager, userFileManager,
userTracker userTracker,
Optional.of(taskViewFactory),
dumpManager
) )
`when`( `when`(
userFileManager.getSharedPreferences( userFileManager.getSharedPreferences(
@@ -171,4 +185,18 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
assertThat(selected).isEqualTo(panel) assertThat(selected).isEqualTo(panel)
} }
@Test
fun testPanelDoesNotRefreshControls() {
val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
sharedPreferences
.edit()
.putString("controls_component", panel.componentName.flattenToString())
.putString("controls_structure", panel.appName.toString())
.putBoolean("controls_is_panel", true)
.commit()
underTest.show(parent, {}, activityContext)
verify(controlsController, never()).refreshStatus(any(), any())
}
} }

View File

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