1 /*
2  * Copyright (C) 2022 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 
18 package com.android.systemui.shared.customization.data.content
19 
20 import android.graphics.drawable.BitmapDrawable
21 import android.graphics.drawable.Drawable
22 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
23 import kotlinx.coroutines.flow.Flow
24 import kotlinx.coroutines.flow.MutableStateFlow
25 import kotlinx.coroutines.flow.asStateFlow
26 import kotlinx.coroutines.flow.combine
27 
28 class FakeCustomizationProviderClient(
29     slots: List<CustomizationProviderClient.Slot> =
30         listOf(
31             CustomizationProviderClient.Slot(
32                 id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
33                 capacity = 1,
34             ),
35             CustomizationProviderClient.Slot(
36                 id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
37                 capacity = 1,
38             ),
39         ),
40     affordances: List<CustomizationProviderClient.Affordance> =
41         listOf(
42             CustomizationProviderClient.Affordance(
43                 id = AFFORDANCE_1,
44                 name = AFFORDANCE_1,
45                 iconResourceId = 1,
46             ),
47             CustomizationProviderClient.Affordance(
48                 id = AFFORDANCE_2,
49                 name = AFFORDANCE_2,
50                 iconResourceId = 2,
51             ),
52             CustomizationProviderClient.Affordance(
53                 id = AFFORDANCE_3,
54                 name = AFFORDANCE_3,
55                 iconResourceId = 3,
56             ),
57         ),
58     flags: List<CustomizationProviderClient.Flag> =
59         listOf(
60             CustomizationProviderClient.Flag(
61                 name =
62                     CustomizationProviderContract.FlagsTable
63                         .FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED,
64                 value = true,
65             )
66         ),
67 ) : CustomizationProviderClient {
68 
69     private val slots = MutableStateFlow(slots)
70     private val affordances = MutableStateFlow(affordances)
71     private val flags = MutableStateFlow(flags)
72 
73     private val selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
74 
75     override suspend fun insertSelection(slotId: String, affordanceId: String) {
76         val slotCapacity =
77             querySlots().find { it.id == slotId }?.capacity
78                 ?: error("Slot with ID \"$slotId\" not found!")
79         val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList()
80         while (affordances.size + 1 > slotCapacity) {
81             affordances.removeAt(0)
82         }
83         affordances.remove(affordanceId)
84         affordances.add(affordanceId)
85         selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances }
86     }
87 
88     override suspend fun querySlots(): List<CustomizationProviderClient.Slot> {
89         return slots.value
90     }
91 
92     override suspend fun queryFlags(): List<CustomizationProviderClient.Flag> {
93         return flags.value
94     }
95 
96     override fun observeSlots(): Flow<List<CustomizationProviderClient.Slot>> {
97         return slots.asStateFlow()
98     }
99 
100     override fun observeFlags(): Flow<List<CustomizationProviderClient.Flag>> {
101         return flags.asStateFlow()
102     }
103 
104     override suspend fun queryAffordances(): List<CustomizationProviderClient.Affordance> {
105         return affordances.value
106     }
107 
108     override fun observeAffordances(): Flow<List<CustomizationProviderClient.Affordance>> {
109         return affordances.asStateFlow()
110     }
111 
112     override suspend fun querySelections(): List<CustomizationProviderClient.Selection> {
113         return toSelectionList(selections.value, affordances.value)
114     }
115 
116     override fun observeSelections(): Flow<List<CustomizationProviderClient.Selection>> {
117         return combine(selections, affordances) { selections, affordances ->
118             toSelectionList(selections, affordances)
119         }
120     }
121 
122     override suspend fun deleteSelection(slotId: String, affordanceId: String) {
123         val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList()
124         affordances.remove(affordanceId)
125 
126         selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances }
127     }
128 
129     override suspend fun deleteAllSelections(slotId: String) {
130         selections.value = selections.value.toMutableMap().apply { this[slotId] = emptyList() }
131     }
132 
133     override suspend fun getAffordanceIcon(iconResourceId: Int, tintColor: Int): Drawable {
134         return when (iconResourceId) {
135             1 -> ICON_1
136             2 -> ICON_2
137             3 -> ICON_3
138             else -> BitmapDrawable()
139         }
140     }
141 
142     fun setFlag(
143         name: String,
144         value: Boolean,
145     ) {
146         flags.value =
147             flags.value.toMutableList().apply {
148                 removeIf { it.name == name }
149                 add(CustomizationProviderClient.Flag(name = name, value = value))
150             }
151     }
152 
153     fun setSlotCapacity(slotId: String, capacity: Int) {
154         slots.value =
155             slots.value.toMutableList().apply {
156                 val index = indexOfFirst { it.id == slotId }
157                 check(index != -1) { "Slot with ID \"$slotId\" doesn't exist!" }
158                 set(index, CustomizationProviderClient.Slot(id = slotId, capacity = capacity))
159             }
160     }
161 
162     fun addAffordance(affordance: CustomizationProviderClient.Affordance): Int {
163         affordances.value = affordances.value + listOf(affordance)
164         return affordances.value.size - 1
165     }
166 
167     private fun toSelectionList(
168         selections: Map<String, List<String>>,
169         affordances: List<CustomizationProviderClient.Affordance>,
170     ): List<CustomizationProviderClient.Selection> {
171         return selections
172             .map { (slotId, affordanceIds) ->
173                 affordanceIds.map { affordanceId ->
174                     val affordanceName =
175                         affordances.find { it.id == affordanceId }?.name
176                             ?: error("No affordance with ID of \"$affordanceId\"!")
177                     CustomizationProviderClient.Selection(
178                         slotId = slotId,
179                         affordanceId = affordanceId,
180                         affordanceName = affordanceName,
181                     )
182                 }
183             }
184             .flatten()
185     }
186 
187     companion object {
188         const val AFFORDANCE_1 = "affordance_1"
189         const val AFFORDANCE_2 = "affordance_2"
190         const val AFFORDANCE_3 = "affordance_3"
191         val ICON_1 = BitmapDrawable()
192         val ICON_2 = BitmapDrawable()
193         val ICON_3 = BitmapDrawable()
194     }
195 }
196