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