Adding Complications to your Wear OS Watch Face

1. Adding Complications to your watch face

This codelab demonstrates how to add complications to an existing watch face.

Concepts and setup

You'll learn to quickly add multiple complications to an existing watch face. At the end of the codelab, you can expect to know how to extend your own Wear OS watch face with complications.


To start, let's learn a little about complications. A complication is a feature of a watch face beyond hours and minutes. The unread message count and steps indicator are examples of complications in the image below.


The Complications API is for both watch faces and data provider apps. Let's look at the players:

  • Complication data providers supply the data, e.g., battery-level, weather, step-count, etc.
  • Watch face developers can display that data within a complication on their watch face
  • The user selects which data providers they want to power their complications

Screen Shot 2016-05-17 at 5.14.50 PM.png

In this codelab, we cover adding complications to a pre-existing watch face. If you are also interested in exposing your app's data to complications, check out our other codelab, "Exposing data to watch face complications on Wear OS," after you are finished with this codelab.

Let's get started!

Clone the starter project repo

To get started as quickly as possible, we have prepared a starter project for you to build on. It contains some basic code and application settings necessary for the codelab.

If you have Git installed, you can simply run the command below. (You can check if Git is installed by typing git --version in the terminal / command line and verify it executes correctly.)

 git clone

If you do not have Git, you can download the project as a zip file:

Download Zip

Import the project

Start Android Studio, and select "Open an existing Android Studio project" from the Welcome screen. Open the project directory and double-click on the build.gradle file in the Complications directory:


Click OK on "Import Project from Gradle" screen without making any changes. (You may see a screenshot like the one below.)


