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

Concepts and setup

You'll learn how 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 Android Wear Watch Face with complications.

Concepts

To start off, let's learn a little bit about Complications. A complication is a feature of a watch face beyond hours and minutes. For example, the date or a steps indicator is a complication (see image below).

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

In this code lab, 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 code lab, "Exposing data to watch face complications on Android Wear," 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 code lab.

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 https://github.com/googlecodelabs/complications

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 also 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.)

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 off using the 1-base module, which is the starting point for everything we'll be building upon. You will be adding 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

Emulator setup

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

Run the starter project

Let's run it on a watch.

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/com.example.android.wearable.complications
Installing com.android.example.watchface
DEVICE SHELL COMMAND: pm install -r "/data/local/tmp/com.example.android.wearable.complications"
pkg: /data/local/tmp/com.example.android.wearable.complications
Success

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.

Your watch face should now look like the screenshots below (active and ambient mode). 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 / internet for this code lab.

Summary

In this step you've learned about:

Next up

Let's start exposing some data.

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 at any point you are confused by 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 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 -->
<uses-permission
   android:name="com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA" />

Introduction to the complication constants

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

You should see something like this:

private static final int LEFT_DIAL_COMPLICATION = 0;
private static final int RIGHT_DIAL_COMPLICATION = 1;

public static final int[] COMPLICATION_IDS = {LEFT_DIAL_COMPLICATION, RIGHT_DIAL_COMPLICATION};

// Left and right dial supported types.
public static final int[][] COMPLICATION_SUPPORTED_TYPES = {
       {ComplicationData.TYPE_SHORT_TEXT},
       {ComplicationData.TYPE_SHORT_TEXT}
};

We will be creating two complications (left and right dial). The Complications API requires that you create a unique ID for each complication (which we do in the first several lines). We put those IDs together in an array for the API to use along with a matching array for the type of data each complication supports.

To keep things simple, each complication will only support Short Text. There are many more types of data you can support as well (long text, ranged values, icons, etc.).

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:

private Paint mComplicationPaint;

private int mComplicationsY;

private SparseArray<ComplicationData> mActiveComplicationDataSparseArray;

The Paint instance mComplicationPaint holds the style and color information for painting our complications.

To properly place each complication, we need their x and y coordinates (this maps to the top-left of the complication). While the width of the text (number of characters) may change each moment based on the context, the height (height of the text) will not change unless the surface of the watch changes, so we store mComplicationsY as a local variable and only calculate it when the surface changes (onSurfaceChanged()).

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

Initializing local complications variables

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

@Override
public void onCreate(SurfaceHolder holder) {
   ...
   initializeBackground();

   // TODO: Step 2, intro 3
   initializeComplications();

   initializeHands();

}

We created the background and the watch face hands 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 the empty method. You can also search for "TODO: Step 2, initializeComplications()" to get there as well. Within that empty method, paste the code below. It should now look like this:

private void initializeComplications() {
   Log.d(TAG, "initializeComplications()");
   // TODO: Step 2, initializeComplications()
   mActiveComplicationDataSparseArray = new SparseArray<>(COMPLICATION_IDS.length);

   mComplicationPaint = new Paint();
   mComplicationPaint.setColor(Color.WHITE);
   mComplicationPaint.setTextSize(COMPLICATION_TEXT_SIZE);
   mComplicationPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
   mComplicationPaint.setAntiAlias(true);

   setActiveComplications(COMPLICATION_IDS);
}

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

The last line is the important one. The setActiveComplications() method tells Android Wear 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 a complication on Android Wear (from the watch face developer's standpoint) is just the space on the screen where a piece of information will be displayed.

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

Override onComplicationDataUpdate() to receive complication data

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

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()
@Override
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);
   invalidate();
}

Since we are using mActiveComplicationDataSparseArray to track our active complications, we want to make sure we add the data into our SparseArray. We also want to invalidate the screen to let the watch face know to call onDraw(). Later we will add the code to onDraw() to render the complications.

Handling Ambient mode

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

We want to make sure we do that for our complication Paint object as well.

Search for "TODO: Step 2, ambient" and uncomment the line below the comment. It should look like this:

// TODO: Step 2, ambient
mComplicationPaint.setAntiAlias(!inAmbientMode);

Calculate complications' Y value

This is the final step in this section. Search for "TODO: Step 2, calculating complications' Y" and uncomment the line below the comment. It should look like this:

// TODO: Step 2, calculating complications' Y
mComplicationsY = (int) ((mHeight / 2) + (mComplicationPaint.getTextSize() / 2));

As mentioned earlier, we want to pre-calculate the y coordinates for our complications. Both the left and right dial will use the same value, i.e., they will both be vertically set at the same location.

