diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index c6f03271f9316..5141f2f5a0a7c 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -679,6 +679,15 @@
android:visibleToInstantApps="true">
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/layout/controls_management_apps.xml b/packages/SystemUI/res/layout/controls_management_apps.xml
index 42d73f3cc9cea..94df9d8f47753 100644
--- a/packages/SystemUI/res/layout/controls_management_apps.xml
+++ b/packages/SystemUI/res/layout/controls_management_apps.xml
@@ -14,18 +14,11 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
+ android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/controls_management_list_margin"
+/>
-
-
-
diff --git a/packages/SystemUI/res/layout/controls_management_editing.xml b/packages/SystemUI/res/layout/controls_management_editing.xml
new file mode 100644
index 0000000000000..8a14ec3666b21
--- /dev/null
+++ b/packages/SystemUI/res/layout/controls_management_editing.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 566d143208fc2..7c0b6054dddbd 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2670,8 +2670,11 @@
ControlsChoose controls to access from the power menu
-
- Hold and drag a control to move it
+
+ Hold & drag to rearrange controls
+
+
+ All controls removedThe list of all controls could not be loaded.
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
index dec60073a55e7..5891a7f705c8b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
@@ -18,10 +18,34 @@ package com.android.systemui.controls
import android.content.ComponentName
import android.service.controls.Control
+import android.service.controls.DeviceTypes
+
+interface ControlInterface {
+ val favorite: Boolean
+ val component: ComponentName
+ val controlId: String
+ val title: CharSequence
+ val subtitle: CharSequence
+ val removed: Boolean
+ get() = false
+ @DeviceTypes.DeviceType val deviceType: Int
+}
data class ControlStatus(
val control: Control,
- val component: ComponentName,
- var favorite: Boolean,
- val removed: Boolean = false
-)
+ override val component: ComponentName,
+ override var favorite: Boolean,
+ override val removed: Boolean = false
+) : ControlInterface {
+ override val controlId: String
+ get() = control.controlId
+
+ override val title: CharSequence
+ get() = control.title
+
+ override val subtitle: CharSequence
+ get() = control.subtitle
+
+ @DeviceTypes.DeviceType override val deviceType: Int
+ get() = control.deviceType
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
index 6e59ac162657d..40606c2689e5d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
@@ -16,6 +16,7 @@
package com.android.systemui.controls.controller
+import android.service.controls.Control
import android.service.controls.DeviceTypes
/**
@@ -39,6 +40,14 @@ data class ControlInfo(
companion object {
private const val SEPARATOR = ":"
+ fun fromControl(control: Control): ControlInfo {
+ return ControlInfo(
+ control.controlId,
+ control.title,
+ control.subtitle,
+ control.deviceType
+ )
+ }
}
/**
@@ -49,13 +58,4 @@ data class ControlInfo(
override fun toString(): String {
return "$SEPARATOR$controlId$SEPARATOR$controlTitle$SEPARATOR$deviceType"
}
-
- class Builder {
- lateinit var controlId: String
- lateinit var controlTitle: CharSequence
- lateinit var controlSubtitle: CharSequence
- var deviceType: Int = DeviceTypes.TYPE_UNKNOWN
-
- fun build() = ControlInfo(controlId, controlTitle, controlSubtitle, deviceType)
- }
}
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 568fb289027d5..7cab847d52f7f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -147,6 +147,18 @@ interface ControlsController : UserAwareController {
*/
fun getFavoritesForComponent(componentName: ComponentName): List
+ /**
+ * Get all the favorites for a given structure.
+ *
+ * @param componentName the name of the service that provides the [Control]
+ * @param structureName the name of the structure
+ * @return a list of the current favorites in that structure
+ */
+ fun getFavoritesForStructure(
+ componentName: ComponentName,
+ structureName: CharSequence
+ ): List
+
/**
* Adds a single favorite to a given component and structure
* @param componentName the name of the service that provides the [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 8805694616a4b..6d34009169d50 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -544,6 +544,15 @@ class ControlsControllerImpl @Inject constructor (
override fun getFavoritesForComponent(componentName: ComponentName): List =
Favorites.getStructuresForComponent(componentName)
+ override fun getFavoritesForStructure(
+ componentName: ComponentName,
+ structureName: CharSequence
+ ): List {
+ return Favorites.getControlsForStructure(
+ StructureInfo(componentName, structureName, emptyList())
+ )
+ }
+
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/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index 946a2365585ae..3bed559123322 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -22,6 +22,7 @@ import com.android.systemui.controls.controller.ControlsBindingControllerImpl
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
+import com.android.systemui.controls.management.ControlsEditingActivity
import com.android.systemui.controls.management.ControlsFavoritingActivity
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsListingControllerImpl
@@ -71,6 +72,13 @@ abstract class ControlsModule {
activity: ControlsFavoritingActivity
): Activity
+ @Binds
+ @IntoMap
+ @ClassKey(ControlsEditingActivity::class)
+ abstract fun provideControlsEditingActivity(
+ activity: ControlsEditingActivity
+ ): Activity
+
@Binds
@IntoMap
@ClassKey(ControlsRequestDialog::class)
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 11181e56838ec..175ed061c7146 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
@@ -37,23 +37,22 @@ import com.android.systemui.controls.controller.ControlInfo
* @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.
+ * @property controlsModelCallback callback to notify that favorites have changed for the first time
*/
class AllModel(
private val controls: List,
initialFavoriteIds: List,
- private val emptyZoneString: CharSequence
+ private val emptyZoneString: CharSequence,
+ private val controlsModelCallback: ControlsModel.ControlsModelCallback
) : ControlsModel {
- override val favorites: List
+ private var modified = false
+
+ override val favorites: List
get() = favoriteIds.mapNotNull { id ->
val control = controls.firstOrNull { it.control.controlId == id }?.control
control?.let {
- ControlInfo.Builder().apply {
- controlId = it.controlId
- controlTitle = it.title
- controlSubtitle = it.subtitle
- deviceType = it.deviceType
- }
+ ControlInfo.fromControl(it)
}
}
@@ -66,14 +65,18 @@ class AllModel(
override fun changeFavoriteStatus(controlId: String, favorite: Boolean) {
val toChange = elements.firstOrNull {
- it is ControlWrapper && it.controlStatus.control.controlId == controlId
- } as ControlWrapper?
+ it is ControlStatusWrapper && it.controlStatus.control.controlId == controlId
+ } as ControlStatusWrapper?
if (favorite == toChange?.controlStatus?.favorite) return
- if (favorite) {
+ val changed: Boolean = if (favorite) {
favoriteIds.add(controlId)
} else {
favoriteIds.remove(controlId)
}
+ if (changed && !modified) {
+ modified = true
+ controlsModelCallback.onFirstChange()
+ }
toChange?.let {
it.controlStatus.favorite = favorite
}
@@ -84,9 +87,9 @@ class AllModel(
it.control.zone ?: ""
}
val output = mutableListOf()
- var emptyZoneValues: Sequence? = null
+ var emptyZoneValues: Sequence? = null
for (zoneName in map.orderedKeys) {
- val values = map.getValue(zoneName).asSequence().map { ControlWrapper(it) }
+ val values = map.getValue(zoneName).asSequence().map { ControlStatusWrapper(it) }
if (TextUtils.isEmpty(zoneName)) {
emptyZoneValues = values
} else {
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 1291dd98932e5..607934c3bae73 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -28,6 +28,7 @@ import android.widget.TextView
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
+import com.android.systemui.controls.ControlInterface
import com.android.systemui.controls.ui.RenderInfo
private typealias ModelFavoriteChanger = (String, Boolean) -> Unit
@@ -35,11 +36,10 @@ private typealias ModelFavoriteChanger = (String, Boolean) -> Unit
/**
* Adapter for binding [Control] information to views.
*
- * The model for this adapter is provided by a [FavoriteModel] that is set using
+ * The model for this adapter is provided by a [ControlModel] that is set using
* [changeFavoritesModel]. This allows for updating the model if there's a reload.
*
- * @param layoutInflater an inflater for the views in the containing [RecyclerView]
- * @param onlyFavorites set to true to only display favorites instead of all controls
+ * @property elevation elevation of each control view
*/
class ControlAdapter(
private val elevation: Float
@@ -48,11 +48,12 @@ class ControlAdapter(
companion object {
private const val TYPE_ZONE = 0
private const val TYPE_CONTROL = 1
+ private const val TYPE_DIVIDER = 2
}
val spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
- return if (getItemViewType(position) == TYPE_ZONE) 2 else 1
+ return if (getItemViewType(position) != TYPE_CONTROL) 2 else 1
}
}
@@ -78,6 +79,10 @@ class ControlAdapter(
TYPE_ZONE -> {
ZoneHolder(layoutInflater.inflate(R.layout.controls_zone_header, parent, false))
}
+ TYPE_DIVIDER -> {
+ DividerHolder(layoutInflater.inflate(
+ R.layout.controls_horizontal_divider_withEmpty, parent, false))
+ }
else -> throw IllegalStateException("Wrong viewType: $viewType")
}
}
@@ -95,11 +100,26 @@ class ControlAdapter(
}
}
+ override fun onBindViewHolder(holder: Holder, position: Int, payloads: MutableList) {
+ if (payloads.isEmpty()) {
+ super.onBindViewHolder(holder, position, payloads)
+ } else {
+ model?.let {
+ val el = it.elements[position]
+ if (el is ControlInterface) {
+ holder.updateFavorite(el.favorite)
+ }
+ }
+ }
+ }
+
override fun getItemViewType(position: Int): Int {
model?.let {
return when (it.elements.get(position)) {
is ZoneNameWrapper -> TYPE_ZONE
- is ControlWrapper -> TYPE_CONTROL
+ is ControlStatusWrapper -> TYPE_CONTROL
+ is ControlInfoWrapper -> TYPE_CONTROL
+ is DividerWrapper -> TYPE_DIVIDER
}
} ?: throw IllegalStateException("Getting item type for null model")
}
@@ -115,6 +135,24 @@ sealed class Holder(view: View) : RecyclerView.ViewHolder(view) {
* Bind the data from the model into the view
*/
abstract fun bindData(wrapper: ElementWrapper)
+
+ open fun updateFavorite(favorite: Boolean) {}
+}
+
+/**
+ * Holder for using with [DividerWrapper] to display a divider between zones.
+ *
+ * The divider can be shown or hidden. It also has a frame view the height of a control, that can
+ * be toggled visible or gone.
+ */
+private class DividerHolder(view: View) : Holder(view) {
+ private val frame: View = itemView.requireViewById(R.id.frame)
+ private val divider: View = itemView.requireViewById(R.id.divider)
+ override fun bindData(wrapper: ElementWrapper) {
+ wrapper as DividerWrapper
+ frame.visibility = if (wrapper.showNone) View.VISIBLE else View.GONE
+ divider.visibility = if (wrapper.showDivider) View.VISIBLE else View.GONE
+ }
}
/**
@@ -130,11 +168,14 @@ private class ZoneHolder(view: View) : Holder(view) {
}
/**
- * Holder for using with [ControlWrapper] to display names of zones.
+ * Holder for using with [ControlStatusWrapper] to display names of zones.
* @param favoriteCallback this callback will be called whenever the favorite state of the
* [Control] this view represents changes.
*/
-private class ControlHolder(view: View, val favoriteCallback: ModelFavoriteChanger) : Holder(view) {
+internal class ControlHolder(
+ view: View,
+ val favoriteCallback: ModelFavoriteChanger
+) : Holder(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)
@@ -144,20 +185,23 @@ private class ControlHolder(view: View, val favoriteCallback: ModelFavoriteChang
}
override fun bindData(wrapper: ElementWrapper) {
- wrapper as ControlWrapper
- val data = wrapper.controlStatus
- val renderInfo = getRenderInfo(data.component, data.control.deviceType)
- title.text = data.control.title
- subtitle.text = data.control.subtitle
- favorite.isChecked = data.favorite
- removed.text = if (data.removed) "Removed" else ""
+ wrapper as ControlInterface
+ val renderInfo = getRenderInfo(wrapper.component, wrapper.deviceType)
+ title.text = wrapper.title
+ subtitle.text = wrapper.subtitle
+ favorite.isChecked = wrapper.favorite
+ removed.text = if (wrapper.removed) "Removed" else ""
itemView.setOnClickListener {
favorite.isChecked = !favorite.isChecked
- favoriteCallback(data.control.controlId, favorite.isChecked)
+ favoriteCallback(wrapper.controlId, favorite.isChecked)
}
applyRenderInfo(renderInfo)
}
+ override fun updateFavorite(favorite: Boolean) {
+ this.favorite.isChecked = favorite
+ }
+
private fun getRenderInfo(
component: ComponentName,
@DeviceTypes.DeviceType deviceType: Int
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
new file mode 100644
index 0000000000000..ee1ce7ab3d83c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -0,0 +1,176 @@
+/*
+ * 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.controls.management
+
+import android.app.Activity
+import android.content.ComponentName
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.view.ViewStub
+import android.widget.Button
+import android.widget.TextView
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.ItemTouchHelper
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.R
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.settings.CurrentUserTracker
+import javax.inject.Inject
+
+/**
+ * Activity for rearranging and removing controls for a given structure
+ */
+class ControlsEditingActivity @Inject constructor(
+ private val controller: ControlsControllerImpl,
+ broadcastDispatcher: BroadcastDispatcher
+) : Activity() {
+
+ companion object {
+ private const val TAG = "ControlsEditingActivity"
+ private const val EXTRA_STRUCTURE = ControlsFavoritingActivity.EXTRA_STRUCTURE
+ private val SUBTITLE_ID = R.string.controls_favorite_rearrange
+ private val EMPTY_TEXT_ID = R.string.controls_favorite_removed
+ }
+
+ private lateinit var component: ComponentName
+ private lateinit var structure: CharSequence
+ private lateinit var model: FavoritesModel
+ private lateinit var subtitle: TextView
+ private lateinit var saveButton: View
+
+ private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+ private val startingUser = controller.currentUserId
+
+ override fun onUserSwitched(newUserId: Int) {
+ if (newUserId != startingUser) {
+ stopTracking()
+ finish()
+ }
+ }
+ }
+
+ override fun onBackPressed() {
+ finish()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME)?.let {
+ component = it
+ } ?: run(this::finish)
+
+ intent.getCharSequenceExtra(EXTRA_STRUCTURE)?.let {
+ structure = it
+ } ?: run(this::finish)
+
+ bindViews()
+
+ bindButtons()
+
+ setUpList()
+
+ currentUserTracker.startTracking()
+ }
+
+ private fun bindViews() {
+ setContentView(R.layout.controls_management)
+ requireViewById(R.id.stub).apply {
+ layoutResource = R.layout.controls_management_editing
+ inflate()
+ }
+ requireViewById(R.id.title).text = structure
+ subtitle = requireViewById(R.id.subtitle).apply {
+ setText(SUBTITLE_ID)
+ }
+ }
+
+ private fun bindButtons() {
+ requireViewById