As unpopular as it may sound, I think developers should be writing Wear OS apps and watch faces. It seems to be fairly common to think that Wear OS is not doing to well and that Samsung and Apple watch are dominating, however I think that is a misconception. Not only are the Wear OS team constantly updating and releasing. They’re even improving the codelabs, which are really fantastic, even if the code base was last updated 4 years ago. The fact that there is just as much developer interest in Wear OS as there is in Apple Watch should be enough to evaporate any reluctance.

wear-os compared to apple watch

Hurdles

That is not to say there aren’t issues. The code that is contained in the sample app is not easy to navigate. Starting with a file that is over 500 lines and contains many different elements that need to be learned is not simple. Also, the example makes use of deprecated API’s, namely the default handler().

On top of that Wear OS faces are an implementation of Android Wallpapers, which becomes immediately apparent when you fire up an emulator and see it ticking away: After about 15 - 20 seconds it just stops. And as a developer you’re left wondering if the emulator is frozen, or if the watch face has just consumed so much CPU that it’s causing ANR’s.

The other problems occur are when developers want to test the different screen states in which a watch face may find itself: low bit ambient mode, muted mode, ambient mode, etc. are all unavailable options on the emulator. Developers need to get a device and somehow navigate the states in order to test them.

The goal was to simplify the code, pull out the elementary structures and make it easier to create and debug watch faces. Along the way I learned a little more about HILT, Coroutines and MotionLayout.

In summary, the problems experienced with the Wear OS emulator were:

  1. keeping the watch face alive without running adb shell input keyevent KEYCODE_WAKEUP
  2. setting the time without running adb shell su root date $(date +%m%d%H%M%Y.%S)
  3. control the progress of time
  4. seamlessly adjust the states of the watch face
  5. test the scalability without modifying screen density of the emulator
  6. Has anyone seen the Oppo Watch - why is there no rectangular emulator? Rectangular support seems to be specified in the manifest

Architecture

The first task was to extract an abstract class, WatchFaceRenderable.kt, that uses a canvas on which to draw a watch face element. The ExampleWatchRenderer.kt is an implementation of the WatchFaceRenderable.kt class which is an external dependency that can be injected into any application which provides a canvas. ExampleWatchRenderer.kt still bears many of the original features of the Google sample app.

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)
}

All the display settings concerned with Wear OS elements have been concentrated into a single data class. Hopefully this structure or something similar will be adopted by Wear OS in the future, but has not been yet as Ambient Mode has been recently added to the API’s and is not a part of the Wallpaper API.

data class WatchScreenSettings(
    var isAmbientMode: Boolean = false,
    var isMuteMode: Boolean = false,
    var isLowBitAmbient: Boolean = false,
    var isBurnInProtection: Boolean = false,
    var isTwentyFourHour: Boolean = false
)

Harness

The code base is separated into three components:

  1. app: The main watch face - deploy this to a Wear OS device or emulator
  2. harness: An Android app capable of rendering a simulated view of the Wear OS face, without any Wear OS libraries
  3. watchfacerenderer: The provider module used to inject the example view to both the app and harness

HILT is used to inject the ExampleWatchRenderer.kt into both an Android Wear OS app and a regular Android App called the “harness.” The harness’ job is to create a view, WatchFaceView.kt, that is capable of rendering the watch face on a standard Android canvas. This allows developers to see their watch faces running inside a normal Android app rather than running on a Wear OS emulator. The main purpose is to host the view on a standard android application that allows developers to control the view without waiting for Wear OS to fire specific events.

alt text

By doing this developers are now enabled to

  1. animate the watch face
  2. dynamically set the time
  3. control the display settings
  4. change the size of the face

The simple view that renders the watch face on a plain Android canvas can be found in the WatchFaceView.kt class. The output from the harness allows the user to control the rendering of the watch face and toggle the settings.

alt text

ExampleWatchRenderer.kt is adapted from the sample code that Google provides when creating an empty Wear OS watch face project. The only changes that have been made have been to:

  1. Separate the code for rendering from the WatchFaceEngine that is an inseparable subclass of WatchFaceService.kt, allowing developers to focus on the drawing elements separately from the watch service elements
  2. (Bug fix) Properly deal with scaling - when rendered on larger screens
  3. (Bug fix) Take the milliseconds out of the calculation for where the seconds hand should appear

The gist is that creating a watch face should be as simple as drawing on a view, which the attached code proves is possible. Part 2 will cover the basic elements that concern an actual watch face, and after that complications will be discussed.