Tutorial: Building an Android Wear Watch Face, Part 3 : Wearable APIs

This is part 3 of a 4 part series where we explore the challenges of making wearable apps, by building an Android wear watch face with real-time weather data. In Part 1, we provided an overview and created our first watch face. In Part 2 we took a deeper dive into the watch face. In this part we explore the Wearable Communication APIs and build the real-time weather communication component of our app. Finally, in Part 4, we show how to package and publish a watch face app.

You can review the full source code on Github or download the final watch face from the Google Play Store.

This tutorial is separated to four posts:

Wearable APIs

A key thing to note about wearable devices is that they do not connect directly to the internet. A mobile phone or device must be paired with a wearable. Furthermore, once paired, they can only transfer data only via the Wearable APIs.

Edit: This may be changing soon with wifi available directly on wearable devices see google announcement.

There are three wearable APIs:

  1. Node API : for detecting devices connect or disconnect.
  2. Message API : for sending messages to another device.
  3. Data API: for storing and retrieving data from a device.

As with other Google APIs, we need to use the GoogleApiClient class in order to use Wearable APIs. Figure 1 shows how wearable & mobile apps work with the GoogleApiClient and the wearable APIs.

Figure 1: Communicating with Wearable APIs

Google Api Client

The first step is to create an instance of GoogleApiClient.

@Overridepublic void onCreate(SurfaceHolder holder) {    mGoogleApiClient = new GoogleApiClient.Builder(WeatherWatchFaceService.this)            .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {                @Override                public void onConnected(Bundle bundle) {                    Log.d(TAG, "GoogleApiClient is Connected");                }                @Override                public void onConnectionSuspended(int i) {                    Log.d(TAG, "GoogleApiClient is ConnectionSuspended");                }})            .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {                @Override                public void onConnectionFailed(ConnectionResult connectionResult) {                    Log.d(TAG, "The connection of GoogleApiClient is failed");                }            })           .addApi(Wearable.API) // tell Google API that we want to use Warable API           .build();}

TIP: If your app also use one of the other API components (such as Google+, Games, or Drive), remember to use a separate instance of GoogleApiClient for the Wearable API, as the connect call will fail unless the Android Wear App has also been installed on the device. For more information, see here.

Once the GoogleApiClient is connected, add listener for each of the Wearable APIs:

@Overridepublic void onConnected(Bundle bundle) { // the part of GoogleApiClient.ConnectionCallbacks    Wearable.NodeApi.addListener(mGoogleApiClient, new NodeApi.NodeListener() {        @Override        public void onPeerConnected(Node node) {            Log.d(TAG, "A node is connected and its id: " + node.getId());        }                                        @Override        public void onPeerDisconnected(Node node) {            Log.d(TAG, "A node is disconnected and its id: " + node.getId());        }    });        Wearable.MessageApi.addListener(mGoogleApiClient, new MessageApi.MessageListener() {        @Override        public void onMessageReceived(MessageEvent messageEvent) {            Log.d(TAG, "You have a message from " + messageEvent.getPath());        }    });    Wearable.DataApi.addListener(mGoogleApiClient, new DataApi.DataListener() {        @Override        public void onDataChanged(DataEventBuffer dataEvents) {            Log.d(TAG, "Your data is changed");        }    });}

Also, remember to remove listeners and disconnect GoogleApiClient if they are no longer needed.

@Overridepublic void onVisibilityChanged(boolean visible) {    if (visible) {        mGoogleApiClient.connect();    } else {        if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {            Wearable.NodeApi.removeListener(mGoogleApiClient, this);            Wearable.MessageApi.removeListener(mGoogleApiClient, this);            Wearable.DataApi.removeListener(mGoogleApiClient, this);            mGoogleApiClient.disconnect();        }    }}

In our example, we connect the Wearable APIs when screen is visible and disconnect from the APIs when screen is invisible.

Node Api

NodeApi has two methods you can use:

  • getLocalNode: Get device’s node id. You will need this id to retrieve data via DataApi
  • getConnectedNodes: Get what devices are connected to this device.

For example,

Wearable.NodeApi.getLocalNode(mGoogleApiClient)        .setResultCallback(new ResultCallback<NodeApi.GetLocalNodeResult>() {            @Override            public void onResult(NodeApi.GetLocalNodeResult result) {                Log.d(TAG, "My node id is " + result.getNode().getId());            }        });Wearable.NodeApi.getConnectedNodes(mGoogleApiClient)        .setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {            @Override            public void onResult(NodeApi.GetConnectedNodesResult result) {                for(Node node: result.getNodes()){                    Log.d(TAG, "Node " + node.getId() + " is connected");                }            }        });

Message Api

