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