Configure your Wear OS app for Watch Face Push

Watch Face Push lets your app manage watch faces on a Wear OS device. This includes adding, updating, and removing watch faces, as well as setting the active watch face. Configure your Wear OS app to use the Watch Face Push API.

Setup

Include the necessary dependencies:

implementation("androidx.wear.watchface:watchface-push:1.3.0-alpha07")

Add the following to your AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- Required to use the Watch Face Push API.  -->
    <uses-permission android:name="com.google.wear.permission.PUSH_WATCH_FACES" />

    <!-- Required to be able to call the setWatchFaceAsActive() method. -->
    <uses-permission android:name="com.google.wear.permission.SET_PUSHED_WATCH_FACE_AS_ACTIVE" />

</manifest>

Get a reference to the manager instance

Obtain an instance of WatchFacePushManager:

val manager = WatchFacePushManager(context)

WatchFacePushManager provides access to all the methods for interacting with Watch Face Push.

Work with slots

A key concept when working with Watch Face Push is slots. Slots are a way of addressing installed watch faces that belong to your application. The system sets a maximum number of slots that a marketplace can have; with Wear OS 6, the limit is 1.

When updating or removing a watch face, slotId is used to identify the watch face to perform the operation on.

List watch faces

To list the set of installed watch faces, use listWatchFaces():

val response = watchFacePushManager.listWatchFaces()
val installedList = response.installedWatchFaceDetails
val remainingSlots = response.remainingSlots

This lets you determine whether the slot is available, or whether adding another watch face requires replacing the existing one. The list also gives you details about the installed watch face. For example, to check whether a given watch face package is installed:

suspend fun isInstalled(packageName: String) = watchFacePush.listWatchFaces()
    .installedWatchFaceDetails.any { it.packageName == packageName }

Add a watch face

If there are slots available, as determined by the listWatchFaces response, then the addWatchFace() method should be used:

try {
    // Supply the validation token along with the watch face package data itself.
    val slot = watchFacePushManager.addWatchFace(parcelFileDescriptor, token)
    Log.i(TAG, "${slot.packageName} (${slot.versionCode}) added in slot ${slot.slotId}")
} catch (e: AddWatchFaceException) {
    // Something went wrong adding the watch face.
}

Update a watch face

Updating a watch face lets you replace the contents of a given slot with a new package. This could either be upgrading the same watch face to a newer version or replacing the watch face entirely with another.

// Replacing the com.example.watchfacepush.green watch face with
// com.example.watchfacepush.red.
val slotId = watchFacePushManager.listWatchFaces().installedWatchFaceDetails.
    firstOrNull { it.packageName == "com.example.watchfacepush.green" }?.slotId

try {
    watchFacePushManager.updateWatchFace(slotId, redParcelFileDesc, redValidationToken)
} catch (e: UpdateWatchFaceException) {
    // Something went wrong updating the watch face.
}

Remove a watch face

To remove a watch face:

// Remove the com.example.watchfacepush.green watch face.
val slotId = watchFacePushManager.listWatchFaces().installedWatchFaceDetails.
    firstOrNull { it.packageName == "com.example.watchfacepush.green" }?.slotId

try {
    watchFacePushManager.removeWatchFace(slotId)
} catch (e: RemoveWatchFaceException) {
    // Something went wrong removing the watch face.
}

This will ensure that your watch face can always be found in the system watch face picker, could feature your logo prominently, and could even feature a button to launch your Marketplace app on the phone.

Check if your watch face is active

Determining whether your marketplace has the active watch face set is important in ensuring that the user has a smooth experience: If the marketplace already has the active watch face set, then if the user wishes to choose another watch face, they only need to replace the current one through the marketplace app for this to take effect. However, if the marketplace does not have the active watch face set, the phone app must offer the user more guidance. See the section on the phone app for more details on how to handle this user experience.

To determine whether the marketplace has the active watch face set:

Supply a default watch face

Watch Face Push offers the ability to install a default watch face when your marketplace app is installed. This doesn't, by itself, set that default watch face as active (see setting the active watch face), but will ensure that your watch face is available in the system watch face picker.

To use this feature:

  1. In your Wear OS app build, include the default watch face in the path: assets/default_watchface.apk
  2. Add the following entry to your AndroidManifest.xml

    <application ...>
    <meta-data
        android:name="com.google.android.wearable.marketplace.DEFAULT_WATCHFACE_VALIDATION_TOKEN"
        android:value="@string/default_wf_token" />
    

Set the active watch face

The Watch Face Push provides the means for the marketplace app to set the active watch face.

This means specifically that the app can set the active watch face to one belonging to the marketplace in the case where the current active watch face does not belong to the marketplace. Note that in the case where the marketplace already has the active watch face, changing this to another watch face is done through a call to updateWatchFace to replace the contents of the watch face slot with another watch face.

