blob: e707d4db15cbf5e7f4e712411c00f674060decb6 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.shared.clocks
import android.content.Context
import android.database.ContentObserver
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Handler
import android.os.UserHandle
import android.provider.Settings
import android.util.Log
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.Clock
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProvider
import com.android.systemui.plugins.ClockProviderPlugin
import com.android.systemui.plugins.PluginListener
import com.android.systemui.shared.plugins.PluginManager
import com.google.gson.Gson
import javax.inject.Inject
private val TAG = ClockRegistry::class.simpleName
private val DEBUG = true
typealias ClockChangeListener = () -> Unit
/** ClockRegistry aggregates providers and plugins */
open class ClockRegistry(
val context: Context,
val pluginManager: PluginManager,
val handler: Handler,
defaultClockProvider: ClockProvider
) {
@Inject constructor(
context: Context,
pluginManager: PluginManager,
@Main handler: Handler,
defaultClockProvider: DefaultClockProvider
) : this(context, pluginManager, handler, defaultClockProvider as ClockProvider) { }
var isEnabled: Boolean = false
private val gson = Gson()
private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
private val settingObserver = object : ContentObserver(handler) {
override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int) =
clockChangeListeners.forEach { it() }
}
private val pluginListener = object : PluginListener<ClockProviderPlugin> {
override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) =
connectClocks(plugin)
override fun onPluginDisconnected(plugin: ClockProviderPlugin) =
disconnectClocks(plugin)
}
open var currentClockId: ClockId
get() {
return try {
val json = Settings.Secure.getString(
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
)
gson.fromJson(json, ClockSetting::class.java)?.clockId ?: DEFAULT_CLOCK_ID
} catch (ex: Exception) {
Log.e(TAG, "Failed to parse clock setting", ex)
DEFAULT_CLOCK_ID
}
}
set(value) {
try {
val json = gson.toJson(ClockSetting(value, System.currentTimeMillis()))
Settings.Secure.putString(
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json
)
} catch (ex: Exception) {
Log.e(TAG, "Failed to set clock setting", ex)
}
}
init {
connectClocks(defaultClockProvider)
if (!availableClocks.containsKey(DEFAULT_CLOCK_ID)) {
throw IllegalArgumentException(
"$defaultClockProvider did not register clock at $DEFAULT_CLOCK_ID"
)
}
pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java)
context.contentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
false,
settingObserver,
UserHandle.USER_ALL
)
}
private fun connectClocks(provider: ClockProvider) {
val currentId = currentClockId
for (clock in provider.getClocks()) {
val id = clock.clockId
val current = availableClocks[id]
if (current != null) {
Log.e(TAG, "Clock Id conflict: $id is registered by both " +
"${provider::class.simpleName} and ${current.provider::class.simpleName}")
return
}
availableClocks[id] = ClockInfo(clock, provider)
if (currentId == id) {
if (DEBUG) {
Log.i(TAG, "Current clock ($currentId) was connected")
}
clockChangeListeners.forEach { it() }
}
}
}
private fun disconnectClocks(provider: ClockProvider) {
val currentId = currentClockId
for (clock in provider.getClocks()) {
availableClocks.remove(clock.clockId)
if (currentId == clock.clockId) {
Log.w(TAG, "Current clock ($currentId) was disconnected")
clockChangeListeners.forEach { it() }
}
}
}
fun getClocks(): List<ClockMetadata> {
if (!isEnabled) {
return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
}
return availableClocks.map { (_, clock) -> clock.metadata }
}
fun getClockThumbnail(clockId: ClockId): Drawable? =
availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
fun createExampleClock(clockId: ClockId): Clock? = createClock(clockId)
fun registerClockChangeListener(listener: ClockChangeListener) =
clockChangeListeners.add(listener)
fun unregisterClockChangeListener(listener: ClockChangeListener) =
clockChangeListeners.remove(listener)
fun createCurrentClock(): Clock {
val clockId = currentClockId
if (isEnabled && clockId.isNotEmpty()) {
val clock = createClock(clockId)
if (clock != null) {
return clock
} else {
Log.e(TAG, "Clock $clockId not found; using default")
}
}
return createClock(DEFAULT_CLOCK_ID)!!
}
private fun createClock(clockId: ClockId): Clock? =
availableClocks[clockId]?.provider?.createClock(clockId)
private data class ClockInfo(
val metadata: ClockMetadata,
val provider: ClockProvider
)
private data class ClockSetting(
val clockId: ClockId,
val _applied_timestamp: Long?
)
}