diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 149eaf47b97d5..139a8c3f7411d 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -644,7 +644,7 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/app_item.xml b/packages/SystemUI/res/layout/app_item.xml deleted file mode 100644 index 83e7887314426..0000000000000 --- a/packages/SystemUI/res/layout/app_item.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/SystemUI/res/layout/control_item.xml b/packages/SystemUI/res/layout/control_item.xml deleted file mode 100644 index 85701aaca41d2..0000000000000 --- a/packages/SystemUI/res/layout/control_item.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - diff --git a/packages/SystemUI/res/layout/controls_app_item.xml b/packages/SystemUI/res/layout/controls_app_item.xml new file mode 100644 index 0000000000000..d54cd6db867a1 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_app_item.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml index cd7ec5eb5c818..68c824698b2d4 100644 --- a/packages/SystemUI/res/layout/controls_base_item.xml +++ b/packages/SystemUI/res/layout/controls_base_item.xml @@ -23,8 +23,8 @@ android:padding="@dimen/control_padding" android:clickable="true" android:focusable="true" - android:layout_marginLeft="2dp" - android:layout_marginRight="2dp" + android:layout_marginLeft="@dimen/control_base_item_margin" + android:layout_marginRight="@dimen/control_base_item_margin" android:background="@drawable/control_background"> @@ -49,10 +47,8 @@ android:id="@+id/status_extra" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textSize="@dimen/control_status_normal" - android:textColor="?android:attr/textColorPrimary" - android:fontFamily="@*android:string/config_bodyFontFamily" - android:paddingLeft="3dp" + android:textAppearance="@style/TextAppearance.Control.Status" + android:paddingStart="@dimen/control_status_padding" app:layout_constraintBottom_toBottomOf="@+id/icon" app:layout_constraintStart_toEndOf="@+id/status" /> @@ -60,9 +56,7 @@ android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textSize="14sp" - android:textColor="?android:attr/textColorPrimary" - android:textAppearance="?android:attr/textAppearanceSmall" + android:textAppearance="@style/TextAppearance.Control.Title" app:layout_constraintBottom_toTopOf="@+id/subtitle" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/icon" /> @@ -71,9 +65,15 @@ android:id="@+id/subtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textSize="12sp" - android:textColor="?android:attr/textColorSecondary" - android:textAppearance="?android:attr/textAppearanceSmall" + android:textAppearance="@style/TextAppearance.Control.Subtitle" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" /> + + diff --git a/packages/SystemUI/res/layout/controls_row.xml b/packages/SystemUI/res/layout/controls_row.xml index 13a6b36accd35..4cc461a281875 100644 --- a/packages/SystemUI/res/layout/controls_row.xml +++ b/packages/SystemUI/res/layout/controls_row.xml @@ -16,7 +16,7 @@ --> diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml index 6a3e95d34358d..40b2476941c22 100644 --- a/packages/SystemUI/res/layout/controls_with_favorites.xml +++ b/packages/SystemUI/res/layout/controls_with_favorites.xml @@ -13,7 +13,7 @@ android:layout_height="wrap_content" android:singleLine="true" android:gravity="center" - android:textSize="25dp" + android:textSize="25sp" android:textColor="@*android:color/foreground_material_dark" android:fontFamily="@*android:string/config_headlineFontFamily" app:layout_constraintBottom_toBottomOf="parent" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 33cabc1fea52a..9c997e88c0c23 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1187,7 +1187,27 @@ 15dp 100dp 15dp - 12dp - 18dp - 32dp + 12sp + 18sp + 2dp + 3dp + + + 48dp + 8dp + 8dp + 16dp + 26sp + + 32dp + 8dp + 4dp + 8dp + 8dp + 2dp + 32dp + + 2dp + + diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 1f13f8dc02fea..acccbf5f1986f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2547,6 +2547,24 @@ Magnification Window Controls - + + Quick Controls + + + Add Controls + + Choose an app from which to add controls + + + %s current favorite. + %s current favorites. + + + + Controls + + Choose controls for quick access + + diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 10f59a4238053..ecc8efc220ba3 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -583,4 +583,28 @@ ?android:attr/selectableItemBackground ?android:attr/textColorPrimary + + + + + + + + + + + diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index b02de4500043a..b3ba2b22f6df9 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -29,6 +29,7 @@ interface ControlsController : UserAwareController { fun loadForComponent(componentName: ComponentName, callback: (List) -> Unit) fun subscribeToFavorites() fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) + fun countFavoritesForComponent(componentName: ComponentName): Int = 0 fun unsubscribe() fun action(controlInfo: ControlInfo, action: ControlAction) fun refreshStatus(componentName: ComponentName, control: Control) 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 6ff1cf8474d34..a6f1d84877c56 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -328,6 +328,12 @@ class ControlsControllerImpl @Inject constructor ( } } + override fun countFavoritesForComponent(componentName: ComponentName): Int { + return synchronized(currentFavorites) { + currentFavorites.get(componentName)?.size ?: 0 + } + } + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array) { pw.println("ControlsController state:") pw.println(" Available: $available") 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 22c69086cf8c2..b12243964fc18 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt @@ -17,6 +17,7 @@ package com.android.systemui.controls.management import android.content.ComponentName +import android.content.res.Resources import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -25,6 +26,7 @@ import android.widget.TextView import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.RecyclerView +import com.android.settingslib.applications.DefaultAppInfo import com.android.settingslib.widget.CandidateInfo import com.android.systemui.R import java.util.concurrent.Executor @@ -46,7 +48,8 @@ class AppAdapter( lifecycle: Lifecycle, controlsListingController: ControlsListingController, private val layoutInflater: LayoutInflater, - private val onAppSelected: (ComponentName?) -> Unit = {} + private val onAppSelected: (ComponentName?) -> Unit = {}, + private val favoritesRenderer: FavoritesRenderer ) : RecyclerView.Adapter() { private var listOfServices = emptyList() @@ -54,7 +57,9 @@ class AppAdapter( private val callback = object : ControlsListingController.ControlsListingCallback { override fun onServicesUpdated(list: List) { uiExecutor.execute { - listOfServices = list + listOfServices = list.sortedBy { + it.loadLabel().toString() + } notifyDataSetChanged() } } @@ -65,7 +70,8 @@ class AppAdapter( } override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder { - return Holder(layoutInflater.inflate(R.layout.app_item, parent, false)) + return Holder(layoutInflater.inflate(R.layout.controls_app_item, parent, false), + favoritesRenderer) } override fun getItemCount() = listOfServices.size @@ -80,9 +86,10 @@ class AppAdapter( /** * Holder for binding views in the [RecyclerView]- */ - class Holder(view: View) : RecyclerView.ViewHolder(view) { + class Holder(view: View, val favRenderer: FavoritesRenderer) : RecyclerView.ViewHolder(view) { private val icon: ImageView = itemView.requireViewById(com.android.internal.R.id.icon) private val title: TextView = itemView.requireViewById(com.android.internal.R.id.title) + private val favorites: TextView = itemView.requireViewById(R.id.favorites) /** * Bind data to the view @@ -91,6 +98,19 @@ class AppAdapter( fun bindData(data: CandidateInfo) { icon.setImageDrawable(data.loadIcon()) title.text = data.loadLabel() + favorites.text = favRenderer.renderFavoritesForComponent( + (data as DefaultAppInfo).componentName) } } +} + +class FavoritesRenderer( + private val resources: Resources, + private val favoriteFunction: (ComponentName) -> Int +) { + + fun renderFavoritesForComponent(component: ComponentName): String { + val qty = favoriteFunction(component) + return resources.getQuantityString(R.plurals.controls_number_of_favorites, qty, qty) + } } \ No newline at end of file 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 e6d3c26ea7b82..65dcc2b193d5e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -16,15 +16,20 @@ package com.android.systemui.controls.management +import android.graphics.Rect +import android.graphics.drawable.Icon +import android.service.controls.DeviceTypes import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.CheckBox +import android.widget.ImageView import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.controller.ControlInfo +import com.android.systemui.controls.ui.RenderInfo /** * Adapter for binding [Control] information to views. @@ -43,7 +48,12 @@ class ControlAdapter( var listOfControls = emptyList() override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder { - return Holder(layoutInflater.inflate(R.layout.control_item, parent, false)) + return Holder(layoutInflater.inflate(R.layout.controls_base_item, parent, false).apply { + layoutParams.apply { + width = ViewGroup.LayoutParams.MATCH_PARENT + } + elevation = 15f + }) } override fun getItemCount() = listOfControls.size @@ -56,9 +66,13 @@ class ControlAdapter( * Holder for binding views in the [RecyclerView]- */ class Holder(view: View) : RecyclerView.ViewHolder(view) { + private val icon: ImageView = itemView.requireViewById(R.id.icon) private val title: TextView = itemView.requireViewById(R.id.title) private val subtitle: TextView = itemView.requireViewById(R.id.subtitle) - private val favorite: CheckBox = itemView.requireViewById(R.id.favorite) + private val removed: TextView = itemView.requireViewById(R.id.status) + private val favorite: CheckBox = itemView.requireViewById(R.id.favorite).apply { + visibility = View.VISIBLE + } /** * Bind data to the view @@ -68,9 +82,11 @@ class ControlAdapter( * pre-populated with the [Control] information and the new favorite status. */ fun bindData(data: ControlStatus, callback: (ControlInfo.Builder, Boolean) -> Unit) { + val renderInfo = getRenderInfo(data.control.deviceType, data.favorite) title.text = data.control.title subtitle.text = data.control.subtitle favorite.isChecked = data.favorite + removed.text = if (data.removed) "Removed" else "" favorite.setOnClickListener { val infoBuilder = ControlInfo.Builder().apply { controlId = data.control.controlId @@ -79,6 +95,25 @@ class ControlAdapter( } callback(infoBuilder, favorite.isChecked) } + itemView.setOnClickListener { + favorite.performClick() + } + applyRenderInfo(renderInfo) + } + + private fun getRenderInfo( + @DeviceTypes.DeviceType deviceType: Int, + favorite: Boolean + ): RenderInfo { + return RenderInfo.lookup(deviceType, favorite) + } + + private fun applyRenderInfo(ri: RenderInfo) { + val context = itemView.context + val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) + + icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId)) + icon.setImageTintList(fg) } } @@ -86,4 +121,23 @@ class ControlAdapter( listOfControls = list notifyDataSetChanged() } +} + +class MarginItemDecorator( + private val topMargin: Int, + private val sideMargins: Int +) : RecyclerView.ItemDecoration() { + + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + outRect.apply { + top = topMargin + left = sideMargins + right = sideMargins + } + } } \ No newline at end of file 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 7ee4fd5b059e5..be52583444927 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -20,8 +20,10 @@ import android.app.Activity import android.content.ComponentName import android.os.Bundle import android.view.LayoutInflater -import androidx.recyclerview.widget.LinearLayoutManager +import android.widget.TextView +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.R import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.controller.ControlInfo import com.android.systemui.controls.controller.ControlsControllerImpl @@ -44,6 +46,7 @@ class ControlsFavoritingActivity @Inject constructor( private lateinit var recyclerView: RecyclerView private lateinit var adapter: ControlAdapter + private var component: ComponentName? = null private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { private val startingUser = controller.currentUserId @@ -56,10 +59,9 @@ class ControlsFavoritingActivity @Inject constructor( } } - private var component: ComponentName? = null - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setContentView(R.layout.controls_management) val app = intent.getCharSequenceExtra(EXTRA_APP) component = intent.getParcelableExtra(EXTRA_COMPONENT) @@ -72,17 +74,17 @@ class ControlsFavoritingActivity @Inject constructor( } } ?: { _, _ -> Unit } - recyclerView = RecyclerView(applicationContext) + recyclerView = requireViewById(R.id.list) adapter = ControlAdapter(LayoutInflater.from(applicationContext), callback) recyclerView.adapter = adapter - recyclerView.layoutManager = LinearLayoutManager(applicationContext) + recyclerView.layoutManager = GridLayoutManager(applicationContext, 2) + val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin) + recyclerView.addItemDecoration(MarginItemDecorator(margin, margin)) - if (app != null) { - setTitle("Controls for $app") - } else { - setTitle("Controls") - } - setContentView(recyclerView) + requireViewById(R.id.title).text = app?.let { it } + ?: resources.getText(R.string.controls_favorite_default_title) + requireViewById(R.id.subtitle).text = + resources.getText(R.string.controls_favorite_subtitle) currentUserTracker.startTracking() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index 5ff949c98806a..645e929d6a103 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -20,9 +20,12 @@ import android.content.ComponentName import android.content.Intent import android.os.Bundle import android.view.LayoutInflater +import android.widget.TextView import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.R import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.controls.controller.ControlsController import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.settings.CurrentUserTracker @@ -37,6 +40,7 @@ class ControlsProviderSelectorActivity @Inject constructor( @Main private val executor: Executor, @Background private val backExecutor: Executor, private val listingController: ControlsListingController, + private val controlsController: ControlsController, broadcastDispatcher: BroadcastDispatcher ) : LifecycleActivity() { @@ -58,13 +62,19 @@ class ControlsProviderSelectorActivity @Inject constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setContentView(R.layout.controls_management) - recyclerView = RecyclerView(applicationContext) + recyclerView = requireViewById(R.id.list) recyclerView.adapter = AppAdapter(executor, lifecycle, listingController, - LayoutInflater.from(this), ::launchFavoritingActivity) + LayoutInflater.from(this), ::launchFavoritingActivity, + FavoritesRenderer(resources, controlsController::countFavoritesForComponent)) recyclerView.layoutManager = LinearLayoutManager(applicationContext) - setContentView(recyclerView) + requireViewById(R.id.title).text = + resources.getText(R.string.controls_providers_title) + requireViewById(R.id.subtitle).text = + resources.getText(R.string.controls_providers_subtitle) + currentUserTracker.startTracking() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt index 5519e32d6c104..fad2d94d6cf32 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -36,8 +36,8 @@ import android.widget.TextView import com.android.systemui.controls.controller.ControlsController import com.android.systemui.R -public const val MIN_LEVEL = 0 -public const val MAX_LEVEL = 10000 +const val MIN_LEVEL = 0 +const val MAX_LEVEL = 10000 class ControlViewHolder( val layout: ViewGroup, diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt index 24c8020529ff2..093c99f57a9a7 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt @@ -50,7 +50,7 @@ data class RenderInfo(val iconResourceId: Int, val foreground: Int, val backgrou private const val BUCKET_SIZE = 1000 private const val THERMOSTAT_RANGE = DeviceTypes.TYPE_THERMOSTAT * BUCKET_SIZE -public val deviceColorMap = mapOf>( +private val deviceColorMap = mapOf>( (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_HEAT) to Pair(R.color.thermo_heat_foreground, R.color.thermo_heat_background), (THERMOSTAT_RANGE + TemperatureControlTemplate.MODE_COOL) to @@ -60,7 +60,7 @@ public val deviceColorMap = mapOf>( Pair(R.color.control_foreground, R.color.control_background) } -public val deviceIconMap = mapOf( +private val deviceIconMap = mapOf( THERMOSTAT_RANGE to IconState( R.drawable.ic_device_thermostat_gm2_24px, R.drawable.ic_device_thermostat_gm2_24px