1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.controls.management
18 
19 import android.text.TextUtils
20 import android.util.ArrayMap
21 import com.android.systemui.controls.ControlStatus
22 import com.android.systemui.controls.controller.ControlInfo
23 
24 /**
25  * This model is used to show controls separated by zones.
26  *
27  * The model will sort the controls and zones in the following manner:
28  *  * The zones will be sorted in a first seen basis
29  *  * The controls in each zone will be sorted in a first seen basis.
30  *
31  *  The controls passed should belong to the same structure, as an instance of this model will be
32  *  created for each structure.
33  *
34  *  The list of favorite ids can contain ids for controls not passed to this model. Those will be
35  *  filtered out.
36  *
37  * @property controls List of controls as returned by loading
38  * @property initialFavoriteIds sorted ids of favorite controls.
39  * @property noZoneString text to use as header for all controls that have blank or `null` zone.
40  * @property controlsModelCallback callback to notify that favorites have changed for the first time
41  */
42 class AllModel(
43     private val controls: List<ControlStatus>,
44     initialFavoriteIds: List<String>,
45     private val emptyZoneString: CharSequence,
46     private val controlsModelCallback: ControlsModel.ControlsModelCallback
47 ) : ControlsModel {
48 
49     private var modified = false
50 
51     override val moveHelper = null
52 
53     override val favorites: List<ControlInfo>
54         get() = favoriteIds.mapNotNull { id ->
55             val control = controls.firstOrNull { it.control.controlId == id }?.control
56             control?.let {
57                 ControlInfo.fromControl(it)
58             }
59         }
60 
61     private val favoriteIds = run {
62         val ids = controls.mapTo(HashSet()) { it.control.controlId }
63         initialFavoriteIds.filter { it in ids }.toMutableList()
64     }
65 
66     override val elements: List<ElementWrapper> = createWrappers(controls)
67 
68     override fun changeFavoriteStatus(controlId: String, favorite: Boolean) {
69         val toChange = elements.firstOrNull {
70             it is ControlStatusWrapper && it.controlStatus.control.controlId == controlId
71         } as ControlStatusWrapper?
72         if (favorite == toChange?.controlStatus?.favorite) return
73         val changed: Boolean = if (favorite) {
74             favoriteIds.add(controlId)
75         } else {
76             favoriteIds.remove(controlId)
77         }
78         if (changed && !modified) {
79             modified = true
80             controlsModelCallback.onFirstChange()
81         }
82         toChange?.let {
83             it.controlStatus.favorite = favorite
84         }
85     }
86 
87     private fun createWrappers(list: List<ControlStatus>): List<ElementWrapper> {
88         val map = list.groupByTo(OrderedMap(ArrayMap<CharSequence, MutableList<ControlStatus>>())) {
89             it.control.zone ?: ""
90         }
91         val output = mutableListOf<ElementWrapper>()
92         var emptyZoneValues: Sequence<ControlStatusWrapper>? = null
93         for (zoneName in map.orderedKeys) {
94             val values = map.getValue(zoneName).asSequence().map { ControlStatusWrapper(it) }
95             if (TextUtils.isEmpty(zoneName)) {
96                 emptyZoneValues = values
97             } else {
98                 output.add(ZoneNameWrapper(zoneName))
99                 output.addAll(values)
100             }
101         }
102         // Add controls with empty zone at the end
103         if (emptyZoneValues != null) {
104             if (map.size != 1) {
105                 output.add(ZoneNameWrapper(emptyZoneString))
106             }
107             output.addAll(emptyZoneValues)
108         }
109         return output
110     }
111 
112     private class OrderedMap<K, V>(private val map: MutableMap<K, V>) : MutableMap<K, V> by map {
113 
114         val orderedKeys = mutableListOf<K>()
115 
116         override fun put(key: K, value: V): V? {
117             if (key !in map) {
118                 orderedKeys.add(key)
119             }
120             return map.put(key, value)
121         }
122 
123         override fun clear() {
124             orderedKeys.clear()
125             map.clear()
126         }
127 
128         override fun remove(key: K): V? {
129             val removed = map.remove(key)
130             if (removed != null) {
131                 orderedKeys.remove(key)
132             }
133             return removed
134         }
135     }
136 }
137