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) {
79             if (!modified) {
80                 modified = true
81                 controlsModelCallback.onFirstChange()
82             }
83             controlsModelCallback.onChange()
84         }
85         toChange?.let {
86             it.controlStatus.favorite = favorite
87         }
88     }
89 
90     private fun createWrappers(list: List<ControlStatus>): List<ElementWrapper> {
91         val map = list.groupByTo(OrderedMap(ArrayMap<CharSequence, MutableList<ControlStatus>>())) {
92             it.control.zone ?: ""
93         }
94         val output = mutableListOf<ElementWrapper>()
95         var emptyZoneValues: Sequence<ControlStatusWrapper>? = null
96         for (zoneName in map.orderedKeys) {
97             val values = map.getValue(zoneName).asSequence().map { ControlStatusWrapper(it) }
98             if (TextUtils.isEmpty(zoneName)) {
99                 emptyZoneValues = values
100             } else {
101                 output.add(ZoneNameWrapper(zoneName))
102                 output.addAll(values)
103             }
104         }
105         // Add controls with empty zone at the end
106         if (emptyZoneValues != null) {
107             if (map.size != 1) {
108                 output.add(ZoneNameWrapper(emptyZoneString))
109             }
110             output.addAll(emptyZoneValues)
111         }
112         return output
113     }
114 
115     private class OrderedMap<K, V>(private val map: MutableMap<K, V>) : MutableMap<K, V> by map {
116 
117         val orderedKeys = mutableListOf<K>()
118 
119         override fun put(key: K, value: V): V? {
120             if (key !in map) {
121                 orderedKeys.add(key)
122             }
123             return map.put(key, value)
124         }
125 
126         override fun clear() {
127             orderedKeys.clear()
128             map.clear()
129         }
130 
131         override fun remove(key: K): V? {
132             val removed = map.remove(key)
133             if (removed != null) {
134                 orderedKeys.remove(key)
135             }
136             return removed
137         }
138     }
139 }
140