After the project has loaded, you may see an alert like the one below. You can click "Ignore" or the "X" in the upper right. (You won't be pushing any changes back to the Git repo.)

Screen Shot 2016-05-17 at 10.17.48 AM.png

In the upper-left corner of the project window, you should see something like the screenshot below if you are in the Android view. (If you are in the Project view, you will need to expand the Complications project to see the same thing.)


There are six folder icons. Each is a "module". Please note that Android Studio might take several seconds to compile the project in the background for the first time. During this time you will see a spinner in the status bar at the bottom of Android Studio:


We recommend that you wait until compilation has finished before making code changes. This allows Android Studio to pull in all the necessary components. In addition, if you get a prompt saying, "Reload for language changes to take effect?" or something similar, select "Yes".

Understand the starter project

You're set up and ready to start adding complications to a watch face. We'll start by using the 1-base module, which is the starting point for everything we'll build upon. You will add code from each step to 1-base.

Each of the following modules can be used as reference points to check your work, or for reference if you encounter any issues. The number in front of the module name corresponds with the codelab step.

Overview of key components

  • ComplicationWatchFaceService - The watch face that we will extend to support two complications. The file is located in the directory 1-base/java/com/example/android/wearable/complications. In Android Studio, this is located under 1-base/java/
  • WatchFaceConfigActivity - The activity tied to the watch face above, used for selecting configuration options. We will use this class to allow the user to select the data they want displayed in each complication. This file is located in the directory 1-base/java/com/example/android/wearable/complications. In Android Studio, this is located under 1-base/java/

Emulator setup

If you need help setting up an Wear OS emulator, please refer to the "Set Up an Wear OS Emulator or Device" section of the "Creating and Running a Wearable App" article.

Run the starter project

Let's run it on a watch.

  • Connect your Wear OS device or start an emulator.
  • In the toolbar, select the "1-base" configuration from the drop-down and click the green triangle run button next to it:


  • If you get an error like the one below (Error Launching activity), you will need to change the default launch Activity (instructions below).


Conversely, you may be asked directly which Activity to use when launching the app. Choose "Do not launch Activity".

  • To change the default launch Activity (if you need to do so from the previous step), click on the drop-down to the left of the green arrow and click "Edit configurations". template-for-dropdown.png

Select "1-base" and you will see a window similar the one below. Select "Nothing" under the Launch Options section and click Run. Later, if you want to try launching any of the other modules, you will need to do this as well.


  • Select your Android device or emulator and click OK. This will install the service on the Wear OS device or emulator.
  • After a couple of seconds, your service is built and ready to deploy. You will see a spinner in the status bar at the bottom of Android Studio:


  • If it is not already at the "4: Run" tab at the bottom-left of Android Studio, select that tab to see the installation progress. At the end of the installation process, you should see something similar to this:
Waiting for device.
Target device: lge-urbane_2-XXXXXXXXXXXXXX
Uploading file
        local path: ~/Downloads/Complications /1-base/build/outputs/apk/1-base-debug.apk
        remote path: /data/local/tmp/
DEVICE SHELL COMMAND: pm install -r "/data/local/tmp/"
pkg: /data/local/tmp/
  • After that occurs, swipe left on your device or emulator to bring up the watch faces picker. Scroll across and select Add more favorites.


Please note, since this is the first time you are running this watch face, you will need to select it from the favorites menu. After it has been selected once, it will show up as one of the options alongside this option.

  • Select Complications Codelab.


  • Now you should be centered on the Complications Codelab watch face in the favorite menu (see image below). Tap it to set the watch face.


  • If you do not see this, try deploying the watch face again by pressing the green triangle button in the toolbar.

Your watch face should now look like the screenshots below (which show the active and ambient modes). Don't worry if your emulator has a cloud with a strikethrough in place of the airplane icon. We will not need a connection to a phone or to the internet for this codelab.




In this step you've learned about:

  • The basics of our starting point (1-base module).
  • How to connect to an Wear OS device, deploy the watch face and run it!

Next up

Let's start exposing some data.

2. Receive complication data in the watch face

Code step 2

In this step, we'll familiarize ourselves with the code we use later for painting the complications and enabling our watch face to accept complications data.

If you are confused by any of the concepts discussed here, please refer to the 2-receive-data module and see how these steps may be implemented.

Add complication permission to the manifest

For your watch face to receive complication data and open a complication provider picker, you must specify the RECEIVE_COMPLICATION_DATA permission.

Open the AndroidManifest.xml file and search for "TODO: Step 2, receiveComplicationData". Below that comment, add the uses-permission line below. Your manifest should now look like this:

<!-- TODO: Step 2, receiveComplicationData -->
   android:name="" />

Introduction to the complication constants

Open and search for "TODO: Step 2, intro 1".

You should see something like this:

// TODO: Step 2, intro 1
private static final int LEFT_COMPLICATION_ID = 0;
private static final int RIGHT_COMPLICATION_ID = 1;


// Left and right dial supported types.
private static final int[][] COMPLICATION_SUPPORTED_TYPES = {

We will create two complications. One will be on the left side of the watch face while the other will be on the right side of the watch face. The Complications API requires that you create a unique ID for each complication (which we do in the first several lines).

The system needs an array of all our complication IDs, so we make an array of the unique IDs we created in the first several lines.

Finally, we create a matching array that contains all the supported complication data types that each unique ID supports.


Notice that we support many complication types.

To summarize, we create one array containing the unique IDs for each complication and another for the data types they support.

Introduction to the complication instance variables

Search for "TODO: Step 2, intro 2". You should see something like this:

// TODO: Step 2, intro 2
private SparseArray<ComplicationData> mActiveComplicationDataSparseArray;

private SparseArray<ComplicationDrawable> mComplicationDrawableSparseArray;

mActiveComplicationDataSparseArray is used to map active complication IDs to the data for that complication (ComplicationData). (We use SparseArray to avoid boxing/unboxing an int.) Note: Data will only be present if the user has chosen a provider via the config activity for the watch face.

Finally, mComplicationDrawableSparseArray is used to map the complication ID we created earlier to a ComplicationDrawable which will take care of rendering all the complication data for us. (We use a SparseArray again here for the same reason as we stated earlier.)

Initialize local complications variables

First, search for "TODO: Step 2, intro 3". You should see something like this:

public void onCreate(SurfaceHolder holder) {

   // TODO: Step 2, intro 3



We create the background and the watch face hands already via their own methods in onCreate(). Now we need to finish with initializeComplications()(the method is currently empty now).

CTRL + click on initializeComplications()to skip to a two line method, or search for "TODO: Step 2, initializeComplications()". Replace the method (only two lines) by copying/pasting the code below in place of the method. It should now look like this:

// TODO: Step 2, initializeComplications()
private void initializeComplications() {
   Log.d(TAG, "initializeComplications()");

   mActiveComplicationDataSparseArray = new SparseArray<>(COMPLICATION_IDS.length);

   ComplicationDrawable leftComplicationDrawable =
           (ComplicationDrawable) getDrawable(R.drawable.custom_complication_styles);

   ComplicationDrawable rightComplicationDrawable =
           (ComplicationDrawable) getDrawable(R.drawable.custom_complication_styles);

   mComplicationDrawableSparseArray = new SparseArray<>(COMPLICATION_IDS.length);
   mComplicationDrawableSparseArray.put(LEFT_COMPLICATION_ID, leftComplicationDrawable);
   mComplicationDrawableSparseArray.put(RIGHT_COMPLICATION_ID, rightComplicationDrawable);


Most of these lines are just initializing the local variables we already discussed.

First we initialize mActiveComplicationDataSparseArray to the correct size. We actually only add data to this array when it is active via a callback we will discuss later. Right now, we initialize it to the right size.

Next we setup our mComplicationDrawableSparseArray. We create two ComplicationDrawable objects to put in that array (one for the right complication and one for the left complication).

Notice we pass in a drawable file. This is a simple XML file that allows you to specify all the styles for your complication (both active and ambient mode). We will customize that in a later step.

Both ComplicationDrawable objects require that you set the Context after you load the style you want. (This helps them in rendering everything for you.)

The last line is very important. The setActiveComplications() method tells Wear OS that your watch face supports complications, and it requires that you pass the unique IDs for each complication. (These are the same constants we defined at the beginning of the lesson.)

It's important to note that a complication on Wear OS (from the watch face developer's standpoint) is just the space on the screen where a piece of information will be displayed.

That information is separate and provided by the complication data provider (not by you).

Override onComplicationDataUpdate() to receive complication data

The method onComplicationDataUpdate() is called when there is an update to the data of an active complication. This can be triggered for various reasons:

  • A watch face complication has become active
  • A watch face complication has changed providers
  • The provider has updated its data

Let's override the method. Search for "TODO: Step 2, onComplicationDataUpdate()" and right below the comment, paste the complete method below. It should now look like this:

// TODO: Step 2, onComplicationDataUpdate()
public void onComplicationDataUpdate(
       int complicationId, ComplicationData complicationData) {
   Log.d(TAG, "onComplicationDataUpdate() id: " + complicationId);

   // Adds/updates active complication data in the array.
   mActiveComplicationDataSparseArray.put(complicationId, complicationData);

   // Updates correct ComplicationDrawable with updated data.
   ComplicationDrawable complicationDrawable =

Since we are using mActiveComplicationDataSparseArray to track our active complications, we ensure that we add the data into our SparseArray.

We also pass along the updated data to the proper ComplicationDrawable, so it has the data available when it renders the complication.

Finally, we invalidate the screen to tell the watch face to call onDraw(). Later we will add the code to onDraw() that instructs each ComplicationDrawable to render its complication.

Handle Ambient mode

Ambient mode helps the Wear OS device conserve power by strictly limiting updates and limiting colors to black, white, and grays. A best practice is to disable anti-aliasing while in ambient mode.

Luckily, ComplicationDrawable handles the switching of styles in a complication from active mode to ambient mode. We just need to tell it when we have entered and exited ambient mode.

Search for "TODO: Step 2, ambient" and copy/paste in the code below.

// TODO: Step 2, ambient
ComplicationDrawable complicationDrawable;

for (int i = 0; i < COMPLICATION_IDS.length; i++) {
   complicationDrawable = mComplicationDrawableSparseArray.get(COMPLICATION_IDS[i]);

Calculate the location for each complication

This is the final step in this section. Search for "TODO: Step 2, calculating ComplicationDrawable locations". This takes you to the end of onSurfaceChanged() (a method that is triggered when the size of the screen is available).

Below the comment, paste the code below inside the onSurfaceChanged() method.

// TODO: Step 2, calculating ComplicationDrawable locations
int sizeOfComplication = width / 4;
int midpointOfScreen = width / 2;

int horizontalOffset = (midpointOfScreen - sizeOfComplication) / 2;
int verticalOffset = midpointOfScreen - (sizeOfComplication / 2);

Rect leftBounds =
       // Left, Top, Right, Bottom
       new Rect(
               (horizontalOffset + sizeOfComplication),
               (verticalOffset + sizeOfComplication));

ComplicationDrawable leftComplicationDrawable =

Rect rightBounds =
       // Left, Top, Right, Bottom
       new Rect(
               (midpointOfScreen + horizontalOffset),
               (midpointOfScreen + horizontalOffset + sizeOfComplication),
               (verticalOffset + sizeOfComplication));

ComplicationDrawable rightComplicationDrawable =

The location of your ComplicationDrawable is determined by a Rect object. For this codelab, we use a common calculation for the size and location of our right and left complications.

We then set those sizes for each ComplicationDrawable, so they know where to render themselves.

The onSurfaceChanged() method is only called when the surface changes. Therefore, we calculate the size and location of complications in this method, so we don't do it repeatedly in onDraw().

Remember, within


**, you should minimize calculations to ensure that the watch face runs smoothly, so calculating it here is one less thing to do in



Run the watch face again

While we now accept complication data, we aren't using it and the watch face behaves as it did before. Run the watch face again to ensure there are no errors from the code addition above.

Generally, the watch face installation and launch are fairly quick, but in some instances, it may take extra time to relaunch (30-40 seconds).


Important note: You may notice when you reinstall your watch face, if it was active before the install, that your watch face changes to something like the screenshot below.


You can bring your watch face back up by swiping and re-selecting your watch face.


In this step you've learned:

  • Where to add the permission needed in the manifest for complications to work
  • The variables needed to identify your complications and what data types they support
  • How to handle updates to active complications' data in onComplicationDataUpdate()
  • How to handle ambient mode
  • How and where to calculate the size and location of the right and left complications

Next up

Let's try expanding our watch face to allow users to select different complication data providers.

3. Allow users to choose a complication data provider

Code step 3

When developing watch faces, developers often enable users to customize the watch face directly from Wear OS. This is done by associating a custom Activity with the watch face Service it configures in the manifest.

To simplify the codelab, our configuration Activity will only modify the left and right complications. If you would like to see a complete example that includes hand styles, background colors, etc., check out our WatchFace sample on GitHub.

Before we can jump into, we need to allow our Activity to retrieve the custom complication information (custom IDs and supported data types) from the watch face. Let's handle that first.

If at any point you are confused by the concepts discussed here, please refer to the 3-choose-data module and see how these steps may be implemented.

Expose complication information to our config activity

In this step, we'll expose the complication information that ComplicationConfigActivity needs to function properly.

In ComplicationConfigActivity, we define constants for location (LEFT and RIGHT). This gives us a way to communicate with the watch face about the information we need.

Specifically, we need to retrieve three pieces of information:

  1. Complication ID by location (left or right)
  2. All complication IDs
  3. Supported complication data types by location (left or right)

We will create methods to retrieve those three pieces of information now.

Open and search for "TODO: Step 3, expose complication information, part 1". You will see a method called getComplicationId(). Right now it only returns -1. Replace the method with the code below.

// TODO: Step 3, expose complication information, part 1
static int getComplicationId(
       ComplicationConfigActivity.ComplicationLocation complicationLocation) {
   switch (complicationLocation) {
       case LEFT:
           return LEFT_COMPLICATION_ID;
       case RIGHT:
           return RIGHT_COMPLICATION_ID;
           return -1;

Again, LEFT and RIGHT are part of a public enum (ComplicationLocation) in ComplicationConfigActivity. We now respond with the correct IDs.

Scroll down to "TODO: Step 3, expose complication information, part 2" and swap out that method with the code below.

// TODO: Step 3, expose complication information, part 2
static int[] getComplicationIds() {

This allows ComplicationConfigActivity to get all custom complication IDs.

Finally, scroll down to "TODO: Step 3, expose complication information, part 3" and swap out that method with the code below.

// TODO: Step 3, expose complication information, part 3
static int[] getSupportedComplicationTypes(
       ComplicationConfigActivity.ComplicationLocation complicationLocation) {
   switch (complicationLocation) {
       case LEFT:
       case RIGHT:
           return new int[] {};

This allows ComplicationConfigActivity to get the supported complication data types by location.

Your code should now look like this.

// Used by {@link ComplicationConfigActivity} to retrieve id for complication locations and
// to check if complication location is supported.
// TODO: Step 3, expose complication information, part 1
static int getComplicationId(
       ComplicationConfigActivity.ComplicationLocation complicationLocation) {
   switch (complicationLocation) {
       case LEFT:
           return LEFT_COMPLICATION_ID;
       case RIGHT:
           return RIGHT_COMPLICATION_ID;
           return -1;

// Used by {@link ComplicationConfigActivity} to retrieve all complication ids.
// TODO: Step 3, expose complication information, part 2
static int[] getComplicationIds() {

// Used by {@link ComplicationConfigActivity} to retrieve complication types supported by
// location.
// TODO: Step 3, expose complication information, part 3
static int[] getSupportedComplicationTypes(
       ComplicationConfigActivity.ComplicationLocation complicationLocation) {
   switch (complicationLocation) {
       case LEFT:
       case RIGHT:
           return new int[] {};

We are done here and you can close this file.

Let's add the missing code to the config activity.

Overview of ComplicationConfigActivity

In this step, we'll familiarize ourselves with the code in ComplicationConfigActivity.

ComplicationConfigActivity renders a preview of the watch face with ImageButton, where the right and left complications would appear.

When an ImageButton object representing a complication is clicked, we pass the location to the custom methods we defined above to get more information about the complication.

For the user interface of our Activity, we assume you are familiar with using an ImageButton with onClick(). Therefore, we won't cover that part of the code.

Instead, we will focus on the portion related to complications.

Open and search for "TODO: Step 3, intro 1". You will see the code below. Note that you aren't writing any code for this section, just reading it.

// TODO: Step 3, intro 1
public enum ComplicationLocation {

You probably recognize this constant from our last step. ComplicationLocation represents each complication location, and is the same one we pass to the watch face to get information about each complication.

Next, scroll down further.

private int mLeftComplicationId;
private int mRightComplicationId;

// Selected complication id by user.
private int mSelectedComplicationId;

We will store the custom IDs for each complication location along with the selected complication ID (if one is chosen by the user).

Finally, scroll down further.

// ComponentName used to identify specific service that renders the watch face.
private ComponentName mWatchFaceComponentName;

// Required to retrieve complication data from watch face for preview.
private ProviderInfoRetriever mProviderInfoRetriever;

The Wearable Support Library provides classes for requesting information about the data providers for the complications. These two classes will be used to help us get the data from the providers. We will see them in action later.

Initialize code

Now that you understand what we're saving, let's get the information needed from the watch face for the initializations. Search for "TODO: Step 3, initialize 1". Copy and paste the code below it.

// TODO: Step 3, initialize 1
mSelectedComplicationId = -1;

mLeftComplicationId =
mRightComplicationId =

mWatchFaceComponentName =
       new ComponentName(getApplicationContext(), ComplicationWatchFaceService.class);

There isn't a selected complication ID, so we initialize it to -1.

You may recognize the next two method calls from our previous work in the watch face class. We save the custom IDs created in the watch face, so we can later tell the system what data provider we want associated with them.

Finally, the we create a ComponentName (more on this shortly).

Scroll down further to "TODO: Step 3, initialize 2". Copy and paste the code block below it.

// TODO: Step 3, initialize 2
mProviderInfoRetriever =
 new ProviderInfoRetriever(getApplicationContext(),Executors.newCachedThreadPool());


This code initializes mProviderInfoRetriever

****, which uses the ComponentName that we initialized in the previous step to get information about apps that provide data to our complications (called data providers).

The last line, retrieveInitialComplicationsData(), will get the data providers currently active with our watch face.

Finally, scroll down further to "TODO: Step 3, release" in the onDestroy() method. Copy and paste the single line below.

// TODO: Step 3, release

This line releases the retriever for active complication data (required) when our activity is finished.

Now that all our variables are initialized (and the mProviderInfoRetriever releases its resources when the Activity finishes), it's time to write the retrieveInitialComplicationsData() method, so our config activity can display the currently-active complication data providers.

Retrieve complication data

Search for "TODO: Step 3, retrieve complication data" and you will see an empty method. Replace that empty method with the code below. Note: This is the full method declaration.

// TODO: Step 3, retrieve complication data
public void retrieveInitialComplicationsData() {

   final int[] complicationIds = ComplicationWatchFaceService.getComplicationIds();

           new ProviderInfoRetriever.OnProviderInfoReceivedCallback() {
               public void onProviderInfoReceived(
                       int watchFaceComplicationId,
                       @Nullable ComplicationProviderInfo complicationProviderInfo) {

                   Log.d(TAG, "\n\nonProviderInfoReceived: " + complicationProviderInfo);

                   updateComplicationViews(watchFaceComplicationId, complicationProviderInfo);

The first line of the method is again a call to the watch face for more information. However, in this case, we want all the IDs, so the system can check which ones are valid and return the data.

The next call to retrieveProviderInfo() tells mProviderInfoRetriever to get the data about the providers associated with our active complications. By active, we mean any complications visible on the watch face.

The method requires a callback method, a ComponentName (to uniquely identify our watch face), and the unique complication IDs (int values) associated with the watch face.

In the callback, we are given the complication ID and the ComplicationProviderInfo associated with that complication ID. ComplicationProviderInfo includes the name of the app exposing the data in the provider, the complication data type, the name the app gives for the provider, and an icon for the provider.

After we have that information (in the callback), we call updateComplicationViews() to update our views. This method swaps out the provider's icons with whatever icon is currently in the view.

We won't cover more about that method, because it is fairly straight forward (swapping out views).

Now, when our Activity is visible, it will preview the icon (right and left) for whatever data providers are currently active. Next we want to allow the user to change the data providers associated with their complications.

Call helper method onClick()

We want to allow the user to tap on a complication in our Activity and choose from a list of data providers that can support that complication.

Fortunately, the Wearable Support Library provides an out of the box solution to allow the user to choose any complication data provider for a complication. It just requires us to pass in a little bit of information about the complication, namely the complication ID and the data types it supports.

We will now implement that part of the code.

Each of the complication locations (left and right) are represented in our Activity by an ImageButton.

Each ImageButton already supports tap events via onClick() which pass the appropriate complication location to an empty method, launchComplicationHelperActivity().

We now need to implement that method. Search for "TODO: Step 3, launch data selector" and replace that empty method with the code below. Note: This is the full method declaration.

// TODO: Step 3, launch data selector
private void launchComplicationHelperActivity(ComplicationLocation complicationLocation) {

   mSelectedComplicationId =

   if (mSelectedComplicationId >= 0) {

       int[] supportedTypes =


   } else {
       Log.d(TAG, "Complication not supported by watch face.");

At the beginning of the method, we ask the watch face for the ID associated with the complication location (left or right) and set that to mSelectedComplicationId. We also check whether the ID is valid, i.e., whether it is greater than or equal to zero.

Next we ask the watch face for the data types that complication ID supports. This is the last piece of data the Wearable Support Library needs to allow the user to select a provider.

Finally, we trigger the Activity that will allow the user to choose a data provider for that complication. This is done by an Intent created by the Wearable Support Library via ComplicationHelperActivity.createProviderChooserHelperIntent().

The method needs the context, the ComponentName of the watch face (to uniquely identify our watch face), the complication ID, and the complication data types it supports.

The Wearable Support Library does the rest (handles complication permissions, allows the user to choose a data provider, saves that choice in the system, and returns the user from that Activity). We want to be alerted when we return from this Activity, so we pass in a request code (COMPLICATION_CONFIG_REQUEST_CODE) to our startActivityForResult().

We are finished here but need to add a snippet of code for when we come back from the Activity launched by this code.

Search for "TODO: Step 3, update views" and add this block of code:

// TODO: Step 3, update views
if (requestCode == COMPLICATION_CONFIG_REQUEST_CODE && resultCode == RESULT_OK) {

   // Retrieves information for selected Complication provider.
   ComplicationProviderInfo complicationProviderInfo =
   Log.d(TAG, "Provider: " + complicationProviderInfo);

   if (mSelectedComplicationId >= 0) {
       updateComplicationViews(mSelectedComplicationId, complicationProviderInfo);

First we check if we are returning from the Activity we launched earlier via the Wearable Support Library. We do that by checking if the request code matches our COMPLICATION_CONFIG_REQUEST_CODE.

If that comes back as true, we know that the new complication data provider information will be passed to us, so we can update our view.

We extract that information from the Intent returned to this method, and as long as the selected complication ID is valid (we set that in the last step), we pass that along to our method that updates the view (updateComplicationViews()).

We are done with the coding section, one last minor change!

Include ComplicationHelperActivity in Manifest

Because we are using the Wearable Support Library's helper Activity to allow users to both accept permissions and choose data providers for their complications, we must include that helper Activity class in the AndroidManifest.xml file. Open the AndroidManifest.xml file and search for "TODO: Step 3, addWearableSupportHelperActivity

". Below that comment, add the line below.

<!-- TODO: Step 3, addWearableSupportHelperActivity -->
<activity android:name=""/>

This line allows the Wearable Support Library to launch the helper activity to handle permissions and the selection of a data provider.

Include our custom activity in Manifest

Next we need to add our custom activity to the manifest and tie it to the watch face.

In AndroidManifest.xml, search for "TODO: Step 3, addConfigActivity". Below that comment, add the XML below.

<!-- TODO: Step 3, addConfigActivity -->
       <action android:name=""/>
       <category android:name=""/>
       <category android:name="android.intent.category.DEFAULT"/>

Because the watch face service already defines its wearable configuration action as CONFIG_COMPLICATION, this Activity will be associated with our watch face as its configuration Activity because it uses the same name in its action.

Ok, we're done. Congratulations, this is the longest step! Let's see how it looks.

How to check your progress and debug

Install your service, swipe, and choose the gear at the bottom of our watch face.


You should now see something like the image below (displaying both our left and right complications):


Tap either complication (left or right). If this is your first time selecting a complication data provider for your watch face, you will see a permission screen.


Go ahead and tap the checkmark to accept the permission. You will see a list of all available data providers that match the complication's supported data types.


The Empty option disables the complication. You can see that Wear OS comes out of the box with several providers already. Tap General.


You should see something like the image below. Go ahead and select one of the data providers.


While the watch face does not look any different, you should now see Log data showing

that the onComplicationDataUpdate() method was called (along with the complication ID).

If you aren't familiar with how to see Log data, click on the tab at the bottom of Android Studio labeled "6: Android Monitor". Set the dropdowns to your device/emulator and the package name, (screenshot below).



In this step you've learned about:

  • Exposing relevant information from the watch face to the config Activity
  • Retrieving and initializing provider information to show a preview in our Activity
  • Using the helper method, ComplicationHelperActivity.createProviderChooserHelperIntent(), to allow the user to accept complication permissions and select from the available data providers for their complications
  • Adding the Wearable Support Library's ComplicationHelperActivity and our own custom class, ComplicationConfigActivity, to the manifest

Next up

We want to render the complications on the watch face.

4. Render complications on the watch face

Code step 4

While our complication data is being sent to our watch face, we only see the ID of our complication (not actual data) in the Log messages within onComplicationDataUpdate().

We still can't see the data on the screen. Let's get the actual data and render it on the screen.

If at any point you are confused by the concepts discussed here, please refer to the 4-render-complications module and see how these steps may be implemented.

Render complications on the screen

Open the ComplicationWatchFaceService again and search for "TODO: Step 4, drawComplications()". It will take you to the empty drawComplications() method. This is called in

draw() every time the screen refreshes.

Replace that method with the complete version below.

private void drawComplications(Canvas canvas, long currentTimeMillis) {
   // TODO: Step 4, drawComplications()
   int complicationId;
   ComplicationDrawable complicationDrawable;

   for (int i = 0; i < COMPLICATION_IDS.length; i++) {
       complicationId = COMPLICATION_IDS[i];
       complicationDrawable = mComplicationDrawableSparseArray.get(complicationId);

       complicationDrawable.draw(canvas, currentTimeMillis);

This code loops through complication IDs (COMPLICATION_IDS), gets the ComplicationDrawable associated with that ID, and asks that ComplicationDrawable to draw the complication.

What's really nice is ComplicationDrawable determines if the complication is active at the current time and, if it is, draws one of the possible data types.

Note: The

No Permission

type will display "–" with an Intent to launch a permission prompt.

Let's see how it looks.

How to check your progress and debug

Install your service, swipe the watch face, and select the gear.


Choose a complication and a data provider as in the previous step. You should now see it on the watch face. It might look something like this (a date and Android Pay example):



In this step you've learned about:

  • Looping through the complication IDs to render a complication via ComplicationDrawable

Next up

We enable tapping on complications.

5. Enable interactions with complications (tap)

Code step 5

In this last step, we will enable tapping for all complications.

Some complications include tap actions that can launch their apps. This can be helpful to a user by adding additional information, e.g., a Next Event data provider opens a Calendar app to show your next meeting details.

If at any point you are confused by the concepts discussed here, please refer to the 5-enable-tap module and see how these steps may be implemented.

Check if the user tapped the complication

Open the ComplicationWatchFaceService again and search for "TODO: Step 5, OnTapCommand()". It will take you to the onTapCommand() method.

The onTapCommand() method will be called whenever the user taps anywhere on the watchface (already set up). That's great, but we need to figure out if the user actually tapped a complication.

Copy and paste the switch statement below the comment. Your code should look like this:

public void onTapCommand(int tapType, int x, int y, long eventTime) {
   Log.d(TAG, "OnTapCommand()");
   // TODO: Step 5, OnTapCommand()
   switch (tapType) {
       case TAP_TYPE_TAP:
           int tappedComplicationId = getTappedComplicationId(x, y);
           if (tappedComplicationId != -1) {

Fortunately, we already have the getTappedComplicationId() method, which checks if the complication was tapped and returns a complication ID (and returns -1 if no complication was tapped).

Check the code if you are interested to see how it was written. Remember, the ComplicationDrawable returns a Rect for its bounds, so it was checking whether or not the x and y were in that area.

In the final step, we need to check if the tapped complication has an associated action (by looking at the tap action that we can retrieve by calling getTapAction()).

Trigger the PendingIntent

Search for "TODO: Step 5, onComplicationTap()". It will take you to the onComplicationTap() method.

Remember we determined the correct complication ID by calling getTappedComplicationId()before, so we just need to check that the ComplicationData is valid, check for a tap action (PendingIntent), and trigger the tap action.

Replace the current empty version of the method with the new one below.

private void onComplicationTap(int complicationId) {
   // TODO: Step 5, onComplicationTap()
   Log.d(TAG, "onComplicationTap()");

   ComplicationData complicationData =

   if (complicationData != null) {

       if (complicationData.getTapAction() != null) {
           try {
           } catch (PendingIntent.CanceledException e) {
               Log.e(TAG, "onComplicationTap() tap action error: " + e);

       } else if (complicationData.getType() == ComplicationData.TYPE_NO_PERMISSION) {

           // Watch face does not have permission to receive complication data, so launch
           // permission request.
           ComponentName componentName = new ComponentName(

           Intent permissionRequestIntent =
                           getApplicationContext(), componentName);


   } else {
       Log.d(TAG, "No PendingIntent for complication " + complicationId + ".");

In this case, we pull out the ComplicationData for the complicationId. If there is an active complication, we check if a tap action is available. If one is available, we perform the operation associated with the PendingIntent returned. (This is done via the send() method.)

If the type happens to be No Permission, we know the watch face no longer has permission to receive complication data, so we launch a permission request with a tap via ComplicationHelperActivity.createPermissionRequestHelperIntent().

Great job, you are done! Let's check our finished product. Next is the fun part, changing all the colors!

How to check your progress and debug

Install your service, swipe the watch face, and click on the gear to launch the complication picker.


For whatever complication you choose, choose Wear OS, and pick either Step Count or Next Event. When your watch face comes back up, click on your complication and it will launch the app associated with it!


In this step you've learned about:

  • Enabling tap events on your complications

Next up

We wrap up the codelab by modifying the complication styles.

6. Modify complication colors

Code step 6

In this last step, we will style our complications.

Note: There isn't a formal module for this step, because it is up to you to have fun with the styles!

Initialize complication styles

If you remember back to step 2, we set the styles of our complications in initializeComplications().

Here is the snippet:


ComplicationDrawable leftComplicationDrawable =


ComplicationDrawable rightComplicationDrawable =



We used an XML file (R.drawable.custom_complication_styles) to initialize each complication style.

Let's look at that file.

Modify the complication styles

Open the custom_complication_styles.xml in the drawable folder.

You should see something like this:



There are two style sections to ComplicationDrawable: The active state and the ambient state (low power state).

The properties at the top represent the styles in the active state while every property in the <ambient> element represents the style in the ambient state.

There is no formal step here, have fun! Try out several crazy color combinations and install it on your watch.

I have defined a number of colors for you to use in the values/color.xml file.

You can see how easy it is to customize the look and feel of your watch face!

Great job, you're done with the codelab! If you had some fun you should have a crazy design like the one below.

How to check your progress and debug

Install your service, swipe the watch face, and click on the gear to launch the complication picker.


For whatever complication you choose, you should see your styles now showing up in the watch face. Below is an example of a different style.



In this step you've learned about:

  • Customizing the style of your complications

7. Learn More

For more details on developing complications for watch faces and creating complication data providers, visit Watch Face Complications.

For more details about developing Wear OS watch faces, visit