Android Wear Watch Face Design And Development

Introduction

The following are the topics at high level which I'm going to cover. By the end of the article you will have an idea of how to build a custom watch face service.

  • Android Wear Watch Face
  • Key things to consider while designing apps for watch faces
  • Demo Watch Face Service
  • Watch Face Service Engine
  • Project Structure
  • AndroidManifest.xml
  • Watch Face Service - onCreate override
  • Watch Face Service – onDraw override
  • Handling Ambient mode
  • Redrawing watch face with the updated time
  • Handling Time zone changes
  • Handling text offsets for rounded and square watches
  • Registering and Unregistering the Broadcast Receiver
  • Reference

Please take a look into the following link to have some understanding about Android Wear 

Android Wear Watch Face

If you are new to watch faces or haven’t heard about it, then you might be wondering what exactly it is? Android wear smart watches supports custom watch faces or apps that are designed to show time and other helpful information to the user. With Android wear, it’s very flexible in terms of changing the way how the watch UI or faces look like. You can literally pick the available watch faces or download one and use the same.

That being said, Android wear provides full customization to developers to build custom watch faces, which is very important as the smart watch users will have an option to choose the watch faces they wish. In December 2014, Google released office API for watch face development. Today, you will see numerous watch faces on play store.

Coming to the design challenges, if you are a developer who is interested in developing a custom watch face, you really have to take a look into the following design guidelines from android wearable team.

Here’s an example of a typical watch face would look like.

typical watch face
(Image credit - Smartwatchface)

Key things to consider while designing apps for watch faces

Quote: The key things are based on Android Design Guidelines.

  • Battery life – It’s very critical and highly important to take the battery usage into account which developing apps for watch faces. There are two modes with which the watch faces run. With ambient mode, you can show something really simply without even having to worry about the background image, etc. that’s because at this point of time, there will be no user interactions. The other mode is, interactive mode. It’s when the user is interacting with the watch, you can show watch faces with graphics and display with pleasing colors.

  • Display – As a watch face designer or a developer should consider displaying watch faces for ambient and interactive modes so one can take advantage of the battery usage. Also, the watch faces should be designed in such a way that it should work transparently in rectangular and rounded watches. Which means, the contents should not strip off or there should not be any offsets in displaying things on watch faces.

  • Data Fetch – There are times when you have to fetch data and show them on watch faces, say you wish to show the weather temperature information, do not fetch the data each and every minute. But instead, you can fetch it ones onCreate of watch faces service and later use the same. The idea is, you don’t want the watch battery drained.

  • Configurable – It’s not really a mandatory option but it’s good to have. You should let the user to configure watch faces, say choosing colors background or setting some text, etc.

Demo Watch Face Service

Let us see a demo on how to build a Watch Face Service and try to understand the inner workings of it. Here's the snapshot of our custom watch face service. The first image is shown on press of watch face, it allows the user to choose one of the available watch faces. The second one is what you will see after choosing the watch face.

Demo Watch Face Service

Please Note – We are going to reuse the sample code from [2]. This code is open source and has “Apache Version 2.0” License.

Before, we take a look into the sample code. First, we need to understand the necessary requirements for building a Watch Face Service. Create a new java class and extend the same from CanvasWatchFaceService to create our own Watch Face Service. Further, we have to override the following method to return an Engine instance.

You can see below, we have a private inner class named “WatchFaceEngine” which extends from Engine and overrides certain methods required for building the watch face service.

  1. public class CustomWatchFaceService extends CanvasWatchFaceService {  
  2.   
  3.     @Override  
  4.     public Engine onCreateEngine() {  
  5.         return new WatchFaceEngine();  
  6.     }  
  7.   
  8.     private class WatchFaceEngine extends Engine {...}  
  9. }  
Watch Face Service Engine

The CanvasWatchFaceService has an “Engine” class, which contains methods that one can override and draw on canvas and that’s how we are going to display something on watch face service. The following is the code snippet of “Engine” class.
  1. public class Engine extends android.support.wearable.watchface.WatchFaceService.Engine {  
  2.     public Engine() {  
  3.         super(CanvasWatchFaceService.this);  
  4.     }  
  5.   
  6.     public void onDestroy() {  
  7.     }  
  8.   
  9.     public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {  
  10.     }  
  11.   
  12.     public void onSurfaceRedrawNeeded(SurfaceHolder holder) {  
  13.     }  
  14.   
  15.     public void onSurfaceCreated(SurfaceHolder holder) {  
  16.     }  
  17.   
  18.     public void invalidate() {  
  19.     }  
  20.   
  21.     public void postInvalidate() {  
  22.     }  
  23.   
  24.     public void onDraw(Canvas canvas, Rect bounds) {  
  25.     }  
  26. }  
