• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.graphics.drawable.Drawable
20 import android.provider.Settings
21 import androidx.annotation.ColorInt
22 import androidx.annotation.IntRange
23 import com.android.customization.picker.clock.shared.ClockSize
24 import com.android.customization.picker.clock.shared.model.ClockMetadataModel
25 import com.android.systemui.plugins.clocks.AxisPresetConfig
26 import com.android.systemui.plugins.clocks.ClockAxisStyle
27 import com.android.systemui.plugins.clocks.ClockId
28 import com.android.systemui.plugins.clocks.ClockMetadata
29 import com.android.systemui.shared.clocks.ClockRegistry
30 import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
31 import com.android.wallpaper.picker.di.modules.MainDispatcher
32 import javax.inject.Inject
33 import javax.inject.Singleton
34 import kotlinx.coroutines.CoroutineDispatcher
35 import kotlinx.coroutines.CoroutineScope
36 import kotlinx.coroutines.ExperimentalCoroutinesApi
37 import kotlinx.coroutines.channels.awaitClose
38 import kotlinx.coroutines.delay
39 import kotlinx.coroutines.flow.Flow
40 import kotlinx.coroutines.flow.SharedFlow
41 import kotlinx.coroutines.flow.SharingStarted
42 import kotlinx.coroutines.flow.callbackFlow
43 import kotlinx.coroutines.flow.distinctUntilChanged
44 import kotlinx.coroutines.flow.flowOn
45 import kotlinx.coroutines.flow.map
46 import kotlinx.coroutines.flow.mapLatest
47 import kotlinx.coroutines.flow.mapNotNull
48 import kotlinx.coroutines.flow.shareIn
49 import org.json.JSONObject
50 
51 /** Implementation of [ClockPickerRepository], using [ClockRegistry]. */
52 @Singleton
53 class ClockPickerRepositoryImpl
54 @Inject
55 constructor(
56     private val secureSettingsRepository: SecureSettingsRepository,
57     private val registry: ClockRegistry,
58     @MainDispatcher mainScope: CoroutineScope,
59     @MainDispatcher mainDispatcher: CoroutineDispatcher,
60 ) : ClockPickerRepository {
61 
62     @OptIn(ExperimentalCoroutinesApi::class)
63     override val allClocks: Flow<List<ClockMetadataModel>> =
64         callbackFlow {
65                 fun send() {
66                     val activeClockId = registry.activeClockId
67                     val allClocks =
68                         registry.getClocks().mapNotNull {
69                             val clockConfig = registry.getClockPickerConfig(it.clockId)
70                             if (clockConfig != null) {
71                                 it.toModel(
72                                     isSelected = it.clockId == activeClockId,
73                                     description = clockConfig.description,
74                                     thumbnail = clockConfig.thumbnail,
75                                     isReactiveToTone = clockConfig.isReactiveToTone,
76                                     axisPresetConfig = clockConfig.presetConfig,
77                                 )
78                             } else {
79                                 null
80                             }
81                         }
82 
83                     trySend(allClocks)
84                 }
85 
86                 val listener =
87                     object : ClockRegistry.ClockChangeListener {
88                         override fun onCurrentClockChanged() {
89                             send()
90                         }
91 
92                         override fun onAvailableClocksChanged() {
93                             send()
94                         }
95                     }
96                 registry.registerClockChangeListener(listener)
97                 send()
98                 awaitClose { registry.unregisterClockChangeListener(listener) }
99             }
100             .flowOn(mainDispatcher)
101             .mapLatest { allClocks ->
102                 // Loading list of clock plugins can cause many consecutive calls of
103                 // onAvailableClocksChanged(). We only care about the final fully-initiated clock
104                 // list. Delay to avoid unnecessary too many emits.
105                 delay(100)
106                 allClocks
107             }
108 
109     /** The currently-selected clock. This also emits the clock color information. */
110     override val selectedClock: Flow<ClockMetadataModel> =
111         callbackFlow<ClockMetadataModel?> {
112                 fun send() {
113                     val activeClockId = registry.activeClockId
114                     val metadata = registry.settings?.metadata
115                     val clockConfig = registry.getClockPickerConfig(activeClockId)
116                     val model =
117                         clockConfig?.let {
118                             registry
119                                 .getClocks()
120                                 .find { clockMetadata -> clockMetadata.clockId == activeClockId }
121                                 ?.toModel(
122                                     isSelected = true,
123                                     description = it.description,
124                                     thumbnail = it.thumbnail,
125                                     isReactiveToTone = it.isReactiveToTone,
126                                     axisPresetConfig = it.presetConfig,
127                                     selectedColorId = metadata?.getSelectedColorId(),
128                                     colorTone =
129                                         metadata?.getColorTone()
130                                             ?: ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS,
131                                     seedColor = registry.seedColor,
132                                 )
133                         }
134                     trySend(model)
135                 }
136 
137                 val listener =
138                     object : ClockRegistry.ClockChangeListener {
139                         override fun onCurrentClockChanged() {
140                             send()
141                         }
142 
143                         override fun onAvailableClocksChanged() {
144                             send()
145                         }
146                     }
147                 registry.registerClockChangeListener(listener)
148                 send()
149                 awaitClose { registry.unregisterClockChangeListener(listener) }
150             }
151             .flowOn(mainDispatcher)
152             .mapNotNull { it }
153             // Make this a shared flow to prevent ClockRegistry.registerClockChangeListener from
154             // being called every time this flow is collected, since ClockRegistry is a singleton.
155             .shareIn(mainScope, SharingStarted.WhileSubscribed(), 1)
156 
157     override suspend fun setSelectedClock(clockId: String) {
158         registry.mutateSetting { oldSettings ->
159             val newSettings = oldSettings.copy(clockId = clockId)
160             newSettings.metadata = oldSettings.metadata
161             newSettings
162         }
163     }
164 
165     override suspend fun setClockColor(
166         selectedColorId: String?,
167         @IntRange(from = 0, to = 100) colorToneProgress: Int,
168         @ColorInt seedColor: Int?,
169     ) {
170         registry.mutateSetting { oldSettings ->
171             val newSettings = oldSettings.copy(seedColor = seedColor)
172             newSettings.metadata =
173                 oldSettings.metadata
174                     .put(KEY_METADATA_SELECTED_COLOR_ID, selectedColorId)
175                     .put(KEY_METADATA_COLOR_TONE_PROGRESS, colorToneProgress)
176             newSettings
177         }
178     }
179 
180     override val selectedClockSize: SharedFlow<ClockSize> =
181         secureSettingsRepository
182             .intSetting(
183                 name = Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
184                 defaultValue = DEFAULT_CLOCK_SIZE,
185             )
186             .map { setting -> setting == 1 }
187             .map { isDynamic -> if (isDynamic) ClockSize.DYNAMIC else ClockSize.SMALL }
188             .distinctUntilChanged()
189             .shareIn(scope = mainScope, started = SharingStarted.Eagerly, replay = 1)
190 
191     override suspend fun setClockSize(size: ClockSize) {
192         secureSettingsRepository.setInt(
193             name = Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
194             value = if (size == ClockSize.DYNAMIC) 1 else 0,
195         )
196     }
197 
198     override suspend fun setClockAxisStyle(axisStyle: ClockAxisStyle) {
199         registry.mutateSetting { oldSettings ->
200             val newSettings = oldSettings.copy(axes = axisStyle)
201             newSettings.metadata = oldSettings.metadata
202             newSettings
203         }
204     }
205 
206     override fun isReactiveToTone(clockId: ClockId): Boolean? {
207         return registry.getClockPickerConfig(clockId)?.isReactiveToTone
208     }
209 
210     private fun JSONObject.getSelectedColorId(): String? {
211         return if (this.isNull(KEY_METADATA_SELECTED_COLOR_ID)) {
212             null
213         } else {
214             this.getString(KEY_METADATA_SELECTED_COLOR_ID)
215         }
216     }
217 
218     private fun JSONObject.getColorTone(): Int {
219         return this.optInt(
220             KEY_METADATA_COLOR_TONE_PROGRESS,
221             ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS,
222         )
223     }
224 
225     /** By default, [ClockMetadataModel] has no color information unless specified. */
226     private fun ClockMetadata.toModel(
227         isSelected: Boolean,
228         description: String,
229         thumbnail: Drawable,
230         isReactiveToTone: Boolean,
231         axisPresetConfig: AxisPresetConfig?,
232         selectedColorId: String? = null,
233         @IntRange(from = 0, to = 100) colorTone: Int = 0,
234         @ColorInt seedColor: Int? = null,
235     ): ClockMetadataModel {
236         return ClockMetadataModel(
237             clockId = clockId,
238             isSelected = isSelected,
239             description = description,
240             thumbnail = thumbnail,
241             isReactiveToTone = isReactiveToTone,
242             axisPresetConfig = axisPresetConfig,
243             selectedColorId = selectedColorId,
244             colorToneProgress = colorTone,
245             seedColor = seedColor,
246         )
247     }
248 
249     companion object {
250         // The selected color in the color option list
251         private const val KEY_METADATA_SELECTED_COLOR_ID = "metadataSelectedColorId"
252 
253         // The color tone to apply to the selected color
254         private const val KEY_METADATA_COLOR_TONE_PROGRESS = "metadataColorToneProgress"
255 
256         // The default clock size is 1, which means dynamic
257         private const val DEFAULT_CLOCK_SIZE = 1
258     }
259 }
260