The onSurfaceChanged() method is only called when the surface changes, so that is where we calculate mComplicationsY.

Remember, within onDraw(), you should minimize calculations as much as possible to ensure the watch face runs smoothly, so calculating it here is one less thing we need to do in onDraw().

Run the watch face again

While we now accept complication data, we aren't doing anything with it, so if you run the watch face, you won't see any different behavior. However, it is probably a good idea to run it again to make sure you didn't make a mistake in adding the new code.

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

Summary

In this step you've learned:

Next up

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

Code step 3

When developing watch faces, developers often enable users to customize the watch face directly from Android Wear. This is done by associating an Activity with the watch face Service in the manifest.

In our case, we already assigned WatchFaceConfigActivity though it's incomplete. (We will finish it in this step.)

To simplify things, we will only list our complications (instead of complications and watch hand styles and background images and etc.) in a WearableListView.

Let's jump in and add the missing code to get our Activity up and running.

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.

Introduction to our custom class

In this step, we'll familiarize ourselves with the code we use to represent our items in the WearableListView. Open WatchFaceConfigActivity.java and search for "TODO: Step 3, intro".

This will take you to the inner class ComplicationItem which is used to represent the items for our WearableListView.Adapter, ConfigurationAdapter. It should look like the code below (again no pasting in this step, just explanation).

private final class ComplicationItem {
   ComponentName watchFace;
   int complicationId;
   int[] supportedTypes;
   Drawable icon;
   String title;

   public ComplicationItem(ComponentName watchFace, int complicationId, int[] supportedTypes,
                           Drawable icon, String title) {
       this.watchFace = watchFace;
       this.complicationId = complicationId;
       this.supportedTypes = supportedTypes;
       this.icon = icon;
       this.title = title;
   }
}

If you aren't familiar with creating lists for an Android Wear app, don't worry. All the code is there to take care of everything. (If you are interested, check out the Creating Lists page for Android Wear.)

The main part to know is this inner class represents each item in our list, so can get back the selected complication ID and the types of complication data it supports. This is important, because we need to call a helper method from the support library to list all the possible data providers that are compatible with our complication.

We need to do two things in this class: populate the list with our complications' data (our custom ComplicationItem class) and call the helper method. Let's do that now.

Populating the wearable list with our complications

Search for "TODO: Step 3, getComplicationItems()". You will see the method returns an empty ArrayList now. We want to change that. Replace the current method with the method below.

private List<ComplicationItem> getComplicationItems() {
   // TODO: Step 3, getComplicationItems()
   ComponentName watchFace = new ComponentName(
           getApplicationContext(), ComplicationWatchFaceService.class);

   String[] complicationNames =
           getResources().getStringArray(R.array.complication_names);

   int[] complicationIds = ComplicationWatchFaceService.COMPLICATION_IDS;

   TypedArray icons = getResources().obtainTypedArray(R.array.complication_icons);

   List<ComplicationItem> items = new ArrayList<>();
   for (int i = 0; i < complicationIds.length; i++) {
       items.add(new ComplicationItem(watchFace,
               complicationIds[i],
               ComplicationWatchFaceService.COMPLICATION_SUPPORTED_TYPES[i],
               icons.getDrawable(i),
               complicationNames[i]));
   }
   return items;
}

We are creating a list from the constants we defined in ComplicationWatchFaceService (IDs for complications and data types they support) and several resources pointing to the icons and names to use in the WearableListView.

Calling helper method onClick()

Now that we have a list of our complications, we want to call the helper method (available in the support library), ComplicationHelperActivity.createProviderChooserHelperIntent(), when a user chooses the complication they want to change.

This method returns an Intent that allows the user to select a provider for the complication chosen.

We will use that Intent to start the Activity, so the user may now choose whatever data provider they wish.

Please note, you must use startActivityForResult() with this intent. When we get a result, we will close the Activity to return to the watch face.

Let's do that now. Search for "TODO: Step 3, onClick()". Below that comment, add the startActivityForResult() code you see below. The method should now look like this:

@Override
public void onClick(WearableListView.ViewHolder viewHolder) {
   Log.d(TAG, "onClick()");

   Integer tag = (Integer) viewHolder.itemView.getTag();
   ComplicationItem complicationItem = mAdapter.getItem(tag);

   // TODO: Step 3, onClick()
   startActivityForResult(
       ComplicationHelperActivity.createProviderChooserHelperIntent(
               getApplicationContext(),
               complicationItem.watchFace,
               complicationItem.complicationId,
               complicationItem.supportedTypes),
       PROVIDER_CHOOSER_REQUEST_CODE);
}