An “Engine” is an abstract class which extends itself from android.service.wallpaper.WallpaperService.Engine and has methods that we can override and provide implementation as part of watch face service.

The following is the skeleton of the “Engine” class. We are going to override and implement some of the methods of Engine class.
  1. public class Engine extends android.support.wearable.watchface.WatchFaceService.Engine {  
  2.     public void onAmbientModeChanged(boolean inAmbientMode) {  
  3.     }  
  4.   
  5.     public void onInterruptionFilterChanged(int interruptionFilter) {  
  6.     }  
  7.   
  8.     public void onPeekCardPositionUpdate(Rect rect) {  
  9.     }  
  10.   
  11.     public void onUnreadCountChanged(int count) {  
  12.     }  
  13.   
  14.     public void onPropertiesChanged(Bundle properties) {  
  15.     }  
  16.   
  17.     public void onTimeTick() {  
  18.     }  
  19.   
  20.     public void onCreate(SurfaceHolder holder) {  
  21.     }  
  22.   
  23.     public void onVisibilityChanged(boolean visible) {  
  24.     }  
  25.   
  26.     public final boolean isInAmbientMode() {  
  27.     }  
  28.   
  29.     public final int getInterruptionFilter() {  
  30.     }  
  31.   
  32.     public final int getUnreadCount() {  
  33.     }  
  34.   
  35.     public final Rect getPeekCardPosition() {  
  36.     }  
  37. }  
Project Structure

Let us take a look into the project structure and see the necessary things required to build a watch face service. The following is the snapshot of the project structure, all we need is a custom watch face service class, launcher icons that we will be using within the AndroidManifest.xml file for displaying the watch face service icon.

Project Structure

AndroidManifest.xml

The following is the code snippet of the AndroidManifest.xml file content where you can see the required permissions for our application. The “WAKE_LOCK” permission is something which makes the smart watch CPU on so that the watch face service can do some operation such as redrawing the canvas. It’s required as the wearable goes to asleep if it’s ideal for some time.

Under <application> element, you will see the <service> element defined with our custom watch service. Also the meta-data for wallpaper, preview and preview_circular is also required. In the end, we are going to set the <intent-filter> with the action as WallpaperService and category as WATCH_FACE.
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="com.tutsplus.androidwearwatchface" >  
  4.   
  5.     <uses-feature android:name="android.hardware.type.watch" />  
  6.   
  7.     <uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />  
  8.     <uses-permission android:name="android.permission.WAKE_LOCK" />  
  9.   
  10.     <application  
  11.         android:allowBackup="true"  
  12.         android:icon="@mipmap/ic_launcher"  
  13.         android:label="@string/app_name"  
  14.         android:theme="@android:style/Theme.DeviceDefault" >  
  15.         <service  
  16.             android:name=".service.CustomWatchFaceService"  
  17.             android:label="Tuts+ Wear Watch Face"  
  18.             android:permission="android.permission.BIND_WALLPAPER" >  
  19.             <meta-data  
  20.                 android:name="android.service.wallpaper"  
  21.                 android:resource="@xml/watch_face" />  
  22.             <meta-data  
  23.                 android:name="com.google.android.wearable.watchface.preview"  
  24.                 android:resource="@mipmap/ic_launcher" />  
  25.             <meta-data  
  26.                 android:name="com.google.android.wearable.watchface.preview_circular"  
  27.                 android:resource="@mipmap/ic_launcher" />  
  28.   
  29.             <intent-filter>  
  30.                 <action android:name="android.service.wallpaper.WallpaperService" />  
  31.   
  32.                 <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />  
  33.             </intent-filter>  
  34.         </service>  
  35.     </application>  
  36.   
  37. </manifest>  
Watch Face Service - onCreate override

Let’s dig into the custom watch face service class to understand the inner working. Here’s the code snippet of onCreate override. We are going to set the watch face style by making a call tosetWatchFaceStyle method by passing in an instance of WatchFaceStyle. Notice the Builder method is called with the custom watch face service. Various other methods for setting the background visibility, card peek mode etc. is called so it sets the appropriate WatchFaceStyle properties.
  1. @Override  
  2. public void onCreate(SurfaceHolder holder) {  
  3.     super.onCreate(holder);  
  4.   
  5.     setWatchFaceStyle( new WatchFaceStyle.Builder( CustomWatchFaceService.this )  
  6.                     .setBackgroundVisibility( WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE )  
  7.                     .setCardPeekMode( WatchFaceStyle.PEEK_MODE_VARIABLE )  
  8.                     .setShowSystemUiTime( false )  
  9.                     .build()  
  10.     );  
  11.   
  12.     initBackground();  
  13.     initDisplayText();  
  14.   
  15.     mDisplayTime = new Time();  
  16. }  
