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.clocks 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 androidx.constraintlayout.widget.ConstraintSet 21 import com.android.internal.annotations.Keep 22 import com.android.systemui.log.core.MessageBuffer 23 import com.android.systemui.plugins.Plugin 24 import com.android.systemui.plugins.annotations.ProvidesInterface 25 import java.io.PrintWriter 26 import java.util.Locale 27 import java.util.TimeZone 28 import org.json.JSONObject 29 30 /** Identifies a clock design */ 31 typealias ClockId = String 32 33 /** A Plugin which exposes the ClockProvider interface */ 34 @ProvidesInterface(action = ClockProviderPlugin.ACTION, version = ClockProviderPlugin.VERSION) 35 interface ClockProviderPlugin : Plugin, ClockProvider { 36 companion object { 37 const val ACTION = "com.android.systemui.action.PLUGIN_CLOCK_PROVIDER" 38 const val VERSION = 1 39 } 40 } 41 42 /** Interface for building clocks and providing information about those clocks */ 43 interface ClockProvider { 44 /** Initializes the clock provider with debug log buffers */ initializenull45 fun initialize(buffers: ClockMessageBuffers?) 46 47 /** Returns metadata for all clocks this provider knows about */ 48 fun getClocks(): List<ClockMetadata> 49 50 /** Initializes and returns the target clock design */ 51 fun createClock(settings: ClockSettings): ClockController 52 53 /** A static thumbnail for rendering in some examples */ 54 fun getClockThumbnail(id: ClockId): Drawable? 55 } 56 57 /** Interface for controlling an active clock */ 58 interface ClockController { 59 /** A small version of the clock, appropriate for smaller viewports */ 60 val smallClock: ClockFaceController 61 62 /** A large version of the clock, appropriate when a bigger viewport is available */ 63 val largeClock: ClockFaceController 64 65 /** Determines the way the hosting app should behave when rendering either clock face */ 66 val config: ClockConfig 67 68 /** Events that clocks may need to respond to */ 69 val events: ClockEvents 70 71 /** Initializes various rendering parameters. If never called, provides reasonable defaults. */ 72 fun initialize( 73 resources: Resources, 74 dozeFraction: Float, 75 foldFraction: Float, 76 ) 77 78 /** Optional method for dumping debug information */ 79 fun dump(pw: PrintWriter) 80 } 81 82 /** Interface for a specific clock face version rendered by the clock */ 83 interface ClockFaceController { 84 /** View that renders the clock face */ 85 val view: View 86 87 /** Layout specification for this clock */ 88 val layout: ClockFaceLayout 89 90 /** Determines the way the hosting app should behave when rendering this clock face */ 91 val config: ClockFaceConfig 92 93 /** Events specific to this clock face */ 94 val events: ClockFaceEvents 95 96 /** Triggers for various animations */ 97 val animations: ClockAnimations 98 } 99 100 /** For clocks that want to report debug information */ 101 data class ClockMessageBuffers( 102 /** Message buffer for general infra */ 103 val infraMessageBuffer: MessageBuffer, 104 105 /** Message buffer for small clock renering */ 106 val smallClockMessageBuffer: MessageBuffer, 107 108 /** Message buffer for large clock rendering */ 109 val largeClockMessageBuffer: MessageBuffer, 110 ) 111 112 data class AodClockBurnInModel( 113 val scale: Float, 114 val translationX: Float, 115 val translationY: Float, 116 ) 117 118 /** Specifies layout information for the */ 119 interface ClockFaceLayout { 120 /** All clock views to add to the root constraint layout before applying constraints. */ 121 val views: List<View> 122 123 /** Custom constraints to apply to Lockscreen ConstraintLayout. */ applyConstraintsnull124 fun applyConstraints(constraints: ConstraintSet): ConstraintSet 125 126 fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet 127 128 fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel) 129 } 130 131 /** A ClockFaceLayout that applies the default lockscreen layout to a single view */ 132 class DefaultClockFaceLayout(val view: View) : ClockFaceLayout { 133 // both small and large clock should have a container (RelativeLayout in 134 // SimpleClockFaceController) 135 override val views = listOf(view) 136 override fun applyConstraints(constraints: ConstraintSet): ConstraintSet { 137 if (views.size != 1) { 138 throw IllegalArgumentException( 139 "Should have only one container view when using DefaultClockFaceLayout" 140 ) 141 } 142 return constraints 143 } 144 145 override fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet { 146 return constraints 147 } 148 149 override fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel) { 150 // Default clock doesn't need detailed control of view 151 } 152 } 153 154 /** Events that should call when various rendering parameters change */ 155 interface ClockEvents { 156 /** Set to enable or disable swipe interaction */ 157 var isReactiveTouchInteractionEnabled: Boolean 158 159 /** Call whenever timezone changes */ onTimeZoneChangednull160 fun onTimeZoneChanged(timeZone: TimeZone) 161 162 /** Call whenever the text time format changes (12hr vs 24hr) */ 163 fun onTimeFormatChanged(is24Hr: Boolean) 164 165 /** Call whenever the locale changes */ 166 fun onLocaleChanged(locale: Locale) 167 168 /** Call whenever the color palette should update */ 169 fun onColorPaletteChanged(resources: Resources) 170 171 /** Call if the seed color has changed and should be updated */ 172 fun onSeedColorChanged(seedColor: Int?) 173 174 /** Call whenever the weather data should update */ 175 fun onWeatherDataChanged(data: WeatherData) 176 177 /** Call with alarm information */ 178 fun onAlarmDataChanged(data: AlarmData) 179 180 /** Call with zen/dnd information */ 181 fun onZenDataChanged(data: ZenData) 182 } 183 184 /** Methods which trigger various clock animations */ 185 interface ClockAnimations { 186 /** Runs an enter animation (if any) */ 187 fun enter() 188 189 /** Sets how far into AOD the device currently is. */ 190 fun doze(fraction: Float) 191 192 /** Sets how far into the folding animation the device is. */ 193 fun fold(fraction: Float) 194 195 /** Runs the battery animation (if any). */ 196 fun charge() 197 198 /** 199 * Runs when the clock's position changed during the move animation. 200 * 201 * @param fromLeft the [View.getLeft] position of the clock, before it started moving. 202 * @param direction the direction in which it is moving. A positive number means right, and 203 * negative means left. 204 * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means 205 * it finished moving. 206 * @deprecated use {@link #onPositionUpdated(float, float)} instead. 207 */ 208 fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) 209 210 /** 211 * Runs when the clock's position changed during the move animation. 212 * 213 * @param distance is the total distance in pixels to offset the glyphs when animation 214 * completes. Negative distance means we are animating the position towards the center. 215 * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means 216 * it finished moving. 217 */ 218 fun onPositionUpdated(distance: Float, fraction: Float) 219 220 /** 221 * Runs when swiping clock picker, swipingFraction: 1.0 -> clock is scaled up in the preview, 222 * 0.0 -> clock is scaled down in the shade; previewRatio is previewSize / screenSize 223 */ 224 fun onPickerCarouselSwiping(swipingFraction: Float) 225 } 226 227 /** Events that have specific data about the related face */ 228 interface ClockFaceEvents { 229 /** Call every time tick */ onTimeTicknull230 fun onTimeTick() 231 232 /** 233 * Region Darkness specific to the clock face. 234 * - isRegionDark = dark theme -> clock should be light 235 * - !isRegionDark = light theme -> clock should be dark 236 */ 237 fun onRegionDarknessChanged(isRegionDark: Boolean) 238 239 /** 240 * Call whenever font settings change. Pass in a target font size in pixels. The specific clock 241 * design is allowed to ignore this target size on a case-by-case basis. 242 */ 243 fun onFontSettingChanged(fontSizePx: Float) 244 245 /** 246 * Target region information for the clock face. For small clock, this will match the bounds of 247 * the parent view mostly, but have a target height based on the height of the default clock. 248 * For large clocks, the parent view is the entire device size, but most clocks will want to 249 * render within the centered targetRect to avoid obstructing other elements. The specified 250 * targetRegion is relative to the parent view. 251 */ 252 fun onTargetRegionChanged(targetRegion: Rect?) 253 254 /** Called to notify the clock about its display. */ 255 fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) 256 } 257 258 /** Tick rates for clocks */ 259 enum class ClockTickRate(val value: Int) { 260 PER_MINUTE(2), // Update the clock once per minute. 261 PER_SECOND(1), // Update the clock once per second. 262 PER_FRAME(0), // Update the clock every second. 263 } 264 265 /** Some data about a clock design */ 266 data class ClockMetadata( 267 val clockId: ClockId, 268 ) 269 270 /** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */ 271 data class ClockConfig( 272 val id: String, 273 274 /** Localized name of the clock */ 275 val name: String, 276 277 /** Localized accessibility description for the clock */ 278 val description: String, 279 280 /** Transition to AOD should move smartspace like large clock instead of small clock */ 281 val useAlternateSmartspaceAODTransition: Boolean = false, 282 283 /** True if the clock will react to tone changes in the seed color. */ 284 val isReactiveToTone: Boolean = true, 285 286 /** True if the clock is large frame clock, which will use weather in compose. */ 287 val useCustomClockScene: Boolean = false, 288 ) 289 290 /** Render configuration options for a clock face. Modifies the way SystemUI behaves. */ 291 data class ClockFaceConfig( 292 /** Expected interval between calls to onTimeTick. Can always reduce to PER_MINUTE in AOD. */ 293 val tickRate: ClockTickRate = ClockTickRate.PER_MINUTE, 294 295 /** Call to check whether the clock consumes weather data */ 296 val hasCustomWeatherDataDisplay: Boolean = false, 297 298 /** 299 * Whether this clock has a custom position update animation. If true, the keyguard will call 300 * `onPositionUpdated` to notify the clock of a position update animation. If false, a default 301 * animation will be used (e.g. a simple translation). 302 */ 303 val hasCustomPositionUpdatedAnimation: Boolean = false, 304 305 /** True if the clock is large frame clock, which will use weatherBlueprint in compose. */ 306 val useCustomClockScene: Boolean = false, 307 ) 308 309 /** Structure for keeping clock-specific settings */ 310 @Keep 311 data class ClockSettings( 312 val clockId: ClockId? = null, 313 val seedColor: Int? = null, 314 ) { 315 // Exclude metadata from equality checks 316 var metadata: JSONObject = JSONObject() 317 318 companion object { 319 private val KEY_CLOCK_ID = "clockId" 320 private val KEY_SEED_COLOR = "seedColor" 321 private val KEY_METADATA = "metadata" 322 serializenull323 fun serialize(setting: ClockSettings?): String { 324 if (setting == null) { 325 return "" 326 } 327 328 return JSONObject() 329 .put(KEY_CLOCK_ID, setting.clockId) 330 .put(KEY_SEED_COLOR, setting.seedColor) 331 .put(KEY_METADATA, setting.metadata) 332 .toString() 333 } 334 deserializenull335 fun deserialize(jsonStr: String?): ClockSettings? { 336 if (jsonStr.isNullOrEmpty()) { 337 return null 338 } 339 340 val json = JSONObject(jsonStr) 341 val result = 342 ClockSettings( 343 if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null, 344 if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null 345 ) 346 if (!json.isNull(KEY_METADATA)) { 347 result.metadata = json.getJSONObject(KEY_METADATA) 348 } 349 return result 350 } 351 } 352 } 353