The diagram below is the result of several hours of considerate pulling apart and re-thinking several different elements regarding Android Wallpaper Wear OS Watch Face Architecture. It halves a 500 line code file into several smaller classes, and hopefully the following discussion will clarify the reasoning of the component separation and provide a better understanding of the Wear OS watch face ecosystem.

The sample code can be found here.

Part 1 focused on the WatchFaceRenderable.kt and WatchFaceRenderer.kt classes, which reduces the complexity of drawing a watch face to simple canvas drawing. This mechanism allows implementations of WatchFaceRenderer.kt to be injected into watch face and standard Android applications, for the purpose of separating concerns, debugging and testing.

The above classes are all that need to be understood in order to get an initial Wear OS watch face rendering on a canvas, with ExampleWatchRenderer.kt as a general guide that also demonstrates how to incorporate bitmaps as background images. The injection of the ExampleWatchRenderer.kt is defined in the WatchFaceModule.kt in the watchfacerenderer module.

Service and Engine

At the core of every Watch Face is the WatchFaceService.kt, which has an inner class of type WatchFaceEngine - this engine does most, if not all, the heavy lifting required for the watch face. It responds to the lifecycle of the watch face (handled by the WatchFaceService.kt), much like an activity. It gets notified if the watch face’s state changes. An interesting discussion to be had is whether inner classes violate SOLID principles, namely that of single responsibility. This discussion is left for the comments and potentially a future article.

The service documentation demonstrates the simple relationship between a service and it’s Engine - the CanvasWatchFaceService is a foreground service that will fire the necessary events in the Engine which in turn can be processed by the watch face.

There are a few methods of which are worth taking note:

Interactive Mode Handler

While the Engine inner class has a onTimeTick() method, it is not sufficient for a watch face in interactive mode. According to the documentation:

In ambient mode, the system calls the Engine.onTimeTick() method every minute. It is usually sufficient to update your watch face once per minute in this mode. To update your watch face while in interactive mode, you must provide a custom timer as described in Initialize the custom timer.

The sample code has an added Handler that is used to “Handle updating the time periodically in interactive mode”. The control of the handler is managed every time onAmbientModeChanged() and onVisibilityChanged() are invoked. The primary function is to ensure that every second, on the second, invalidate is invoked. These methods are mutually recursive with a check every loop.

 /**
  * https://developer.android.com/reference/kotlin/android/os/Handler
  * There are two main uses for a Handler:
  *     (1) to schedule messages and runnables to be executed at
  *         some point in the future; and
  *     (2) to enqueue an action to be performed on a different
  *         thread than your own.
  */
 class EngineHandler<T : TimeUpdateHandler>(reference: T)
     : Handler(Looper.getMainLooper()) {
     private val mWeakReference: WeakReference<T> = WeakReference(reference)

     companion object {
         /**
          * Message id for updating the time periodically in interactive mode.
          */
         const val MSG_UPDATE_TIME = 0
     }

     override fun handleMessage(msg: Message) {
         mWeakReference.get()?.let {
             when (msg.what) {
                 MSG_UPDATE_TIME -> it.handleUpdateTimeMessage()
             }
         }
     }
 }
 ...
 // [WatchFaceService.WatchFaceEngine]
 /**
 * Handle updating the time periodically in interactive mode.
 * INTERACTIVE_UPDATE_RATE_MS = 1000
 */
 override fun update() {
     invalidate()
     if (shouldTimerBeRunning()) {
         val timeMs = System.currentTimeMillis()
         val delayMs = INTERACTIVE_UPDATE_RATE_MS - timeMs % INTERACTIVE_UPDATE_RATE_MS
         updateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs)
     }
}

WatchFaceEngineHandler

In order to separate the WatchFaceEngine (event handler) from the WatchFaceRenderer.kt (rendering handler), the WatchFaceEngineHandler.kt interface is used to surface and simplify the important events from the WatchFaceEngine to the WatchFaceService.kt class, allowing implementations to be agnostic of the WatchFaceEngine lifecycle.

interface WatchFaceEngineHandler {
    val inAmbientMode: Boolean

    fun engineCreated()
    fun updateProperties(lowBitAmbientStatus: Boolean, isBurnInProtectionMode: Boolean)
    fun updateAmbientMode(inAmbientMode: Boolean)
    fun updateMuteMode(inMuteMode: Boolean)
    fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int)
    fun render(canvas: Canvas, bounds: Rect?, time: Long)
    fun updateComplications(watchFaceComplicationId: Int, data: ComplicationData?)
    fun setTimeZone(timeZone: TimeZone)
}

The WatchFaceService.kt class is able communicate events to the renderable components, which are both watch faces and complications. This is why WatchFaceRenderer.kt which implements WatchFaceRenderable rather than being one class.

A small note on complications

The discussion has naturally tended towards the next logical step in the journey to complete an Android Wear OS watch face: complications. This section aims to be a brief teaser to the concept, which will be covered in more detail in part 3.

abstract class WatchFaceRenderable {
    var currentTime: Calendar
    var invalidate: (() -> Unit)?
    var screenSettings: WatchScreenSettings
    fun setTimeZone(timeZone: TimeZone) { ... }

    abstract fun render(canvas: Canvas, time: Long)
    abstract fun initialise()
    abstract fun updateStyle()
    abstract fun surfaceChanged(width: Int, height: Int)
}

As can be seen from the WatchFaceRenderable.kt abstract class, all these methods relate to the rendering of the watch face in addition to any other visual components, such as complications. While these elements will be the particular focus of a subsequent article, complications are defined as follows:

A complication is any feature in a watch face that is displayed in addition to time. For example, a battery indicator is a complication. The Complications API is for both watch faces and data provider apps.

A watch face complication is a traditional horological term for an element displayed on the face that goes beyond of the concept of hours, minutes and seconds. These are the smaller screen elements that display something else that may interest the user, such as steps, light bulb status or calendar events.