Setting the active watch face is a two-stage process:

  1. Acquire the Android Permission required for setting the active watch face.
  2. Call the setWatchFaceAsActive method.

Acquire permissions to set the active watch face

The required permission is SET_PUSHED_WATCH_FACE_AS_ACTIVE, which must be added to your manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    ...
    <uses-permission android:name="com.google.wear.permission.SET_PUSHED_WATCH_FACE_AS_ACTIVE" />
</manifest>

As this is a runtime permission, your app must request this permission from the user when the app runs (consider the Accompanist library to help with this).

Set the watch face as active

Once the permission has been granted, call setWatchFaceAsActive on the slot ID of the watch face that should be active:

watchFacePushManager.setWatchFaceAsActive(slotId)

Once this means has been used, your phone app should instead offer guidance on how to manually set the active watch face.

Read additional metadata from your watch face APK

The WatchFaceSlot object also provides the means to obtain additional information you can declare on your watch face.

This can be useful particularly in scenarios where you have minor variants of the same watch face. For example, you could have a watch face defined:

  • Package name: com.myapp.watchfacepush.mywatchface
  • Package version: 1.0.0

But this watch face might come as four different APKs, where all are almost exactly the same, but with different default colors: red, yellow, green and blue, set in a ColorConfiguration in the Watch Face Format XML.

This slight variation is then reflected in each of four APKs:

<!-- For watch face com.myapp.watchfacepush.mywatchface -->
<property
        android:name="default_color"
        android:value="red" />

Using a custom property allows your app to determine which of these variants is installed:

watchFaceDetails
    .getMetaDataValues("com.myapp.watchfacepush.mywatchface.default_color")
    .invoke()

Considerations

Important considerations when implementing Watch Face Push in your app include focusing on power consumption, caching, updating bundled watch faces, and providing a representative default watch face.

Power

A key consideration for any app that runs on Wear OS is power consumption. For the Wear OS component of your marketplace app:

  1. Your app should run as little and infrequently as possible (unless being directly interacted with by the user). This includes:
    • Minimizing waking the app up from the phone app
    • Minimizing the running of WorkManager jobs
  2. Schedule any analytics reporting for when the watch is charging:
    1. If you want to report usage statistics from the Wear OS app or any other metrics, use WorkManager with the requiresCharging constraint.
  3. Schedule updates for when the watch is charging and utilize WiFi:
    1. You may want to check the versions of the installed watch faces and automatically update them. Again, use the requiresCharging constraint and the UNMETERED network type for requiresNetworkType.
    2. When on-charge, the device is likely to have access to Wi-Fi. Request Wi-Fi to quickly download the updated APKs, and release the network when done.
    3. This same guidance applies for where the marketplace may offer a watch face of the day; pre-download this while the watch is charging.
  4. Do not schedule jobs to check the active watch face:
    1. Periodically checking whether your marketplace still has the active watch face and which watch face it is places a drain on the battery. Avoid this approach.
  5. Do not use notifications on the watch:
    1. If your app uses notifications, focus these on the phone, where the user action opens the phone app to continue the journey. Ensure these don't bridge across to the watch app using setLocalOnly.

Caching

In the canonical marketplace example, watch faces are transferred from the phone to the watch. This connection is typically a Bluetooth connection, which can be quite slow.

To both provide a better user experience, and conserve retransmission power, consider implementing a small cache in the Wear OS device to store a handful of APKs.

In the case where the user tries another watch face but then decides to revert to their previously chosen watch face, this action is then nearly instantaneous.

Similarly, this can be used for precaching for watch face of the day or similar schemes where watch faces are downloaded while the Wear OS device is charging.

Update bundled watch faces

Your app may include a default watch face asset as described previously. It is important to recognize that, while this watch face is installed to the system when your marketplace app is installed, the watch face isn't updated should a newer version be bundled with any update to your marketplace app.

To handle this situation, your marketplace app should listen for the MY_PACKAGE_REPLACED broadcast action and check for the need to update any bundled watch face from package assets.

Representative default watch face

A default watch face is a great way to help your users discover and use your marketplace: The watch face is installed when your marketplace is, so users can find it in the watch face gallery.

Some considerations when working with default watch faces:

  • Don't use removeWatchFace if the user chooses to uninstall a watch face from your marketplace app. Instead, in this case, revert the watch face back to the default watch face using updateWatchFace. This helps users locate your watch face and set it from the gallery.
  • Make the default watch face simple and instantly recognizable through your logo and theming. This helps users find it in the watch face gallery.
  • Add a button to the default watch face to open the phone app. This can be achieved in two stages:

    1. Add a Launch element to the watch face to launch an intent using the Wear OS app, for example:

      <Launch target="com.myapp/com.myapp.LaunchOnPhoneActivity" />

    2. In LaunchOnPhoneActivity, launch the phone app using RemoteActivityHelper.