From 713b727362c66b24560bf8937138aed2d4897aeb Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Tue, 3 Mar 2020 18:35:52 -0500 Subject: [PATCH] Add Structure pages to management screens Pages are sorted alphabetically and show all controls for that structure. The name used is the structure name of the app name for the empty/null structure. Done button saves all favorites selected from all structures, not just the visible one. Test: manual Fixes: 150707923 Change-Id: I8ae822ed5acb6340aa08acb0dc6f3a4e6bb2f2e4 --- packages/SystemUI/Android.bp | 2 + .../res/layout/controls_management.xml | 51 +++-- .../res/layout/controls_management_apps.xml | 18 +- .../layout/controls_management_favorites.xml | 16 +- .../res/layout/controls_structure_page.xml | 31 +++ packages/SystemUI/res/values/dimens.xml | 1 + .../controller/ControlsControllerImpl.kt | 18 +- .../systemui/controls/management/AllModel.kt | 17 +- .../controls/management/ControlAdapter.kt | 2 +- .../management/ControlsFavoritingActivity.kt | 186 ++++++++++++------ .../management/ManagementPageIndicator.kt | 43 ++++ .../controls/management/StructureAdapter.kt | 73 +++++++ .../controller/ControlsControllerImplTest.kt | 2 + 13 files changed, 356 insertions(+), 104 deletions(-) create mode 100644 packages/SystemUI/res/layout/controls_structure_page.xml create mode 100644 packages/SystemUI/src/com/android/systemui/controls/management/ManagementPageIndicator.kt create mode 100644 packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt 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