• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 package com.android.systemui.shared.clocks
15 
16 import android.app.ActivityManager
17 import android.app.UserSwitchObserver
18 import android.content.Context
19 import android.database.ContentObserver
20 import android.graphics.drawable.Drawable
21 import android.net.Uri
22 import android.os.UserHandle
23 import android.provider.Settings
24 import android.util.Log
25 import androidx.annotation.OpenForTesting
26 import com.android.systemui.plugins.ClockController
27 import com.android.systemui.plugins.ClockId
28 import com.android.systemui.plugins.ClockMetadata
29 import com.android.systemui.plugins.ClockProvider
30 import com.android.systemui.plugins.ClockProviderPlugin
31 import com.android.systemui.plugins.ClockSettings
32 import com.android.systemui.plugins.PluginListener
33 import com.android.systemui.plugins.PluginManager
34 import com.android.systemui.util.Assert
35 import kotlinx.coroutines.CoroutineDispatcher
36 import kotlinx.coroutines.CoroutineScope
37 import kotlinx.coroutines.launch
38 
39 private val TAG = ClockRegistry::class.simpleName!!
40 private const val DEBUG = true
41 private val KEY_TIMESTAMP = "appliedTimestamp"
42 
43 /** ClockRegistry aggregates providers and plugins */
44 open class ClockRegistry(
45     val context: Context,
46     val pluginManager: PluginManager,
47     val scope: CoroutineScope,
48     val mainDispatcher: CoroutineDispatcher,
49     val bgDispatcher: CoroutineDispatcher,
50     val isEnabled: Boolean,
51     val handleAllUsers: Boolean,
52     defaultClockProvider: ClockProvider,
53     val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
54 ) {
55     interface ClockChangeListener {
56         // Called when the active clock changes
57         fun onCurrentClockChanged() {}
58 
59         // Called when the list of available clocks changes
60         fun onAvailableClocksChanged() {}
61     }
62 
63     private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
64     private val clockChangeListeners = mutableListOf<ClockChangeListener>()
65     private val settingObserver =
66         object : ContentObserver(null) {
67             override fun onChange(
68                 selfChange: Boolean,
69                 uris: Collection<Uri>,
70                 flags: Int,
71                 userId: Int
72             ) {
73                 scope.launch(bgDispatcher) { querySettings() }
74             }
75         }
76 
77     private val pluginListener =
78         object : PluginListener<ClockProviderPlugin> {
79             override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) =
80                 connectClocks(plugin)
81 
82             override fun onPluginDisconnected(plugin: ClockProviderPlugin) =
83                 disconnectClocks(plugin)
84         }
85 
86     private val userSwitchObserver =
87         object : UserSwitchObserver() {
88             override fun onUserSwitchComplete(newUserId: Int) {
89                 scope.launch(bgDispatcher) { querySettings() }
90             }
91         }
92 
93     // TODO(b/267372164): Migrate to flows
94     var settings: ClockSettings? = null
95         get() = field
96         protected set(value) {
97             if (field != value) {
98                 field = value
99                 scope.launch(mainDispatcher) { onClockChanged { it.onCurrentClockChanged() } }
100             }
101         }
102 
103     var isRegistered: Boolean = false
104         private set
105 
106     @OpenForTesting
107     open fun querySettings() {
108         assertNotMainThread()
109         val result =
110             try {
111                 val json =
112                     if (handleAllUsers) {
113                         Settings.Secure.getStringForUser(
114                             context.contentResolver,
115                             Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
116                             ActivityManager.getCurrentUser()
117                         )
118                     } else {
119                         Settings.Secure.getString(
120                             context.contentResolver,
121                             Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
122                         )
123                     }
124 
125                 ClockSettings.deserialize(json)
126             } catch (ex: Exception) {
127                 Log.e(TAG, "Failed to parse clock settings", ex)
128                 null
129             }
130         settings = result
131     }
132 
133     @OpenForTesting
134     open fun applySettings(value: ClockSettings?) {
135         assertNotMainThread()
136 
137         try {
138             value?.metadata?.put(KEY_TIMESTAMP, System.currentTimeMillis())
139 
140             val json = ClockSettings.serialize(value)
141             if (handleAllUsers) {
142                 Settings.Secure.putStringForUser(
143                     context.contentResolver,
144                     Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
145                     json,
146                     ActivityManager.getCurrentUser()
147                 )
148             } else {
149                 Settings.Secure.putString(
150                     context.contentResolver,
151                     Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
152                     json
153                 )
154             }
155         } catch (ex: Exception) {
156             Log.e(TAG, "Failed to set clock settings", ex)
157         }
158         settings = value
159     }
160 
161     @OpenForTesting
162     protected open fun assertMainThread() {
163         Assert.isMainThread()
164     }
165 
166     @OpenForTesting
167     protected open fun assertNotMainThread() {
168         Assert.isNotMainThread()
169     }
170 
171     private fun onClockChanged(func: (ClockChangeListener) -> Unit) {
172         assertMainThread()
173         clockChangeListeners.forEach(func)
174     }
175 
176     public fun mutateSetting(mutator: (ClockSettings) -> ClockSettings) {
177         scope.launch(bgDispatcher) { applySettings(mutator(settings ?: ClockSettings())) }
178     }
179 
180     var currentClockId: ClockId
181         get() = settings?.clockId ?: fallbackClockId
182         set(value) {
183             mutateSetting { it.copy(clockId = value) }
184         }
185 
186     var seedColor: Int?
187         get() = settings?.seedColor
188         set(value) {
189             mutateSetting { it.copy(seedColor = value) }
190         }
191 
192     init {
193         connectClocks(defaultClockProvider)
194         if (!availableClocks.containsKey(DEFAULT_CLOCK_ID)) {
195             throw IllegalArgumentException(
196                 "$defaultClockProvider did not register clock at $DEFAULT_CLOCK_ID"
197             )
198         }
199     }
200 
201     fun registerListeners() {
202         if (!isEnabled || isRegistered) {
203             return
204         }
205 
206         isRegistered = true
207 
208         pluginManager.addPluginListener(
209             pluginListener,
210             ClockProviderPlugin::class.java,
211             /*allowMultiple=*/ true
212         )
213 
214         scope.launch(bgDispatcher) { querySettings() }
215         if (handleAllUsers) {
216             context.contentResolver.registerContentObserver(
217                 Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
218                 /*notifyForDescendants=*/ false,
219                 settingObserver,
220                 UserHandle.USER_ALL
221             )
222 
223             ActivityManager.getService().registerUserSwitchObserver(userSwitchObserver, TAG)
224         } else {
225             context.contentResolver.registerContentObserver(
226                 Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
227                 /*notifyForDescendants=*/ false,
228                 settingObserver
229             )
230         }
231     }
232 
233     fun unregisterListeners() {
234         if (!isRegistered) {
235             return
236         }
237 
238         isRegistered = false
239 
240         pluginManager.removePluginListener(pluginListener)
241         context.contentResolver.unregisterContentObserver(settingObserver)
242         if (handleAllUsers) {
243             ActivityManager.getService().unregisterUserSwitchObserver(userSwitchObserver)
244         }
245     }
246 
247     private fun connectClocks(provider: ClockProvider) {
248         var isAvailableChanged = false
249         val currentId = currentClockId
250         for (clock in provider.getClocks()) {
251             val id = clock.clockId
252             val current = availableClocks[id]
253             if (current != null) {
254                 Log.e(
255                     TAG,
256                     "Clock Id conflict: $id is registered by both " +
257                         "${provider::class.simpleName} and ${current.provider::class.simpleName}"
258                 )
259                 continue
260             }
261 
262             availableClocks[id] = ClockInfo(clock, provider)
263             isAvailableChanged = true
264             if (DEBUG) {
265                 Log.i(TAG, "Added ${clock.clockId}")
266             }
267 
268             if (currentId == id) {
269                 if (DEBUG) {
270                     Log.i(TAG, "Current clock ($currentId) was connected")
271                 }
272                 onClockChanged { it.onCurrentClockChanged() }
273             }
274         }
275 
276         if (isAvailableChanged) {
277             onClockChanged { it.onAvailableClocksChanged() }
278         }
279     }
280 
281     private fun disconnectClocks(provider: ClockProvider) {
282         var isAvailableChanged = false
283         val currentId = currentClockId
284         for (clock in provider.getClocks()) {
285             availableClocks.remove(clock.clockId)
286             isAvailableChanged = true
287 
288             if (DEBUG) {
289                 Log.i(TAG, "Removed ${clock.clockId}")
290             }
291 
292             if (currentId == clock.clockId) {
293                 Log.w(TAG, "Current clock ($currentId) was disconnected")
294                 onClockChanged { it.onCurrentClockChanged() }
295             }
296         }
297 
298         if (isAvailableChanged) {
299             onClockChanged { it.onAvailableClocksChanged() }
300         }
301     }
302 
303     fun getClocks(): List<ClockMetadata> {
304         if (!isEnabled) {
305             return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
306         }
307         return availableClocks.map { (_, clock) -> clock.metadata }
308     }
309 
310     fun getClockThumbnail(clockId: ClockId): Drawable? =
311         availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
312 
313     fun createExampleClock(clockId: ClockId): ClockController? = createClock(clockId)
314 
315     fun registerClockChangeListener(listener: ClockChangeListener) =
316         clockChangeListeners.add(listener)
317 
318     fun unregisterClockChangeListener(listener: ClockChangeListener) =
319         clockChangeListeners.remove(listener)
320 
321     fun createCurrentClock(): ClockController {
322         val clockId = currentClockId
323         if (isEnabled && clockId.isNotEmpty()) {
324             val clock = createClock(clockId)
325             if (clock != null) {
326                 if (DEBUG) {
327                     Log.i(TAG, "Rendering clock $clockId")
328                 }
329                 return clock
330             } else {
331                 Log.e(TAG, "Clock $clockId not found; using default")
332             }
333         }
334 
335         return createClock(DEFAULT_CLOCK_ID)!!
336     }
337 
338     private fun createClock(targetClockId: ClockId): ClockController? {
339         var settings = this.settings ?: ClockSettings()
340         if (targetClockId != settings.clockId) {
341             settings = settings.copy(clockId = targetClockId)
342         }
343         return availableClocks[targetClockId]?.provider?.createClock(settings)
344     }
345 
346     private data class ClockInfo(
347         val metadata: ClockMetadata,
348         val provider: ClockProvider,
349     )
350 }
351