There exists two private methods for initializing the background and displaying  text. Here’s the code snippet for the same. All we do is, setting the Paint color and Paint text color instance with the color, text size, etc. These paint colors are used later on canvas for displaying text and rectangle.
  1. private void initBackground() {  
  2.     mBackgroundColorPaint = new Paint();  
  3.     mBackgroundColorPaint.setColor( mBackgroundColor );  
  4. }  
  5.   
  6. private void initDisplayText() {  
  7.     mTextColorPaint = new Paint();  
  8.     mTextColorPaint.setColor( mTextColor );  
  9.     mTextColorPaint.setTypeface( WATCH_TEXT_TYPEFACE );  
  10.     mTextColorPaint.setAntiAlias( true );  
  11.     mTextColorPaint.setTextSize( getResources().getDimension( R.dimen.text_size ) );  
  12. }  
Watch Face Service – onDraw override

Now let us try to understand the code block for onDraw override method. We need to set the display time and make a call to draw the background and text displaying the current time.
  1. @Override  
  2. public void onDraw(Canvas canvas, Rect bounds) {  
  3.     super.onDraw(canvas, bounds);  
  4.   
  5.     mDisplayTime.setToNow();  
  6.   
  7.     drawBackground( canvas, bounds );  
  8.     drawTimeText( canvas );  
  9. }  
The following is the code snippet for drawBackground and drawTimeText methods where we are basically drawing on a Canvas instance. The background color is set and a formatted text is displayed showing the current time in “AM” or “PM”.
  1. private void drawBackground( Canvas canvas, Rect bounds ) {  
  2.     canvas.drawRect( 00, bounds.width(), bounds.height(), mBackgroundColorPaint );  
  3. }  
  4.   
  5. private void drawTimeText( Canvas canvas ) {  
  6.     String timeText = getHourString() + ":" + String.format( "%02d", mDisplayTime.minute );  
  7.     if( isInAmbientMode() || mIsInMuteMode ) {  
  8.         timeText += ( mDisplayTime.hour < 12 ) ? "AM" : "PM";  
  9.     } else {  
  10.         timeText += String.format( ":%02d", mDisplayTime.second);  
  11.     }  
  12.     canvas.drawText( timeText, mXOffset, mYOffset, mTextColorPaint );  
  13. }  
  14.   
  15. private String getHourString() {  
  16.     if( mDisplayTime.hour % 12 == 0 )  
  17.         return "12";  
  18.     else if( mDisplayTime.hour <= 12 )  
  19.         return String.valueOf( mDisplayTime.hour );  
  20.     else  
  21.         return String.valueOf( mDisplayTime.hour - 12 );  
  22. }  
Handling Ambient mode

Let us take a look into the code block of watch face service to handle ambient mode. As previously discussed about the watch face service design, the ambient mode is something we have to explicitly handle to make sure we set appropriate background or set color etc. Here’s the place you can do something to minimize the battery usage.

Here’s what we are doing.
  1. Set the text color to white in case of ambient mode only.

  2. Make a call to setAntiAlias method which will be set to false in ambient mode. Anti-aliasing basically makes the borders smooth, visually it looks appealing but we don’t have to do this in ambient mode.

  3. Make a call to invalidate method to redraw the canvas.

  4. Update timer so we are not going to redraw canvas for every one second, which is not really required for ambient mode.
  1. @Override  
  2. public void onAmbientModeChanged(boolean inAmbientMode) {  
  3.     super.onAmbientModeChanged(inAmbientMode);  
  4.   
  5.     if( inAmbientMode ) {  
  6.         mTextColorPaint.setColor( Color.parseColor( "white" ) );  
  7.     } else {  
  8.         mTextColorPaint.setColor( Color.parseColor( "red" ) );  
  9.     }  
  10.   
  11.     if( mIsLowBitAmbient ) {  
  12.         mTextColorPaint.setAntiAlias( !inAmbientMode );  
  13.     }  
  14.   
  15.     invalidate();  
  16.     updateTimer();  
  17. }  
Redrawing watch face with the updated time

