blob: 0080517a722b78ee43d240ba572c15d0950a0979 [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.plugins
import android.content.res.Resources
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.view.View
import com.android.internal.annotations.Keep
import com.android.systemui.plugins.annotations.ProvidesInterface
import com.android.systemui.plugins.log.LogBuffer
import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
import org.json.JSONObject
/** Identifies a clock design */
typealias ClockId = String
/** A Plugin which exposes the ClockProvider interface */
@ProvidesInterface(action = ClockProviderPlugin.ACTION, version = ClockProviderPlugin.VERSION)
interface ClockProviderPlugin : Plugin, ClockProvider {
companion object {
const val ACTION = "com.android.systemui.action.PLUGIN_CLOCK_PROVIDER"
const val VERSION = 1
}
}
/** Interface for building clocks and providing information about those clocks */
interface ClockProvider {
/** Returns metadata for all clocks this provider knows about */
fun getClocks(): List<ClockMetadata>
/** Initializes and returns the target clock design */
@Deprecated("Use overload with ClockSettings")
fun createClock(id: ClockId): ClockController {
return createClock(ClockSettings(id, null))
}
/** Initializes and returns the target clock design */
fun createClock(settings: ClockSettings): ClockController
/** A static thumbnail for rendering in some examples */
fun getClockThumbnail(id: ClockId): Drawable?
}
/** Interface for controlling an active clock */
interface ClockController {
/** A small version of the clock, appropriate for smaller viewports */
val smallClock: ClockFaceController
/** A large version of the clock, appropriate when a bigger viewport is available */
val largeClock: ClockFaceController
/** Events that clocks may need to respond to */
val events: ClockEvents
/** Triggers for various animations */
val animations: ClockAnimations
/** Initializes various rendering parameters. If never called, provides reasonable defaults. */
fun initialize(
resources: Resources,
dozeFraction: Float,
foldFraction: Float,
) {
events.onColorPaletteChanged(resources)
animations.doze(dozeFraction)
animations.fold(foldFraction)
smallClock.events.onTimeTick()
largeClock.events.onTimeTick()
}
/** Optional method for dumping debug information */
fun dump(pw: PrintWriter) {}
}
/** Interface for a specific clock face version rendered by the clock */
interface ClockFaceController {
/** View that renders the clock face */
val view: View
/** Events specific to this clock face */
val events: ClockFaceEvents
/** Some clocks may log debug information */
var logBuffer: LogBuffer?
}
/** Events that should call when various rendering parameters change */
interface ClockEvents {
/** Call whenever timezone changes */
fun onTimeZoneChanged(timeZone: TimeZone) {}
/** Call whenever the text time format changes (12hr vs 24hr) */
fun onTimeFormatChanged(is24Hr: Boolean) {}
/** Call whenever the locale changes */
fun onLocaleChanged(locale: Locale) {}
/** Call whenever the color palette should update */
fun onColorPaletteChanged(resources: Resources) {}
/** Call if the seed color has changed and should be updated */
fun onSeedColorChanged(seedColor: Int?) {}
/** Call whenever the weather data should update */
fun onWeatherDataChanged(data: WeatherData) {}
}
/** Methods which trigger various clock animations */
interface ClockAnimations {
/** Runs an enter animation (if any) */
fun enter() {}
/** Sets how far into AOD the device currently is. */
fun doze(fraction: Float) {}
/** Sets how far into the folding animation the device is. */
fun fold(fraction: Float) {}
/** Runs the battery animation (if any). */
fun charge() {}
/** Move the clock, for example, if the notification tray appears in split-shade mode. */
fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {}
/**
* Runs when swiping clock picker, swipingFraction: 1.0 -> clock is scaled up in the preview,
* 0.0 -> clock is scaled down in the shade; previewRatio is previewSize / screenSize
*/
fun onPickerCarouselSwiping(swipingFraction: Float, previewRatio: Float) {}
/**
* Whether this clock has a custom position update animation. If true, the keyguard will call
* `onPositionUpdated` to notify the clock of a position update animation. If false, a default
* animation will be used (e.g. a simple translation).
*/
val hasCustomPositionUpdatedAnimation
get() = false
}
/** Events that have specific data about the related face */
interface ClockFaceEvents {
/** Call every time tick */
fun onTimeTick() {}
/** Expected interval between calls to onTimeTick. Can always reduce to PER_MINUTE in AOD. */
val tickRate: ClockTickRate
get() = ClockTickRate.PER_MINUTE
/** Call to check whether the clock consumes weather data */
val hasCustomWeatherDataDisplay: Boolean
get() = false
/** Region Darkness specific to the clock face */
fun onRegionDarknessChanged(isDark: Boolean) {}
/**
* Call whenever font settings change. Pass in a target font size in pixels. The specific clock
* design is allowed to ignore this target size on a case-by-case basis.
*/
fun onFontSettingChanged(fontSizePx: Float) {}
/**
* Target region information for the clock face. For small clock, this will match the bounds of
* the parent view mostly, but have a target height based on the height of the default clock.
* For large clocks, the parent view is the entire device size, but most clocks will want to
* render within the centered targetRect to avoid obstructing other elements. The specified
* targetRegion is relative to the parent view.
*/
fun onTargetRegionChanged(targetRegion: Rect?) {}
}
/** Tick rates for clocks */
enum class ClockTickRate(val value: Int) {
PER_MINUTE(2), // Update the clock once per minute.
PER_SECOND(1), // Update the clock once per second.
PER_FRAME(0), // Update the clock every second.
}
/** Some data about a clock design */
data class ClockMetadata(
val clockId: ClockId,
val name: String,
)
/** Structure for keeping clock-specific settings */
@Keep
data class ClockSettings(
val clockId: ClockId? = null,
val seedColor: Int? = null,
) {
// Exclude metadata from equality checks
var metadata: JSONObject = JSONObject()
companion object {
private val KEY_CLOCK_ID = "clockId"
private val KEY_SEED_COLOR = "seedColor"
private val KEY_METADATA = "metadata"
fun serialize(setting: ClockSettings?): String {
if (setting == null) {
return ""
}
return JSONObject()
.put(KEY_CLOCK_ID, setting.clockId)
.put(KEY_SEED_COLOR, setting.seedColor)
.put(KEY_METADATA, setting.metadata)
.toString()
}
fun deserialize(jsonStr: String?): ClockSettings? {
if (jsonStr.isNullOrEmpty()) {
return null
}
val json = JSONObject(jsonStr)
val result =
ClockSettings(
if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null,
if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null
)
if (!json.isNull(KEY_METADATA)) {
result.metadata = json.getJSONObject(KEY_METADATA)
}
return result
}
}
}