MessageApi is similar to a Message Queue, you can send messages into a queue and GoogleApiClient will deliver the messages to the other device, but unlike some other message queue technologies, there is no persistence. A active connection is needed between both the wearable and the phone. If the connections is lost, GoogleApiClient doesn’t keep the messages.The following example shows how to send and receive a message containing weather data via Wearable.MessageApi.

// SEND a message// we can use data map to generate a byte array.DataMap config = new DataMap();//Weather Informationconfig.putInt("Temperature", 100);config.putString("Condition", "cloudy");// the parameter of  node id can be empty string "".// the third parameter is message path.Wearable.MessageApi.sendMessage(mGoogleApiClient, mPeerId, "/WeatherInfo", config.toByteArray())        .setResultCallback(new ResultCallback<MessageApi.SendMessageResult>() {            @Override            public void onResult(MessageApi.SendMessageResult sendMessageResult) {                Log.d(TAG, "SendMessageStatus: " + sendMessageResult.getStatus());            }        });// RECIVE a message@Overridepublic void onMessageReceived(MessageEvent messageEvent) {    // convert a byte array to DataMap    byte[] rawData = messageEvent.getData();    DataMap dataMap = DataMap.fromByteArray(rawData);        // we have different methods for different messages    if (messageEvent.getPath().equals("/WeatherInfo")) {        fetchInfo(dataMap);    }    if (messageEvent.getPath().equals("/WeatherWatchFace/Config")) {        fetchConfig(dataMap);            }}

Note: The limit of a message is 100 KB.

Data Api

DataApi allows us to store the wears’ data persistently. We use a PutDataMapRequest to save persistent data.

PutDataMapRequest putDataMapRequest = PutDataMapRequest.create("/WeatherWatchFace/Config");DataMap config = putDataMapRequest.getDataMap();config.putInt("TemperatureScale", mTemperatureScale);config.putInt("BackgroundColor", mBackgroundColor);config.putInt("RequireInterval", mRequireInterval);Wearable.DataApi.putDataItem(mGoogleApiClient, putDataMapRequest.asPutDataRequest())        .setResultCallback(new ResultCallback<DataApi.DataItemResult>() {            @Override            public void onResult(DataApi.DataItemResult dataItemResult) {                log("SaveConfig: " + dataItemResult.getStatus() + ", " + dataItemResult.getDataItem().getUri());            }        });

WARNING: When you update data via PutDataMapRequest, be sure to include all the attributes as update replaces all items with the new map, instead of updating individual properties.

When the data has been saved successfully, the path to access it again will look like the following

wear://<NodeId>/<path>    //the path is given when you call PutDataMapRequest.create()

You can’t assign the node Id. DataApi uses the local node id when you call Wearable.DataApi.putDataItem() either on a wear or handheld. If you want to access the data, you have to use this path to access it:

// to get local node idWearable.NodeApi.getLocalNode(mGoogleApiClient).setResultCallback(new ResultCallback<NodeApi.GetLocalNodeResult>() {    @Override    public void onResult(NodeApi.GetLocalNodeResult getLocalNodeResult) {        Uri uri = new Uri.Builder()                .scheme("wear")                .path("/WeatherWatchFace/Config")                .authority(getLocalNodeResult.getNode().getId())                .build();        Wearable.DataApi.getDataItem(mGoogleApiClient, uri)                .setResultCallback(                        new ResultCallback<DataApi.DataItemResult>() {                            @Override                            public void onResult(DataApi.DataItemResult dataItemResult) {                                log("Finish Config: " + dataItemResult.getStatus());                                if (dataItemResult.getStatus().isSuccess() && dataItemResult.getDataItem() != null) {                                    fetchConfig(dataItemResult.getDataItem());                                }                            }                        }                );    }});

You might have multiple wearable devices connected to the the phone that store data with the same path. For that you can use getDataItems() to get data for all devices.

Uri uri = new Uri.Builder()        .scheme("wear")        .path("/WeatherWatchFace/Config")        .build();Wearable.DataApi.getDataItems(mGoogleApiClient, uri)        .setResultCallback(                new ResultCallback<DataItemBuffer>() {                    @Override                    public void onResult(DataItemBuffer dataItems) {                        for(int i=0;i<dataItems.getCount();i++){                            Log.d(TAG,"The data is from: " + dataItems.get(i).getUri().getAuthority());                        }                    }                }        );

Note also, the data for an app is isolated by it’s package name, let’s says you install another app, you cannot use DataApi get to the data from a different app, even if it shares the same path.

Wearable Listener Service

You can use WearableListenerService to listen Wearable APIs’ events when a device boots (either Wear or Handheld). This feature is the same as you add listeners on the wearable app example which is inside of WatchFaceService, but the benefit of using WearableListenerService is that it runs on background independently. Let’s say you are using a watch face called A, and you open the config Activity on a phone of another watch face called B. Your config changes will lost because watch face B isn’t active so it can’t receive any message. Therefore, if we add a WearableListenerService, you can get all messages although a  watch face isn’t active.With the following example, we will use this class on a phone app and listen a message(a require) from a wear. When WearableListenerService on a phone app get the message, it will call weather API to get current weather data and send the data to the wear.Step 1: Create a WeatherService class which extends WearableListenerService