When the watch face service is running, you want to make sure the time gets updated every second. The following is the code which is responsible for redrawing the canvas with the update rate as 1000 millisecond.

Below you see we are overriding handleMessage method and coding the logic to update or redraw canvas only for interactive mode. If the user is actively using the watch and it’s not in ambient mode, then we get the current time in milliseconds and calculate the delay based on the current time and the update rate i.e. 1000 millisecond.

If you are wondering how the time hander gets called for every one second, there is a method sendEmptyMessageDelayed which we are calling with the “int” value - MSG_UPDATE_TIME_ID and delay which triggers the handleMessage call.
  1. private final Handler mTimeHandler = new Handler() {  
  2.     @Override  
  3.     public void handleMessage(Message msg) {  
  4.         switch( msg.what ) {  
  5.             case MSG_UPDATE_TIME_ID: {  
  6.                 invalidate();  
  7.                 if( isVisible() && !isInAmbientMode() ) {  
  8.                     long currentTimeMillis = System.currentTimeMillis();  
  9.                     long delay = mUpdateRateMs - ( currentTimeMillis % mUpdateRateMs );  
  10.                     mTimeHandler.sendEmptyMessageDelayed( MSG_UPDATE_TIME_ID, delay );  
  11.                 }  
  12.                 break;  
  13.             }  
  14.         }  
  15.     }  
  16. };  
Handling Time zone changes

Let us see how the time zone changes are handled in updating the watch face with the time as per the time zone where you are in.

The following is the code snippet where you can see the time instance is cleared by making a call to “clear” method and then we are setting the time to current time.
  1. final BroadcastReceiver mTimeZoneBroadcastReceiver = new BroadcastReceiver() {  
  2.     @Override  
  3.     public void onReceive(Context context, Intent intent) {  
  4.         mDisplayTime.clear( intent.getStringExtra( "time-zone" ) );  
  5.         mDisplayTime.setToNow();  
  6.     }  
  7. };  
Handling text offsets for rounded and square watches

When it comes to displaying text in canvas for square and rounded devices, we should be setting the appropriate offset so the text gets displayed correctly. For that, we need to override onApplyWindowInsets method and set the offset based on whether the device is rounded or not.
  1. @Override  
  2. public void onApplyWindowInsets(WindowInsets insets) {  
  3.     super.onApplyWindowInsets(insets);  
  4.   
  5.     mYOffset = getResources().getDimension( R.dimen.y_offset );  
  6.   
  7.     if( insets.isRound() ) {  
  8.         mXOffset = getResources().getDimension( R.dimen.x_offset_round );  
  9.     } else {  
  10.         mXOffset = getResources().getDimension( R.dimen.x_offset_square );  
  11.     }  
  12. }  
Registering and Unregistering the Broadcast Receiver

Previously, we have seen the usage of a broadcast receiver in our watch face service. In order for the broadcast receiver to function, we have to register with the appropriate IntentFilter. We are going to register the broadcast receiver only when the watch face is visible and if we have not yet registered. You can see below, we are creating an instance of IntentFilter with the action Intent.ACTION_TIMEZONE_CHANGED and then make a call to registerReceiver method passing in the broadcast receiver and intent filter instance.

There’s one important thing we have to do is to unregister receiver when the watch is not active. The visibility gets set to false so we can make a call to unregisterReceiver passing in the broadcast receiver instance.
  1. @Override  
  2. public void onVisibilityChanged( boolean visible ) {  
  3.     super.onVisibilityChanged(visible);  
  4.   
  5.     if( visible ) {  
  6.         if( !mHasTimeZoneReceiverBeenRegistered ) {  
  7.   
  8.             IntentFilter filter = new IntentFilter( Intent.ACTION_TIMEZONE_CHANGED );  
  9.             CustomWatchFaceService.this.registerReceiver( mTimeZoneBroadcastReceiver, filter );  
  10.   
  11.             mHasTimeZoneReceiverBeenRegistered = true;  
  12.         }  
  13.   
  14.         mDisplayTime.clear( TimeZone.getDefault().getID() );  
  15.         mDisplayTime.setToNow();  
  16.     } else {  
  17.         if( mHasTimeZoneReceiverBeenRegistered ) {  
  18.             CustomWatchFaceService.this.unregisterReceiver( mTimeZoneBroadcastReceiver );  
  19.             mHasTimeZoneReceiverBeenRegistered = false;  
  20.         }  
  21.     }  
  22.   
  23.     updateTimer();  
  24. }  
Reference
  1. Android Design Guidelines.
  2. Github Code Sample with Apache 2.0 opensource License.


Similar Articles