When you buy a watch you want it to, well, tell the time. So most Android Wear watches include an always-on screen—no tapping, twisting or shaking required to see what time it is. With Android Wear 5.1 or higher, we have expanded this option to apps so that they can stay visible as long as you need them instead of disappearing when you drop your arm. Apps that supports this functionality are called always-on apps. There are numerous use cases that can benefit from being an always-on app, from shopping lists to exercise tracking.

This codelab will introduce you to the key concepts behind always-on apps. It will then walk you through converting an existing stopwatch app into an always-on app which will go on for as long as the user needs it . There is also a bonus section on increasing the refresh rate of the app while it is in ambient mode from once per minute to once every 10 seconds.

Concepts

To start off let's learn a little bit about Android Wear and the key concepts behind always-on apps.

Android Wear is a wearable platform designed for small, powerful devices worn on the body. It is designed to deliver useful information when you need it most, intelligent answers to spoken questions, and tools to help reach fitness goals.

Some of these interaction can be brief, for example checking the location of your next meeting. Others can be longer, such as referring to your grocery list while shopping. During such longer interactions the default screen timeout can pause the application in the middle of the activity, resulting in friction in the user experience.. There are two potential solutions to this issue:

Similar to watch faces, an always on app has two modes and is able to switch between them in a session:

With the basic concepts out of the way, let's get started! To make this as quick as possible, we have prepared a sample project for you to build on. It contains some basic code and application settings necessary for building watch faces.

You can either download all the sample code to your computer...

Download Zip

...or clone the GitHub repository from the command line.

$ git clone https://github.com/googlesamples/io2015-codelabs.git

Start Android Studio, and select "Open an existing Android Studio project" from the Welcome screen, open the project directory, navigate to the directory wear/always-on and open the build.gradle file in that directory:

If the following screen appears, click OK on "Import Project from Gradle" screen without making any changes.

In the upper left hand corner of the project window, you should see something like this:

There are three folder icons. Each of them are known as 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 this has finished before making code changes. This will allow 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".

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.

Let's run it on a watch / emulator.

Waiting for device.
Target device: Android_Wear_Round_API_22 [emulator-5554]
Uploading file
        local path: /Users/hellouser/AndroidStudioProject/android-codelab-always-on/1-base/build/outputs/apk/1-base-debug.apk
        remote path: /data/local/tmp/com.android.example.ambientstopwatch
Installing com.android.example.ambientstopwatch
DEVICE SHELL COMMAND: pm install -r "/data/local/tmp/com.android.example.ambientstopwatch"
pkg: /data/local/tmp/com.android.example.ambientstopwatch
Success


Launching application: com.android.example.ambientstopwatch/com.android.example.alwaysonstopwatch.StopwatchActivity.
DEVICE SHELL COMMAND: am start -n "com.android.example.ambientstopwatch/com.android.example.alwaysonstopwatch.StopwatchActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.android.example.ambientstopwatch/com.android.example.alwaysonstopwatch.StopwatchActivity }

Here's what it should look like. Don't worry if the power button to the right do not appear in the emulator - this is okay!

All right, you're set up and ready to start converting the application into an always-on app. We'll set off using the 1-base module, which is the starting point for the stopwatch that 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.

Overview of key components

Summary

In this step you've learned about:

Next up

Let's start making this app an always-on app!

In this step, we will start making our app an always-on app. It will have the ability to jump between active and ambient mode and it will stay in the foreground until the user explicitly dismisses it. The screen will update once a minute which is the default for always-on apps.

Gradle file changes

In order to reference the new functionalities related to always-on apps, we need to add one dependencies to the build.gradle file for com.google.android.wearable:wearable:1.0.0:

dependencies {
    compile 'com.google.android.support:wearable:1.2.0'
    // Add the following line
    provided 'com.google.android.wearable:wearable:1.0.0'
}

Manifest changes

To convert our app to an always-on app, we need to change several attributes related to our app in AndroidManifest.xml . This is located here:

We need to make the following changes to the file:

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-library android:name="com.google.android.wearable" android:required="true" />
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

Switching on Ambient Mode

Supporting ambient mode and making our app an always-on app requires additional lifecycle states for the activity. These additional states are built into a new activity class called WearableActivity. To support ambient mode, we need to perform the following steps:

public class StopwatchActivity extends WearableActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_stop_watch);
    setAmbientEnabled();
    // ...

Tell the application what to do in ambient mode

If you run the application now, it will stay in the foreground but it does not understand what it should do differently in interactive mode and ambient mode. As a result, in ambient mode, it will be stuck at whatever time that it gets to and there will be no update until you wake it up into interactive mode.

Before we begin changing the lifecycle code, we will need to: 1) add a notice to remind the user that the screen is updated once a minute (this is the default for always-on apps and you can find out how you can change it in the next section) and 2) load some of the settings including the colours that we use for when we are in interactive mode so we can wake up to them from a black and white ambient mode:

<string name="updatefrequencynotice">Updates every minute</string>
<TextView android:id="@+id/notice"
            android:layout_columnSpan="2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="@string/updatefrequencynotice"
            android:visibility="invisible"
            android:textColor="@color/white"
            android:includeFontPadding="false"
            android:textSize="12sp" />
private int mActiveBackgroundColor;
private int mActiveForegroundColor;
mNotice = (TextView) findViewById(R.id.notice);
mNotice.getPaint().setAntiAlias(false);
mBackground = (GridLayout) findViewById(R.id.grid_background);
mActiveBackgroundColor = getResources().getColor(R.color.activeBackground);
mActiveForegroundColor = getResources().getColor(R.color.activeText);

