Merge "Changed management to just show all controls" into rvc-dev am: d37dcf856b
Change-Id: Iff7b076cc0fc27a41595d34a9f01f3510e4d05a0
This commit is contained in:
@@ -70,12 +70,14 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="4dp">
|
android:padding="4dp">
|
||||||
|
|
||||||
<TextView
|
<Button
|
||||||
|
android:id="@+id/other_apps"
|
||||||
|
android:visibility="gone"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_vertical"
|
||||||
android:text="See other apps"
|
android:text="See other apps"
|
||||||
android:textAppearance="@style/TextAppearance.Control.Title"
|
style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
|
||||||
android:textColor="?android:attr/colorPrimary"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"/>
|
app:layout_constraintStart_toStartOf="parent"/>
|
||||||
@@ -85,6 +87,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:text="Done"
|
android:text="Done"
|
||||||
|
style="@*android:style/Widget.DeviceDefault.Button.Colored"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"/>
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|||||||
@@ -14,97 +14,26 @@
|
|||||||
~ See the License for the specific language governing permissions and
|
~ See the License for the specific language governing permissions and
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<LinearLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/error_message"
|
android:id="@+id/status_message"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/controls_management_list_margin"
|
android:layout_marginTop="@dimen/controls_management_list_margin"
|
||||||
android:text="@string/controls_favorite_load_error"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:visibility="gone"
|
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="parent"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/text_favorites"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/text_favorites"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/controls_management_list_margin"
|
|
||||||
android:text="@string/controls_favorite_header_favorites"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
|
||||||
android:textAllCaps="true"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/divider1"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/error_message"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/divider1"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/controls_app_divider_height"
|
|
||||||
android:layout_gravity="center_horizontal|top"
|
|
||||||
android:background="?android:attr/listDivider"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/listFavorites"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/text_favorites"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/listFavorites"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="@dimen/controls_management_list_margin"
|
|
||||||
android:nestedScrollingEnabled="false"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/text_all"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/divider1"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/text_all"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/controls_management_list_margin"
|
|
||||||
android:text="@string/controls_favorite_header_all"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
|
||||||
android:textAllCaps="true"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/divider2"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/listFavorites"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/divider2"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/controls_app_divider_height"
|
|
||||||
android:layout_gravity="center_horizontal|top"
|
|
||||||
android:background="?android:attr/listDivider"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/listAll"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/text_all"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/listAll"
|
android:id="@+id/listAll"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="@dimen/controls_management_list_margin"
|
android:layout_marginTop="@dimen/controls_management_list_margin"
|
||||||
android:nestedScrollingEnabled="false"
|
android:nestedScrollingEnabled="false"/>
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/divider2"/>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</LinearLayout>
|
||||||
@@ -2614,8 +2614,8 @@
|
|||||||
<string name="controls_providers_subtitle">Choose an app from which to add controls</string>
|
<string name="controls_providers_subtitle">Choose an app from which to add controls</string>
|
||||||
<!-- Number of favorites for controls management screen [CHAR LIMIT=NONE]-->
|
<!-- Number of favorites for controls management screen [CHAR LIMIT=NONE]-->
|
||||||
<plurals name="controls_number_of_favorites">
|
<plurals name="controls_number_of_favorites">
|
||||||
<item quantity="one"><xliff:g id="number" example="1">%s</xliff:g> current favorite.</item>
|
<item quantity="one"><xliff:g id="number" example="1">%s</xliff:g> control added.</item>
|
||||||
<item quantity="other"><xliff:g id="number" example="3">%s</xliff:g> current favorites.</item>
|
<item quantity="other"><xliff:g id="number" example="3">%s</xliff:g> controls added.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
<!-- Controls management controls screen default title [CHAR LIMIT=30] -->
|
<!-- Controls management controls screen default title [CHAR LIMIT=30] -->
|
||||||
@@ -2626,6 +2626,10 @@
|
|||||||
<string name="controls_favorite_header_favorites">Favorites</string>
|
<string name="controls_favorite_header_favorites">Favorites</string>
|
||||||
<!-- Controls management controls screen all header [CHAR LIMIT=50] -->
|
<!-- Controls management controls screen all header [CHAR LIMIT=50] -->
|
||||||
<string name="controls_favorite_header_all">All</string>
|
<string name="controls_favorite_header_all">All</string>
|
||||||
<!-- Controls management controls screen error on load message [CHAR LIMIT=50] -->
|
<!-- Controls management controls screen error on load message [CHAR LIMIT=60] -->
|
||||||
<string name="controls_favorite_load_error">The list of all controls could not be loaded.</string>
|
<string name="controls_favorite_load_error">The list of all controls could not be loaded.</string>
|
||||||
|
<!-- Controls management controls screen header for Other zone [CHAR LIMIT=60] -->
|
||||||
|
<string name="controls_favorite_other_zone_header">Other</string>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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.text.TextUtils
|
||||||
|
import android.util.ArrayMap
|
||||||
|
import com.android.systemui.controls.ControlStatus
|
||||||
|
import com.android.systemui.controls.controller.ControlInfo
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This model is used to show all 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
|
||||||
|
* @property noZoneString text to use as header for all controls that have blank or `null` zone.
|
||||||
|
*/
|
||||||
|
class AllModel(
|
||||||
|
private val controls: List<ControlStatus>,
|
||||||
|
initialFavoriteIds: List<String>,
|
||||||
|
private val emptyZoneString: CharSequence
|
||||||
|
) : ControlsModel {
|
||||||
|
|
||||||
|
override val favorites: List<ControlInfo.Builder>
|
||||||
|
get() = favoriteIds.mapNotNull { id ->
|
||||||
|
val control = controls.firstOrNull { it.control.controlId == id }?.control
|
||||||
|
control?.let {
|
||||||
|
ControlInfo.Builder().apply {
|
||||||
|
controlId = it.controlId
|
||||||
|
controlTitle = it.title
|
||||||
|
deviceType = it.deviceType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val favoriteIds = initialFavoriteIds.toMutableList()
|
||||||
|
|
||||||
|
override val elements: List<ElementWrapper> = createWrappers(controls)
|
||||||
|
|
||||||
|
override fun changeFavoriteStatus(controlId: String, favorite: Boolean) {
|
||||||
|
if (favorite) {
|
||||||
|
favoriteIds.add(controlId)
|
||||||
|
} else {
|
||||||
|
favoriteIds.remove(controlId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createWrappers(list: List<ControlStatus>): List<ElementWrapper> {
|
||||||
|
val map = list.groupByTo(OrderedMap(ArrayMap<CharSequence, MutableList<ControlStatus>>())) {
|
||||||
|
it.control.zone ?: ""
|
||||||
|
}
|
||||||
|
val output = mutableListOf<ElementWrapper>()
|
||||||
|
var emptyZoneValues: Sequence<ControlWrapper>? = null
|
||||||
|
for (zoneName in map.orderedKeys) {
|
||||||
|
val values = map.getValue(zoneName).asSequence().map { ControlWrapper(it) }
|
||||||
|
if (TextUtils.isEmpty(zoneName)) {
|
||||||
|
emptyZoneValues = values
|
||||||
|
} else {
|
||||||
|
output.add(ZoneNameWrapper(zoneName))
|
||||||
|
output.addAll(values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add controls with empty zone at the end
|
||||||
|
if (emptyZoneValues != null) {
|
||||||
|
if (map.size != 1) {
|
||||||
|
output.add(ZoneNameWrapper(emptyZoneString))
|
||||||
|
}
|
||||||
|
output.addAll(emptyZoneValues)
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OrderedMap<K, V>(private val map: MutableMap<K, V>) : MutableMap<K, V> by map {
|
||||||
|
|
||||||
|
val orderedKeys = mutableListOf<K>()
|
||||||
|
|
||||||
|
override fun put(key: K, value: V): V? {
|
||||||
|
if (key !in map) {
|
||||||
|
orderedKeys.add(key)
|
||||||
|
}
|
||||||
|
return map.put(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clear() {
|
||||||
|
orderedKeys.clear()
|
||||||
|
map.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove(key: K): V? {
|
||||||
|
val removed = map.remove(key)
|
||||||
|
if (removed != null) {
|
||||||
|
orderedKeys.remove(key)
|
||||||
|
}
|
||||||
|
return removed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -116,6 +116,10 @@ class FavoritesRenderer(
|
|||||||
|
|
||||||
fun renderFavoritesForComponent(component: ComponentName): String {
|
fun renderFavoritesForComponent(component: ComponentName): String {
|
||||||
val qty = favoriteFunction(component)
|
val qty = favoriteFunction(component)
|
||||||
return resources.getQuantityString(R.plurals.controls_number_of_favorites, qty, qty)
|
if (qty != 0) {
|
||||||
|
return resources.getQuantityString(R.plurals.controls_number_of_favorites, qty, qty)
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,8 +42,7 @@ private typealias ModelFavoriteChanger = (String, Boolean) -> Unit
|
|||||||
* @param onlyFavorites set to true to only display favorites instead of all controls
|
* @param onlyFavorites set to true to only display favorites instead of all controls
|
||||||
*/
|
*/
|
||||||
class ControlAdapter(
|
class ControlAdapter(
|
||||||
private val layoutInflater: LayoutInflater,
|
private val layoutInflater: LayoutInflater
|
||||||
private val onlyFavorites: Boolean = false
|
|
||||||
) : RecyclerView.Adapter<Holder>() {
|
) : RecyclerView.Adapter<Holder>() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -57,22 +56,21 @@ class ControlAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelList: List<ElementWrapper> = emptyList()
|
private var model: ControlsModel? = null
|
||||||
private var favoritesModel: FavoriteModel? = null
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
TYPE_CONTROL -> {
|
TYPE_CONTROL -> {
|
||||||
ControlHolder(
|
ControlHolder(
|
||||||
layoutInflater.inflate(R.layout.controls_base_item, parent, false).apply {
|
layoutInflater.inflate(R.layout.controls_base_item, parent, false).apply {
|
||||||
layoutParams.apply {
|
layoutParams.apply {
|
||||||
width = ViewGroup.LayoutParams.MATCH_PARENT
|
width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
}
|
}
|
||||||
elevation = 15f
|
elevation = 15f
|
||||||
},
|
}
|
||||||
{ id, favorite ->
|
) { id, favorite ->
|
||||||
favoritesModel?.changeFavoriteStatus(id, favorite)
|
model?.changeFavoriteStatus(id, favorite)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
TYPE_ZONE -> {
|
TYPE_ZONE -> {
|
||||||
ZoneHolder(layoutInflater.inflate(R.layout.controls_zone_header, parent, false))
|
ZoneHolder(layoutInflater.inflate(R.layout.controls_zone_header, parent, false))
|
||||||
@@ -81,27 +79,26 @@ class ControlAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeFavoritesModel(favoritesModel: FavoriteModel) {
|
fun changeModel(model: ControlsModel) {
|
||||||
this.favoritesModel = favoritesModel
|
this.model = model
|
||||||
if (onlyFavorites) {
|
|
||||||
modelList = favoritesModel.favorites
|
|
||||||
} else {
|
|
||||||
modelList = favoritesModel.all
|
|
||||||
}
|
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = modelList.size
|
override fun getItemCount() = model?.elements?.size ?: 0
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: Holder, index: Int) {
|
override fun onBindViewHolder(holder: Holder, index: Int) {
|
||||||
holder.bindData(modelList[index])
|
model?.let {
|
||||||
|
holder.bindData(it.elements[index])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
return when (modelList[position]) {
|
model?.let {
|
||||||
is ZoneNameWrapper -> TYPE_ZONE
|
return when (it.elements.get(position)) {
|
||||||
is ControlWrapper -> TYPE_CONTROL
|
is ZoneNameWrapper -> TYPE_ZONE
|
||||||
}
|
is ControlWrapper -> TYPE_CONTROL
|
||||||
|
}
|
||||||
|
} ?: throw IllegalStateException("Getting item type for null model")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,9 @@ import android.view.ViewStub
|
|||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
import com.android.systemui.controls.controller.ControlInfo
|
|
||||||
import com.android.systemui.controls.controller.ControlsControllerImpl
|
import com.android.systemui.controls.controller.ControlsControllerImpl
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
import com.android.systemui.settings.CurrentUserTracker
|
import com.android.systemui.settings.CurrentUserTracker
|
||||||
@@ -51,33 +49,10 @@ class ControlsFavoritingActivity @Inject constructor(
|
|||||||
|
|
||||||
private lateinit var recyclerViewAll: RecyclerView
|
private lateinit var recyclerViewAll: RecyclerView
|
||||||
private lateinit var adapterAll: ControlAdapter
|
private lateinit var adapterAll: ControlAdapter
|
||||||
private lateinit var recyclerViewFavorites: RecyclerView
|
private lateinit var statusText: TextView
|
||||||
private lateinit var adapterFavorites: ControlAdapter
|
private var model: ControlsModel? = null
|
||||||
private lateinit var errorText: TextView
|
|
||||||
private var component: ComponentName? = null
|
private var component: ComponentName? = null
|
||||||
|
|
||||||
private var currentModel: FavoriteModel? = null
|
|
||||||
private var itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback(
|
|
||||||
/* dragDirs */ ItemTouchHelper.UP
|
|
||||||
or ItemTouchHelper.DOWN
|
|
||||||
or ItemTouchHelper.LEFT
|
|
||||||
or ItemTouchHelper.RIGHT,
|
|
||||||
/* swipeDirs */0
|
|
||||||
) {
|
|
||||||
override fun onMove(
|
|
||||||
recyclerView: RecyclerView,
|
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
|
||||||
target: RecyclerView.ViewHolder
|
|
||||||
): Boolean {
|
|
||||||
return currentModel?.onMoveItem(
|
|
||||||
viewHolder.layoutPosition, target.layoutPosition) != null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
|
|
||||||
|
|
||||||
override fun isItemViewSwipeEnabled() = false
|
|
||||||
}
|
|
||||||
|
|
||||||
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
|
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
|
||||||
private val startingUser = controller.currentUserId
|
private val startingUser = controller.currentUserId
|
||||||
|
|
||||||
@@ -89,6 +64,10 @@ class ControlsFavoritingActivity @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.controls_management)
|
setContentView(R.layout.controls_management)
|
||||||
@@ -99,21 +78,27 @@ class ControlsFavoritingActivity @Inject constructor(
|
|||||||
|
|
||||||
val app = intent.getCharSequenceExtra(EXTRA_APP)
|
val app = intent.getCharSequenceExtra(EXTRA_APP)
|
||||||
component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
|
component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
|
||||||
errorText = requireViewById(R.id.error_message)
|
statusText = requireViewById(R.id.status_message)
|
||||||
|
|
||||||
setUpRecyclerViews()
|
setUpRecyclerView()
|
||||||
|
|
||||||
requireViewById<TextView>(R.id.title).text = app?.let { it }
|
requireViewById<TextView>(R.id.title).text = app?.let { it }
|
||||||
?: resources.getText(R.string.controls_favorite_default_title)
|
?: resources.getText(R.string.controls_favorite_default_title)
|
||||||
requireViewById<TextView>(R.id.subtitle).text =
|
requireViewById<TextView>(R.id.subtitle).text =
|
||||||
resources.getText(R.string.controls_favorite_subtitle)
|
resources.getText(R.string.controls_favorite_subtitle)
|
||||||
|
|
||||||
|
requireViewById<Button>(R.id.other_apps).apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
setOnClickListener {
|
||||||
|
this@ControlsFavoritingActivity.onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
requireViewById<Button>(R.id.done).setOnClickListener {
|
requireViewById<Button>(R.id.done).setOnClickListener {
|
||||||
if (component == null) return@setOnClickListener
|
if (component == null) return@setOnClickListener
|
||||||
val favoritesForStorage = currentModel?.favorites?.map {
|
val favoritesForStorage = model?.favorites?.map {
|
||||||
with(it.controlStatus.control) {
|
it.componentName = component!!
|
||||||
ControlInfo(component!!, controlId, title, deviceType)
|
it.build()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (favoritesForStorage != null) {
|
if (favoritesForStorage != null) {
|
||||||
controller.replaceFavoritesForComponent(component!!, favoritesForStorage)
|
controller.replaceFavoritesForComponent(component!!, favoritesForStorage)
|
||||||
@@ -122,20 +107,22 @@ class ControlsFavoritingActivity @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
component?.let {
|
component?.let {
|
||||||
|
statusText.text = resources.getText(com.android.internal.R.string.loading)
|
||||||
controller.loadForComponent(it, Consumer { data ->
|
controller.loadForComponent(it, Consumer { data ->
|
||||||
val allControls = data.allControls
|
val allControls = data.allControls
|
||||||
val favoriteKeys = data.favoritesIds
|
val favoriteKeys = data.favoritesIds
|
||||||
val error = data.errorOnLoad
|
val error = data.errorOnLoad
|
||||||
executor.execute {
|
executor.execute {
|
||||||
val favoriteModel = FavoriteModel(
|
val emptyZoneString = resources.getText(
|
||||||
allControls,
|
R.string.controls_favorite_other_zone_header)
|
||||||
favoriteKeys,
|
val model = AllModel(allControls, favoriteKeys, emptyZoneString)
|
||||||
allAdapter = adapterAll,
|
adapterAll.changeModel(model)
|
||||||
favoritesAdapter = adapterFavorites)
|
this.model = model
|
||||||
adapterAll.changeFavoritesModel(favoriteModel)
|
if (error) {
|
||||||
adapterFavorites.changeFavoritesModel(favoriteModel)
|
statusText.text = resources.getText(R.string.controls_favorite_load_error)
|
||||||
currentModel = favoriteModel
|
} else {
|
||||||
errorText.visibility = if (error) View.VISIBLE else View.GONE
|
statusText.visibility = View.GONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -143,7 +130,7 @@ class ControlsFavoritingActivity @Inject constructor(
|
|||||||
currentUserTracker.startTracking()
|
currentUserTracker.startTracking()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setUpRecyclerViews() {
|
private fun setUpRecyclerView() {
|
||||||
val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin)
|
val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin)
|
||||||
val itemDecorator = MarginItemDecorator(margin, margin)
|
val itemDecorator = MarginItemDecorator(margin, margin)
|
||||||
val layoutInflater = LayoutInflater.from(applicationContext)
|
val layoutInflater = LayoutInflater.from(applicationContext)
|
||||||
@@ -156,14 +143,6 @@ class ControlsFavoritingActivity @Inject constructor(
|
|||||||
}
|
}
|
||||||
addItemDecoration(itemDecorator)
|
addItemDecoration(itemDecorator)
|
||||||
}
|
}
|
||||||
|
|
||||||
adapterFavorites = ControlAdapter(layoutInflater, true)
|
|
||||||
recyclerViewFavorites = requireViewById<RecyclerView>(R.id.listFavorites).apply {
|
|
||||||
layoutManager = GridLayoutManager(applicationContext, 2)
|
|
||||||
adapter = adapterFavorites
|
|
||||||
addItemDecoration(itemDecorator)
|
|
||||||
}
|
|
||||||
ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(recyclerViewFavorites)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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 com.android.systemui.controls.ControlStatus
|
||||||
|
import com.android.systemui.controls.controller.ControlInfo
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model for using with [ControlAdapter].
|
||||||
|
*
|
||||||
|
* Implementations of this interface provide different views of the controls to show.
|
||||||
|
*/
|
||||||
|
interface ControlsModel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of favorites (builders) in order.
|
||||||
|
*
|
||||||
|
* This should be obtained prior to storing the favorites using
|
||||||
|
* [ControlsController.replaceFavoritesForComponent].
|
||||||
|
*/
|
||||||
|
val favorites: List<ControlInfo.Builder>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all the elements to display by the corresponding [RecyclerView].
|
||||||
|
*/
|
||||||
|
val elements: List<ElementWrapper>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the favorite status of a particular control.
|
||||||
|
*/
|
||||||
|
fun changeFavoriteStatus(controlId: String, favorite: Boolean) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move an item (in elements) from one position to another.
|
||||||
|
*/
|
||||||
|
fun onMoveItem(from: Int, to: Int) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper classes for the different types of elements shown in the [RecyclerView]s in
|
||||||
|
* [ControlAdapter].
|
||||||
|
*/
|
||||||
|
sealed class ElementWrapper
|
||||||
|
data class ZoneNameWrapper(val zoneName: CharSequence) : ElementWrapper()
|
||||||
|
data class ControlWrapper(val controlStatus: ControlStatus) : ElementWrapper()
|
||||||
@@ -21,6 +21,7 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewStub
|
import android.view.ViewStub
|
||||||
|
import android.widget.Button
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@@ -86,6 +87,10 @@ class ControlsProviderSelectorActivity @Inject constructor(
|
|||||||
requireViewById<TextView>(R.id.subtitle).text =
|
requireViewById<TextView>(R.id.subtitle).text =
|
||||||
resources.getText(R.string.controls_providers_subtitle)
|
resources.getText(R.string.controls_providers_subtitle)
|
||||||
|
|
||||||
|
requireViewById<Button>(R.id.done).setOnClickListener {
|
||||||
|
this@ControlsProviderSelectorActivity.finishAffinity()
|
||||||
|
}
|
||||||
|
|
||||||
currentUserTracker.startTracking()
|
currentUserTracker.startTracking()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,12 +142,4 @@ class CharSequenceComparator : Comparator<CharSequence> {
|
|||||||
else if (p0 != null && p1 == null) return 1
|
else if (p0 != null && p1 == null) return 1
|
||||||
return p0.toString().compareTo(p1.toString())
|
return p0.toString().compareTo(p1.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrapper classes for the different types of elements shown in the [RecyclerView]s in
|
|
||||||
* [ControlsFavoritingActivity].
|
|
||||||
*/
|
|
||||||
sealed class ElementWrapper
|
|
||||||
data class ZoneNameWrapper(val zoneName: CharSequence) : ElementWrapper()
|
|
||||||
data class ControlWrapper(val controlStatus: ControlStatus) : ElementWrapper()
|
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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.PendingIntent
|
||||||
|
import android.service.controls.Control
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.controls.ControlStatus
|
||||||
|
import com.android.systemui.controls.controller.ControlInfo
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
class AllModelTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val EMPTY_STRING = "Other"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
lateinit var pendingIntent: PendingIntent
|
||||||
|
|
||||||
|
val idPrefix = "controlId"
|
||||||
|
val favoritesIndices = listOf(7, 3, 1, 9)
|
||||||
|
val favoritesList = favoritesIndices.map { "controlId$it" }
|
||||||
|
lateinit var controls: List<ControlStatus>
|
||||||
|
|
||||||
|
lateinit var model: AllModel
|
||||||
|
|
||||||
|
private fun zoneMap(id: Int): String? {
|
||||||
|
return when (id) {
|
||||||
|
10 -> ""
|
||||||
|
11 -> null
|
||||||
|
else -> ((id + 1) % 3).toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
|
// controlId0 --> zone = 1
|
||||||
|
// controlId1 --> zone = 2, favorite
|
||||||
|
// controlId2 --> zone = 0
|
||||||
|
// controlId3 --> zone = 1, favorite
|
||||||
|
// controlId4 --> zone = 2
|
||||||
|
// controlId5 --> zone = 0
|
||||||
|
// controlId6 --> zone = 1
|
||||||
|
// controlId7 --> zone = 2, favorite
|
||||||
|
// controlId8 --> zone = 0
|
||||||
|
// controlId9 --> zone = 1, favorite
|
||||||
|
// controlId10 --> zone = ""
|
||||||
|
// controlId11 --> zone = null
|
||||||
|
controls = (0..11).map {
|
||||||
|
ControlStatus(
|
||||||
|
Control.StatelessBuilder("$idPrefix$it", pendingIntent)
|
||||||
|
.setZone(zoneMap(it))
|
||||||
|
.build(),
|
||||||
|
it in favoritesIndices
|
||||||
|
)
|
||||||
|
}
|
||||||
|
model = AllModel(controls, favoritesList, EMPTY_STRING)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testElements() {
|
||||||
|
|
||||||
|
// Zones are sorted by order of appearance, with empty at the end with special header.
|
||||||
|
val expected = listOf(
|
||||||
|
ZoneNameWrapper("1"),
|
||||||
|
ControlWrapper(controls[0]),
|
||||||
|
ControlWrapper(controls[3]),
|
||||||
|
ControlWrapper(controls[6]),
|
||||||
|
ControlWrapper(controls[9]),
|
||||||
|
ZoneNameWrapper("2"),
|
||||||
|
ControlWrapper(controls[1]),
|
||||||
|
ControlWrapper(controls[4]),
|
||||||
|
ControlWrapper(controls[7]),
|
||||||
|
ZoneNameWrapper("0"),
|
||||||
|
ControlWrapper(controls[2]),
|
||||||
|
ControlWrapper(controls[5]),
|
||||||
|
ControlWrapper(controls[8]),
|
||||||
|
ZoneNameWrapper(EMPTY_STRING),
|
||||||
|
ControlWrapper(controls[10]),
|
||||||
|
ControlWrapper(controls[11])
|
||||||
|
)
|
||||||
|
expected.zip(model.elements).forEachIndexed { index, it ->
|
||||||
|
assertEquals("Error in item at index $index", it.first, it.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sameControl(controlInfo: ControlInfo.Builder, control: Control): Boolean {
|
||||||
|
return controlInfo.controlId == control.controlId &&
|
||||||
|
controlInfo.controlTitle == control.title &&
|
||||||
|
controlInfo.deviceType == control.deviceType
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAllEmpty_noHeader() {
|
||||||
|
val selected_controls = listOf(controls[10], controls[11])
|
||||||
|
val new_model = AllModel(selected_controls, emptyList(), EMPTY_STRING)
|
||||||
|
val expected = listOf(
|
||||||
|
ControlWrapper(controls[10]),
|
||||||
|
ControlWrapper(controls[11])
|
||||||
|
)
|
||||||
|
|
||||||
|
expected.zip(new_model.elements).forEachIndexed { index, it ->
|
||||||
|
assertEquals("Error in item at index $index", it.first, it.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFavorites() {
|
||||||
|
val expectedFavorites = favoritesIndices.map(controls::get).map(ControlStatus::control)
|
||||||
|
model.favorites.zip(expectedFavorites).forEach {
|
||||||
|
assertTrue(sameControl(it.first, it.second))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAddFavorite() {
|
||||||
|
val indexToAdd = 6
|
||||||
|
model.changeFavoriteStatus("$idPrefix$indexToAdd", true)
|
||||||
|
|
||||||
|
val expectedFavorites = favoritesIndices.map(controls::get).map(ControlStatus::control) +
|
||||||
|
controls[indexToAdd].control
|
||||||
|
|
||||||
|
model.favorites.zip(expectedFavorites).forEach {
|
||||||
|
assertTrue(sameControl(it.first, it.second))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAddFavorite_alreadyThere() {
|
||||||
|
val indexToAdd = 7
|
||||||
|
model.changeFavoriteStatus("$idPrefix$indexToAdd", true)
|
||||||
|
|
||||||
|
val expectedFavorites = favoritesIndices.map(controls::get).map(ControlStatus::control)
|
||||||
|
|
||||||
|
model.favorites.zip(expectedFavorites).forEach {
|
||||||
|
assertTrue(sameControl(it.first, it.second))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testRemoveFavorite() {
|
||||||
|
val indexToRemove = 3
|
||||||
|
model.changeFavoriteStatus("$idPrefix$indexToRemove", false)
|
||||||
|
|
||||||
|
val expectedFavorites = (favoritesIndices.filterNot { it == indexToRemove })
|
||||||
|
.map(controls::get)
|
||||||
|
.map(ControlStatus::control)
|
||||||
|
|
||||||
|
model.favorites.zip(expectedFavorites).forEach {
|
||||||
|
assertTrue(sameControl(it.first, it.second))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testRemoveFavorite_notThere() {
|
||||||
|
val indexToRemove = 4
|
||||||
|
model.changeFavoriteStatus("$idPrefix$indexToRemove", false)
|
||||||
|
|
||||||
|
val expectedFavorites = favoritesIndices.map(controls::get).map(ControlStatus::control)
|
||||||
|
|
||||||
|
model.favorites.zip(expectedFavorites).forEach {
|
||||||
|
assertTrue(sameControl(it.first, it.second))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user