public class WeatherService extends WearableListenerService {    @Override    public void onMessageReceived(MessageEvent messageEvent) {                mPeerId = messageEvent.getSourceNodeId();        if (messageEvent.getPath().equals("/WeatherService/Require")) {            // Get Location            mLocationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);            mLocation = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);            // Start getting weather data asynchronously            GetWeatherTask task = new GetWeatherTask();            task.execute();        }    }    private class GetWeatherTask extends AsyncTask {        @Override        protected Object doInBackground(Object[] params) {            try {                              WeatherApi api = new OpenWeatherApi();                WeatherInfo info = api.getCurrentWeatherInfo(mLocation.getLatitude(), mLocation.getLongitude());                // Send Data to the wear.                DataMap config = new DataMap();                                config.putInt(KEY_WEATHER_TEMPERATURE, info.getTemperature());                config.putString(KEY_WEATHER_CONDITION, info.getCondition());                config.putLong(KEY_WEATHER_SUNSET, info.getSunset());                config.putLong(KEY_WEATHER_SUNRISE, info.getSunrise());                Wearable.MessageApi.sendMessage(mGoogleApiClient, mPeerId, PATH_WEATHER_INFO, config.toByteArray())                        .setResultCallback(new ResultCallback<MessageApi.SendMessageResult>() {                    @Override                    public void onResult(MessageApi.SendMessageResult sendMessageResult) {                        Log.d(TAG, "SendUpdateMessage: " + sendMessageResult.getStatus());                    }                });            } catch (Exception e) {                Log.d(TAG, "Task Fail: " + e);            }            return null;        }    }}

Step 2: Add service element on AndroidManifest.xml

<service android:name=".WeatherService" >    <intent-filter>        <action android:name="com.google.android.gms.wearable.BIND_LISTENER" />    </intent-filter></service>

Weather API

There are several Weather APIs available to use, such as Yahoo Weather API, Open Weather API, or AccuWeather API. We chose Open Weather as it’s easy to use and free.

// We create a interface in case we change to another API hostpublic interface WeatherApi {    WeatherInfo getCurrentWeatherInfo(double lon, double lat);}// WeatherInfo includes the narrow data which we needpublic class WeatherInfo {    private String cityName;    private String condition;       private int temperature;    private long sunrise;       private long sunset;    }// Implement OpenWeatherApipublic class OpenWeatherApi implements WeatherApi {    private static final String TAG ="OpenWeatherApi";    private static final String APIURL = "http://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&units=imperial&APPID=%s";    @Inject     private Context context;    @Override    public WeatherInfo getCurrentWeatherInfo(double lat, double lon) {        WeatherInfo w = null;        try {            // ObjectMapper is a RestClient supplied by Jackson            ObjectMapper mapper = new ObjectMapper();            mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);            String key = context.getResources().getString(R.string.openweather_appid);            String url = String.format(APIURL, lat, lon, key);            Log.d(TAG,"ApiUrl: "+url);            OpenWeatherQueryResult result = mapper.readValue(new URL(url), OpenWeatherQueryResult.class);            if ("200".equals(result.getCod())) {                w = new WeatherInfo();                w.setCityName(result.getName());                w.setTemperature((int) result.getMain().getTemp());                w.setSunset(result.getSys().getSunset());                w.setSunrise(result.getSys().getSunrise());                OpenWeatherData[] dataArray = result.getWeather();                if (dataArray != null && dataArray.length > 0) {                    w.setCondition(ConvertCondition(dataArray[0].getId()));                }            }        } catch (Exception e) {            e.printStackTrace();        }        return w;    }    }

Performance Considerations

We considered several approaches for communication between our wearable and handheld apps. The goal here was to preserve battery while providing up to date weather data.The approaches we tried included:

