1 /* <lambda>null2 * Copyright (C) 2023 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.customization.picker.clock.data.repository 18 19 import android.provider.Settings 20 import androidx.annotation.ColorInt 21 import androidx.annotation.IntRange 22 import com.android.customization.picker.clock.shared.ClockSize 23 import com.android.customization.picker.clock.shared.model.ClockMetadataModel 24 import com.android.systemui.plugins.clocks.ClockMetadata 25 import com.android.systemui.shared.clocks.ClockRegistry 26 import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository 27 import kotlinx.coroutines.CoroutineDispatcher 28 import kotlinx.coroutines.CoroutineScope 29 import kotlinx.coroutines.ExperimentalCoroutinesApi 30 import kotlinx.coroutines.channels.awaitClose 31 import kotlinx.coroutines.delay 32 import kotlinx.coroutines.flow.Flow 33 import kotlinx.coroutines.flow.SharedFlow 34 import kotlinx.coroutines.flow.SharingStarted 35 import kotlinx.coroutines.flow.callbackFlow 36 import kotlinx.coroutines.flow.distinctUntilChanged 37 import kotlinx.coroutines.flow.flowOn 38 import kotlinx.coroutines.flow.map 39 import kotlinx.coroutines.flow.mapLatest 40 import kotlinx.coroutines.flow.mapNotNull 41 import kotlinx.coroutines.flow.shareIn 42 import org.json.JSONObject 43 44 /** Implementation of [ClockPickerRepository], using [ClockRegistry]. */ 45 class ClockPickerRepositoryImpl( 46 private val secureSettingsRepository: SecureSettingsRepository, 47 private val registry: ClockRegistry, 48 scope: CoroutineScope, 49 mainDispatcher: CoroutineDispatcher, 50 ) : ClockPickerRepository { 51 52 @OptIn(ExperimentalCoroutinesApi::class) 53 override val allClocks: Flow<List<ClockMetadataModel>> = 54 callbackFlow { 55 fun send() { 56 val activeClockId = registry.activeClockId 57 val allClocks = 58 registry.getClocks().map { 59 it.toModel(isSelected = it.clockId == activeClockId) 60 } 61 62 trySend(allClocks) 63 } 64 65 val listener = 66 object : ClockRegistry.ClockChangeListener { 67 override fun onAvailableClocksChanged() { 68 send() 69 } 70 } 71 registry.registerClockChangeListener(listener) 72 send() 73 awaitClose { registry.unregisterClockChangeListener(listener) } 74 } 75 .flowOn(mainDispatcher) 76 .mapLatest { allClocks -> 77 // Loading list of clock plugins can cause many consecutive calls of 78 // onAvailableClocksChanged(). We only care about the final fully-initiated clock 79 // list. Delay to avoid unnecessary too many emits. 80 delay(100) 81 allClocks 82 } 83 84 /** The currently-selected clock. This also emits the clock color information. */ 85 override val selectedClock: Flow<ClockMetadataModel> = 86 callbackFlow { 87 fun send() { 88 val activeClockId = registry.activeClockId 89 val metadata = registry.settings?.metadata 90 val model = 91 registry 92 .getClocks() 93 .find { clockMetadata -> clockMetadata.clockId == activeClockId } 94 ?.toModel( 95 isSelected = true, 96 selectedColorId = metadata?.getSelectedColorId(), 97 colorTone = metadata?.getColorTone() 98 ?: ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS, 99 seedColor = registry.seedColor 100 ) 101 trySend(model) 102 } 103 104 val listener = 105 object : ClockRegistry.ClockChangeListener { 106 override fun onCurrentClockChanged() { 107 send() 108 } 109 110 override fun onAvailableClocksChanged() { 111 send() 112 } 113 } 114 registry.registerClockChangeListener(listener) 115 send() 116 awaitClose { registry.unregisterClockChangeListener(listener) } 117 } 118 .flowOn(mainDispatcher) 119 .mapNotNull { it } 120 121 override suspend fun setSelectedClock(clockId: String) { 122 registry.mutateSetting { oldSettings -> 123 val newSettings = oldSettings.copy(clockId = clockId) 124 newSettings.metadata = oldSettings.metadata 125 newSettings 126 } 127 } 128 129 override suspend fun setClockColor( 130 selectedColorId: String?, 131 @IntRange(from = 0, to = 100) colorToneProgress: Int, 132 @ColorInt seedColor: Int?, 133 ) { 134 registry.mutateSetting { oldSettings -> 135 val newSettings = oldSettings.copy(seedColor = seedColor) 136 newSettings.metadata = 137 oldSettings.metadata 138 .put(KEY_METADATA_SELECTED_COLOR_ID, selectedColorId) 139 .put(KEY_METADATA_COLOR_TONE_PROGRESS, colorToneProgress) 140 newSettings 141 } 142 } 143 144 override val selectedClockSize: SharedFlow<ClockSize> = 145 secureSettingsRepository 146 .intSetting( 147 name = Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 148 defaultValue = DEFAULT_CLOCK_SIZE, 149 ) 150 .map { setting -> setting == 1 } 151 .map { isDynamic -> if (isDynamic) ClockSize.DYNAMIC else ClockSize.SMALL } 152 .distinctUntilChanged() 153 .shareIn( 154 scope = scope, 155 started = SharingStarted.Eagerly, 156 replay = 1, 157 ) 158 159 override suspend fun setClockSize(size: ClockSize) { 160 secureSettingsRepository.setInt( 161 name = Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 162 value = if (size == ClockSize.DYNAMIC) 1 else 0, 163 ) 164 } 165 166 private fun JSONObject.getSelectedColorId(): String? { 167 return if (this.isNull(KEY_METADATA_SELECTED_COLOR_ID)) { 168 null 169 } else { 170 this.getString(KEY_METADATA_SELECTED_COLOR_ID) 171 } 172 } 173 174 private fun JSONObject.getColorTone(): Int { 175 return this.optInt( 176 KEY_METADATA_COLOR_TONE_PROGRESS, 177 ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS 178 ) 179 } 180 181 /** By default, [ClockMetadataModel] has no color information unless specified. */ 182 private fun ClockMetadata.toModel( 183 isSelected: Boolean, 184 selectedColorId: String? = null, 185 @IntRange(from = 0, to = 100) colorTone: Int = 0, 186 @ColorInt seedColor: Int? = null, 187 ): ClockMetadataModel { 188 return ClockMetadataModel( 189 clockId = clockId, 190 isSelected = isSelected, 191 selectedColorId = selectedColorId, 192 colorToneProgress = colorTone, 193 seedColor = seedColor, 194 ) 195 } 196 197 companion object { 198 // The selected color in the color option list 199 private const val KEY_METADATA_SELECTED_COLOR_ID = "metadataSelectedColorId" 200 201 // The color tone to apply to the selected color 202 private const val KEY_METADATA_COLOR_TONE_PROGRESS = "metadataColorToneProgress" 203 204 // The default clock size is 1, which means dynamic 205 private const val DEFAULT_CLOCK_SIZE = 1 206 } 207 } 208