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.log.core.MessageBuffer 22 import com.android.systemui.plugins.annotations.ProvidesInterface 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 */ 43 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 */ 52 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 /** Determines the way the hosting app should behave when rendering either clock face */ 67 val config: ClockConfig 68 69 /** Events that clocks may need to respond to */ 70 val events: ClockEvents 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 79 /** Optional method for dumping debug information */ 80 fun dump(pw: PrintWriter) 81 } 82 83 /** Interface for a specific clock face version rendered by the clock */ 84 interface ClockFaceController { 85 /** View that renders the clock face */ 86 val view: View 87 88 /** Determines the way the hosting app should behave when rendering this clock face */ 89 val config: ClockFaceConfig 90 91 /** Events specific to this clock face */ 92 val events: ClockFaceEvents 93 94 /** Triggers for various animations */ 95 val animations: ClockAnimations 96 97 /** Some clocks may log debug information */ 98 var messageBuffer: MessageBuffer? 99 } 100 101 /** Events that should call when various rendering parameters change */ 102 interface ClockEvents { 103 /** Call whenever timezone changes */ 104 fun onTimeZoneChanged(timeZone: TimeZone) 105 106 /** Call whenever the text time format changes (12hr vs 24hr) */ 107 fun onTimeFormatChanged(is24Hr: Boolean) 108 109 /** Call whenever the locale changes */ 110 fun onLocaleChanged(locale: Locale) 111 112 /** Call whenever the color palette should update */ 113 fun onColorPaletteChanged(resources: Resources) 114 115 /** Call if the seed color has changed and should be updated */ 116 fun onSeedColorChanged(seedColor: Int?) 117 118 /** Call whenever the weather data should update */ 119 fun onWeatherDataChanged(data: WeatherData) 120 } 121 122 /** Methods which trigger various clock animations */ 123 interface ClockAnimations { 124 /** Runs an enter animation (if any) */ 125 fun enter() 126 127 /** Sets how far into AOD the device currently is. */ 128 fun doze(fraction: Float) 129 130 /** Sets how far into the folding animation the device is. */ 131 fun fold(fraction: Float) 132 133 /** Runs the battery animation (if any). */ 134 fun charge() 135 136 /** 137 * Runs when the clock's position changed during the move animation. 138 * 139 * @param fromLeft the [View.getLeft] position of the clock, before it started moving. 140 * @param direction the direction in which it is moving. A positive number means right, and 141 * negative means left. 142 * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means 143 * it finished moving. 144 */ 145 fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) 146 147 /** 148 * Runs when swiping clock picker, swipingFraction: 1.0 -> clock is scaled up in the preview, 149 * 0.0 -> clock is scaled down in the shade; previewRatio is previewSize / screenSize 150 */ 151 fun onPickerCarouselSwiping(swipingFraction: Float) 152 } 153 154 /** Events that have specific data about the related face */ 155 interface ClockFaceEvents { 156 /** Call every time tick */ 157 fun onTimeTick() 158 159 /** 160 * Region Darkness specific to the clock face. 161 * - isRegionDark = dark theme -> clock should be light 162 * - !isRegionDark = light theme -> clock should be dark 163 */ 164 fun onRegionDarknessChanged(isRegionDark: Boolean) 165 166 /** 167 * Call whenever font settings change. Pass in a target font size in pixels. The specific clock 168 * design is allowed to ignore this target size on a case-by-case basis. 169 */ 170 fun onFontSettingChanged(fontSizePx: Float) 171 172 /** 173 * Target region information for the clock face. For small clock, this will match the bounds of 174 * the parent view mostly, but have a target height based on the height of the default clock. 175 * For large clocks, the parent view is the entire device size, but most clocks will want to 176 * render within the centered targetRect to avoid obstructing other elements. The specified 177 * targetRegion is relative to the parent view. 178 */ 179 fun onTargetRegionChanged(targetRegion: Rect?) 180 181 /** Called to notify the clock about its display. */ 182 fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) 183 } 184 185 /** Tick rates for clocks */ 186 enum class ClockTickRate(val value: Int) { 187 PER_MINUTE(2), // Update the clock once per minute. 188 PER_SECOND(1), // Update the clock once per second. 189 PER_FRAME(0), // Update the clock every second. 190 } 191 192 /** Some data about a clock design */ 193 data class ClockMetadata( 194 val clockId: ClockId, 195 val name: String, 196 ) { 197 constructor(clockId: ClockId) : this(clockId, clockId) {} 198 } 199 200 /** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */ 201 data class ClockConfig( 202 val id: String, 203 204 /** Transition to AOD should move smartspace like large clock instead of small clock */ 205 val useAlternateSmartspaceAODTransition: Boolean = false, 206 207 /** True if the clock will react to tone changes in the seed color. */ 208 val isReactiveToTone: Boolean = true, 209 ) 210 211 /** Render configuration options for a clock face. Modifies the way SystemUI behaves. */ 212 data class ClockFaceConfig( 213 /** Expected interval between calls to onTimeTick. Can always reduce to PER_MINUTE in AOD. */ 214 val tickRate: ClockTickRate = ClockTickRate.PER_MINUTE, 215 216 /** Call to check whether the clock consumes weather data */ 217 val hasCustomWeatherDataDisplay: Boolean = false, 218 219 /** 220 * Whether this clock has a custom position update animation. If true, the keyguard will call 221 * `onPositionUpdated` to notify the clock of a position update animation. If false, a default 222 * animation will be used (e.g. a simple translation). 223 */ 224 val hasCustomPositionUpdatedAnimation: Boolean = false, 225 ) 226 227 /** Structure for keeping clock-specific settings */ 228 @Keep 229 data class ClockSettings( 230 val clockId: ClockId? = null, 231 val seedColor: Int? = null, 232 ) { 233 // Exclude metadata from equality checks 234 var metadata: JSONObject = JSONObject() 235 236 companion object { 237 private val KEY_CLOCK_ID = "clockId" 238 private val KEY_SEED_COLOR = "seedColor" 239 private val KEY_METADATA = "metadata" 240 241 fun serialize(setting: ClockSettings?): String { 242 if (setting == null) { 243 return "" 244 } 245 246 return JSONObject() 247 .put(KEY_CLOCK_ID, setting.clockId) 248 .put(KEY_SEED_COLOR, setting.seedColor) 249 .put(KEY_METADATA, setting.metadata) 250 .toString() 251 } 252 253 fun deserialize(jsonStr: String?): ClockSettings? { 254 if (jsonStr.isNullOrEmpty()) { 255 return null 256 } 257 258 val json = JSONObject(jsonStr) 259 val result = 260 ClockSettings( 261 if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null, 262 if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null 263 ) 264 if (!json.isNull(KEY_METADATA)) { 265 result.metadata = json.getJSONObject(KEY_METADATA) 266 } 267 return result 268 } 269 } 270 } 271