  1. A service that always runs on the phone with regular weather updates and pushes them to the watch face. This is not ideal as the service runs constantly though the wearable device may not even be paired.
  2. Next we tried enabling the service only when a wearable device is connected. i.e. When a wear is paired, get weather information and push it to the wearable device. This, however, meant that the service is running, even if our watch face is not selected. Here the handheld app may send data but there is no receiver.
  3. Finally, we moved the trigger for updating the weather data to the watch face. The timer runs on the watch face (we use the same timer for redrawing). If the weather information is stale, it sends a message to the handheld. The service on the handheld receives the message and gets the latest weather information and updates it on the wearable. Using this approach, the service only runs when the watch face is in use, and the weather data is kept up to date!

Figure 2: Weather Data Communication

 

Configuration Activity

A configuration activity to manage watch face attributes such as color, time format etc can exist on either the wearable or on the phone. The implementation of the activity is exactly like a regular activity, so we don’t cover this in detail, the source code is here. We will focus on how to set up the connection between a watch face and it’s configuration activity via AndroidManifest.xml.

AndroidManifest.xml on Wear

Add an activity element and add meta data inside of service

<service android:name=".WeatherWatchFaceService">    <!-- the setting is for the config activity on the handhold app-->               <meta-data            android:name="com.google.android.wearable.watchface.wearableConfigurationAction"                        android:value="com.mycompany.wearable.watchface.CONFIG" />        <!-- the setting is for the config activity on the wear app-->               <meta-data            android:name="com.google.android.wearable.watchface.wearableConfigurationAction"            android:value="com.mycompany.wearable.watchface.CONFIG" /></service><activity android:name=".WeatherWatchFaceConfigActivtiy">    <intent-filter>        <!--The value has to match the value of service’s meta-data-->        <action android:name="com.mycompany.wearable.watchface.CONFIG" />        <category android:name="com.google.android.wearable.watchface.category.WEARABLE_CONFIGURATION" />        <category android:name="android.intent.category.DEFAULT" />    </intent-filter></activity>

AndroidManifest.xml on Handheld

Add the activity on AndroidManifest.xml on Handheld.

<activity android:name=".WeatherWatchFaceConfigActivtiy">    <intent-filter>        <!--The value has to match the value of service’s meta-data-->        <action android:name="com.mycompany.wearable.watchface.CONFIG" />        <category android:name="com.google.android.wearable.watchface.category.COMPANION_CONFIGURATION" />        <category android:name="android.intent.category.DEFAULT" />    </intent-filter></activity>

After you deploy the apps, navigate to the change watch face screen, if the watch face has the config activity, a gear icon appears below the watch face on the wearable. On a phone, open the Android Wear app, a settings gear appears as well as a menu option is added for “Settings”. see Figure 4.

Figure 4: The config gear and menu on a wearable and on the android wear app.

 Sending and receiving the watch face configuration data is similar to how we sent and received weather data, so we don’t duplicate the same code here, you can refer the example above.

Storing data for multiple wearables

We want each wearable device to have its own configuration settings. Let’s say one watch face has a blue background and another has an orange background. In this case, we can’t just use DataApi to save the data on a phone because when we save the data, as it will use the phone’s ID for identification. We want custom settings for each watch face. So we used the following approach:

  1. Use MessageApi to send a ‘setting changed’ message to the wearable from the phone.
  2. When the wearable receives the message, use the DataApi on the wearable to save the data. Now, the DataApi will use the wearable’s ID to save the info and each wearable can use it’s own ID to map to it’s own settings.

Figure 5: Managing data for multiple devices.

Congratulations! Now the only step left is to publish your watch face on the Google Play Store. We’ll cover this in Part 4 of this series.

Have a question? Let us know here.

Tutorial: Building an Android Wear Watch Face, Part 2 : The Watch Interface

This is part 2 of a 4 part series where we explore the challenges of making wearable apps, by building an Android wear watch face with real-time weather data. In Part 1 We provided an overview of the app and created our first watch face. In this part we take a deeper dive into the watch face and the considerations that must be made in making the watch face. We will follow up in the Part 3 about building the real-time weather communication api and finally in Part 4, we show how to package and publish a watch face app.

You can checkout the source code on Github or download the final watch face from the Google Play Store.

This tutorial is separated to four posts:

Square Screen and Round Screen

Android Wear supports two different screen shapes, square (280px * 280px) and round (320px * 320px). In order to build a face that works well for both styles we’ll have to account for these differences.Some areas to consider are:

  • Font and Image Sizes: The round screen is larger, a font or image size that is suitable for this ends up taking too much room on a square face. Our solution was to apply two sets of sizes for fonts and images, one for each style.
  • Layout: The round screen doesn’t have corners, so you’ll have to design two layouts for them or one layout suitable for both.

Additional details can be found in the Android Design Guidelines.

Style Settings

There are system elements that you can set within your watch face, for example:

  • Indicators: the charging icon, disconnected icon, mute icon, etc.
  • The “OK Google” hot word.
  • The peek card.
  • The default clock.

Figure 1: the elements on a square screen.

You can control the positioning of these elements so that they better blend into your designs. These configurable options include:

  1. setCardPeekMode: Change the height of the peek card

      PEEK_MODE_SHORT

     PEEK_MODE_VARIABLE