Note: In the previous alpha library, there was another method called ProviderChooserIntent.createProviderChooserIntent() which provided a similar service. That method has been deprecated for ComplicationHelperActivity.createProviderChooserHelperIntent(). The latter also handles the runtime permission request for your watch face to receive complication data.

Include ComplicationHelperActivity in Manifest

Because we are using the helper Activity to allow users to both accept permissions and choose a data provider for their complication, we must include that helper Activity class in the AndroidManifest.xml.

Open the AndroidManifest.xml and search for "TODO: Step 3, addHelperActivity". Below that comment, add Activity below. Your manifest should now look like this:

<!-- TODO: Step 3, addHelperActivity -->
<activity android:name="android.support.wearable.complications.ComplicationHelperActivity"/>

Ok, we're done. 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 (listing both our complications [Right Dial and Left Dial]):

Choose either complication (Left dial or Right dial), and it should bring up all available data providers that match the complications' supported data types.

Empty represents disabling the complication. You can see Android Wear comes with 5 items already. Tap Android Wear and you should see something like this:

While the watch face will 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, com.example.android.wearable.complications (screenshot below).

Summary

In this step you've learned about:

Next up

We want to render the 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 it 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.

Rendering 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()
   ComplicationData complicationData;

   for (int i = 0; i < COMPLICATION_IDS.length; i++) {

       complicationData = mActiveComplicationDataSparseArray.get(COMPLICATION_IDS[i]);

       if ((complicationData != null)
               && (complicationData.isActive(currentTimeMillis))) {

           if (complicationData.getType() == ComplicationData.TYPE_SHORT_TEXT
                   || complicationData.getType() == ComplicationData.TYPE_NO_PERMISSION) {

               ComplicationText mainText = complicationData.getShortText();
               ComplicationText subText = complicationData.getShortTitle();

               CharSequence complicationMessage =
                       mainText.getText(getApplicationContext(), currentTimeMillis);

               /* In most cases you would want the subText (Title) under the
                * mainText (Text), but to keep it simple for the code lab, we are
                * concatenating them all on one line.
                */
               if (subText != null) {
                   complicationMessage = TextUtils.concat(
                           complicationMessage,
                           " ",
                           subText.getText(getApplicationContext(), currentTimeMillis));
               }

               double textWidth =
                       mComplicationPaint.measureText(
                               complicationMessage,
                               0,
                               complicationMessage.length());

               int complicationsX;

               if (COMPLICATION_IDS[i] == LEFT_DIAL_COMPLICATION) {
                   complicationsX = (int) ((mWidth / 2) - textWidth) / 2;
               } else {
                   // RIGHT_DIAL_COMPLICATION calculations
                   int offset = (int) ((mWidth / 2) - textWidth) / 2;
                   complicationsX = (mWidth / 2) + offset;
               }

               canvas.drawText(
                       complicationMessage,
                       0,
                       complicationMessage.length(),
                       complicationsX,
                       mComplicationsY,
                       mComplicationPaint);
           }
       }
   }
}

This code loops through mActiveComplicationDataSparseArray and gets active complications that are of type Short Text and No Permission.

Both Short Text and No Permission Types can be rendered with the same code. (No Permission is returned when the user hasn't accepted the run time RECEIVE_COMPLICATION_DATA for your watch face.)

Note: The No Permission type will display "--" with an Intent to launch a permission prompt.

The code pulls out the appropriate fields to make a one line message. A ComplicationData has a number of fields, but for us, we only want the Short text (Primary text field for small complications that doesn't exceed 7 characters) and the Short title if it's available (optional descriptive field for small complications that doesn't exceed 7 characters).

The code after that is just some simple code for calculating the width of the complication and placing it on the screen properly. (You can see we use the Y we calculated earlier.)

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 like the previous step. You should now see it on the watch face. It might look something like this (date example):

Summary

In this step you've learned about:

Next up

We wrap up the code lab, enabling tapping on complications.

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., Next Event data provider opens a Calendar app to show you 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. Which is what we're going to do right now.

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

@Override
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) {
               onComplicationTap(tappedComplicationId);
           }
           break;
   }
}

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

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

Triggering 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 finally, 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 =
           mActiveComplicationDataSparseArray.get(complicationId);

   if (complicationData != null) {

       if (complicationData.getTapAction() != null) {
           try {
               complicationData.getTapAction().send();
           } 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(
                   getApplicationContext(),
                   ComplicationWatchFaceService.class);

           Intent permissionRequestIntent =
                   ComplicationHelperActivity.createPermissionRequestHelperIntent(
                           getApplicationContext(), componentName);

           startActivity(permissionRequestIntent);
       }

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

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 Android Wear, 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!

Summary

In this step you've learned about:

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

For more details about developering Android Wear watch faces, visit https://developer.android.com/training/wearables/watch-faces/index.html