blob: cfc5e8b765fc34eee6f2688cb77575fb464efda2 [file] [log] [blame]
jnicholb25f3c02021-09-15 17:15:06 +01001/*
2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package androidx.wear.watchface
18
19import android.annotation.SuppressLint
20import android.content.ComponentName
21import android.content.Context
22import android.content.Intent
Alex Clarkeddffd802021-10-13 16:55:05 +010023import android.content.IntentFilter
Alex Clarke7bd783a2021-10-27 15:48:45 +010024import android.content.pm.PackageManager
jnicholb25f3c02021-09-15 17:15:06 +010025import android.graphics.Canvas
26import android.graphics.Rect
27import android.os.Build
28import android.os.Bundle
29import android.os.Handler
30import android.os.HandlerThread
31import android.os.Looper
32import android.os.PowerManager
Alex Clarke70444172021-10-11 16:48:29 +010033import android.os.Process
jnicholb25f3c02021-09-15 17:15:06 +010034import android.os.RemoteException
35import android.os.Trace
36import android.service.wallpaper.WallpaperService
37import android.support.wearable.watchface.Constants
38import android.support.wearable.watchface.IWatchFaceService
39import android.support.wearable.watchface.accessibility.AccessibilityUtils
40import android.support.wearable.watchface.accessibility.ContentDescriptionLabel
41import android.util.Base64
42import android.util.Log
43import android.view.Choreographer
44import android.view.Surface
45import android.view.SurfaceHolder
46import android.view.WindowInsets
47import android.view.accessibility.AccessibilityManager
48import androidx.annotation.Px
49import androidx.annotation.RequiresApi
50import androidx.annotation.RestrictTo
51import androidx.annotation.UiThread
52import androidx.annotation.VisibleForTesting
53import androidx.annotation.WorkerThread
54import androidx.versionedparcelable.ParcelUtils
55import androidx.wear.watchface.complications.SystemDataSources.DataSourceId
56import androidx.wear.watchface.complications.data.ComplicationData
Alex Clarkee787b482021-11-03 11:58:13 +000057import androidx.wear.watchface.complications.data.ComplicationType
jnicholb25f3c02021-09-15 17:15:06 +010058import androidx.wear.watchface.complications.data.toApiComplicationData
59import androidx.wear.watchface.complications.data.toWireTypes
60import androidx.wear.watchface.utility.AsyncTraceEvent
61import androidx.wear.watchface.utility.TraceEvent
62import androidx.wear.watchface.control.HeadlessWatchFaceImpl
63import androidx.wear.watchface.control.IWatchfaceReadyListener
64import androidx.wear.watchface.control.InteractiveInstanceManager
65import androidx.wear.watchface.control.InteractiveWatchFaceImpl
66import androidx.wear.watchface.control.data.CrashInfoParcel
67import androidx.wear.watchface.control.data.HeadlessWatchFaceInstanceParams
68import androidx.wear.watchface.control.data.IdTypeAndDefaultProviderPolicyWireFormat
69import androidx.wear.watchface.control.data.WallpaperInteractiveWatchFaceInstanceParams
70import androidx.wear.watchface.data.ComplicationSlotMetadataWireFormat
71import androidx.wear.watchface.data.DeviceConfig
72import androidx.wear.watchface.data.IdAndComplicationDataWireFormat
73import androidx.wear.watchface.data.WatchUiState
74import androidx.wear.watchface.editor.EditorService
75import androidx.wear.watchface.style.CurrentUserStyleRepository
76import androidx.wear.watchface.style.UserStyle
77import androidx.wear.watchface.style.UserStyleData
78import androidx.wear.watchface.style.UserStyleSchema
79import androidx.wear.watchface.style.UserStyleSetting
Dmitry Zhestilevskiy22101182022-03-17 15:11:07 +000080import androidx.wear.watchface.style.data.UserStyleFlavorsWireFormat
jnicholb25f3c02021-09-15 17:15:06 +010081import androidx.wear.watchface.style.data.UserStyleWireFormat
82import kotlinx.coroutines.CompletableDeferred
83import kotlinx.coroutines.CoroutineScope
84import kotlinx.coroutines.Runnable
85import kotlinx.coroutines.android.asCoroutineDispatcher
86import kotlinx.coroutines.cancel
jnicholb25f3c02021-09-15 17:15:06 +010087import kotlinx.coroutines.launch
88import kotlinx.coroutines.runBlocking
Alex Clarkefb720782021-11-24 17:05:14 +000089import java.io.ByteArrayInputStream
90import java.io.ByteArrayOutputStream
jnicholb25f3c02021-09-15 17:15:06 +010091import java.io.FileDescriptor
92import java.io.FileNotFoundException
93import java.io.InputStreamReader
Alex Clarkefb720782021-11-24 17:05:14 +000094import java.io.ObjectInputStream
95import java.io.ObjectOutputStream
jnicholb25f3c02021-09-15 17:15:06 +010096import java.io.PrintWriter
97import java.time.Instant
Dmitry Zhestilevskiy2e6575c2022-02-22 15:02:35 +030098import kotlinx.coroutines.CancellationException
Alex Clarkeb6f9cab2022-03-02 10:59:44 +000099import kotlinx.coroutines.withContext
jnicholb25f3c02021-09-15 17:15:06 +0100100
101/** The wire format for [ComplicationData]. */
102internal typealias WireComplicationData = android.support.wearable.complications.ComplicationData
103
104/**
105 * After user code finishes, we need up to 100ms of wake lock holding for the drawing to occur. This
106 * isn't the ideal approach, but the framework doesn't expose a callback that would tell us when our
107 * Canvas was drawn. 100 ms should give us time for a few frames to be drawn, in case there is a
108 * backlog. If we encounter issues with this approach, we should consider asking framework team to
109 * expose a callback.
110 */
111internal const val SURFACE_DRAW_TIMEOUT_MS = 100L
112
113/**
114 * WatchFaceService and [WatchFace] are a pair of classes intended to handle much of
115 * the boilerplate needed to implement a watch face without being too opinionated. The suggested
116 * structure of a WatchFaceService based watch face is:
117 *
118 * @sample androidx.wear.watchface.samples.kDocCreateExampleWatchFaceService
119 *
120 * Sub classes of WatchFaceService are expected to implement [createWatchFace] which is the
121 * factory for making [WatchFace]s. If the watch faces uses complications then
122 * [createComplicationSlotsManager] should be overridden. All [ComplicationSlot]s are assumed to be
123 * enumerated up upfront and passed as a collection into [ComplicationSlotsManager]'s constructor
124 * which is returned by [createComplicationSlotsManager].
125 *
126 * Watch face styling (color and visual look of watch face elements such as numeric fonts, watch
127 * hands and ticks, etc...) and companion editing is directly supported via [UserStyleSchema] and
128 * [UserStyleSetting]. To enable support for styling override [createUserStyleSchema].
129 *
Dmitry Zhestilevskiy22101182022-03-17 15:11:07 +0000130 * WatchFaceService can expose pre-populated style presets by overriding [createUserStyleFlavors]
131 * or via XML (see below). Each presents represents separate style configured with [UserStyle]
132 * and complication slot policies configured with
133 * [androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy]. The system will
134 * only access flavors if metadata tag is present in manifest:
135 *
136 * <meta-data
137 * android:name="androidx.wear.watchface.FLAVORS_SUPPORTED"
138 * android:value="true" />
139 *
jnicholb25f3c02021-09-15 17:15:06 +0100140 * WatchFaces are initially constructed on a background thread before being used exclusively on
141 * the ui thread afterwards. There is a memory barrier between construction and rendering so no
142 * special threading primitives are required.
143 *
144 * To aid debugging watch face animations, WatchFaceService allows you to speed up or slow down
145 * time, and to loop between two instants. This is controlled by MOCK_TIME_INTENT intents
146 * with a float extra called "androidx.wear.watchface.extra.MOCK_TIME_SPEED_MULTIPLIE" and to long
147 * extras called "androidx.wear.watchface.extra.MOCK_TIME_WRAPPING_MIN_TIME" and
148 * "androidx.wear.watchface.extra.MOCK_TIME_WRAPPING_MAX_TIME" (which are UTC time in milliseconds).
149 * If minTime is omitted or set to -1 then the current time is sampled as minTime.
150 *
151 * E.g, to make time go twice as fast:
152 * adb shell am broadcast -a androidx.wear.watchface.MockTime \
153 * --ef androidx.wear.watchface.extra.MOCK_TIME_SPEED_MULTIPLIER 2.0
154 *
155 *
156 * To use the sample on watch face editor UI, import the wear:wear-watchface-editor-samples library
157 * and add the following into your watch face's AndroidManifest.xml:
158 *
159 * ```
160 * <activity
161 * android:name="androidx.wear.watchface.editor.sample.WatchFaceConfigActivity"
162 * android:exported="true"
163 * android:label="Config"
164 * android:theme="@android:style/Theme.Translucent.NoTitleBar">
165 * <intent-filter>
166 * <action android:name="androidx.wear.watchface.editor.action.WATCH_FACE_EDITOR" />
167 * <category android:name=
168 * "com.google.android.wearable.watchface.category.WEARABLE_CONFIGURATION" />
169 * <category android:name="android.intent.category.DEFAULT" />
170 * </intent-filter>
171 * </activity>
172 * ```
173 *
174 * To register a WatchFaceService with the system add a <service> tag to the <application> in your
175 * watch face's AndroidManifest.xml:
176 *
177 * ```
178 * <service
179 * android:name=".MyWatchFaceServiceClass"
180 * android:exported="true"
181 * android:label="@string/watch_face_name"
182 * android:permission="android.permission.BIND_WALLPAPER">
183 * <intent-filter>
184 * <action android:name="android.service.wallpaper.WallpaperService" />
185 * <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
186 * </intent-filter>
187 * <meta-data
188 * android:name="com.google.android.wearable.watchface.preview"
189 * android:resource="@drawable/my_watch_preview" />
190 * <meta-data
191 * android:name="com.google.android.wearable.watchface.preview_circular"
192 * android:resource="@drawable/my_watch_circular_preview" />
193 * <meta-data
194 * android:name="com.google.android.wearable.watchface.wearableConfigurationAction"
195 * android:value="androidx.wear.watchface.editor.action.WATCH_FACE_EDITOR"/>
196 * <meta-data
197 * android:name="android.service.wallpaper"
198 * android:resource="@xml/watch_face" />
199 * <meta-data
200 * android:name=
201 * "com.google.android.wearable.watchface.companionBuiltinConfigurationEnabled"
202 * android:value="true" />
203 * </service>
204 * ```
205 *
206 * Multiple watch faces can be defined in the same package, requiring multiple <service> tags.
Alex Clarke7bd783a2021-10-27 15:48:45 +0100207 *
Alex Clarkea4c07c72021-11-30 13:54:31 +0000208 * By default the system will only allow the user to create a single instance of the watch face. You
209 * can choose to allow the user to create multiple instances (each with their own styling and a
210 * distinct [WatchState.watchFaceInstanceId]) by adding this meta-data to your watch face's
211 * manifest:
212 *
213 * <meta-data
214 * android:name="androidx.wear.watchface.MULTIPLE_INSTANCES_ALLOWED"
215 * android:value="true" />
216 *
Alex Clarke7bd783a2021-10-27 15:48:45 +0100217 *
Dmitry Zhestilevskiy22101182022-03-17 15:11:07 +0000218 * A watch face can declare the [UserStyleSchema], [ComplicationSlot]s and [UserStyleFlavors] in
219 * XML. The main advantage is simplicity for the developer, however meta data queries (see
Alex Clarke7bd783a2021-10-27 15:48:45 +0100220 * androidx.wear.watchface.client.WatchFaceMetadataClient) are faster because they can be
221 * performed without having to bind to the WatchFaceService.
222 *
223 * To use xml inflation, add an androidx.wear.watchface.XmlSchemaAndComplicationSlotsDefinition
224 * meta date tag to your service:
225 *
226 * <meta-data
227 * android:name="androidx.wear.watchface.XmlSchemaAndComplicationSlotsDefinition"
228 * android:resource="@xml/my_watchface_definition" />
229 *
230 * And the linked xml/my_watchface_definition resource must contain a XmlWatchFace node. E.g.:
231 *
232 * <XmlWatchFace xmlns:android="http://schemas.android.com/apk/res/android"
233 * xmlns:app="http://schemas.android.com/apk/res-auto">
234 * <UserStyleSchema>
235 * <ListUserStyleSetting
236 * android:icon="@drawable/time_style_icon"
237 * app:affectedWatchFaceLayers="BASE|COMPLICATIONS|COMPLICATIONS_OVERLAY"
238 * app:defaultOptionIndex="1"
239 * app:description="@string/time_style_description"
240 * app:displayName="@string/time_style_name"
241 * app:id="TimeStyle">
242 * <ListOption
243 * android:icon="@drawable/time_style_minimal_icon"
244 * app:displayName="@string/time_style_minimal_name"
245 * app:id="minimal" />
246 * <ListOption
247 * android:icon="@drawable/time_style_seconds_icon"
248 * app:displayName="@string/time_style_seconds_name"
249 * app:id="seconds" />
250 * </ListUserStyleSetting>
251 * </UserStyleSchema>
252 * </ComplicationSlot>
253 * app:slotId="1"
254 * app:boundsType="ROUND_RECT"
255 * app:supportedTypes="SHORT_TEXT|RANGED_VALUE|SMALL_IMAGE"
256 * app:defaultDataSourceType="RANGED_VALUE"
257 * app:systemDataSourceFallback="DATA_SOURCE_WATCH_BATTERY">
258 * <ComplicationSlotBounds
259 * app:left="0.3" app:top="0.7" app:right="0.7" app:bottom="0.9"/>
260 * </ComplicationSlot>
Dmitry Zhestilevskiy22101182022-03-17 15:11:07 +0000261 * <UserStyleFlavors>
262 * <UserStyleFlavor app:id="flavor1">
263 * <StyleOption app:id="TimeStyle" app:value="minimal"/>
264 * <ComplicationPolicy
265 * app:slotId="1"
266 * app:primaryDataSource="com.package/com.app"
267 * app:primaryDataSourceDefaultType="SHORT_TEXT"
268 * app:systemDataSourceFallback="DATA_SOURCE_DAY_AND_DATE"
269 * app:systemDataSourceFallbackDefaultType="SHORT_TEXT"/>
270 * </UserStyleFlavor>
271 * </UserStyleFlavors>
Alex Clarke7bd783a2021-10-27 15:48:45 +0100272 * </XmlWatchFace>
273 *
274 * If you use XmlSchemaAndComplicationSlotsDefinition then you shouldn't override
275 * [createUserStyleSchema] or [createComplicationSlotsManager]. However if <ComplicationSlot> tags
276 * are defined then you must override [getComplicationSlotInflationFactory] in order to provide the
277 * [CanvasComplicationFactory] and where necessary edge complication [ComplicationTapFilter]s.
278 *
279 * Note the <ComplicationSlot> tag does not support configExtras because in general a [Bundle] can
280 * not be inflated from XML.
281 *
282 * Note it is an error to define a XmlSchemaAndComplicationSlotsDefinition and not use it.
jnicholb25f3c02021-09-15 17:15:06 +0100283 */
284public abstract class WatchFaceService : WallpaperService() {
285
286 public companion object {
287 private const val TAG = "WatchFaceService"
288
289 /** Whether to log every frame. */
290 private const val LOG_VERBOSE = false
291
292 /**
293 * Whether to enable tracing for each call to [WatchFaceImpl.onDraw()] and
294 * [WatchFaceImpl.onSurfaceRedrawNeeded()]
295 */
296 private const val TRACE_DRAW = false
297
298 // Reference time for editor screenshots for analog watch faces.
299 // 2020/10/10 at 09:30 Note the date doesn't matter, only the hour.
300 private const val ANALOG_WATCHFACE_REFERENCE_TIME_MS = 1602318600000L
301
302 // Reference time for editor screenshots for digital watch faces.
303 // 2020/10/10 at 10:10 Note the date doesn't matter, only the hour.
304 private const val DIGITAL_WATCHFACE_REFERENCE_TIME_MS = 1602321000000L
305
306 // Filename for persisted preferences to be used in a direct boot scenario.
307 private const val DIRECT_BOOT_PREFS = "directboot.prefs"
308
309 // The index of the watch element in the content description labels. Usually it will be
310 // first.
311 private const val WATCH_ELEMENT_ACCESSIBILITY_TRAVERSAL_INDEX = -1
312
313 /** The maximum permitted duration of [WatchFaceService.createWatchFace]. */
314 public const val MAX_CREATE_WATCHFACE_TIME_MILLIS: Int = 5000
315
316 /** The maximum reasonable wire size for a [UserStyleSchema] in bytes. */
317 internal const val MAX_REASONABLE_SCHEMA_WIRE_SIZE_BYTES = 50000
318
319 /** The maximum reasonable wire size for an Icon in a [UserStyleSchema] in pixels. */
320 @Px internal const val MAX_REASONABLE_SCHEMA_ICON_WIDTH = 400
321 @Px internal const val MAX_REASONABLE_SCHEMA_ICON_HEIGHT = 400
Alex Clarke7bd783a2021-10-27 15:48:45 +0100322
323 /** @hide */
324 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
325 @JvmField
326 public val XML_WATCH_FACE_METADATA =
327 "androidx.wear.watchface.XmlSchemaAndComplicationSlotsDefinition"
Alex Clarkeb6f9cab2022-03-02 10:59:44 +0000328
329 internal fun <R> awaitDeferredWatchFaceImplThenRunOnUiThreadBlocking(
330 engine: WatchFaceService.EngineWrapper?,
331 traceName: String,
332 task: (watchFaceImpl: WatchFaceImpl) -> R
333 ): R? = TraceEvent(traceName).use {
334 if (engine == null) {
335 Log.w(TAG, "Task $traceName posted after close(), ignoring.")
336 return null
337 }
338 runBlocking {
339 try {
340 val watchFaceImpl = engine.deferredWatchFaceImpl.await()
341 withContext(engine.uiThreadCoroutineScope.coroutineContext) {
342 task(watchFaceImpl)
343 }
344 } catch (e: Exception) {
345 Log.e(HeadlessWatchFaceImpl.TAG, "Operation failed", e)
346 throw e
347 }
348 }
349 }
350
351 internal fun <R> deferredWatchFaceAndComplicationManagerThenRunOnBinderThread(
352 engine: WatchFaceService.EngineWrapper?,
353 traceName: String,
354 task: (watchFaceInitDetails: WatchFaceService.WatchFaceInitDetails) -> R
355 ): R? = TraceEvent(traceName).use {
356 if (engine == null) {
357 Log.w(TAG, "Task $traceName posted after close(), ignoring.")
358 return null
359 }
360 runBlocking {
361 try {
362 task(engine.watchFaceInitDetails.await())
363 } catch (e: Exception) {
364 Log.e(HeadlessWatchFaceImpl.TAG, "Operation failed", e)
365 throw e
366 }
367 }
368 }
jnicholb25f3c02021-09-15 17:15:06 +0100369 }
370
371 /**
Alex Clarke7bd783a2021-10-27 15:48:45 +0100372 * Returns the id of the XmlSchemaAndComplicationSlotsDefinition XML resource or 0 if it can't
373 * be found.
374 *
375 * @hide
376 */
377 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
Aurimas Liutikasef5e7102022-02-14 17:24:14 -0800378 @Suppress("DEPRECATION")
Alex Clarke7bd783a2021-10-27 15:48:45 +0100379 public open fun getXmlWatchFaceResourceId(): Int {
380 return try {
381 packageManager.getServiceInfo(
382 ComponentName(this, javaClass),
383 PackageManager.GET_META_DATA
384 ).metaData.getInt(XML_WATCH_FACE_METADATA)
385 } catch (e: Exception) {
386 // If an exception occurs here, we'll ignore it and return 0 meaning it can't be fond.
387 0
388 }
389 }
390
391 private val
392 xmlSchemaAndComplicationSlotsDefinition: XmlSchemaAndComplicationSlotsDefinition by lazy {
393 val resourceId = getXmlWatchFaceResourceId()
394 if (resourceId == 0) {
Dmitry Zhestilevskiy22101182022-03-17 15:11:07 +0000395 XmlSchemaAndComplicationSlotsDefinition(
396 schema = null,
397 complicationSlots = emptyList(),
398 flavors = null)
Alex Clarke7bd783a2021-10-27 15:48:45 +0100399 } else {
400 XmlSchemaAndComplicationSlotsDefinition.inflate(
401 resources,
402 resources.getXml(resourceId)
403 )
404 }
405 }
406
407 /**
408 * If the WatchFaceService's manifest doesn't define a
409 * androidx.wear.watchface.XmlSchemaAndComplicationSlotsDefinition meta data tag then override
410 * this factory method to create a non-empty [UserStyleSchema]. A [CurrentUserStyleRepository]
Dmitry Zhestilevskiy22101182022-03-17 15:11:07 +0000411 * constructed with this schema will be passed to [createComplicationSlotsManager],
412 * [createUserStyleFlavors] and [createWatchFace]. This is called on a background thread.
jnicholb25f3c02021-09-15 17:15:06 +0100413 *
414 * @return The [UserStyleSchema] to create a [CurrentUserStyleRepository] with, which is passed
415 * to [createComplicationSlotsManager] and [createWatchFace].
416 */
417 @WorkerThread
Alex Clarke7bd783a2021-10-27 15:48:45 +0100418 protected open fun createUserStyleSchema(): UserStyleSchema =
419 xmlSchemaAndComplicationSlotsDefinition.schema ?: UserStyleSchema(emptyList())
jnicholb25f3c02021-09-15 17:15:06 +0100420
421 /**
Alex Clarke7bd783a2021-10-27 15:48:45 +0100422 * If the WatchFaceService's manifest doesn't define a
423 * androidx.wear.watchface.XmlSchemaAndComplicationSlotsDefinition meta data tag then override
424 * this factory method to create a non-empty [ComplicationSlotsManager]. This manager will be
Dmitry Zhestilevskiy22101182022-03-17 15:11:07 +0000425 * passed to [createUserStyleFlavors] and [createWatchFace].
426 * This will be called from a background thread but the ComplicationSlotsManager should be
427 * accessed exclusively from the UiThread afterwards.
jnicholb25f3c02021-09-15 17:15:06 +0100428 *
429 * @param currentUserStyleRepository The [CurrentUserStyleRepository] constructed using the
430 * [UserStyleSchema] returned by [createUserStyleSchema].
431 * @return The [ComplicationSlotsManager] to pass into [createWatchFace].
432 */
433 @WorkerThread
434 protected open fun createComplicationSlotsManager(
435 currentUserStyleRepository: CurrentUserStyleRepository
Alex Clarke7bd783a2021-10-27 15:48:45 +0100436 ): ComplicationSlotsManager =
437 xmlSchemaAndComplicationSlotsDefinition.buildComplicationSlotsManager(
438 currentUserStyleRepository,
439 getComplicationSlotInflationFactory()
440 )
441
442 /**
443 * Used when inflating [ComplicationSlot]s from XML to provide a
444 * [ComplicationSlotInflationFactory] which provides the [CanvasComplicationFactory] and where
445 * necessary edge complication [ComplicationTapFilter]s needed for inflating
446 * [ComplicationSlot]s.
447 *
448 * If an androidx.wear.watchface.XmlSchemaAndComplicationSlotsDefinition metadata tag is defined
449 * for your WatchFaceService 's manifest, and your XML includes <ComplicationSlot> tags then you
450 * must override this method.
451 */
452 @WorkerThread
453 protected open fun getComplicationSlotInflationFactory(): ComplicationSlotInflationFactory? =
454 null
jnicholb25f3c02021-09-15 17:15:06 +0100455
456 /**
Dmitry Zhestilevskiy22101182022-03-17 15:11:07 +0000457 * If the WatchFaceService's manifest doesn't define a
458 * androidx.wear.watchface.XmlSchemaAndComplicationSlotsDefinition meta data tag then override
459 * this factory method to create non-empty [UserStyleFlavors].
460 * This is called on a background thread. The system reads the flavors once and changes may be
461 * ignored until the APK is updated.
462 * Metadata tag "androidx.wear.watchface.FLAVORS_SUPPORTED" should be added to let the system
463 * know the service supports flavors.
464 *
465 * @param currentUserStyleRepository The [CurrentUserStyleRepository] constructed using the
466 * [UserStyleSchema] returned by [createUserStyleSchema].
467 * @param complicationSlotsManager The [ComplicationSlotsManager] returned by
468 * [createComplicationSlotsManager]
469 * @return The [UserStyleFlavors], which is exposed to the system.
470 */
471 @WorkerThread
472 @WatchFaceFlavorsExperimental
473 protected open fun createUserStyleFlavors(
474 currentUserStyleRepository: CurrentUserStyleRepository,
475 complicationSlotsManager: ComplicationSlotsManager
476 ): UserStyleFlavors =
477 xmlSchemaAndComplicationSlotsDefinition.flavors ?: UserStyleFlavors()
478
479 /**
Alex Clarke12047142021-09-29 12:42:28 +0100480 * Override this factory method to create your WatchFaceImpl. This method will be called by the
481 * library on a background thread, if possible any expensive initialization should be done
482 * asynchronously. The [WatchFace] and its [Renderer] should be accessed exclusively from the
jnicholb25f3c02021-09-15 17:15:06 +0100483 * UiThread afterwards. There is a memory barrier between construction and rendering so no
484 * special threading primitives are required.
485 *
486 * Warning the system will likely time out waiting for watch face initialization if it takes
487 * longer than [MAX_CREATE_WATCHFACE_TIME_MILLIS] milliseconds.
488 *
489 * @param surfaceHolder The [SurfaceHolder] to pass to the [Renderer]'s constructor.
490 * @param watchState The [WatchState] for the watch face.
491 * @param complicationSlotsManager The [ComplicationSlotsManager] returned by
492 * [createComplicationSlotsManager].
493 * @param currentUserStyleRepository The [CurrentUserStyleRepository] constructed using the
494 * [UserStyleSchema] returned by [createUserStyleSchema].
495 * @return A [WatchFace] whose [Renderer] uses the provided [surfaceHolder].
496 */
497 @WorkerThread
498 protected abstract suspend fun createWatchFace(
499 surfaceHolder: SurfaceHolder,
500 watchState: WatchState,
501 complicationSlotsManager: ComplicationSlotsManager,
502 currentUserStyleRepository: CurrentUserStyleRepository
503 ): WatchFace
504
505 /** Creates an interactive engine for WallpaperService. */
506 final override fun onCreateEngine(): Engine =
507 EngineWrapper(getUiThreadHandler(), getBackgroundThreadHandler(), false)
508
509 /** Creates a headless engine. */
510 internal fun createHeadlessEngine(): Engine =
511 EngineWrapper(getUiThreadHandler(), getBackgroundThreadHandler(), true)
512
513 /** Returns the ui thread [Handler]. */
514 public fun getUiThreadHandler(): Handler = getUiThreadHandlerImpl()
515
516 /** This is open for testing. */
517 internal open fun getUiThreadHandlerImpl(): Handler = Handler(Looper.getMainLooper())
518
Alex Clarke90add702022-03-25 15:18:16 +0000519 /**
520 * Override to force the watchface to be regarded as being visible. This must not be used in
521 * production code or significant battery life regressions may occur.
522 *
523 * @hide
524 */
525 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
526 open fun forceIsVisibleForTesting() = false
527
Alex Clarke93e09f72021-10-21 14:21:01 +0100528 /* Interface for setting the main thread priority. This exists for testing. */
529 internal interface MainThreadPriorityDelegate {
530 fun setNormalPriority()
531
532 fun setInteractivePriority()
533 }
534
535 internal open fun getMainThreadPriorityDelegate() = object : MainThreadPriorityDelegate {
536 override fun setNormalPriority() {
537 // NB pID is the same as the main thread tID.
538 Process.setThreadPriority(Process.myPid(), Process.THREAD_PRIORITY_DEFAULT)
539 }
540
541 override fun setInteractivePriority() {
542 Process.setThreadPriority(Process.myPid(), Process.THREAD_PRIORITY_DISPLAY)
543 }
544 }
545
jnicholb25f3c02021-09-15 17:15:06 +0100546 /**
547 * Returns the lazily constructed background thread [Handler]. During initialization
548 * [createUserStyleSchema], [createComplicationSlotsManager] and [createWatchFace] are posted on
549 * this handler.
550 */
551 public fun getBackgroundThreadHandler(): Handler = getBackgroundThreadHandlerImpl()
552
553 internal var backgroundThread: HandlerThread? = null
554
Alex Clarke70444172021-10-11 16:48:29 +0100555 /** This is open for testing. The background thread is used for watch face initialization. */
jnicholb25f3c02021-09-15 17:15:06 +0100556 internal open fun getBackgroundThreadHandlerImpl(): Handler {
557 synchronized(this) {
558 if (backgroundThread == null) {
Alex Clarke70444172021-10-11 16:48:29 +0100559 backgroundThread = HandlerThread(
560 "WatchFaceBackground",
561 Process.THREAD_PRIORITY_FOREGROUND // The user is waiting on WF init.
Alex Clarkedd968d22021-11-25 17:47:23 +0000562 ).apply {
563 uncaughtExceptionHandler = Thread.UncaughtExceptionHandler {
564 _, throwable ->
565 Log.e(TAG, "Uncaught exception on watch face background thread", throwable)
566 }
567 start()
568 }
jnicholb25f3c02021-09-15 17:15:06 +0100569 }
570 return Handler(backgroundThread!!.looper)
571 }
572 }
573
574 /** This is open to allow mocking. */
575 internal open fun getMutableWatchState() = MutableWatchState()
576
577 /** This is open for use by tests. */
578 internal open fun allowWatchFaceToAnimate() = true
579
580 /**
581 * Whether or not the pre R style init flow (SET_BINDER wallpaper command) is expected.
582 * This is open for use by tests.
583 */
584 internal open fun expectPreRInitFlow() = Build.VERSION.SDK_INT < Build.VERSION_CODES.R
585
Alex Clarke249f2112021-10-18 13:01:54 +0100586 /** [Choreographer] isn't supposed to be mocked, so we use a thin wrapper. */
587 internal interface ChoreographerWrapper {
588 fun postFrameCallback(callback: Choreographer.FrameCallback)
589 fun removeFrameCallback(callback: Choreographer.FrameCallback)
590 }
591
Alex Clarkeda008e52021-10-05 19:59:35 +0100592 /** This is open to allow mocking. */
Alex Clarke249f2112021-10-18 13:01:54 +0100593 internal open fun getChoreographer(): ChoreographerWrapper = object : ChoreographerWrapper {
594 private val choreographer = Choreographer.getInstance()
595
596 override fun postFrameCallback(callback: Choreographer.FrameCallback) {
597 choreographer.postFrameCallback(callback)
598 }
599
600 override fun removeFrameCallback(callback: Choreographer.FrameCallback) {
601 choreographer.removeFrameCallback(callback)
602 }
603 }
Alex Clarkeda008e52021-10-05 19:59:35 +0100604
jnicholb25f3c02021-09-15 17:15:06 +0100605 /**
606 * This is open for use by tests, it allows them to inject a custom [SurfaceHolder].
607 * @hide
608 */
609 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
610 public open fun getWallpaperSurfaceHolderOverride(): SurfaceHolder? = null
611
612 internal fun setContext(context: Context) {
613 attachBaseContext(context)
614 }
615
616 /**
617 * Reads WallpaperInteractiveWatchFaceInstanceParams from a file. This is only used in the
618 * android R flow.
619 */
620 internal open fun readDirectBootPrefs(
621 context: Context,
622 fileName: String
623 ): WallpaperInteractiveWatchFaceInstanceParams? = TraceEvent(
624 "WatchFaceService.readDirectBootPrefs"
625 ).use {
626 try {
627 val directBootContext = context.createDeviceProtectedStorageContext()
628 val reader = directBootContext.openFileInput(fileName)
629 reader.use {
630 ParcelUtils.fromInputStream<WallpaperInteractiveWatchFaceInstanceParams>(reader)
631 }
632 } catch (e: Exception) {
633 null
634 }
635 }
636
637 /**
638 * Writes WallpaperInteractiveWatchFaceInstanceParams to a file. This is only used in the
639 * android R flow.
640 */
641 internal open fun writeDirectBootPrefs(
642 context: Context,
643 fileName: String,
644 prefs: WallpaperInteractiveWatchFaceInstanceParams
645 ): Unit = TraceEvent("WatchFaceService.writeDirectBootPrefs").use {
646 val directBootContext = context.createDeviceProtectedStorageContext()
647 val writer = directBootContext.openFileOutput(fileName, Context.MODE_PRIVATE)
648 writer.use {
649 ParcelUtils.toOutputStream(prefs, writer)
650 }
651 }
652
Alex Clarkefb720782021-11-24 17:05:14 +0000653 internal open fun readComplicationDataCacheByteArray(
654 context: Context,
655 fileName: String
656 ): ByteArray? = TraceEvent(
657 "WatchFaceService.readComplicationCache"
658 ).use {
659 try {
660 val directBootContext = context.createDeviceProtectedStorageContext()
661 val reader = directBootContext.openFileInput(fileName)
662 reader.use {
663 it.readBytes()
664 }
665 } catch (e: Exception) {
666 null
667 }
668 }
669
670 internal open fun writeComplicationDataCacheByteArray(
671 context: Context,
672 fileName: String,
673 byteArray: ByteArray
674 ) {
675 val directBootContext = context.createDeviceProtectedStorageContext()
676 val writer = directBootContext.openFileOutput(fileName, Context.MODE_PRIVATE)
677 writer.use {
678 writer.write(byteArray)
679 }
680 }
681
682 internal fun writeComplicationDataCache(
683 context: Context,
684 complicationSlotsManager: ComplicationSlotsManager,
685 fileName: String
686 ) = TraceEvent(
687 "WatchFaceService.writeComplicationCache"
688 ).use {
689 try {
690 val stream = ByteArrayOutputStream()
691 val objectOutputStream = ObjectOutputStream(stream)
692 objectOutputStream.writeInt(complicationSlotsManager.complicationSlots.size)
693 for (slot in complicationSlotsManager.complicationSlots) {
694 objectOutputStream.writeInt(slot.key)
695 objectOutputStream.writeObject(
696 slot.value.complicationData.value.asWireComplicationData()
697 )
698 }
699 objectOutputStream.close()
700 val byteArray = stream.toByteArray()
701
702 // File IO can be slow so perform the write from a background thread.
703 getBackgroundThreadHandler().post {
704 writeComplicationDataCacheByteArray(context, fileName, byteArray)
705 }
706 } catch (e: Exception) {
707 Log.w(TAG, "Failed to write to complication cache due to exception", e)
708 }
709 }
710
711 internal fun readComplicationDataCache(
712 context: Context,
713 fileName: String
714 ): List<IdAndComplicationDataWireFormat>? = TraceEvent(
715 "WatchFaceService.readComplicationCache"
716 ).use {
717 return readComplicationDataCacheByteArray(context, fileName)?.let {
718 try {
719 val objectInputStream = ObjectInputStream(ByteArrayInputStream(it))
720 val complicationData = ArrayList<IdAndComplicationDataWireFormat>()
721 val numComplications = objectInputStream.readInt()
722 for (i in 0 until numComplications) {
723 complicationData.add(
724 IdAndComplicationDataWireFormat(
725 objectInputStream.readInt(),
726 (objectInputStream.readObject() as WireComplicationData)
727 )
728 )
729 }
730 objectInputStream.close()
731 complicationData
732 } catch (e: Exception) {
733 Log.w(TAG, "Failed to read to complication cache due to exception", e)
734 null
735 }
736 }
737 }
738
jnicholb25f3c02021-09-15 17:15:06 +0100739 /** Reads user style from a file. This is only used in the pre-android R flow. */
740 internal fun readPrefs(context: Context, fileName: String): UserStyleWireFormat {
741 val hashMap = HashMap<String, ByteArray>()
742 try {
743 val reader = InputStreamReader(context.openFileInput(fileName)).buffered()
744 reader.use {
745 while (true) {
746 val key = reader.readLine() ?: break
747 val value = reader.readLine() ?: break
748 hashMap[key] = Base64.decode(value, Base64.NO_WRAP)
749 }
750 }
751 } catch (e: FileNotFoundException) {
752 // We don't need to do anything special here.
753 }
754 return UserStyleWireFormat(hashMap)
755 }
756
757 /** Reads the user style to a file. This is only used in the pre-android R flow. */
758 internal fun writePrefs(context: Context, fileName: String, style: UserStyle) {
759 val writer = context.openFileOutput(fileName, Context.MODE_PRIVATE).bufferedWriter()
760 writer.use {
761 for ((key, value) in style) {
762 writer.write(key.id.value)
763 writer.newLine()
764 writer.write(Base64.encodeToString(value.id.value, Base64.NO_WRAP))
765 writer.newLine()
766 }
767 }
768 }
769
770 /** This is the old pre Android R flow that's needed for backwards compatibility. */
771 internal class WslFlow(private val engineWrapper: EngineWrapper) {
772 class PendingComplicationData(val complicationSlotId: Int, val data: ComplicationData)
773
774 lateinit var iWatchFaceService: IWatchFaceService
775
776 var pendingBackgroundAction: Bundle? = null
777 var pendingProperties: Bundle? = null
778 var pendingSetWatchFaceStyle = false
779 var pendingVisibilityChanged: Boolean? = null
780 var pendingComplicationDataUpdates = ArrayList<PendingComplicationData>()
781 var complicationsActivated = false
782 var watchFaceInitStarted = false
783 var lastActiveComplicationSlots: IntArray? = null
784
785 // Only valid after onSetBinder has been called.
786 var systemApiVersion = -1
787
788 fun iWatchFaceServiceInitialized() = this::iWatchFaceService.isInitialized
789
790 fun requestWatchFaceStyle() {
791 engineWrapper.uiThreadCoroutineScope.launch {
792 TraceEvent("requestWatchFaceStyle").use {
793 try {
794 iWatchFaceService.setStyle(
795 engineWrapper.deferredWatchFaceImpl.await().getWatchFaceStyle()
796 )
797 } catch (e: RemoteException) {
798 Log.e(TAG, "Failed to set WatchFaceStyle: ", e)
799 }
800
801 val activeComplications = lastActiveComplicationSlots
802 if (activeComplications != null) {
803 engineWrapper.setActiveComplicationSlots(activeComplications)
804 }
805
806 if (engineWrapper.contentDescriptionLabels.isNotEmpty()) {
807 engineWrapper.contentDescriptionLabels =
808 engineWrapper.contentDescriptionLabels
809 }
810 }
811 }
812 }
813
814 fun setDefaultComplicationProviderWithFallbacks(
815 complicationSlotId: Int,
816 dataSources: List<ComponentName>?,
817 @DataSourceId fallbackSystemDataSource: Int,
818 type: Int
819 ) {
820
821 // For android R flow iWatchFaceService won't have been set.
822 if (!iWatchFaceServiceInitialized()) {
823 return
824 }
825
826 if (systemApiVersion >= 2) {
827 iWatchFaceService.setDefaultComplicationProviderWithFallbacks(
828 complicationSlotId,
829 dataSources,
830 fallbackSystemDataSource,
831 type
832 )
833 } else {
834 // If the implementation doesn't support the new API we emulate its behavior by
835 // setting complication data sources in the reverse order. This works because if
836 // setDefaultComplicationProvider attempts to set a non-existent or incompatible
837 // data source it does nothing, which allows us to emulate the same semantics as
838 // setDefaultComplicationProviderWithFallbacks albeit with more calls.
839 if (fallbackSystemDataSource != WatchFaceImpl.NO_DEFAULT_DATA_SOURCE) {
840 iWatchFaceService.setDefaultSystemComplicationProvider(
841 complicationSlotId, fallbackSystemDataSource, type
842 )
843 }
844
845 if (dataSources != null) {
846 // Iterate in reverse order. This could be O(n^2) but n is expected to be small
847 // and the list is probably an ArrayList so it's probably O(n) in practice.
848 for (i in dataSources.size - 1 downTo 0) {
849 iWatchFaceService.setDefaultComplicationProvider(
850 complicationSlotId, dataSources[i], type
851 )
852 }
853 }
854 }
855 }
856
857 fun setActiveComplications(complicationSlotIds: IntArray) {
858 // For android R flow iWatchFaceService won't have been set.
859 if (!iWatchFaceServiceInitialized()) {
860 return
861 }
862
863 lastActiveComplicationSlots = complicationSlotIds
864
865 try {
866 iWatchFaceService.setActiveComplications(
867 complicationSlotIds, /* updateAll= */ !complicationsActivated
868 )
869 complicationsActivated = true
870 } catch (e: RemoteException) {
871 Log.e(TAG, "Failed to set active complicationSlots: ", e)
872 }
873 }
874
875 fun onRequestStyle() {
876 // We can't guarantee the binder has been set and onSurfaceChanged called before this
877 // command.
878 if (!engineWrapper.watchFaceCreated()) {
879 pendingSetWatchFaceStyle = true
880 return
881 }
882 requestWatchFaceStyle()
883 pendingSetWatchFaceStyle = false
884 }
885
886 @UiThread
887 fun onBackgroundAction(extras: Bundle) {
888 // We can't guarantee the binder has been set and onSurfaceChanged called before this
889 // command.
890 if (!engineWrapper.watchFaceCreated()) {
891 pendingBackgroundAction = extras
892 return
893 }
894
895 engineWrapper.setWatchUiState(
896 WatchUiState(
897 extras.getBoolean(
898 Constants.EXTRA_AMBIENT_MODE,
899 engineWrapper.mutableWatchState.isAmbient.getValueOr(false)
900 ),
901 extras.getInt(
902 Constants.EXTRA_INTERRUPTION_FILTER,
903 engineWrapper.mutableWatchState.interruptionFilter.getValueOr(0)
904 )
905 )
906 )
907
908 pendingBackgroundAction = null
909 }
910
Alan Viverettee3af0e42022-03-04 15:08:46 -0500911 @Suppress("DEPRECATION")
jnicholb25f3c02021-09-15 17:15:06 +0100912 fun onComplicationSlotDataUpdate(extras: Bundle) {
913 extras.classLoader = WireComplicationData::class.java.classLoader
914 val complicationData: WireComplicationData =
915 extras.getParcelable(Constants.EXTRA_COMPLICATION_DATA)!!
916 engineWrapper.setComplicationSlotData(
917 extras.getInt(Constants.EXTRA_COMPLICATION_ID),
918 complicationData.toApiComplicationData()
919 )
920 }
921
922 fun onSetBinder(extras: Bundle) {
923 val binder = extras.getBinder(Constants.EXTRA_BINDER)
924 if (binder == null) {
925 Log.w(TAG, "Binder is null.")
926 return
927 }
928
929 iWatchFaceService = IWatchFaceService.Stub.asInterface(binder)
930
931 try {
932 // Note if the implementation doesn't support getVersion this will return zero
933 // rather than throwing an exception.
934 systemApiVersion = iWatchFaceService.apiVersion
935 } catch (e: RemoteException) {
936 Log.w(TAG, "Failed to getVersion: ", e)
937 }
938
939 engineWrapper.uiThreadCoroutineScope.launch { maybeCreateWatchFace() }
940 }
941
942 @UiThread
943 fun onPropertiesChanged(properties: Bundle) {
944 if (!watchFaceInitStarted) {
945 pendingProperties = properties
946 engineWrapper.uiThreadCoroutineScope.launch { maybeCreateWatchFace() }
947 return
948 }
949
950 engineWrapper.setImmutableSystemState(
951 DeviceConfig(
952 properties.getBoolean(Constants.PROPERTY_LOW_BIT_AMBIENT),
953 properties.getBoolean(Constants.PROPERTY_BURN_IN_PROTECTION),
954 ANALOG_WATCHFACE_REFERENCE_TIME_MS,
955 DIGITAL_WATCHFACE_REFERENCE_TIME_MS
956 )
957 )
958 }
959
960 private suspend fun maybeCreateWatchFace(): Unit = TraceEvent(
961 "EngineWrapper.maybeCreateWatchFace"
962 ).use {
963 // To simplify handling of watch face state, we only construct the [WatchFaceImpl]
964 // once iWatchFaceService have been initialized and pending properties sent.
965 if (iWatchFaceServiceInitialized() &&
966 pendingProperties != null && !engineWrapper.watchFaceCreatedOrPending()
967 ) {
968 watchFaceInitStarted = true
969
970 // Apply immutable properties to mutableWatchState before creating the watch face.
971 onPropertiesChanged(pendingProperties!!)
972 pendingProperties = null
973
974 val watchState = engineWrapper.mutableWatchState.asWatchState()
975 engineWrapper.createWatchFaceInternal(
976 watchState, null, "maybeCreateWatchFace"
977 )
978
979 // Wait for watchface init to complete.
980 val watchFaceImpl = engineWrapper.deferredWatchFaceImpl.await()
981
982 val backgroundAction = pendingBackgroundAction
983 if (backgroundAction != null) {
984 onBackgroundAction(backgroundAction)
985 pendingBackgroundAction = null
986 }
987 if (pendingSetWatchFaceStyle) {
988 onRequestStyle()
989 }
990 val visibility = pendingVisibilityChanged
991 if (visibility != null) {
992 engineWrapper.onVisibilityChanged(visibility)
993 pendingVisibilityChanged = null
994 }
995 for (complicationDataUpdate in pendingComplicationDataUpdates) {
996 watchFaceImpl.onComplicationSlotDataUpdate(
997 complicationDataUpdate.complicationSlotId,
998 complicationDataUpdate.data
999 )
1000 }
1001 watchFaceImpl.complicationSlotsManager.onComplicationsUpdated()
1002 }
1003 }
1004 }
1005
Dmitry Zhestilevskiy22101182022-03-17 15:11:07 +00001006 @OptIn(WatchFaceFlavorsExperimental::class)
Alex Clarkeb6f9cab2022-03-02 10:59:44 +00001007 internal class WatchFaceInitDetails(
Alex Clarke3f121e52022-02-23 16:47:43 +00001008 val watchFace: WatchFace,
Alex Clarkeb6f9cab2022-03-02 10:59:44 +00001009 val complicationSlotsManager: ComplicationSlotsManager,
Dmitry Zhestilevskiy22101182022-03-17 15:11:07 +00001010 val userStyleRepository: CurrentUserStyleRepository,
1011 val userStyleFlavors: UserStyleFlavors
jnicholb25f3c02021-09-15 17:15:06 +01001012 )
1013
1014 /** @hide */
1015 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
1016 public inner class EngineWrapper(
1017 private val uiThreadHandler: Handler,
1018 private val backgroundThreadHandler: Handler,
1019 headless: Boolean
1020 ) : WallpaperService.Engine(), WatchFaceHostApi {
1021 internal val backgroundThreadCoroutineScope =
1022 CoroutineScope(backgroundThreadHandler.asCoroutineDispatcher().immediate)
1023
1024 internal val uiThreadCoroutineScope =
1025 CoroutineScope(uiThreadHandler.asCoroutineDispatcher().immediate)
1026
1027 private val _context = this@WatchFaceService as Context
1028
1029 // State to support the old WSL style interface
1030 internal val wslFlow = WslFlow(this)
1031
1032 /**
Alex Clarkeb6f9cab2022-03-02 10:59:44 +00001033 * [watchFaceInitDetails] will complete before [deferredWatchFaceImpl].
jnicholb25f3c02021-09-15 17:15:06 +01001034 */
Alex Clarkeb6f9cab2022-03-02 10:59:44 +00001035 internal var watchFaceInitDetails = CompletableDeferred<WatchFaceInitDetails>()
jnicholb25f3c02021-09-15 17:15:06 +01001036
1037 /**
Alex Clarkeb6f9cab2022-03-02 10:59:44 +00001038 * [deferredWatchFaceImpl] will complete after [watchFaceInitDetails].
jnicholb25f3c02021-09-15 17:15:06 +01001039 */
1040 @VisibleForTesting
1041 public var deferredWatchFaceImpl = CompletableDeferred<WatchFaceImpl>()
1042
Dmitry Zhestilevskiy2e6575c2022-02-22 15:02:35 +03001043 @VisibleForTesting
1044 public var deferredValidation = CompletableDeferred<Unit>()
1045
jnicholb25f3c02021-09-15 17:15:06 +01001046 /**
1047 * [deferredSurfaceHolder] will complete after [onSurfaceChanged], before then it's not
1048 * safe to create a UiThread OpenGL context.
1049 */
1050 internal var deferredSurfaceHolder = CompletableDeferred<SurfaceHolder>()
1051
1052 internal val mutableWatchState = getMutableWatchState().apply {
Alex Clarke90add702022-03-25 15:18:16 +00001053 isVisible.value = this@EngineWrapper.isVisible || forceIsVisibleForTesting()
jnicholb25f3c02021-09-15 17:15:06 +01001054 // Watch faces with the old [onSetBinder] init flow don't know whether the system
1055 // is ambient until they have received a background action wallpaper command.
1056 // That's supposed to get sent very quickly, but in case it doesn't we initially
1057 // assume we're not in ambient mode which should be correct most of the time.
1058 isAmbient.value = false
1059 isHeadless = headless
1060 }
1061
1062 // It's possible for two getOrCreateInteractiveWatchFaceClient calls to come in back to
1063 // back for the same instance. If the second one specifies a UserStyle we need to apply it
1064 // but if the instance isn't fully initialized we need to defer application to avoid
1065 // blocking in getOrCreateInteractiveWatchFaceClient until the watch face is ready.
1066 internal var pendingUserStyle: UserStyleWireFormat? = null
1067
1068 /**
1069 * Whether or not we allow watch faces to animate. In some tests or for headless
1070 * rendering (for remote config) we don't want this.
1071 */
1072 internal var allowWatchfaceToAnimate = allowWatchFaceToAnimate()
1073
1074 internal var destroyed = false
Alex Clarkeac0dcfe2021-09-28 14:03:11 +01001075 internal var surfaceDestroyed = false
Alex Clarkefb720782021-11-24 17:05:14 +00001076 internal var pendingComplicationDataCacheWrite = false
jnicholb25f3c02021-09-15 17:15:06 +01001077
1078 internal lateinit var ambientUpdateWakelock: PowerManager.WakeLock
1079
Alex Clarke249f2112021-10-18 13:01:54 +01001080 private lateinit var choreographer: ChoreographerWrapper
jnicholb25f3c02021-09-15 17:15:06 +01001081
1082 /**
1083 * Whether we already have a [frameCallback] posted and waiting in the [Choreographer]
1084 * queue. This protects us from drawing multiple times in a single frame.
1085 */
1086 private var frameCallbackPending = false
1087
1088 private val frameCallback = object : Choreographer.FrameCallback {
1089 @SuppressWarnings("SyntheticAccessor")
1090 override fun doFrame(frameTimeNs: Long) {
1091 if (destroyed) {
1092 return
1093 }
1094 require(allowWatchfaceToAnimate) {
1095 "Choreographer doFrame called but allowWatchfaceToAnimate is false"
1096 }
1097 frameCallbackPending = false
Alex Clarkeda008e52021-10-05 19:59:35 +01001098
1099 val watchFaceImpl: WatchFaceImpl? = getWatchFaceImplOrNull()
1100
1101 /**
1102 * It's possible we went ambient by the time our callback occurred in which case
Alex Clarkec199fad2022-01-19 15:54:34 +00001103 * there's no point drawing.
Alex Clarkeda008e52021-10-05 19:59:35 +01001104 */
1105 if (watchFaceImpl?.renderer?.shouldAnimate() != false) {
1106 draw()
1107 }
jnicholb25f3c02021-09-15 17:15:06 +01001108 }
1109 }
1110
1111 private val invalidateRunnable = Runnable(this::invalidate)
1112
1113 // If non-null then changes to the style must be persisted.
1114 private var directBootParams: WallpaperInteractiveWatchFaceInstanceParams? = null
1115
1116 internal var contentDescriptionLabels: Array<ContentDescriptionLabel> = emptyArray()
1117 set(value) {
1118 field = value
1119
1120 // For the old pre-android R flow.
1121 if (wslFlow.iWatchFaceServiceInitialized()) {
1122 try {
1123 wslFlow.iWatchFaceService.setContentDescriptionLabels(value)
1124 } catch (e: RemoteException) {
1125 Log.e(TAG, "Failed to set accessibility labels: ", e)
1126 }
1127 }
1128 }
1129
1130 internal var firstSetWatchUiState = true
1131 internal var immutableSystemStateDone = false
1132 internal var immutableChinHeightDone = false
1133
1134 private var asyncWatchFaceConstructionPending = false
1135
1136 // Stores the initial ComplicationSlots which could get updated before they're applied.
1137 private var pendingInitialComplications: List<IdAndComplicationDataWireFormat>? = null
1138
1139 private var initialUserStyle: UserStyleWireFormat? = null
1140 private lateinit var interactiveInstanceId: String
1141
1142 private var createdBy = "?"
1143
Alex Clarke93e09f72021-10-21 14:21:01 +01001144 private val mainThreadPriorityDelegate = getMainThreadPriorityDelegate()
1145
jnicholb25f3c02021-09-15 17:15:06 +01001146 /**
1147 * Returns the [WatchFaceImpl] if [deferredWatchFaceImpl] has completed successfully or
Dmitry Zhestilevskiy2e6575c2022-02-22 15:02:35 +03001148 * `null` otherwise. Throws exception if there were problems with watchface validation.
jnicholb25f3c02021-09-15 17:15:06 +01001149 */
Dmitry Zhestilevskiy2e6575c2022-02-22 15:02:35 +03001150 internal fun getWatchFaceImplOrNull(): WatchFaceImpl? {
1151 if (deferredValidation.isCompleted) {
jnicholb25f3c02021-09-15 17:15:06 +01001152 runBlocking {
Dmitry Zhestilevskiy2e6575c2022-02-22 15:02:35 +03001153 // if validation fails exception will be thrown here
1154 deferredValidation.await()
1155 }
1156 }
1157
1158 return if (deferredWatchFaceImpl.isCompleted) {
1159 runBlocking {
1160 deferredWatchFaceImpl.await()
jnicholb25f3c02021-09-15 17:15:06 +01001161 }
1162 } else {
1163 null
1164 }
Dmitry Zhestilevskiy2e6575c2022-02-22 15:02:35 +03001165 }
jnicholb25f3c02021-09-15 17:15:06 +01001166
1167 init {
1168 maybeCreateWCSApi()
1169 }
1170
1171 /** Note this function should only be called once. */
1172 @SuppressWarnings("NewApi")
1173 @UiThread
1174 private fun maybeCreateWCSApi(): Unit = TraceEvent(
1175 "EngineWrapper.maybeCreateWCSApi"
1176 ).use {
1177 // If this is a headless instance then we don't want to create a WCS instance.
1178 if (mutableWatchState.isHeadless) {
1179 return
1180 }
1181
1182 val pendingWallpaperInstance =
1183 InteractiveInstanceManager.takePendingWallpaperInteractiveWatchFaceInstance()
1184
1185 // In a direct boot scenario attempt to load the previously serialized parameters.
1186 if (pendingWallpaperInstance == null && !expectPreRInitFlow()) {
1187 val params = readDirectBootPrefs(_context, DIRECT_BOOT_PREFS)
1188 directBootParams = params
1189 // In tests a watchface may already have been created.
1190 if (params != null && !watchFaceCreatedOrPending()) {
1191 val asyncTraceEvent = AsyncTraceEvent("DirectBoot")
1192 try {
1193 val instance = createInteractiveInstance(params, "DirectBoot")
1194 // WatchFace init is async so its possible we now have a pending
1195 // WallpaperInteractiveWatchFaceInstance request.
1196 InteractiveInstanceManager
1197 .takePendingWallpaperInteractiveWatchFaceInstance()?.let {
1198 require(it.params.instanceId == params.instanceId) {
1199 "Mismatch between pendingWallpaperInstance id " +
1200 "${it.params.instanceId} and constructed instance id " +
1201 params.instanceId
1202 }
1203 it.callback.onInteractiveWatchFaceCreated(instance)
1204 }
1205 } catch (e: Exception) {
1206 InteractiveInstanceManager
1207 .takePendingWallpaperInteractiveWatchFaceInstance()?.let {
1208 it.callback.onInteractiveWatchFaceCrashed(
1209 CrashInfoParcel(e)
1210 )
1211 }
1212 } finally {
1213 asyncTraceEvent.close()
1214 }
1215 }
1216 }
1217
1218 // If there's a pending WallpaperInteractiveWatchFaceInstance then create it.
1219 if (pendingWallpaperInstance != null) {
1220 val asyncTraceEvent =
1221 AsyncTraceEvent("Create PendingWallpaperInteractiveWatchFaceInstance")
Alex Clarke37650ac2021-10-14 15:35:37 +01001222 val instance: InteractiveWatchFaceImpl? = try {
1223 val instance = createInteractiveInstance(
1224 pendingWallpaperInstance.params,
1225 "Boot with pendingWallpaperInstance"
jnicholb25f3c02021-09-15 17:15:06 +01001226 )
Alex Clarke37650ac2021-10-14 15:35:37 +01001227 pendingWallpaperInstance.callback.onInteractiveWatchFaceCreated(instance)
1228 instance
jnicholb25f3c02021-09-15 17:15:06 +01001229 } catch (e: Exception) {
1230 pendingWallpaperInstance.callback.onInteractiveWatchFaceCrashed(
1231 CrashInfoParcel(e)
1232 )
Alex Clarke37650ac2021-10-14 15:35:37 +01001233 null
jnicholb25f3c02021-09-15 17:15:06 +01001234 }
1235 asyncTraceEvent.close()
1236 val params = pendingWallpaperInstance.params
1237 directBootParams = params
jnicholb25f3c02021-09-15 17:15:06 +01001238
1239 // Writing even small amounts of data to storage is quite slow and if we did
1240 // that immediately, we'd delay the first frame which is rendered via
1241 // onSurfaceRedrawNeeded. By posting this task we expedite first frame
1242 // rendering. There is a small window where the direct boot could be stale if
1243 // the watchface crashed but this seems unlikely in practice.
Alex Clarke37650ac2021-10-14 15:35:37 +01001244 backgroundThreadCoroutineScope.launch {
1245 // Wait for init to complete before writing the direct boot prefs, or we might
1246 // sneak in before higher priority init tasks.
1247 instance?.engine?.deferredWatchFaceImpl?.await()
1248
1249 // We don't want to display complications in direct boot mode so replace with an
1250 // empty list. NB we can't actually serialise complications anyway so that's
1251 // just as well...
1252 params.idAndComplicationDataWireFormats = emptyList()
1253
jnicholb25f3c02021-09-15 17:15:06 +01001254 writeDirectBootPrefs(_context, DIRECT_BOOT_PREFS, params)
1255 }
1256 }
1257 }
1258
1259 @UiThread
1260 internal fun ambientTickUpdate(): Unit = TraceEvent("EngineWrapper.ambientTickUpdate").use {
1261 if (mutableWatchState.isAmbient.value!!) {
1262 ambientUpdateWakelock.acquire()
1263 // It's unlikely an ambient tick would be sent to a watch face that hasn't loaded
1264 // yet. The watch face will render at least once upon loading so we don't need to do
1265 // anything special here.
Alex Clarkeda008e52021-10-05 19:59:35 +01001266 draw()
jnicholb25f3c02021-09-15 17:15:06 +01001267 ambientUpdateWakelock.acquire(SURFACE_DRAW_TIMEOUT_MS)
1268 }
1269 }
1270
1271 @UiThread
1272 internal fun setWatchUiState(watchUiState: WatchUiState) {
1273 if (firstSetWatchUiState ||
1274 watchUiState.inAmbientMode != mutableWatchState.isAmbient.value
1275 ) {
1276 mutableWatchState.isAmbient.value = watchUiState.inAmbientMode
1277 }
1278
1279 if (firstSetWatchUiState ||
1280 watchUiState.interruptionFilter != mutableWatchState.interruptionFilter.value
1281 ) {
1282 mutableWatchState.interruptionFilter.value = watchUiState.interruptionFilter
1283 }
1284
1285 firstSetWatchUiState = false
1286 }
1287
1288 @UiThread
Alex Clarkefb720782021-11-24 17:05:14 +00001289 internal suspend fun setUserStyle(userStyle: UserStyleWireFormat): Unit = TraceEvent(
1290 "EngineWrapper.setUserStyle"
1291 ).use {
jnicholb25f3c02021-09-15 17:15:06 +01001292 setUserStyleImpl(deferredWatchFaceImpl.await(), userStyle)
1293 }
1294
1295 @UiThread
Alex Clarkefb720782021-11-24 17:05:14 +00001296 private fun setUserStyleImpl(
1297 watchFaceImpl: WatchFaceImpl,
1298 userStyle: UserStyleWireFormat
1299 ) {
jnicholb25f3c02021-09-15 17:15:06 +01001300 watchFaceImpl.onSetStyleInternal(
1301 UserStyle(UserStyleData(userStyle), watchFaceImpl.currentUserStyleRepository.schema)
1302 )
1303
1304 // Update direct boot params if we have any.
1305 val params = directBootParams ?: return
1306 val currentStyle =
1307 watchFaceImpl.currentUserStyleRepository.userStyle.value.toWireFormat()
1308 if (params.userStyle.equals(currentStyle)) {
1309 return
1310 }
1311 params.userStyle = currentStyle
1312 // We don't want to display complications in direct boot mode so replace with an empty
1313 // list. NB we can't actually serialise complications anyway so that's just as well...
1314 params.idAndComplicationDataWireFormats = emptyList()
1315
Alex Clarke048fdd22022-02-16 10:19:37 +00001316 // Let wallpaper manager know the wallpaper has changed.
1317 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
1318 NotifyColorsChangedHelper.notifyColorsChanged(this)
1319 }
1320
Alex Clarke37650ac2021-10-14 15:35:37 +01001321 backgroundThreadCoroutineScope.launch {
jnicholb25f3c02021-09-15 17:15:06 +01001322 writeDirectBootPrefs(_context, DIRECT_BOOT_PREFS, params)
1323 }
1324 }
1325
jnicholb25f3c02021-09-15 17:15:06 +01001326 /** This can be called on any thread. */
Alex Clarkeac0dcfe2021-09-28 14:03:11 +01001327 @UiThread
1328 internal suspend fun addWatchfaceReadyListener(listener: IWatchfaceReadyListener) {
1329 deferredWatchFaceImpl.await()
1330 listener.onWatchfaceReady()
jnicholb25f3c02021-09-15 17:15:06 +01001331 }
1332
1333 @UiThread
1334 internal fun setImmutableSystemState(deviceConfig: DeviceConfig) {
1335 // These properties never change so set them once only.
1336 if (!immutableSystemStateDone) {
1337 mutableWatchState.hasLowBitAmbient = deviceConfig.hasLowBitAmbient
1338 mutableWatchState.hasBurnInProtection =
1339 deviceConfig.hasBurnInProtection
1340 mutableWatchState.analogPreviewReferenceTimeMillis =
1341 deviceConfig.analogPreviewReferenceTimeMillis
1342 mutableWatchState.digitalPreviewReferenceTimeMillis =
1343 deviceConfig.digitalPreviewReferenceTimeMillis
1344
1345 immutableSystemStateDone = true
1346 }
1347 }
1348
1349 @SuppressLint("SyntheticAccessor")
1350 internal fun setComplicationSlotData(
1351 complicationSlotId: Int,
1352 data: ComplicationData
1353 ): Unit = TraceEvent("EngineWrapper.setComplicationSlotData").use {
1354 val watchFaceImpl = getWatchFaceImplOrNull()
1355 if (watchFaceImpl != null) {
1356 watchFaceImpl.onComplicationSlotDataUpdate(complicationSlotId, data)
1357 watchFaceImpl.complicationSlotsManager.onComplicationsUpdated()
1358 } else {
1359 // If the watch face hasn't loaded yet then we append
1360 // pendingComplicationDataUpdates so it can be applied later.
1361 wslFlow.pendingComplicationDataUpdates.add(
1362 WslFlow.PendingComplicationData(complicationSlotId, data)
1363 )
1364 }
1365 }
1366
1367 @UiThread
1368 internal fun setComplicationDataList(
Alex Clarkefb720782021-11-24 17:05:14 +00001369 complicationDataWireFormats: List<IdAndComplicationDataWireFormat>
jnicholb25f3c02021-09-15 17:15:06 +01001370 ): Unit = TraceEvent("EngineWrapper.setComplicationDataList").use {
1371 val watchFaceImpl = getWatchFaceImplOrNull()
1372 if (watchFaceImpl != null) {
1373 for (idAndComplicationData in complicationDataWireFormats) {
1374 watchFaceImpl.onComplicationSlotDataUpdate(
1375 idAndComplicationData.id,
1376 idAndComplicationData.complicationData.toApiComplicationData()
1377 )
1378 }
1379 watchFaceImpl.complicationSlotsManager.onComplicationsUpdated()
1380 } else {
1381 // If the watchface hasn't been created yet, update pendingInitialComplications so
1382 // it can be applied later.
1383 pendingInitialComplications = complicationDataWireFormats
1384 }
1385 }
1386
Alex Clarkea4a8df42021-11-22 14:08:25 +00001387 @UiThread
1388 internal suspend fun updateInstance(newInstanceId: String) {
1389 val watchFaceImpl = deferredWatchFaceImpl.await()
1390 // If the favorite ID has changed then the complications are probably invalid.
1391 watchFaceImpl.complicationSlotsManager.clearComplicationData()
1392
Alex Clarkefb720782021-11-24 17:05:14 +00001393 // However we may have valid complications cached.
1394 readComplicationDataCache(_context, newInstanceId)?.let {
1395 this.setComplicationDataList(it)
1396 }
1397
Alex Clarke23a0cf42022-02-09 15:24:31 +00001398 mutableWatchState.watchFaceInstanceId.value = sanitizeWatchFaceId(newInstanceId)
Alex Clarkea4a8df42021-11-22 14:08:25 +00001399 }
1400
jnicholb25f3c02021-09-15 17:15:06 +01001401 override fun getContext(): Context = _context
1402
1403 override fun getUiThreadHandler(): Handler = uiThreadHandler
1404
1405 override fun getUiThreadCoroutineScope(): CoroutineScope = uiThreadCoroutineScope
1406
1407 override fun getBackgroundThreadHandler(): Handler = backgroundThreadHandler
1408
1409 override fun onCreate(
1410 holder: SurfaceHolder
1411 ): Unit = TraceEvent("EngineWrapper.onCreate").use {
1412 super.onCreate(holder)
jnicholb25f3c02021-09-15 17:15:06 +01001413 ambientUpdateWakelock =
1414 (getSystemService(Context.POWER_SERVICE) as PowerManager)
1415 .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "$TAG:[AmbientUpdate]")
1416 // Disable reference counting for our wake lock so that we can use the same wake lock
1417 // for user code in invalidate() and after that for having canvas drawn.
1418 ambientUpdateWakelock.setReferenceCounted(false)
1419
1420 // Rerender watch face if the surface changes.
1421 holder.addCallback(
1422 object : SurfaceHolder.Callback {
1423 override fun surfaceChanged(
1424 holder: SurfaceHolder,
1425 format: Int,
1426 width: Int,
1427 height: Int
1428 ) {
1429 // We can sometimes get this callback before the watchface has been created
1430 // in which case it's safe to drop it.
1431 if (deferredWatchFaceImpl.isCompleted) {
1432 invalidate()
1433 }
1434 }
1435
1436 override fun surfaceDestroyed(holder: SurfaceHolder) {
1437 }
1438
1439 override fun surfaceCreated(holder: SurfaceHolder) {
1440 }
1441 }
1442 )
1443 }
1444
1445 override fun onSurfaceChanged(
1446 holder: SurfaceHolder,
1447 format: Int,
1448 width: Int,
1449 height: Int
1450 ): Unit = TraceEvent("EngineWrapper.onSurfaceChanged").use {
1451 super.onSurfaceChanged(holder, format, width, height)
1452 deferredSurfaceHolder.complete(holder)
1453 }
1454
1455 override fun onApplyWindowInsets(
1456 insets: WindowInsets?
1457 ): Unit = TraceEvent("EngineWrapper.onApplyWindowInsets").use {
1458 super.onApplyWindowInsets(insets)
1459 @Px val chinHeight =
1460 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
1461 ChinHeightApi30.extractFromWindowInsets(insets)
1462 } else {
1463 ChinHeightApi25.extractFromWindowInsets(insets)
1464 }
1465 if (immutableChinHeightDone) {
1466 // The chin size cannot change so this should be called only once.
1467 if (mutableWatchState.chinHeight != chinHeight) {
1468 Log.w(
1469 TAG,
1470 "unexpected chin size change ignored: " +
1471 "${mutableWatchState.chinHeight} != $chinHeight"
1472 )
1473 }
1474 return
1475 }
1476 mutableWatchState.chinHeight = chinHeight
1477 immutableChinHeightDone = true
1478 }
1479
1480 private fun quitBackgroundThreadIfCreated() {
1481 synchronized(this) {
1482 backgroundThread?.quitSafely()
1483 backgroundThread = null
1484 }
1485 }
1486
1487 @UiThread
1488 override fun onDestroy(): Unit = TraceEvent("EngineWrapper.onDestroy").use {
Alex Clarkef9952dc2021-10-14 12:22:06 +01001489 super.onDestroy()
Alex Clarke93e09f72021-10-21 14:21:01 +01001490 if (!mutableWatchState.isHeadless) {
1491 mainThreadPriorityDelegate.setNormalPriority()
1492 }
Alex Clarkef9952dc2021-10-14 12:22:06 +01001493
jnicholb25f3c02021-09-15 17:15:06 +01001494 destroyed = true
1495 backgroundThreadCoroutineScope.cancel()
1496 quitBackgroundThreadIfCreated()
1497 uiThreadHandler.removeCallbacks(invalidateRunnable)
1498 if (this::choreographer.isInitialized) {
1499 choreographer.removeFrameCallback(frameCallback)
1500 }
jnicholb25f3c02021-09-15 17:15:06 +01001501 if (this::interactiveInstanceId.isInitialized) {
1502 InteractiveInstanceManager.deleteInstance(interactiveInstanceId)
1503 }
1504
Alex Clarkef9952dc2021-10-14 12:22:06 +01001505 // NB user code could throw an exception so do this last.
1506 runBlocking {
1507 try {
1508 // The WatchFaceImpl is created on the UiThread so if we get here and it's not
1509 // created we can be sure it'll never be created hence we don't need to destroy
1510 // it.
1511 if (deferredWatchFaceImpl.isCompleted) {
1512 deferredWatchFaceImpl.await().onDestroy()
Alex Clarkeb6f9cab2022-03-02 10:59:44 +00001513 } else if (watchFaceInitDetails.isCompleted) {
Alex Clarkef9952dc2021-10-14 12:22:06 +01001514 // However we should destroy the renderer if its been created.
Alex Clarkeb6f9cab2022-03-02 10:59:44 +00001515 watchFaceInitDetails
Alex Clarke3f121e52022-02-23 16:47:43 +00001516 .await().watchFace.renderer.onDestroy()
Alex Clarkef9952dc2021-10-14 12:22:06 +01001517 }
1518 } catch (e: Exception) {
1519 // Throwing an exception here leads to a cascade of errors, log instead.
1520 Log.e(
1521 TAG,
1522 "WatchFace exception observed in onDestroy (may have occurred during init)",
1523 e
1524 )
1525 }
1526 }
jnicholb25f3c02021-09-15 17:15:06 +01001527 }
1528
Alex Clarkeac0dcfe2021-09-28 14:03:11 +01001529 override fun onSurfaceDestroyed(holder: SurfaceHolder) {
1530 surfaceDestroyed = true
1531 }
1532
jnicholb25f3c02021-09-15 17:15:06 +01001533 override fun onCommand(
1534 action: String?,
1535 x: Int,
1536 y: Int,
1537 z: Int,
1538 extras: Bundle?,
1539 resultRequested: Boolean
1540 ): Bundle? {
1541 // From android R onwards the integration changes and no wallpaper commands are allowed
1542 // or expected and can/should be ignored.
1543 if (!expectPreRInitFlow()) {
1544 TraceEvent("onCommand Ignored").close()
1545 return null
1546 }
1547 when (action) {
1548 Constants.COMMAND_AMBIENT_UPDATE ->
1549 uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_AMBIENT_UPDATE") {
1550 ambientTickUpdate()
1551 }
1552 Constants.COMMAND_BACKGROUND_ACTION ->
1553 uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_BACKGROUND_ACTION") {
1554 wslFlow.onBackgroundAction(extras!!)
1555 }
1556 Constants.COMMAND_COMPLICATION_DATA ->
1557 uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_COMPLICATION_DATA") {
1558 wslFlow.onComplicationSlotDataUpdate(extras!!)
1559 }
1560 Constants.COMMAND_REQUEST_STYLE ->
1561 uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_REQUEST_STYLE") {
1562 wslFlow.onRequestStyle()
1563 }
1564 Constants.COMMAND_SET_BINDER ->
1565 uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_SET_BINDER") {
1566 wslFlow.onSetBinder(extras!!)
1567 }
1568 Constants.COMMAND_SET_PROPERTIES ->
1569 uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_SET_PROPERTIES") {
1570 wslFlow.onPropertiesChanged(extras!!)
1571 }
1572 Constants.COMMAND_TAP ->
1573 uiThreadCoroutineScope.runBlockingWithTracing("onCommand COMMAND_TAP") {
1574 val watchFaceImpl = deferredWatchFaceImpl.await()
1575 watchFaceImpl.onTapCommand(
1576 TapType.UP,
1577 TapEvent(
1578 x,
1579 y,
1580 Instant.ofEpochMilli(
1581 watchFaceImpl.systemTimeProvider.getSystemTimeMillis()
1582 )
1583 )
1584 )
1585 }
1586 Constants.COMMAND_TOUCH ->
1587 uiThreadCoroutineScope.runBlockingWithTracing("onCommand COMMAND_TOUCH") {
1588 val watchFaceImpl = deferredWatchFaceImpl.await()
1589 watchFaceImpl.onTapCommand(
1590 TapType.DOWN,
1591 TapEvent(
1592 x,
1593 y,
1594 Instant.ofEpochMilli(
1595 watchFaceImpl.systemTimeProvider.getSystemTimeMillis()
1596 )
1597 )
1598 )
1599 }
1600 Constants.COMMAND_TOUCH_CANCEL ->
1601 uiThreadCoroutineScope.runBlockingWithTracing(
1602 "onCommand COMMAND_TOUCH_CANCEL"
1603 ) {
1604 val watchFaceImpl = deferredWatchFaceImpl.await()
1605 watchFaceImpl.onTapCommand(
1606 TapType.CANCEL,
1607 TapEvent(
1608 x,
1609 y,
1610 Instant.ofEpochMilli(
1611 watchFaceImpl.systemTimeProvider.getSystemTimeMillis()
1612 )
1613 )
1614 )
1615 }
1616 else -> {
1617 }
1618 }
1619 return null
1620 }
1621
1622 override fun getInitialUserStyle(): UserStyleWireFormat? = initialUserStyle
1623
1624 /** This will be called from a binder thread. */
1625 @WorkerThread
1626 internal fun getDefaultProviderPolicies(): Array<IdTypeAndDefaultProviderPolicyWireFormat> {
1627 return createComplicationSlotsManager(
1628 CurrentUserStyleRepository(createUserStyleSchema())
1629 ).getDefaultProviderPolicies()
1630 }
1631
1632 /** This will be called from a binder thread. */
1633 @WorkerThread
1634 internal fun getUserStyleSchemaWireFormat() = createUserStyleSchema().toWireFormat()
1635
1636 /** This will be called from a binder thread. */
Dmitry Zhestilevskiy22101182022-03-17 15:11:07 +00001637 @OptIn(WatchFaceFlavorsExperimental::class)
1638 @WorkerThread
1639 internal fun getUserStyleFlavorsWireFormat(): UserStyleFlavorsWireFormat {
1640 val currentUserStyleRepository = CurrentUserStyleRepository(createUserStyleSchema())
1641 return createUserStyleFlavors(
1642 currentUserStyleRepository,
1643 createComplicationSlotsManager(currentUserStyleRepository)
1644 ).toWireFormat()
1645 }
1646
1647 /** This will be called from a binder thread. */
jnicholb25f3c02021-09-15 17:15:06 +01001648 @WorkerThread
1649 internal fun getComplicationSlotMetadataWireFormats() =
1650 createComplicationSlotsManager(
1651 CurrentUserStyleRepository(createUserStyleSchema())
1652 ).complicationSlots.map {
Alex Clarke7ba83032021-11-08 12:05:55 +00001653 val systemDataSourceFallbackDefaultType =
1654 it.value.defaultDataSourcePolicy.systemDataSourceFallbackDefaultType
1655 .toWireComplicationType()
jnicholb25f3c02021-09-15 17:15:06 +01001656 ComplicationSlotMetadataWireFormat(
1657 it.key,
1658 it.value.complicationSlotBounds.perComplicationTypeBounds.keys.map {
1659 it.toWireComplicationType()
1660 }.toIntArray(),
1661 it.value.complicationSlotBounds.perComplicationTypeBounds
1662 .values.toTypedArray(),
1663 it.value.boundsType,
1664 it.value.supportedTypes.toWireTypes(),
1665 it.value.defaultDataSourcePolicy.dataSourcesAsList(),
1666 it.value.defaultDataSourcePolicy.systemDataSourceFallback,
Alex Clarke7ba83032021-11-08 12:05:55 +00001667 systemDataSourceFallbackDefaultType,
1668 it.value.defaultDataSourcePolicy.primaryDataSourceDefaultType
1669 ?.toWireComplicationType() ?: systemDataSourceFallbackDefaultType,
1670 it.value.defaultDataSourcePolicy.secondaryDataSourceDefaultType
1671 ?.toWireComplicationType() ?: systemDataSourceFallbackDefaultType,
jnicholb25f3c02021-09-15 17:15:06 +01001672 it.value.initiallyEnabled,
1673 it.value.fixedComplicationDataSource,
1674 it.value.configExtras
1675 )
1676 }.toTypedArray()
1677
1678 @RequiresApi(27)
1679 internal fun createHeadlessInstance(
1680 params: HeadlessWatchFaceInstanceParams
1681 ): HeadlessWatchFaceImpl = TraceEvent("EngineWrapper.createHeadlessInstance").use {
1682 require(!watchFaceCreatedOrPending()) {
1683 "WatchFace already exists! Created by $createdBy"
1684 }
1685 setImmutableSystemState(params.deviceConfig)
1686
1687 // Fake SurfaceHolder with just enough methods implemented for headless rendering.
1688 val fakeSurfaceHolder = object : SurfaceHolder {
1689 val callbacks = HashSet<SurfaceHolder.Callback>()
1690
Jim Sprochbcbd33a2022-01-12 14:45:37 -08001691 @Deprecated(
1692 message = "this is ignored, this value is set automatically when needed."
1693 )
jnicholb25f3c02021-09-15 17:15:06 +01001694 override fun setType(type: Int) {
1695 throw NotImplementedError()
1696 }
1697
1698 override fun getSurface(): Surface {
1699 throw NotImplementedError()
1700 }
1701
1702 override fun setSizeFromLayout() {
1703 throw NotImplementedError()
1704 }
1705
1706 override fun lockCanvas(): Canvas {
1707 throw NotImplementedError()
1708 }
1709
1710 override fun lockCanvas(dirty: Rect?): Canvas {
1711 throw NotImplementedError()
1712 }
1713
1714 override fun getSurfaceFrame() = Rect(0, 0, params.width, params.height)
1715
1716 override fun setFixedSize(width: Int, height: Int) {
1717 throw NotImplementedError()
1718 }
1719
1720 override fun removeCallback(callback: SurfaceHolder.Callback) {
1721 callbacks.remove(callback)
1722 }
1723
1724 override fun isCreating(): Boolean {
1725 throw NotImplementedError()
1726 }
1727
1728 override fun addCallback(callback: SurfaceHolder.Callback) {
1729 callbacks.add(callback)
1730 }
1731
1732 override fun setFormat(format: Int) {
1733 throw NotImplementedError()
1734 }
1735
1736 override fun setKeepScreenOn(screenOn: Boolean) {
1737 throw NotImplementedError()
1738 }
1739
1740 override fun unlockCanvasAndPost(canvas: Canvas?) {
1741 throw NotImplementedError()
1742 }
1743 }
1744
1745 allowWatchfaceToAnimate = false
1746 require(mutableWatchState.isHeadless)
Alex Clarke23a0cf42022-02-09 15:24:31 +00001747 mutableWatchState.watchFaceInstanceId.value = sanitizeWatchFaceId(params.instanceId)
jnicholb25f3c02021-09-15 17:15:06 +01001748 val watchState = mutableWatchState.asWatchState()
1749
1750 createWatchFaceInternal(
1751 watchState,
1752 fakeSurfaceHolder,
1753 "createHeadlessInstance"
1754 )
1755
1756 mutableWatchState.isVisible.value = true
1757 mutableWatchState.isAmbient.value = false
1758 return HeadlessWatchFaceImpl(this)
1759 }
1760
1761 @UiThread
1762 @RequiresApi(27)
1763 internal fun createInteractiveInstance(
1764 params: WallpaperInteractiveWatchFaceInstanceParams,
1765 _createdBy: String
1766 ): InteractiveWatchFaceImpl = TraceEvent(
1767 "EngineWrapper.createInteractiveInstance"
1768 ).use {
Alex Clarke93e09f72021-10-21 14:21:01 +01001769 mainThreadPriorityDelegate.setInteractivePriority()
1770
jnicholb25f3c02021-09-15 17:15:06 +01001771 require(!watchFaceCreatedOrPending()) {
1772 "WatchFace already exists! Created by $createdBy"
1773 }
1774 require(!mutableWatchState.isHeadless)
1775
1776 setImmutableSystemState(params.deviceConfig)
1777 setWatchUiState(params.watchUiState)
1778 initialUserStyle = params.userStyle
1779
Alex Clarke23a0cf42022-02-09 15:24:31 +00001780 mutableWatchState.watchFaceInstanceId.value = sanitizeWatchFaceId(params.instanceId)
jnicholb25f3c02021-09-15 17:15:06 +01001781 val watchState = mutableWatchState.asWatchState()
1782
1783 // Store the initial complications, this could be modified by new data before being
1784 // applied.
1785 pendingInitialComplications = params.idAndComplicationDataWireFormats
1786
Alex Clarke6c199952021-11-30 11:01:37 +00001787 if (pendingInitialComplications == null || pendingInitialComplications!!.isEmpty()) {
Alex Clarkefb720782021-11-24 17:05:14 +00001788 pendingInitialComplications = readComplicationDataCache(_context, params.instanceId)
1789 }
1790
jnicholb25f3c02021-09-15 17:15:06 +01001791 createWatchFaceInternal(
1792 watchState,
1793 getWallpaperSurfaceHolderOverride(),
1794 _createdBy
1795 )
1796
1797 val instance = InteractiveWatchFaceImpl(this, params.instanceId)
1798 InteractiveInstanceManager.addInstance(instance)
1799 interactiveInstanceId = params.instanceId
1800 return instance
1801 }
1802
1803 override fun onSurfaceRedrawNeeded(holder: SurfaceHolder) {
1804 if (TRACE_DRAW) {
1805 Trace.beginSection("onSurfaceRedrawNeeded")
1806 }
1807 // The watch face will draw at least once upon creation so it doesn't matter if it's
1808 // not been created yet.
1809 getWatchFaceImplOrNull()?.onSurfaceRedrawNeeded()
1810 if (TRACE_DRAW) {
1811 Trace.endSection()
1812 }
1813 }
1814
Dmitry Zhestilevskiy22101182022-03-17 15:11:07 +00001815 @OptIn(WatchFaceFlavorsExperimental::class)
jnicholb25f3c02021-09-15 17:15:06 +01001816 internal fun createWatchFaceInternal(
1817 watchState: WatchState,
1818 overrideSurfaceHolder: SurfaceHolder?,
1819 _createdBy: String
1820 ) {
1821 asyncWatchFaceConstructionPending = true
1822 createdBy = _createdBy
1823
1824 backgroundThreadCoroutineScope.launch {
1825 val timeBefore = System.currentTimeMillis()
1826 val currentUserStyleRepository =
1827 TraceEvent("WatchFaceService.createUserStyleSchema").use {
1828 CurrentUserStyleRepository(createUserStyleSchema())
1829 }
1830 val complicationSlotsManager =
1831 TraceEvent("WatchFaceService.createComplicationsManager").use {
1832 createComplicationSlotsManager(currentUserStyleRepository)
1833 }
1834 complicationSlotsManager.watchState = watchState
Dmitry Zhestilevskiy22101182022-03-17 15:11:07 +00001835 val userStyleFlavors =
1836 TraceEvent("WatchFaceService.createUserStyleFlavors").use {
1837 createUserStyleFlavors(currentUserStyleRepository, complicationSlotsManager)
1838 }
jnicholb25f3c02021-09-15 17:15:06 +01001839
1840 val deferredWatchFace = CompletableDeferred<WatchFace>()
1841 val initStyleAndComplicationsDone = CompletableDeferred<Unit>()
1842
1843 // WatchFaceImpl (which registers broadcast observers) needs to be constructed
1844 // on the UIThread. Part of this process can be done in parallel with
1845 // createWatchFace.
1846 uiThreadCoroutineScope.launch {
1847 createWatchFaceImpl(
1848 complicationSlotsManager,
1849 currentUserStyleRepository,
1850 deferredWatchFace,
1851 initStyleAndComplicationsDone,
1852 watchState
1853 )
1854 }
1855
1856 try {
1857 val watchFace = TraceEvent("WatchFaceService.createWatchFace").use {
1858 // Note by awaiting deferredSurfaceHolder we ensure onSurfaceChanged has
1859 // been called and we're passing the correct updated surface holder. This is
1860 // important for GL rendering.
1861 createWatchFace(
1862 overrideSurfaceHolder ?: deferredSurfaceHolder.await(),
1863 watchState,
1864 complicationSlotsManager,
1865 currentUserStyleRepository
1866 )
1867 }
Alex Clarkeb6f9cab2022-03-02 10:59:44 +00001868 watchFaceInitDetails.complete(
1869 WatchFaceInitDetails(
Alex Clarke3f121e52022-02-23 16:47:43 +00001870 watchFace,
Alex Clarkeb6f9cab2022-03-02 10:59:44 +00001871 complicationSlotsManager,
Dmitry Zhestilevskiy22101182022-03-17 15:11:07 +00001872 currentUserStyleRepository,
1873 userStyleFlavors
jnicholb25f3c02021-09-15 17:15:06 +01001874 )
1875 )
1876
Alex Clarkeb8dc6a22021-11-18 18:52:00 +00001877 watchFace.renderer.backgroundThreadInitInternal()
jnicholb25f3c02021-09-15 17:15:06 +01001878
1879 // For Gles watch faces this will trigger UIThread context creation and must be
1880 // done after initBackgroundThreadOpenGlContext.
1881 deferredWatchFace.complete(watchFace)
1882
1883 val timeAfter = System.currentTimeMillis()
1884 val timeTaken = timeAfter - timeBefore
1885 if (timeTaken > MAX_CREATE_WATCHFACE_TIME_MILLIS) {
1886 Log.e(
1887 TAG,
1888 "createUserStyleSchema, createComplicationSlotsManager and " +
1889 "createWatchFace should complete in less than " +
1890 MAX_CREATE_WATCHFACE_TIME_MILLIS + " milliseconds."
1891 )
1892 }
1893
1894 // Perform more initialization on the background thread.
1895 initStyleAndComplications(
1896 complicationSlotsManager,
1897 currentUserStyleRepository,
1898 watchFace.renderer
1899 )
1900
1901 // Now init has completed, it's OK to complete deferredWatchFaceImpl.
1902 initStyleAndComplicationsDone.complete(Unit)
1903
1904 // validateSchemaWireSize is fairly expensive so only perform it for
1905 // interactive watch faces.
1906 if (!watchState.isHeadless) {
1907 validateSchemaWireSize(currentUserStyleRepository.schema)
1908 }
Dmitry Zhestilevskiy2e6575c2022-02-22 15:02:35 +03001909 } catch (e: CancellationException) {
1910 throw e
jnicholb25f3c02021-09-15 17:15:06 +01001911 } catch (e: Exception) {
1912 Log.e(TAG, "WatchFace crashed during init", e)
Dmitry Zhestilevskiy2e6575c2022-02-22 15:02:35 +03001913 deferredValidation.completeExceptionally(e)
jnicholb25f3c02021-09-15 17:15:06 +01001914 }
Dmitry Zhestilevskiy2e6575c2022-02-22 15:02:35 +03001915
1916 deferredValidation.complete(Unit)
jnicholb25f3c02021-09-15 17:15:06 +01001917 }
1918 }
1919
1920 /**
1921 * This function contains the parts of watch face init that have to be done on the UI
1922 * thread.
1923 */
1924 @UiThread
1925 private suspend fun createWatchFaceImpl(
1926 complicationSlotsManager: ComplicationSlotsManager,
1927 currentUserStyleRepository: CurrentUserStyleRepository,
1928 deferredWatchFace: CompletableDeferred<WatchFace>,
1929 initStyleAndComplicationsDone: CompletableDeferred<Unit>,
1930 watchState: WatchState
1931 ) {
1932 pendingInitialComplications?.let {
1933 for (idAndData in it) {
1934 complicationSlotsManager.onComplicationDataUpdate(
1935 idAndData.id,
Alex Clarke87ccaea2021-11-29 14:27:54 +00001936 idAndData.complicationData.toApiComplicationData(),
1937 Instant.EPOCH // The value here doesn't matter, will be corrected when drawn
jnicholb25f3c02021-09-15 17:15:06 +01001938 )
1939 }
1940 }
1941
1942 val broadcastsObserver = BroadcastsObserver(
1943 watchState,
1944 this,
1945 deferredWatchFaceImpl,
1946 uiThreadCoroutineScope
1947 )
1948
1949 // There's no point creating BroadcastsReceiver for headless instances.
1950 val broadcastsReceiver = TraceEvent("create BroadcastsReceiver").use {
1951 if (watchState.isHeadless) {
1952 null
1953 } else {
Alex Clarkeddffd802021-10-13 16:55:05 +01001954 BroadcastsReceiver(_context, broadcastsObserver).apply {
1955 processBatteryStatus(
1956 IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { iFilter ->
1957 _context.registerReceiver(null, iFilter)
1958 }
1959 )
1960 }
jnicholb25f3c02021-09-15 17:15:06 +01001961 }
1962 }
1963
1964 val watchFace = deferredWatchFace.await()
1965 TraceEvent("WatchFaceImpl.init").use {
1966 val watchFaceImpl = WatchFaceImpl(
1967 watchFace,
1968 this@EngineWrapper,
1969 watchState,
1970 currentUserStyleRepository,
1971 complicationSlotsManager,
1972 broadcastsObserver,
1973 broadcastsReceiver
1974 )
1975
1976 // Perform UI thread render init.
Alex Clarkeac0dcfe2021-09-28 14:03:11 +01001977 if (!surfaceDestroyed) {
1978 watchFaceImpl.renderer.uiThreadInitInternal(uiThreadCoroutineScope)
1979 }
jnicholb25f3c02021-09-15 17:15:06 +01001980
1981 // Make sure no UI thread rendering (a consequence of completing
1982 // deferredWatchFaceImpl) occurs before initStyleAndComplications has
1983 // executed. NB usually we won't have to wait at all.
1984 initStyleAndComplicationsDone.await()
1985
1986 // Its possible a second getOrCreateInteractiveWatchFaceClient call came in before
1987 // the watch face for the first one had finished initializing, in that case we want
1988 // to apply the updated style. NB pendingUserStyle is accessed on the UiThread so
1989 // there shouldn't be any problems with race conditions.
1990 pendingUserStyle?.let {
1991 setUserStyleImpl(watchFaceImpl, it)
1992 pendingUserStyle = null
1993 }
1994 deferredWatchFaceImpl.complete(watchFaceImpl)
1995 asyncWatchFaceConstructionPending = false
1996 watchFaceImpl.initComplete = true
1997
1998 // For interactive instances we want to expedite the first frame to get something
1999 // rendered as soon as its possible to do so. NB in tests we may not always want
2000 // to draw this expedited first frame.
2001 if (!watchState.isHeadless && allowWatchFaceToAnimate()) {
2002 TraceEvent("WatchFace.drawFirstFrame").use {
Alex Clarkeac0dcfe2021-09-28 14:03:11 +01002003 if (!surfaceDestroyed) {
2004 watchFaceImpl.onDraw()
2005 }
jnicholb25f3c02021-09-15 17:15:06 +01002006 }
2007 }
2008 }
2009 }
2010
2011 /**
2012 * It is OK to call this from a worker thread because we carefully ensure there's no
2013 * concurrent writes to the ComplicationSlotsManager. No UI thread rendering can be done
2014 * until after this has completed.
2015 */
2016 @WorkerThread
2017 internal fun initStyleAndComplications(
2018 complicationSlotsManager: ComplicationSlotsManager,
2019 currentUserStyleRepository: CurrentUserStyleRepository,
2020 renderer: Renderer
2021 ) = TraceEvent("initStyleAndComplications").use {
2022 // If the system has a stored user style then Home/SysUI is in charge of style
2023 // persistence, otherwise we need to do our own.
2024 val storedUserStyle = getInitialUserStyle()
2025 if (storedUserStyle != null) {
2026 TraceEvent("WatchFaceImpl.init apply userStyle").use {
Alex Clarkee89ba382021-09-17 18:00:48 +01002027 currentUserStyleRepository.updateUserStyle(
jnicholb25f3c02021-09-15 17:15:06 +01002028 UserStyle(UserStyleData(storedUserStyle), currentUserStyleRepository.schema)
Alex Clarkee89ba382021-09-17 18:00:48 +01002029 )
jnicholb25f3c02021-09-15 17:15:06 +01002030 }
2031 } else {
2032 TraceEvent("WatchFaceImpl.init apply userStyle from prefs").use {
2033 // The system doesn't support preference persistence we need to do it ourselves.
2034 val preferencesFile = "watchface_prefs_${_context.javaClass.name}.txt"
2035
Alex Clarkee89ba382021-09-17 18:00:48 +01002036 currentUserStyleRepository.updateUserStyle(
2037 UserStyle(
2038 UserStyleData(readPrefs(_context, preferencesFile)),
2039 currentUserStyleRepository.schema
2040 )
jnicholb25f3c02021-09-15 17:15:06 +01002041 )
2042
2043 backgroundThreadCoroutineScope.launch {
2044 currentUserStyleRepository.userStyle.collect {
2045 writePrefs(_context, preferencesFile, it)
2046 }
2047 }
2048 }
2049 }
2050
2051 // We need to inhibit an immediate callback during initialization because members are
2052 // not fully constructed and it will fail. It's also superfluous because we're going
2053 // to render soon anyway.
2054 var initFinished = false
2055 complicationSlotsManager.init(
2056 this, renderer,
2057 object : ComplicationSlot.InvalidateListener {
2058 @SuppressWarnings("SyntheticAccessor")
2059 override fun onInvalidate() {
2060 // This could be called on any thread.
2061 uiThreadHandler.runOnHandlerWithTracing("onInvalidate") {
2062 if (initFinished) {
2063 getWatchFaceImplOrNull()?.invalidateIfNotAnimating()
2064 }
2065 }
2066 }
2067 }
2068 )
2069 initFinished = true
2070 }
2071
2072 override fun onVisibilityChanged(visible: Boolean): Unit = TraceEvent(
2073 "onVisibilityChanged"
2074 ).use {
2075 super.onVisibilityChanged(visible)
2076
2077 // In the WSL flow Home doesn't know when WallpaperService has actually launched a
2078 // watchface after requesting a change. It used [Constants.ACTION_REQUEST_STATE] as a
2079 // signal to trigger the old boot flow (sending the binder etc). This is no longer
2080 // required from android R onwards. See (b/181965946).
2081 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
2082 // We are requesting state every time the watch face changes its visibility because
2083 // wallpaper commands have a tendency to be dropped. By requesting it on every
2084 // visibility change, we ensure that we don't become a victim of some race
2085 // condition.
2086 sendBroadcast(
2087 Intent(Constants.ACTION_REQUEST_STATE).apply {
2088 putExtra(Constants.EXTRA_WATCH_FACE_VISIBLE, visible)
2089 }
2090 )
2091
2092 // We can't guarantee the binder has been set and onSurfaceChanged called before
2093 // this command.
2094 if (!watchFaceCreated()) {
2095 wslFlow.pendingVisibilityChanged = visible
2096 return
2097 }
2098 }
2099
Alex Clarke93e09f72021-10-21 14:21:01 +01002100 // During WF init the watch face is initially not visible but we want to keep UI thread
2101 // priority high. Once init has completed we only want the WF UI thread to have high
2102 // priority when visible.
2103 if (deferredWatchFaceImpl.isCompleted && !mutableWatchState.isHeadless) {
2104 if (visible) {
2105 mainThreadPriorityDelegate.setInteractivePriority()
2106 } else {
2107 mainThreadPriorityDelegate.setNormalPriority()
2108 }
2109 }
2110
Alex Clarke90add702022-03-25 15:18:16 +00002111 mutableWatchState.isVisible.value = visible || forceIsVisibleForTesting()
jnicholb25f3c02021-09-15 17:15:06 +01002112 wslFlow.pendingVisibilityChanged = null
Alex Clarkea6092842021-11-15 18:14:49 +00002113
2114 getWatchFaceImplOrNull()?.onVisibility(visible)
jnicholb25f3c02021-09-15 17:15:06 +01002115 }
2116
2117 override fun invalidate() {
2118 if (!allowWatchfaceToAnimate) {
2119 return
2120 }
2121 if (!frameCallbackPending) {
2122 if (LOG_VERBOSE) {
2123 Log.v(TAG, "invalidate: requesting draw")
2124 }
2125 frameCallbackPending = true
2126 if (!this::choreographer.isInitialized) {
Alex Clarkeda008e52021-10-05 19:59:35 +01002127 choreographer = getChoreographer()
jnicholb25f3c02021-09-15 17:15:06 +01002128 }
2129 choreographer.postFrameCallback(frameCallback)
2130 } else {
2131 if (LOG_VERBOSE) {
2132 Log.v(TAG, "invalidate: draw already requested")
2133 }
2134 }
2135 }
2136
Alex Clarke223171e2021-11-04 15:58:58 +00002137 override fun getComplicationDeniedIntent() =
2138 getWatchFaceImplOrNull()?.complicationDeniedDialogIntent
2139
2140 override fun getComplicationRationaleIntent() =
2141 getWatchFaceImplOrNull()?.complicationRationaleDialogIntent
2142
Alex Clarkefb720782021-11-24 17:05:14 +00002143 @UiThread
2144 override fun scheduleWriteComplicationDataCache() {
2145 if (mutableWatchState.isHeadless || pendingComplicationDataCacheWrite) {
2146 return
2147 }
2148 // During start up we'll typically get half a dozen or so updates. Here we de-duplicate.
2149 pendingComplicationDataCacheWrite = true
2150 getUiThreadHandler().postDelayed(
2151 {
2152 pendingComplicationDataCacheWrite = false
Alex Clarke23a0cf42022-02-09 15:24:31 +00002153 getWatchFaceImplOrNull()?.let { watchFaceImpl ->
2154 writeComplicationDataCache(
2155 _context,
2156 watchFaceImpl.complicationSlotsManager,
2157 mutableWatchState.watchFaceInstanceId.value
2158 )
Alex Clarkefb720782021-11-24 17:05:14 +00002159 }
2160 },
2161 1000
2162 )
2163 }
2164
jnicholb25f3c02021-09-15 17:15:06 +01002165 internal fun draw() {
2166 try {
2167 if (TRACE_DRAW) {
2168 Trace.beginSection("onDraw")
2169 }
2170 if (LOG_VERBOSE) {
Alex Clarkeda008e52021-10-05 19:59:35 +01002171 Log.v(TAG, "drawing frame")
jnicholb25f3c02021-09-15 17:15:06 +01002172 }
2173
2174 val watchFaceImpl: WatchFaceImpl? = getWatchFaceImplOrNull()
Alex Clarkec199fad2022-01-19 15:54:34 +00002175 watchFaceImpl?.onDraw()
jnicholb25f3c02021-09-15 17:15:06 +01002176 } finally {
2177 if (TRACE_DRAW) {
2178 Trace.endSection()
2179 }
2180 }
2181 }
2182
2183 internal fun validateSchemaWireSize(schema: UserStyleSchema) = TraceEvent(
2184 "WatchFaceService.validateSchemaWireSize"
2185 ).use {
2186 var estimatedBytes = 0
2187 for (styleSetting in schema.userStyleSettings) {
2188 estimatedBytes += styleSetting.estimateWireSizeInBytesAndValidateIconDimensions(
2189 _context,
2190 MAX_REASONABLE_SCHEMA_ICON_WIDTH,
2191 MAX_REASONABLE_SCHEMA_ICON_HEIGHT,
2192 )
2193 }
2194 require(estimatedBytes < MAX_REASONABLE_SCHEMA_WIRE_SIZE_BYTES) {
2195 "The estimated wire size of the supplied UserStyleSchemas for watch face " +
2196 "$packageName is too big at $estimatedBytes bytes. UserStyleSchemas get sent " +
2197 "to the companion over bluetooth and should be as small as possible for this " +
2198 "to be performant."
2199 }
2200 }
2201
jnicholb25f3c02021-09-15 17:15:06 +01002202 internal fun watchFaceCreated() = deferredWatchFaceImpl.isCompleted
2203
2204 internal fun watchFaceCreatedOrPending() =
2205 watchFaceCreated() || asyncWatchFaceConstructionPending
2206
2207 override fun setDefaultComplicationDataSourceWithFallbacks(
2208 complicationSlotId: Int,
2209 dataSources: List<ComponentName>?,
2210 @DataSourceId fallbackSystemProvider: Int,
2211 type: Int
2212 ) {
2213 wslFlow.setDefaultComplicationProviderWithFallbacks(
2214 complicationSlotId,
2215 dataSources,
2216 fallbackSystemProvider,
2217 type
2218 )
2219 }
2220
2221 override fun setActiveComplicationSlots(complicationSlotIds: IntArray): Unit = TraceEvent(
2222 "WatchFaceService.setActiveComplications"
2223 ).use {
2224 wslFlow.setActiveComplications(complicationSlotIds)
2225 }
2226
2227 @UiThread
2228 override fun updateContentDescriptionLabels() {
2229 val labels = mutableListOf<Pair<Int, ContentDescriptionLabel>>()
2230
2231 uiThreadCoroutineScope.launch {
2232 TraceEvent(
2233 "WatchFaceService.updateContentDescriptionLabels A"
2234 ).close()
Alex Clarke3f121e52022-02-23 16:47:43 +00002235 val watchFaceAndComplicationManager =
Alex Clarkeb6f9cab2022-03-02 10:59:44 +00002236 watchFaceInitDetails.await()
jnicholb25f3c02021-09-15 17:15:06 +01002237
2238 TraceEvent(
2239 "WatchFaceService.updateContentDescriptionLabels"
2240 ).use {
2241 // The side effects of this need to be applied before deferredWatchFaceImpl is
2242 // completed.
Alex Clarke3f121e52022-02-23 16:47:43 +00002243 val renderer = watchFaceAndComplicationManager.watchFace.renderer
jnicholb25f3c02021-09-15 17:15:06 +01002244 val complicationSlotsManager =
Alex Clarke3f121e52022-02-23 16:47:43 +00002245 watchFaceAndComplicationManager.complicationSlotsManager
jnicholb25f3c02021-09-15 17:15:06 +01002246
2247 // Add a ContentDescriptionLabel for the main clock element.
2248 labels.add(
2249 Pair(
2250 WATCH_ELEMENT_ACCESSIBILITY_TRAVERSAL_INDEX,
2251 ContentDescriptionLabel(
2252 renderer.getMainClockElementBounds(),
2253 AccessibilityUtils.makeTimeAsComplicationText(_context)
2254 )
2255 )
2256 )
2257
Alex Clarkee787b482021-11-03 11:58:13 +00002258 // Add a ContentDescriptionLabel for each enabled complication that isn't empty
2259 // or no data.
jnicholb25f3c02021-09-15 17:15:06 +01002260 val screenBounds = renderer.screenBounds
2261 for ((_, complication) in complicationSlotsManager.complicationSlots) {
Alex Clarkee787b482021-11-03 11:58:13 +00002262 if (complication.enabled &&
2263 when (complication.complicationData.value.type) {
2264 ComplicationType.EMPTY -> false
2265 ComplicationType.NO_DATA -> false
2266 else -> true
2267 }
2268 ) {
jnicholb25f3c02021-09-15 17:15:06 +01002269 if (complication.boundsType == ComplicationSlotBoundsType.BACKGROUND) {
2270 ComplicationSlotBoundsType.BACKGROUND
2271 } else {
2272 labels.add(
2273 Pair(
2274 complication.accessibilityTraversalIndex,
2275 ContentDescriptionLabel(
2276 _context,
2277 complication.computeBounds(screenBounds),
2278 complication.complicationData.value
2279 .asWireComplicationData()
2280 )
2281 )
2282 )
2283 }
2284 }
2285 }
2286
2287 // Add any additional labels defined by the watch face.
2288 for (labelPair in renderer.additionalContentDescriptionLabels) {
2289 labels.add(
2290 Pair(
2291 labelPair.first,
2292 ContentDescriptionLabel(
2293 labelPair.second.bounds,
2294 labelPair.second.text.toWireComplicationText()
2295 ).apply {
2296 tapAction = labelPair.second.tapAction
2297 }
2298 )
2299 )
2300 }
2301
2302 contentDescriptionLabels =
2303 labels.sortedBy { it.first }.map { it.second }.toTypedArray()
2304
2305 // From Android R Let SysUI know the labels have changed if the accessibility
2306 // manager is enabled.
2307 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
2308 getAccessibilityManager().isEnabled
2309 ) {
2310 // TODO(alexclarke): This should require a permission. See http://b/184717802
2311 _context.sendBroadcast(
2312 Intent(Constants.ACTION_WATCH_FACE_REFRESH_A11Y_LABELS)
2313 )
2314 }
2315 }
2316 }
2317 }
2318
2319 private fun getAccessibilityManager() =
2320 _context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
2321
2322 @UiThread
2323 internal fun dump(writer: IndentingPrintWriter) {
2324 require(uiThreadHandler.looper.isCurrentThread) {
2325 "dump must be called from the UIThread"
2326 }
2327 writer.println("WatchFaceEngine:")
2328 writer.increaseIndent()
2329 when {
2330 wslFlow.iWatchFaceServiceInitialized() -> writer.println("WSL style init flow")
2331 this.watchFaceCreatedOrPending() -> writer.println("Androidx style init flow")
2332 expectPreRInitFlow() -> writer.println("Expecting WSL style init")
2333 else -> writer.println("Expecting androidx style style init")
2334 }
2335
2336 if (wslFlow.iWatchFaceServiceInitialized()) {
2337 writer.println(
2338 "iWatchFaceService.asBinder().isBinderAlive=" +
2339 "${wslFlow.iWatchFaceService.asBinder().isBinderAlive}"
2340 )
2341 if (wslFlow.iWatchFaceService.asBinder().isBinderAlive) {
2342 writer.println(
2343 "iWatchFaceService.apiVersion=" +
2344 "${wslFlow.iWatchFaceService.apiVersion}"
2345 )
2346 }
2347 }
2348 writer.println("createdBy=$createdBy")
2349 writer.println("watchFaceInitStarted=$wslFlow.watchFaceInitStarted")
2350 writer.println("asyncWatchFaceConstructionPending=$asyncWatchFaceConstructionPending")
2351
2352 if (this::interactiveInstanceId.isInitialized) {
2353 writer.println("interactiveInstanceId=$interactiveInstanceId")
2354 }
2355
2356 writer.println("frameCallbackPending=$frameCallbackPending")
2357 writer.println("destroyed=$destroyed")
2358
2359 if (!destroyed) {
2360 getWatchFaceImplOrNull()?.dump(writer)
2361 }
2362 writer.decreaseIndent()
2363 }
2364 }
2365
2366 @UiThread
2367 override fun dump(fd: FileDescriptor, writer: PrintWriter, args: Array<String>) {
2368 super.dump(fd, writer, args)
2369 val indentingPrintWriter = IndentingPrintWriter(writer)
2370 indentingPrintWriter.println("AndroidX WatchFaceService $packageName")
2371 InteractiveInstanceManager.dump(indentingPrintWriter)
2372 EditorService.globalEditorService.dump(indentingPrintWriter)
2373 HeadlessWatchFaceImpl.dump(indentingPrintWriter)
2374 indentingPrintWriter.flush()
2375 }
2376
2377 private object ChinHeightApi25 {
2378 @Suppress("DEPRECATION")
2379 @Px
2380 fun extractFromWindowInsets(insets: WindowInsets?) =
2381 insets?.systemWindowInsetBottom ?: 0
2382 }
2383
2384 @RequiresApi(30)
2385 private object ChinHeightApi30 {
2386 @Px
2387 fun extractFromWindowInsets(insets: WindowInsets?) =
2388 insets?.getInsets(WindowInsets.Type.systemBars())?.bottom ?: 0
2389 }
Alex Clarke048fdd22022-02-16 10:19:37 +00002390
2391 @RequiresApi(27)
2392 private object NotifyColorsChangedHelper {
2393 fun notifyColorsChanged(engine: Engine) {
2394 engine.notifyColorsChanged()
2395 }
2396 }
jnicholb25f3c02021-09-15 17:15:06 +01002397}
2398
2399/**
jnicholb25f3c02021-09-15 17:15:06 +01002400 * Runs the supplied task on the handler thread. If we're not on the handler thread a task is
2401 * posted.
2402 *
2403 * @param traceEventName The name of the trace event to emit.
2404 * @param task The task to post on the handler.
2405 */
2406internal fun Handler.runOnHandlerWithTracing(
2407 traceEventName: String,
2408 task: () -> Unit
2409) = TraceEvent(traceEventName).use {
2410 if (looper == Looper.myLooper()) {
2411 task.invoke()
2412 } else {
2413 post {
2414 TraceEvent("$traceEventName invokeTask").use { task.invoke() }
2415 }
2416 }
2417}
2418
2419/**
2420 * Runs a task in the [CoroutineScope] and blocks until it has completed.
2421 *
2422 * @param traceEventName The name of the trace event to emit.
2423 * @param task The task to run on the [CoroutineScope].
2424 */
2425internal fun <R> CoroutineScope.runBlockingWithTracing(
2426 traceEventName: String,
2427 task: suspend () -> R
2428): R = TraceEvent(traceEventName).use {
Alex Clarke12ca62e2022-04-25 22:33:22 +01002429 try {
2430 return runBlocking {
2431 withContext(coroutineContext) {
2432 task()
2433 }
jnicholb25f3c02021-09-15 17:15:06 +01002434 }
Alex Clarke12ca62e2022-04-25 22:33:22 +01002435 } catch (e: Exception) {
2436 Log.e("CoroutineScope", "Exception in traceEventName", e)
2437 throw e
jnicholb25f3c02021-09-15 17:15:06 +01002438 }
jnicholb25f3c02021-09-15 17:15:06 +01002439}
Alex Clarke23a0cf42022-02-09 15:24:31 +00002440
2441/**
2442 * If the instance ID for [MutableWatchState.watchFaceInstanceId] begin with this prefix, then the
2443 * system sends consistent IDs for interactive, headless and editor sessions.
2444 *
2445 * @hide
2446 */
2447@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
2448const val SYSTEM_SUPPORTS_CONSISTENT_IDS_PREFIX = "wfId-"
2449
2450/**
2451 * Instance ID to use when either there's no system id or it doesn't start with
2452 * [SYSTEM_SUPPORTS_CONSISTENT_IDS_PREFIX].
2453 * @hide
2454 */
2455@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
2456const val DEFAULT_INSTANCE_ID = "defaultInstance"
2457
2458/**
2459 * This is needed to make the instance id consistent between Interactive, Headless and
2460 * EditorSession for old versions of the system.
2461 *
2462 * @hide
2463 */
2464@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
2465fun sanitizeWatchFaceId(instanceId: String?) =
2466 if (instanceId == null || !instanceId.startsWith(SYSTEM_SUPPORTS_CONSISTENT_IDS_PREFIX)
2467 ) {
2468 DEFAULT_INSTANCE_ID
2469 } else {
2470 instanceId
2471 }