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