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