Another thing we should do is to stop the handler from running in ambient mode, in the method updateDisplayAndSetRefresh we should add a check to see if the watch is in ambient mode before we schedule a delay call to the handler. Developers can check whether the app is currently in ambient mode by calling isAmbient() - wrap this code in a check like so

//...
if(!isAmbient()) {
    long timeMs = System.currentTimeMillis();
    long delayMs = ACTIVE_INTERVAL_MS - 
        (timeMs % ACTIVE_INTERVAL_MS);
    Log.d(TAG, "NOT ambient - delaying by: " + delayMs);
    mActiveModeUpdateHandler
        .sendEmptyMessageDelayed(MSG_UPDATE_SCREEN, delayMs);
}
//...

When the watch is in ambient mode (and in deep sleep), there is a callback for updates, so you do not need the handler. In addition, a handler can not wake up the processor from a sleep state, so it won't work anyway. We will expand on this in the next section.

To tell the system what to do in ambient mode, we override three methods

onEnterAmbient(...)

This is called when the application goes from interactive to ambient mode. This is where we typically change the formatting of the screen elements. Feel free to Copy & Paste the following code into the StopwatchActivity to account for ambient mode.

@Override
public void onEnterAmbient(Bundle ambientDetails) {
   Log.d(TAG, "ENTER Ambient");
   super.onEnterAmbient(ambientDetails);

   if (mRunning) {
       mActiveModeUpdateHandler.removeMessages(R.id.msg_update);
       mNotice.setVisibility(View.VISIBLE);
   }

   mActiveClockUpdateHandler.removeMessages(R.id.msg_update);

   mTimeView.setTextColor(Color.WHITE);
   Paint textPaint = mTimeView.getPaint();
   textPaint.setAntiAlias(false);
   textPaint.setStyle(Paint.Style.STROKE);
   textPaint.setStrokeWidth(2);

   mStartStopButton.setVisibility(View.INVISIBLE);
   mResetButton.setVisibility(View.INVISIBLE);
   mBackground.setBackgroundColor(Color.BLACK);

   mClockView.setTextColor(Color.WHITE);
   mClockView.getPaint().setAntiAlias(false);

   updateClock();
}

onExitAmbient()

Basically we invert what we have done before within onEnterAmbient(...)

@Override
public void onExitAmbient() {
   Log.d(TAG, "EXIT Ambient");
   super.onExitAmbient();

   mTimeView.setTextColor(mActiveForegroundColor);
   Paint textPaint = mTimeView.getPaint();
   textPaint.setAntiAlias(true);
   textPaint.setStyle(Paint.Style.FILL);

   mStartStopButton.setVisibility(View.VISIBLE);
   mResetButton.setVisibility(View.VISIBLE);
   mBackground.setBackgroundColor(mActiveBackgroundColor);

   mClockView.setTextColor(mActiveForegroundColor);
   mClockView.getPaint().setAntiAlias(true);

   mActiveClockUpdateHandler.sendEmptyMessage(R.id.msg_update);

   if (mRunning) {
       mNotice.setVisibility(View.INVISIBLE);
       updateDisplayAndSetRefresh();
   }
}

onUpdateAmbient()

This method is being called once a minute while in ambient mode by the framework and developers should tell Android Wear what to update by:

If you run the app now, it should show the something similar to following in interactive mode - it's not all that different to before:

However, if you wait for a timeout on a real device (for emulator press F7 or fn + F7) to switch to ambient mode, you will see the following and this will update once a minute:

Having problems?

If you see the error INSTALL_FAILED_MISSING_SHARED_LIBRARY, try updating the Wear emulator to version 22 or later. The ambient APIs are only available from this version.

Summary

In this step you've learned about:

Next up

An optional activity to learn about increasing the update frequency of the app to more than once a minute while in ambient mode. Most apps won't need to update faster than once per minute but this maybe appropriate, for example, for a running app showing the user the current pace.

If you still have time but don't fancy having a go at increasing the refresh rate, we encourage you to alter the different parameters of the screen elements, for example, layout, stroke size, color of the various screen element, etc. Let's see what you get!

For some applications, developers may want to update more frequently than once per minute. Since the Android Wear device is in deep sleep in ambient mode, the Handler class will not run in this state. As a result, we will need to setup an AlarmManager which will wake up the application at set intervals and update the screen.

Set up the AlarmManager

The first step is to set up the AlarmManager variable and a PendingIntent which will tell Android which Activity to launch when the AlarmManager fires. To do this, we will make the following changes in StopwatchActivity:

Intent intent = new Intent(getApplicationContext(),
                        StopwatchActivity.class);
mAmbientStatePendingIntent = PendingIntent.getActivity(
                getApplicationContext(),
                MSG_UPDATE_SCREEN,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT);
<uses-permission
    android:name="com.android.alarm.permission.SET_ALARM"/>
<string name="updatefrequencynotice">Updates every 10 seconds</string>

Activate the AlarmManager

With the basics in place, it's now time to add the code to trigger the AlarmManager. We need to update the code where previously we would have had the Handler code updating the screen. So this will be in the updateDisplayAndSetRefresh method:

if (!isAmbient()) {
    // same code as before...
} else {
    // Our new code for AlarmManager goes here!
}
long timeMs = System.currentTimeMillis();
long delayMs = AMBIENT_INTERVAL_MS - (timeMs % AMBIENT_INTERVAL_MS);
long triggerTimeMs = timeMs + delayMs;
mAmbientStateAlarmManager.setExact(
                        AlarmManager.RTC_WAKEUP,
                        triggerTimeMs,
                        mAmbientStatePendingIntent);
@Override
public void onNewIntent(Intent intent) {
    super.onNewIntent(intent);

    setIntent(intent);
    updateDisplayAndSetRefresh();
}

If you run the application again, it should update every 10 seconds:

Summary

In this step you've learned about: