1 /* 2 * 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.plugins 15 16 import android.content.res.Resources 17 import android.graphics.Rect 18 import android.graphics.drawable.Drawable 19 import android.view.View 20 import com.android.internal.annotations.Keep 21 import com.android.systemui.plugins.annotations.ProvidesInterface 22 import com.android.systemui.plugins.log.LogBuffer 23 import java.io.PrintWriter 24 import java.util.Locale 25 import java.util.TimeZone 26 import org.json.JSONObject 27 28 /** Identifies a clock design */ 29 typealias ClockId = String 30 31 /** A Plugin which exposes the ClockProvider interface */ 32 @ProvidesInterface(action = ClockProviderPlugin.ACTION, version = ClockProviderPlugin.VERSION) 33 interface ClockProviderPlugin : Plugin, ClockProvider { 34 companion object { 35 const val ACTION = "com.android.systemui.action.PLUGIN_CLOCK_PROVIDER" 36 const val VERSION = 1 37 } 38 } 39 40 /** Interface for building clocks and providing information about those clocks */ 41 interface ClockProvider { 42 /** Returns metadata for all clocks this provider knows about */ getClocksnull43 fun getClocks(): List<ClockMetadata> 44 45 /** Initializes and returns the target clock design */ 46 @Deprecated("Use overload with ClockSettings") 47 fun createClock(id: ClockId): ClockController { 48 return createClock(ClockSettings(id, null)) 49 } 50 51 /** Initializes and returns the target clock design */ createClocknull52 fun createClock(settings: ClockSettings): ClockController 53 54 /** A static thumbnail for rendering in some examples */ 55 fun getClockThumbnail(id: ClockId): Drawable? 56 } 57 58 /** Interface for controlling an active clock */ 59 interface ClockController { 60 /** A small version of the clock, appropriate for smaller viewports */ 61 val smallClock: ClockFaceController 62 63 /** A large version of the clock, appropriate when a bigger viewport is available */ 64 val largeClock: ClockFaceController 65 66 /** Events that clocks may need to respond to */ 67 val events: ClockEvents 68 69 /** Triggers for various animations */ 70 val animations: ClockAnimations 71 72 /** Initializes various rendering parameters. If never called, provides reasonable defaults. */ 73 fun initialize( 74 resources: Resources, 75 dozeFraction: Float, 76 foldFraction: Float, 77 ) { 78 events.onColorPaletteChanged(resources) 79 animations.doze(dozeFraction) 80 animations.fold(foldFraction) 81 smallClock.events.onTimeTick() 82 largeClock.events.onTimeTick() 83 } 84 85 /** Optional method for dumping debug information */ 86 fun dump(pw: PrintWriter) {} 87 } 88 89 /** Interface for a specific clock face version rendered by the clock */ 90 interface ClockFaceController { 91 /** View that renders the clock face */ 92 val view: View 93 94 /** Events specific to this clock face */ 95 val events: ClockFaceEvents 96 97 /** Some clocks may log debug information */ 98 var logBuffer: LogBuffer? 99 } 100 101 /** Events that should call when various rendering parameters change */ 102 interface ClockEvents { 103 /** Call whenever timezone changes */ onTimeZoneChangednull104 fun onTimeZoneChanged(timeZone: TimeZone) {} 105 106 /** Call whenever the text time format changes (12hr vs 24hr) */ onTimeFormatChangednull107 fun onTimeFormatChanged(is24Hr: Boolean) {} 108 109 /** Call whenever the locale changes */ onLocaleChangednull110 fun onLocaleChanged(locale: Locale) {} 111 112 /** Call whenever the color palette should update */ onColorPaletteChangednull113 fun onColorPaletteChanged(resources: Resources) {} 114 115 /** Call if the seed color has changed and should be updated */ onSeedColorChangednull116 fun onSeedColorChanged(seedColor: Int?) {} 117 118 /** Call whenever the weather data should update */ onWeatherDataChangednull119 fun onWeatherDataChanged(data: WeatherData) {} 120 } 121 122 /** Methods which trigger various clock animations */ 123 interface ClockAnimations { 124 /** Runs an enter animation (if any) */ enternull125 fun enter() {} 126 127 /** Sets how far into AOD the device currently is. */ dozenull128 fun doze(fraction: Float) {} 129 130 /** Sets how far into the folding animation the device is. */ foldnull131 fun fold(fraction: Float) {} 132 133 /** Runs the battery animation (if any). */ chargenull134 fun charge() {} 135 136 /** Move the clock, for example, if the notification tray appears in split-shade mode. */ onPositionUpdatednull137 fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {} 138 139 /** 140 * Whether this clock has a custom position update animation. If true, the keyguard will call 141 * `onPositionUpdated` to notify the clock of a position update animation. If false, a default 142 * animation will be used (e.g. a simple translation). 143 */ 144 val hasCustomPositionUpdatedAnimation 145 get() = false 146 } 147 148 /** Events that have specific data about the related face */ 149 interface ClockFaceEvents { 150 /** Call every time tick */ onTimeTicknull151 fun onTimeTick() {} 152 153 /** Expected interval between calls to onTimeTick. Can always reduce to PER_MINUTE in AOD. */ 154 val tickRate: ClockTickRate 155 get() = ClockTickRate.PER_MINUTE 156 157 /** Region Darkness specific to the clock face */ onRegionDarknessChangednull158 fun onRegionDarknessChanged(isDark: Boolean) {} 159 160 /** 161 * Call whenever font settings change. Pass in a target font size in pixels. The specific clock 162 * design is allowed to ignore this target size on a case-by-case basis. 163 */ onFontSettingChangednull164 fun onFontSettingChanged(fontSizePx: Float) {} 165 166 /** 167 * Target region information for the clock face. For small clock, this will match the bounds of 168 * the parent view mostly, but have a target height based on the height of the default clock. 169 * For large clocks, the parent view is the entire device size, but most clocks will want to 170 * render within the centered targetRect to avoid obstructing other elements. The specified 171 * targetRegion is relative to the parent view. 172 */ onTargetRegionChangednull173 fun onTargetRegionChanged(targetRegion: Rect?) {} 174 } 175 176 /** Tick rates for clocks */ 177 enum class ClockTickRate(val value: Int) { 178 PER_MINUTE(2), // Update the clock once per minute. 179 PER_SECOND(1), // Update the clock once per second. 180 PER_FRAME(0), // Update the clock every second. 181 } 182 183 /** Some data about a clock design */ 184 data class ClockMetadata( 185 val clockId: ClockId, 186 val name: String, 187 ) 188 189 /** Structure for keeping clock-specific settings */ 190 @Keep 191 data class ClockSettings( 192 val clockId: ClockId? = null, 193 val seedColor: Int? = null, 194 ) { 195 // Exclude metadata from equality checks 196 var metadata: JSONObject = JSONObject() 197 198 companion object { 199 private val KEY_CLOCK_ID = "clockId" 200 private val KEY_SEED_COLOR = "seedColor" 201 private val KEY_METADATA = "metadata" 202 serializenull203 fun serialize(setting: ClockSettings?): String { 204 if (setting == null) { 205 return "" 206 } 207 208 return JSONObject() 209 .put(KEY_CLOCK_ID, setting.clockId) 210 .put(KEY_SEED_COLOR, setting.seedColor) 211 .put(KEY_METADATA, setting.metadata) 212 .toString() 213 } 214 deserializenull215 fun deserialize(jsonStr: String?): ClockSettings? { 216 if (jsonStr.isNullOrEmpty()) { 217 return null 218 } 219 220 val json = JSONObject(jsonStr) 221 val result = 222 ClockSettings( 223 if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null, 224 if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null 225 ) 226 if (!json.isNull(KEY_METADATA)) { 227 result.metadata = json.getJSONObject(KEY_METADATA) 228 } 229 return result 230 } 231 } 232 } 233