  2. setBackgroundVisibility: whether a notification’s background is always displayed or shown temporarily. The background will obscure your watch face.
      Options: BACKGROUND_VISIBILITY_PERSISTENT, BACKGROUND_VISIBILITY_INTERRUPTIVE
  3. setShowSystemUiTime: Whether or not to show the default clock.
  4. setAmbientPeekMode: Whether the peek card is visible in ambient mode or not.
      Options: AMBIENT_PEEK_MODE_HIDDEN, AMBIENT_PEEK_MODE_VISIBLE
  5. setPeekOpacityMode: The opacity of the peek card.
      Options: PEEK_OPACITY_MODE_OPAQUE, PEEK_OPACITY_MODE_TRANSLUCENT
  6. setStatusBarGravity: The position of indicators.
    1. Options: Gravity.TOP, Gravity.CENTER, Gravity.BOTTOM, Gravity.RIGHT, Gravity.LEFT
    1. You combine a vertical position and a horiztonal position like so: .setStatusBarGravity(Gravity.TOP | Gravity.LEFT)

Note: the bottom positions are above the peek card and corner positions are not allowed on round screens.

  • setHotwordIndicatorGravity : the position of “OK Google”.Positioning for the Hotword is set the same way as the Status Bar.
  • setShowUnreadCountIndicator: Whether or not to show the unread count indicator

The round screens are less flexible when it comes to positioning of UI Elements. For example, because of the lack of corners, setStatusBarGravity(Gravity.RIGHT|Gravity.TOP) is not allowed on round Android Wear devices.In our watch face we use short peek cards, hide the peek card when in ambient mode, the peek card background is shown briefly, and we don’t show the default clock. Other watch faces may use different options.

@Overridepublic void onCreate(SurfaceHolder holder) {    super.onCreate(holder);    setWatchFaceStyle(new WatchFaceStyle.Builder(WeatherWatchFaceService.this)        .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)        .setAmbientPeekMode(WatchFaceStyle.AMBIENT_PEEK_MODE_HIDDEN)        .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)        .setShowSystemUiTime(false)        .build());}

Determine screen shape

We can determine the screen’s shape using ApplyWindowInsets, and change some of our face’s styles within this event. For example, we can make the font size bigger for the round face.

boolean mIsRound;@Overridepublic void onApplyWindowInsets(WindowInsets insets) {    super.onApplyWindowInsets(insets);    mIsRound = insets.isRound();    if (mIsRound){      mTextPaint.setTextSize(30);      } else{      mTextPaint.setTextSize(25);    }}

Interactive Mode and Ambient Mode

Unlike Android phones and tablets which just have two screen states, on or off, Wear devices have a third screen mode called ambient mode.The following are the definitions of the two active modes from the Android Developer Site

Interactive modeWhen the user moves their wrist to glance at their watch, the screen goes into interactive mode. Your design can use full color with fluid animation in this mode.Ambient modeAmbient mode helps the device conserve power. Your design should make clear to the user that the screen is in ambient mode by using only grayscale colors. Do not use a lot of white in ambient mode, since this distracting and hurts battery life on some screens. In this mode, the screen is only updated once every minute. Only show hours and minutes in ambient mode; do not show seconds. Your watch face is notified when the device switches to ambient mode, and you should thoughtfully design for it.

According to Google’s specifications, we need to change the watch face’s color palette to grayscale when in ambient mode. We can detect changes in screen mode with onAmbientModeChanged.

Step 1: Change paints in onAmbientModeChanged method

@Overridepublic void onAmbientModeChanged(boolean inAmbientMode) {    super.onAmbientModeChanged(inAmbientMode);    // when Ambient Mode changes, we changes the color of the background paint.    if (inAmbientMode){        mBackgroundPaint.setColor(Color.BLACK);        mWeatherConditionDrawable = mGrayWeatherConditionDrawable;    }    else {        mBackgroundPaint.setColor(mBackgroundColor);        mWeatherConditionDrawable = mColorWeatherConditionDrawable;    }    //Call invalidate() to cause the onDraw is invoked.    invalidate();}

Important Note: Wear will not redraw unless we call invalidate()

TIP:Inside onDraw() you can use isInAmbientMode() to determine which state the screen is in. However, it is more efficient to use onAmbientModeChanged, as you will not be checking the screen mode on every draw call.

Step 2: Draw the background

@Overridepublic void onDraw(Canvas canvas, Rect bounds) {    // Draw the background.    canvas.drawRect(bounds, mBackgroundPaint);    // Use canvas object to draw text on the center of the screen.    canvas.drawText(time,            bounds.centerX() - mTextXOffset,            bounds.centerY() - mTextYOffset,            mTextPaint);}

Step 3: Test it.In the Android Wear Emulator you can press Command + F7 (OSX) or F7 (Windows) to toggle between screen modes.

Setting Redraw Frequency

If you want your watch face to update more frequently than once a minute you have to create timers to redraw the screen yourself. For our watch we want to redraw the screen twice every second, ie. 500 milliseconds. In Android, there are a few classes we could use for our timer. We will use one of Google’s recommendations: Handler, which is a recursive timer. For more information on it, see this document.

final Handler mUpdateTimeHandler = new Handler() {    @Override    public void handleMessage(Message message) {        // when the handler is active, call invalidate() to make the screen redraw.        invalidate();        // only visible and interaction mode, we active the handler        // AmbientMode use another time.        if (isVisible() && !isInAmbientMode()) {            // run again in next 500 milliseconds            // the first parameter is message code, we don't use it here, so put 0.              mUpdateTimeHandler.sendEmptyMessageDelayed(0, 500);        }    }};

Step 1: Send a message to the handler when the screen is in interactive mode and remove all the messages from the handler when the screen is turned off.

@Overridepublic void onVisibilityChanged(boolean visible) {    super.onVisibilityChanged(visible);    // remove the messages which are already in the queue.    mUpdateTimeHandler.removeMessages(0);    if (isVisible() && !isInAmbientMode()) {        // send the instant message into the queue.        mUpdateTimeHandler.sendEmptyMessage(0);    }    }

2: Remove all the messages from the handler when the screen changes to ambient mode.

@Overridepublic void onAmbientModeChanged(boolean inAmbientMode) {    super.onAmbientModeChanged(inAmbientMode);    invalidate();    // remove the messages which are already in the queue.    mUpdateTimeHandler.removeMessages(0);}

3: Use the default timer for ambient mode. Once a minute is fast enough.

@Overridepublic void onTimeTick() {    super.onTimeTick();    // the event is only invoked once a minute when the wear is in ambient mode    invalidate();}

4: In onDraw, render all the images, designs, and text to the screen.

@Overridepublic void onDraw(Canvas canvas, Rect bounds) {    mTime.setToNow();    boolean mShouldDrawColon = (System.currentTimeMillis() % 1000) < 500;    String hourString = String.format("%02d", convertTo12Hour(mTime.hour));    String minString = String.format("%02d", mTime.minute);    float hourWidth = mTimePoint.measureText(hourString);    float minWidth = mTimePoint.measureText(minString);    float timeXOffset = (hourWidth + mColonWidth + minWidth) / 2;    float x = bounds.centerX() - timeXOffset;    float y = bounds.centerY() - mTimeYOffset;    canvas.drawText(hourString, x, y, mTimePoint);    x += hourWidth;    // draw colon when in ambient mode or the mShouldDrawColon is true.    if (isInAmbientMode() || mShouldDrawColon) {        canvas.drawText(":", x, y, mTimePoint);    }    x += mColonWidth;    canvas.drawText(minString, x, y, mTimePoint);}

Logging

Running the debugger is very slow, so we recommend you use the android.unit.Log class to help you debug.For example,

@Overridepublic void onDraw(Canvas canvas, Rect bounds) {    Log.d("HelloWatchFaceService", "I am drawing.");}

You can then use logcat to see what you have logged in Android Studio(Figure 20) Now you know all about the Android Wear Interface! In our next post we will show you how to connect your watch face to an Android Device and get real time weather data from the Open Weather API.

Check out Part 1 of the Tutorial here, or continue on to Part 3 here.

Tutorial: Building an Android Wear Watch Face, Part 1 : Overview

Wearable devices such as Android wear and the upcoming Apple watch are pioneering a brand new user experience, “Information at a glance”.This presents new opportunities for app developers to provide end-users with contextual, relevant and personalized information. However, it also introduces a whole new set of challenges for both designers and developers.In this tutorial we explore these challenges by building an Android wear watch face with real-time weather data (Figure 1). This tutorial is separated into four parts:

Before we get started, you can download the watch face from the Google Play Store or explore the source code on Github.

The Watch face

The watch face is our primary user interface. It provides contextual user information like time, date, weather & temperature “at a glance”. Furthermore, the watch face must also cater for system notifications, status icons and provide support for ambient mode that preserves battery life. We explore these in Part 2 -The Watch Interface.

The Weather data

We use the Open Weather Map API to retrieve the real-time weather at the user’s location. The API is great as it includes information such as sunrise, sunset, temperature and weather info to facilitate display of weather icons.

The Phone

The watch face cannot connect to the internet on it’s own, it must pair with a device to send or receive data. In our example, we create an android service for the overall watch face app. This service connects to the the Open Weather Map API to get the latest weather information. It then notifies the watch and provides it with the data needed to render the watch face. We explore these details in Part 3 – Real-time weather communication.

Data communication with android wear

Getting ready for development

Pair your wearable and phone.

Tip: Use a physical Android phone or tablet device

The Google Play Services API is required to communicate with an android watch face. It’s much easier and faster to use a physical device than to set it up on a phone emulator. Note: you can still use the emulator for the watch face if you wish.

Step 1: Install the Android Wear app on your mobile device.Step 2: Launch the Android Wear app and use it to connect your phone to a wearable or to an emulator.If you want to connect an emulator, click Connect Emulator. On the other hand, if you’re connecting to an actual wearable, click “Pair with a new wearable” (Figure 2).

Figure 2: Pair a wearable via the Android Wear app

Step 3:  (Emulator only) If you’re having issues is connecting to the wear emulator, you may need to open a tcp channel on your device.When we wrote this tutorial, we needed to this manually. If you see “Connecting…” in the Android Wear app for a long time, but nothing actually happens (Figure 3), you will need to follow this step.

Figure 3: Waiting connecting an emulator

Open CMD on Windows or Terminal on Mac, and switch to the Android SDK/Platform Tools folder (my path on mac is ~/Library/Android/sdk/platform-tools). Then execute the following command.

adb -d forward tcp:5601 tcp:5601

See here for more details on the adb command.

Enable Developer Options on your watch and phone

To test on a physical wearable device or phone during development, developer options for the device must be enabled. We’ll cover this flow for a wearable device, it’s essentially the same flow for enabling android devices for debugging:

Enable Developer settings on the Android Watch

Step 1: On a physical wearable device go to Settings -> About page. 2: Tap “Build number” seven times (Figure 4) until you can see a toast message which says you are a developer.At this point, a new entry called “Developer Options” should be added on the bottom of settings page (Figure 5). 3: Enabled ADB Debugging (Figure 6).ADB debugging is needed for debugging, logging and deploying packages make sure to enable it!

 4: The about page on an Android wear.

5: The developer options on the setting page.

6: ADB debugging option

Java, Android and IDE installation

In order to start developing, you will need familiarity with the Java language, Android and have Android Studio installed on your machine.

If you are new to Java, checkout this tutorial, for Android, checkout this tutorial.If you don’t have Android Studio yet, see here for installation instructions.

Install Android 5.0.1 SDK or higher

Next, you need to install the Android SDK. Be sure to install Android 5.0.1 SDK (API 21) or higher because classes needed for watch faces are only introduced in this version.To install the android SDK. Launch Android Studio and then open the SDK Manager (Menu > Tools > Android > SDK Manager), and ensure “Android 5.0.1 (API 21)”->”SDK Platform” is checked. To use the Android Wear emulators, you can also install the Android Wear Intel x86 Atom System Image (Figure 7).

Figure 7: Install packages & emulator for Android wear

Drawing the watch face

Create a new project for your mobile and wearable apps

The first step of making a watch face is to create a new project. We can use a template that Android Studio provides for this. The project template contains the basic environment for building a wearable app such as the gradle build files and project dependencies.Step 1: Open Android Studio and choose create a new Android Studio project (Figure 8).

Figure 8: Create a new Android Studio project.

Step 2: Enter project information such as the app name, company domain and the project location.Step 3: Choose Phone and Tablet where the “Minimum SDK” must be API 18 or higher and Wear where the “Minimum SDK” must be API 21 or higher. (Figure 9)

Figure 9: Choose apps for building Watch Faces

Step 4: In our example, an activity is not needed for either app. Click the Finish button and Android Studio will create the project.

TIP: We need to create a phone & tablet app even if data connections are not required for the wearable app. This is because the wearable app must be embedded inside a phone/tablet app for deployment to the Play Store. We will cover this in Part 4 – Packaging and publishing the watch face

Create a WatchFaceService

WatchFaceService is the base class for Android watch faces. There are two types of WatchFaceService that can be extended to provide a custom watch face:CanvasWatchFaceService which provides a Canvas to draw a watch face.Gles2WatchFaceService which supports OpenGL ES 2.0 features to render a 3D watch face.In the tutorial, we focus on CanvasWatchFaceService as it meet the needs for our app.Step 1: Create a new class named WeatherWatchFaceService that extends a CanvasWatchFaceService in the wear module. (Figure 10)

Figure 10: Create WeatherWatchFaceService.java

TIP: 

A wearable app can include more than one watch face, so you don’t need to create different apps for different watch faces.

Step 2: Add a subclass which extends CanvasWatchFaceService.Engine.

public class WeatherWatchFaceService extends CanvasWatchFaceService {    
    @Override    
    public Engine onCreateEngine() {        
        return new Engine();    
    }    
    private class Engine extends CanvasWatchFaceService.Engine {            
}}

Step 3: Override onDraw and use the canvas to draw the current time on the center of the screen.

private class Engine extends CanvasWatchFaceService.Engine {            
    Paint mTextPaint;    
    Float mTextXOffset;    
    Float mTextYOffset;    
    @Override    public void onCreate(SurfaceHolder holder) {        
        super.onCreate(holder);        // Create the Paint for later use        
        mTextPaint = new Paint();        
        mTextPaint.setTextSize(40);        
        mTextPaint.setColor(Color.WHITE);        
        mTextPaint.setAntiAlias(true);        
        // In order to make text in the center, we need adjust its position        
        mTextXOffset = mTextPaint.measureText("12:00") / 2;        
        mTextYOffset = (mTextPaint.ascent() + mTextPaint.descent()) / 2;    
    }    
@Override    public void onDraw(Canvas canvas, Rect bounds) {
        canvas.drawText("12:00",
                bounds.centerX() - mTextXOffset,
                bounds.centerY() - mTextYOffset,
                mTextPaint);
    }}

Figure 11 explains why we need to use Paint.ascent() and Paint.decent() to calculate the width and height of the time display. We can then adjust it’s starting position (in canvas.drawText) to center the time on the screen.

Figure 11: Font Metrics (Source)

TIP: Create objects such as the Paint object only once (in the onCreate method) instead if doing it every time onDraw. This reduce the performance overhead of creating and destroying objects in each invocation of the onDraw method.

Configure AndroidManifest.xml

If you have developed an Android app, you may already be familiar with the AndroidManifest.xml. It contains details such as the app name, version, permissions, default activity, services etc. The mechanism is similar for wearable apps. The following steps will add the necessary settings for this watch face.Step 1: Add a watch_face.xml in xml folder (Figure 12).

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

For watch faces we have to add this xml and configure it in AndroidManifest.xmlStep 2: Add preview images in drawable folder (Figure 12).You also need to create preview images for the square and round faces. You can download our preview images to get started.

Figure 12: Add watch face resources.

Step 3: Configure AndroidManifest.xmlAdd a service element as shown in the example below, which includes all the necessary configuration for a watch face service.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.mycomany.mywatchface" >    
<uses-feature android:name="android.hardware.type.watch" />    
<uses-permission 
android:name="com.google.android.permission.PROVIDE_BACKGROUND" />    
<uses-permission android:name="android.permission.WAKE_LOCK" />    
<application  android:allowBackup="true" 
android:icon="@drawable/ic_launcher" android:label="@string/app_name"  
android:theme="@android:style/Theme.DeviceDefault" >        
<service android:name=".WeatherWatchFaceService" android:label="Weather"  
android:allowEmbedded="true" 
android:permission="android.permission.BIND_WALLPAPER" >            
<meta-data  android:name="android.service.wallpaper"  
android:resource="@xml/watch_face" />            
<meta-data  android:name="com.google.android.wearable.watchface.preview"  
android:resource="@drawable/preview" />            
<meta-data 
android:name="com.google.android.wearable.watchface.preview_circular"                
android:resource="@drawable/preview_circular" />            
<intent-filter>                
<action android:name="android.service.wallpaper.WallpaperService" />                
<category 
android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />            
</intent-filter>        
</service>    
</application>
</manifest>

We have now created a basic watch face app, next we will explain how to run it.

Part 3: Run the App on a Wearable Emulator

In the following section, we will explain how to create a wearable emulator and run a watch face app on it.

Step 1: Open AVD Manager(Android Virtual Device)There are two ways to open AVD Manager:

  1. Menu > Tools > Android > AVD Manager
  2. Click AVD Manager button on Toolbar (Figure 13)

 

Figure 13: AVD Manager button

Step 2: Create an wearable emulator on AVD ManagerClick “Create Virtual Device” button. Then you can choose either Round or Square. In the example, we choose Round first. Then follow the instructions to finish the creation.

Figure 14: Create a wearable emulator

Step 3: Launch the AVD you just createdStep 4: Run the wear moduleClick run button to run the wear module(Figure 15). Android Studio will show up a Edit configuration dialog (Figure 16). For the watch face we don’t have a default Activity however, the Run command defaults to “Launch default Activity”. Simply select “Do not launch Activity” setting and click “Run” button (Figure 16).

Figure 15: Run button

Figure 16: Edit Configuration

If everything went as planned, you’ll see Success message on “Run” window(Figure 17).

 

Figure 17: Success message on “Run” window

Step 5: Change to the watch face.On the watch, there are two ways to change the current watch face.

  1. Short tap on the home screen > Settings > Roll to the bottom and tap “Change Watch face”
  2. Long tap on the home screen.

After open the setting page, select the watch face we just created (Figure 18).

Figure 18:

Choose the watch face.

Thats it! Our watch face is now running. Sure, it’s still basic, but we’ll cover the more advanced aspects in upcoming posts

  • Part 2 – System UI requirements for the watch face
  • 3 – Real-time weather communication
  • 4 – Packaging and publishing the watch face

We look forward to your thoughts, comments and question. Please do not hesitate to reach out via the comments section below.

Continue on to Part 2 of the tutorial here.