Mobile
Article

Designing Always-On Smart Watch Apps

By Huan Liu

This is the year when smart watches take off. Some estimate the Apple Watch sold several million units alone on its first day of release. Android Wear watches have sold more than 1 million units in less than a year and the Pebble watch launched a successful Kickstarter campaign for its new Pebble Color product.

As smart watch platforms increase in popularity, it’s time to think about developing apps for smart watches. Smart watch apps differ from smart phone apps, in that most are designed for light interactions. A user may spend hours on their phone, but seconds on their watch. Smart watch platforms limit what an app can do to reduce interaction time. For example, iOS WatchKit suspends a running app when users lower their wrist and Android Wear returns to the clock face when users dim the screen.

If a watch app requires longer interaction time, developers must take special care to circumvent platform limitations. An example is a golf swing analyzer app I helped develop that runs constantly to track the user’s golf swing. In this article, I will show you some of the ways to keep an Android Wear watch app “always on”.

“always-on” mode

In recognition of the need for “always-on” apps, Google introduced the Ambient Mode for Android Wear watches. If you are using the latest wearable support library 1.2.0 and up, it can be enabled easily. First, make sure the activity inherits from Wearable Activity and enable Ambient Mode when the activity starts:

public class MainActivity extends WearableActivity {

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setAmbientEnabled();
    ...
}

Next, listen to Ambient switching callback functions. When in Ambient Mode, aim for a minimal UI to conserve battery, for example a black background with minimal white graphics and text.

@Override
public void onEnterAmbient(Bundle ambientDetails) {
    super.onEnterAmbient(ambientDetails);

    mStateTextView.setTextColor(Color.WHITE);  // use minimal white graphics and text
    mStateTextView.getPaint().setAntiAlias(false);
}

@Override
public void onExitAmbient() {
    super.onExitAmbient();

    mStateTextView.setTextColor(Color.GREEN);
    mStateTextView.getPaint().setAntiAlias(true);
}

Apart from a couple of lines to set in the application manifest file (consult Android Wear’s documentation for more details), these lines of code are all that is required to turn on Ambient Mode. When a user clicks the power button or palms the screen, instead of showing the clock, the app remains on top and it enters Ambient Mode.

Using notifications to bring an app to the foreground

There are limitations to the Android Wear Ambient Mode. Developers should update the screen infrequently, e.g., once every minute. Otherwise an alarm must be set to wake the CPU periodically. However, the CPU cycle in Ambient Mode is limited.

If an app requires constant CPU cycles, Ambient Mode is not suitable. I worked on an Android Wear fitness app that is a perfect example. It constantly monitors users’ movement and looks for exercise patterns, thus it constantly requires CPU cycles for computation.

In order not to lose CPU cycles, the fitness app must disable the Ambient Mode. If the user accidentally touches the power button or palms the screen, the app will go to the background. Because the Ambient Mode is disabled, when users look at their watch again, it will be showing the clock. This is a poor user experience because, it’s easy to accidentally touch the power button. If users want to continue, they must go to the start menu and find the app again.

Fortunately, there is an easy trick. The app can send a notification when the user closes the app, then the user can tap the notification card to resume. The app can run the following code in onStop().

public void onStop() {
        super.onStop();
        if (!isFinishing()) {  // if not terminating, send a notification so that we can wake up if needed
            // create intent that re-launches the activity
            Intent intent = new Intent(getApplicationContext(), MyActivity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        // when user dismiss the notification, terminate the app as well
            Intent deleteIntent = new Intent(getApplicationContext(), MyActivity.class);
            deleteIntent.setAction("delete");
            PendingIntent pendingDeleteIntent = PendingIntent.getActivity(getApplicationContext(), 0, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT);

            NotificationCompat.Action action = new NotificationCompat.Action.Builder(android.R.drawable.ic_media_play, null, pendingIntent).build();

            // set notification to let user know
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                    .setSmallIcon(R.drawable.ic_launcher)
                    .setContentTitle("MyApplication")
                    .setContentText("Tap to resume.")
                    .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.launchbg))
                    .setLocalOnly(true)
                    .setDefaults(Notification.DEFAULT_VIBRATE)   // must vibrate to wake up the watch
                    .setDeleteIntent(pendingDeleteIntent)
                    .addAction(action)
                    .extend(new NotificationCompat.WearableExtender().setContentAction(0))
                    ;


            Notification notification = builder.build();

            NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(this);
            notificationManagerCompat.notify(Constant.NOTIFICATION_RESUME, notification);
        }
  }

When a user presses the power button, the app merely goes to the background, still maintaining all its states. When the user presses the notification, the activity will be brought back from the background in its original state.

The code above adds a delete intent in the notification. This intent is triggered when the user dismisses the notification. As the user may have pressed the power button deliberately, this gives the user a chance to completely kill the application. Inside the app, the delete intent is captured and terminates the app properly:

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);

   String command = intent.getAction();
   if (command != null && command.equals("delete")) {
        finish();
    }
}

Apple Watch lacks an “always-on” concept. But Apple has taken a different approach to handle this need. When users lower their wrist, an app may be suspended, and when users raise their wrist, the app will be live again. Because the framework interaction is completely different from Android Wear, designing an always-on app on Apple Watch requires a new approach, which I will cover in a future post.

More:

No Reader comments

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in Mobile, once a week, for free.