diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index e0662309f5714..0eadcc7417474 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -46,6 +46,7 @@ android_library {
"SystemUIPluginLib",
"SystemUISharedLib",
"SettingsLib",
+ "androidx.viewpager2_viewpager2",
"androidx.legacy_legacy-support-v4",
"androidx.recyclerview_recyclerview",
"androidx.preference_preference",
@@ -106,6 +107,7 @@ android_library {
"SystemUIPluginLib",
"SystemUISharedLib",
"SettingsLib",
+ "androidx.viewpager2_viewpager2",
"androidx.legacy_legacy-support-v4",
"androidx.recyclerview_recyclerview",
"androidx.preference_preference",
diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml
index 6533c18a41a97..34a966ceea754 100644
--- a/packages/SystemUI/res/layout/controls_management.xml
+++ b/packages/SystemUI/res/layout/controls_management.xml
@@ -25,13 +25,40 @@
android:paddingStart="@dimen/controls_management_side_padding"
android:paddingEnd="@dimen/controls_management_side_padding" >
-
+ android:gravity="center_vertical">
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+ android:layout_weight="1"/>
-
-
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:layout_marginTop="@dimen/controls_management_list_margin">
-
\ No newline at end of file
+
+
+
diff --git a/packages/SystemUI/res/layout/controls_management_favorites.xml b/packages/SystemUI/res/layout/controls_management_favorites.xml
index aab32f45e77a4..d2ccfcb11c5c2 100644
--- a/packages/SystemUI/res/layout/controls_management_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_management_favorites.xml
@@ -17,7 +17,7 @@
-
+ android:visibility="gone" />
+
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_structure_page.xml b/packages/SystemUI/res/layout/controls_structure_page.xml
new file mode 100644
index 0000000000000..2c7e1681f2e1c
--- /dev/null
+++ b/packages/SystemUI/res/layout/controls_structure_page.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9437485165a0a..291db65da2252 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1249,6 +1249,7 @@
32dp
8dp
4dp
+ @dimen/controls_app_icon_frame_top_padding
8dp
8dp
2dp
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 50bd1ade1e227..5e1ed5892e2ee 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -253,17 +253,21 @@ class ControlsControllerImpl @Inject constructor (
}
override fun error(message: String) {
- val loadData = Favorites.getControlsForComponent(componentName).let {
- controls ->
+ executor.execute {
+ val loadData = Favorites.getControlsForComponent(componentName)
+ .let { controls ->
val keys = controls.map { it.controlId }
createLoadDataObject(
- controls.map { createRemovedStatus(componentName, it, false) },
- keys,
- true
+ controls.map {
+ createRemovedStatus(componentName, it, false)
+ },
+ keys,
+ true
)
- }
+ }
- dataCallback.accept(loadData)
+ dataCallback.accept(loadData)
+ }
}
}
)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
index c05351795aed2..01f906958fc11 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
@@ -22,14 +22,20 @@ import com.android.systemui.controls.ControlStatus
import com.android.systemui.controls.controller.ControlInfo
/**
- * This model is used to show all controls separated by zones.
+ * This model is used to show controls separated by zones.
*
* The model will sort the controls and zones in the following manner:
* * The zones will be sorted in a first seen basis
* * The controls in each zone will be sorted in a first seen basis.
*
- * @property controls List of all controls as returned by loading
- * @property initialFavoriteIds sorted ids of favorite controls
+ * The controls passed should belong to the same structure, as an instance of this model will be
+ * created for each structure.
+ *
+ * The list of favorite ids can contain ids for controls not passed to this model. Those will be
+ * filtered out.
+ *
+ * @property controls List of controls as returned by loading
+ * @property initialFavoriteIds sorted ids of favorite controls.
* @property noZoneString text to use as header for all controls that have blank or `null` zone.
*/
class AllModel(
@@ -50,7 +56,10 @@ class AllModel(
}
}
- private val favoriteIds = initialFavoriteIds.toMutableList()
+ private val favoriteIds = run {
+ val ids = controls.mapTo(HashSet()) { it.control.controlId }
+ initialFavoriteIds.filter { it in ids }.toMutableList()
+ }
override val elements: List = createWrappers(controls)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index c21f7241180bb..179e9fb027974 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -42,7 +42,6 @@ private typealias ModelFavoriteChanger = (String, Boolean) -> Unit
* @param onlyFavorites set to true to only display favorites instead of all controls
*/
class ControlAdapter(
- private val layoutInflater: LayoutInflater,
private val elevation: Float
) : RecyclerView.Adapter() {
@@ -60,6 +59,7 @@ class ControlAdapter(
private var model: ControlsModel? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
+ val layoutInflater = LayoutInflater.from(parent.context)
return when (viewType) {
TYPE_CONTROL -> {
ControlHolder(
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 471f9d30b08b3..04715abe5f99c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -19,20 +19,24 @@ package com.android.systemui.controls.management
import android.app.Activity
import android.content.ComponentName
import android.content.Intent
+import android.graphics.drawable.Drawable
import android.os.Bundle
-import android.view.LayoutInflater
+import android.text.TextUtils
import android.view.View
import android.view.ViewStub
import android.widget.Button
+import android.widget.ImageView
import android.widget.TextView
-import androidx.recyclerview.widget.GridLayoutManager
-import androidx.recyclerview.widget.RecyclerView
+import androidx.viewpager2.widget.ViewPager2
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.PageIndicator
import com.android.systemui.settings.CurrentUserTracker
+import java.text.Collator
import java.util.concurrent.Executor
import java.util.function.Consumer
import javax.inject.Inject
@@ -40,6 +44,7 @@ import javax.inject.Inject
class ControlsFavoritingActivity @Inject constructor(
@Main private val executor: Executor,
private val controller: ControlsControllerImpl,
+ private val listingController: ControlsListingController,
broadcastDispatcher: BroadcastDispatcher
) : Activity() {
@@ -48,12 +53,18 @@ class ControlsFavoritingActivity @Inject constructor(
const val EXTRA_APP = "extra_app_label"
}
- private lateinit var recyclerViewAll: RecyclerView
- private lateinit var adapterAll: ControlAdapter
- private lateinit var statusText: TextView
- private var model: ControlsModel? = null
private var component: ComponentName? = null
- private var structureName: CharSequence = ""
+ private var appName: CharSequence? = null
+
+ private lateinit var structurePager: ViewPager2
+ private lateinit var statusText: TextView
+ private lateinit var titleView: TextView
+ private lateinit var iconView: ImageView
+ private lateinit var iconFrame: View
+ private lateinit var pageIndicator: PageIndicator
+ private var listOfStructures = emptyList()
+
+ private lateinit var comparator: Comparator
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
private val startingUser = controller.currentUserId
@@ -66,29 +77,117 @@ class ControlsFavoritingActivity @Inject constructor(
}
}
+ private val listingCallback = object : ControlsListingController.ControlsListingCallback {
+ private var icon: Drawable? = null
+
+ override fun onServicesUpdated(serviceInfos: List) {
+ val newIcon = serviceInfos.firstOrNull { it.componentName == component }?.loadIcon()
+ if (icon == newIcon) return
+ icon = newIcon
+ executor.execute {
+ if (icon != null) {
+ iconView.setImageDrawable(icon)
+ }
+ iconFrame.visibility = if (icon != null) View.VISIBLE else View.GONE
+ }
+ }
+ }
+
override fun onBackPressed() {
finish()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ val collator = Collator.getInstance(resources.configuration.locales[0])
+ comparator = compareBy(collator) { it.structureName }
+ appName = intent.getCharSequenceExtra(EXTRA_APP)
+ component = intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME)
+
+ bindViews()
+
+ setUpPager()
+
+ loadControls()
+
+ listingController.addCallback(listingCallback)
+
+ currentUserTracker.startTracking()
+ }
+
+ private fun loadControls() {
+ component?.let {
+ statusText.text = resources.getText(com.android.internal.R.string.loading)
+ val emptyZoneString = resources.getText(
+ R.string.controls_favorite_other_zone_header)
+ controller.loadForComponent(it, Consumer { data ->
+ val allControls = data.allControls
+ val favoriteKeys = data.favoritesIds
+ val error = data.errorOnLoad
+ val controlsByStructure = allControls.groupBy { it.control.structure ?: "" }
+ listOfStructures = controlsByStructure.map {
+ StructureContainer(it.key, AllModel(it.value, favoriteKeys, emptyZoneString))
+ }.sortedWith(comparator)
+ executor.execute {
+ structurePager.adapter = StructureAdapter(listOfStructures)
+ if (error) {
+ statusText.text = resources.getText(R.string.controls_favorite_load_error)
+ } else {
+ statusText.visibility = View.GONE
+ }
+ pageIndicator.setNumPages(listOfStructures.size)
+ pageIndicator.setLocation(0f)
+ pageIndicator.visibility =
+ if (listOfStructures.size > 1) View.VISIBLE else View.GONE
+ }
+ })
+ }
+ }
+
+ private fun setUpPager() {
+ structurePager.apply {
+ adapter = StructureAdapter(emptyList())
+ registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
+ override fun onPageSelected(position: Int) {
+ super.onPageSelected(position)
+ val name = listOfStructures[position].structureName
+ titleView.text = if (!TextUtils.isEmpty(name)) name else appName
+ }
+
+ override fun onPageScrolled(
+ position: Int,
+ positionOffset: Float,
+ positionOffsetPixels: Int
+ ) {
+ super.onPageScrolled(position, positionOffset, positionOffsetPixels)
+ pageIndicator.setLocation(position + positionOffset)
+ }
+ })
+ }
+ }
+
+ private fun bindViews() {
setContentView(R.layout.controls_management)
requireViewById(R.id.stub).apply {
layoutResource = R.layout.controls_management_favorites
inflate()
}
- val app = intent.getCharSequenceExtra(EXTRA_APP)
- component = intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME)
statusText = requireViewById(R.id.status_message)
+ pageIndicator = requireViewById(R.id.structure_page_indicator)
- setUpRecyclerView()
-
- requireViewById(R.id.title).text = app?.let { it }
- ?: resources.getText(R.string.controls_favorite_default_title)
+ titleView = requireViewById(R.id.title).apply {
+ text = appName ?: resources.getText(R.string.controls_favorite_default_title)
+ }
requireViewById(R.id.subtitle).text =
resources.getText(R.string.controls_favorite_subtitle)
+ iconView = requireViewById(com.android.internal.R.id.icon)
+ iconFrame = requireViewById(R.id.icon_frame)
+ structurePager = requireViewById(R.id.structure_pager)
+ bindButtons()
+ }
+ private fun bindButtons() {
requireViewById