Keep your app visible on Wear

Wear OS automatically handles moving into low-power mode for an active app when a user is no longer using their watch. This is called the system ambient mode. If the user interacts with the watch again within a certain time frame, Wear OS brings the user back into the app where they left off.

For specific use cases, for example, a user wanting to see heart rate and pace during a run, you can also control what's displayed in the low-power (ambient mode). Wear OS apps that run in both ambient and interactive modes are called always-on apps.

Making an app constantly visible impacts battery life, so consider that impact when adding this feature to your app.

Configure your project

To support ambient mode, follow these steps:

  1. Create or update your project based on the configurations on the Create and run a wearable app page.
  2. Add the WAKE_LOCK permission to the Android Manifest file:
<uses-permission android:name="android.permission.WAKE_LOCK" />

Ambient mode using the AmbientModeSupport class

To use the AmbientModeSupport class do the following:

  1. Create a subclass of FragmentActivity or one of its subclasses.
  2. Implement the AmbientCallbackProvider interface, as in the following example. Override the getAmbientCallback() method to provide the callbacks needed for reacting to ambient events from the Android system. In a later step, you will create the custom callback class.

    Kotlin

    class MainActivity : AppCompatActivity(), AmbientModeSupport.AmbientCallbackProvider {
        …
        override fun getAmbientCallback(): AmbientModeSupport.AmbientCallback = MyAmbientCallback()
        …
    }
    

    Java

    public class MainActivity extends AppCompatActivity implements AmbientModeSupport.AmbientCallbackProvider {
        …
        @Override
        public AmbientModeSupport.AmbientCallback getAmbientCallback() {
            return new MyAmbientCallback();
        }
        …
    }
    
  3. In the onCreate() method, enable Ambient mode by calling AmbientModeSupport.attach(FragmentActivity). This method returns an AmbientModeSupport.AmbientController. The controller allows you to check the ambient state outside of the callbacks. You may want to keep a reference to the AmbientModeSupport.AmbientController object:

    Kotlin

    class MainActivity : AppCompatActivity(), AmbientModeSupport.AmbientCallbackProvider {
        ...
        /*
         * Declare an ambient mode controller, which will be used by
         * the activity to determine if the current mode is ambient.
         */
        private lateinit var ambientController: AmbientModeSupport.AmbientController
        ...
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            ...
            ambientController = AmbientModeSupport.attach(this)
        }
        ...
    }
    

    Java

    public class MainActivity extends AppCompatActivity implements AmbientModeSupport.AmbientCallbackProvider {
        ...
        /*
         * Declare an ambient mode controller, which will be used by
         * the activity to determine if the current mode is ambient.
         */
        private AmbientModeSupport.AmbientController ambientController;
        ...
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ...
            ambientController = AmbientModeSupport.attach(this);
        }
        ...
    }
    
  4. Create an inner class that extends the AmbientCallback class in order to act on ambient events. This class becomes the object returned from the method you created in Step 2:

    Kotlin

    private class MyAmbientCallback : AmbientModeSupport.AmbientCallback() {
    
        override fun onEnterAmbient(ambientDetails: Bundle?) {
          // Handle entering ambient mode
        }
    
        override fun onExitAmbient() {
          // Handle exiting ambient mode
        }
    
        override fun onUpdateAmbient() {
          // Update the content
        }
    }
    

    Java

    private class MyAmbientCallback extends AmbientModeSupport.AmbientCallback {
        @Override
        public void onEnterAmbient(Bundle ambientDetails) {
          // Handle entering ambient mode
        }
    
        @Override
        public void onExitAmbient() {
          // Handle exiting ambient mode
         }
    
        @Override
        public void onUpdateAmbient() {
          // Update the content
        }
    }
    

Review the AlwaysOnKotlin sample on GitHub for more information and best practices.

Ambient mode using the WearableActivity class

For new and existing projects, you can add ambient mode support to your Wear app by updating your project configuration.

Create an activity that supports ambient mode

You can enable ambient mode in your activity by using the WearableActivity class:

  1. Create an activity that extends WearableActivity.
  2. In the onCreate() method of your activity, call the setAmbientEnabled() method.

Enable ambient mode in your activity as follows:

Kotlin

class MainActivity : WearableActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setAmbientEnabled()
    ...
    }
}

Java

public class MainActivity extends WearableActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setAmbientEnabled();
        ...
    }

Handle transitions between modes

If the user doesn't interact with your app for a period of time while the app is displayed, or if the screen is covered, the system switches the activity to ambient mode.

After the app switches to ambient mode, update the activity UI to a more basic layout to reduce power consumption. Use a black background with minimal white graphics and text.

To ease the transition from interactive to ambient mode, try to maintain similar placement of items on the screen.

Note: In ambient mode, disable any interactive elements on the screen, such as buttons.

When the activity switches to ambient mode, the system calls the onEnterAmbient() method of your ambient callback. The following code snippet shows how to change the text color to white and disable anti-aliasing after the system switches to ambient mode:

Kotlin

override fun onEnterAmbient(ambientDetails: Bundle?) {
    super.onEnterAmbient(ambientDetails)

    stateTextView.setTextColor(Color.WHITE)
    stateTextView.paint.isAntiAlias = false
}

Java

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

    stateTextView.setTextColor(Color.WHITE);
    stateTextView.getPaint().setAntiAlias(false);
}

When the user taps the screen or brings up their wrist, the activity switches from ambient mode to interactive mode. The system calls the onExitAmbient() method. Override this method to update the UI layout so that your app displays in a full-color, interactive state.

The following code snippet shows how to change the text color to green and enable anti-aliasing when the system switches to interactive mode:

Kotlin

override fun onExitAmbient() {
    super.onExitAmbient()

    stateTextView.setTextColor(Color.GREEN)
    stateTextView.paint.isAntiAlias = true
}

Java

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

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

Update content in ambient mode

Ambient mode allows you to update the screen with new information for the user, but you must carefully balance display updates against the battery life. You should strongly consider overriding only the onUpdateAmbient() method to update the screen once a minute or less in ambient mode.

To update your app content, override the onUpdateAmbient() method in your ambient callback:

Kotlin

override fun onUpdateAmbient() {
    super.onUpdateAmbient()
    // Update the content
}

Java

@Override
public void onUpdateAmbient() {
    super.onUpdateAmbient();
    // Update the content
}

It is possible, though not recommended, to update a Wear OS app in ambient mode more frequently than once per minute. For apps that do require more frequent updates, use an AlarmManager object to wake the processor and update the screen more frequently.

To implement an alarm that updates content more frequently in ambient mode, follow these steps:

  1. Prepare the alarm manager.
  2. Set the frequency of the updates.
  3. Check to see if the device is currently in ambient mode, or schedule the next update when the activity switches to ambient mode.
  4. Cancel the alarm when the activity switches to interactive mode or the activity is stopped.

Note: The alarm manager may create new instances of your activity as they are triggered. To prevent this situation, ensure that your activity is declared with the android:launchMode="singleInstance" parameter in the manifest.

The following sections describe these steps in detail.

Prepare the alarm manager

The alarm manager launches a PendingIntent that updates the screen and schedules the next alarm. The following example shows how to declare the alarm manager and the pending intent in the onCreate() method of your activity:

Kotlin

// Action for updating the display in ambient mode, per our custom refresh cycle.
private const val AMBIENT_UPDATE_ACTION = "com.your.package.action.AMBIENT_UPDATE"
...
private lateinit var ambientUpdateAlarmManager: AlarmManager
private lateinit var ambientUpdatePendingIntent: PendingIntent
private lateinit var ambientUpdateBroadcastReceiver: BroadcastReceiver

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setAmbientEnabled()

    ambientUpdateAlarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager

    ambientUpdatePendingIntent = Intent(AMBIENT_UPDATE_ACTION).let { ambientUpdateIntent ->
        PendingIntent.getBroadcast(this, 0, ambientUpdateIntent, PendingIntent.FLAG_UPDATE_CURRENT)
    }

    ambientUpdateBroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            refreshDisplayAndSetNextUpdate()
        }
    }
    ...
}

Java

// Action for updating the display in ambient mode, per our custom refresh cycle.
private static final String AMBIENT_UPDATE_ACTION = "com.your.package.action.AMBIENT_UPDATE";

private AlarmManager ambientUpdateAlarmManager;
private PendingIntent ambientUpdatePendingIntent;
private BroadcastReceiver ambientUpdateBroadcastReceiver;

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

    setAmbientEnabled();

    ambientUpdateAlarmManager =
        (AlarmManager) getSystemService(Context.ALARM_SERVICE);

    Intent ambientUpdateIntent = new Intent(AMBIENT_UPDATE_ACTION);

    ambientUpdatePendingIntent = PendingIntent.getBroadcast(
        this, 0, ambientUpdateIntent, PendingIntent.FLAG_UPDATE_CURRENT);

    ambientUpdateBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            refreshDisplayAndSetNextUpdate();
        }
    };
    ...
}

Register and unregister the broadcast receiver using onResume() and onPause():

Kotlin

override fun onResume() {
    super.onResume()
    IntentFilter(AMBIENT_UPDATE_ACTION).also { filter ->
        registerReceiver(ambientUpdateBroadcastReceiver, filter)
    }
}

override fun onPause() {
    super.onPause()
    unregisterReceiver(ambientUpdateBroadcastReceiver)
    ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent)
}

Java

@Override
public void onResume() {
    super.onResume();
    IntentFilter filter = new IntentFilter(AMBIENT_UPDATE_ACTION);
    registerReceiver(ambientUpdateBroadcastReceiver, filter);
        ...
}

@Override
public void onPause() {
    super.onPause();
    unregisterReceiver(ambientUpdateBroadcastReceiver);
    ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent);
    ...
}
Update screen and schedule data updates

In this example activity, the alarm manager triggers every 20 seconds in ambient mode. When the timer ticks, the alarm triggers the intent to update the screen and then sets the delay for the next update.

The following example shows how to update information on the screen and set the alarm for the next update:

Kotlin

// Milliseconds between waking processor/screen for updates
private val AMBIENT_INTERVAL_MS: Long = TimeUnit.SECONDS.toMillis(20)
...
private fun refreshDisplayAndSetNextUpdate() {
    if (isAmbient) {
        // Implement data retrieval and update the screen for ambient mode
    } else {
        // Implement data retrieval and update the screen for interactive mode
    }
    val timeMs: Long = System.currentTimeMillis()
    // Schedule a new alarm
    if (isAmbient) {
        // Calculate the next trigger time
        val delayMs: Long = AMBIENT_INTERVAL_MS - timeMs % AMBIENT_INTERVAL_MS
        val triggerTimeMs: Long = timeMs + delayMs
        ambientUpdateAlarmManager.setExact(
                AlarmManager.RTC_WAKEUP,
                triggerTimeMs,
                ambientUpdatePendingIntent)
    } else {
        // Calculate the next trigger time for interactive mode
    }
}

Java

// Milliseconds between waking processor/screen for updates
private static final long AMBIENT_INTERVAL_MS = TimeUnit.SECONDS.toMillis(20);
private void refreshDisplayAndSetNextUpdate() {
    if (isAmbient()) {
        // Implement data retrieval and update the screen for ambient mode
    } else {
        // Implement data retrieval and update the screen for interactive mode
    }
    long timeMs = System.currentTimeMillis();
    // Schedule a new alarm
    if (isAmbient()) {
        // Calculate the next trigger time
        long delayMs = AMBIENT_INTERVAL_MS - (timeMs % AMBIENT_INTERVAL_MS);
        long triggerTimeMs = timeMs + delayMs;
        ambientUpdateAlarmManager.setExact(
            AlarmManager.RTC_WAKEUP,
            triggerTimeMs,
            ambientUpdatePendingIntent);
    } else {
        // Calculate the next trigger time for interactive mode
    }
}
Schedule the next alarm

Schedule the alarm to update the screen by overriding the onEnterAmbient() method and the onUpdateAmbient() methods, as shown in the following code sample:

Kotlin

override fun onEnterAmbient(ambientDetails: Bundle?) {
    super.onEnterAmbient(ambientDetails)

    refreshDisplayAndSetNextUpdate()
}

override fun onUpdateAmbient() {
    super.onUpdateAmbient()
    refreshDisplayAndSetNextUpdate()
}

Java

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

@Override
public void onUpdateAmbient() {
    super.onUpdateAmbient();
    refreshDisplayAndSetNextUpdate();
}

Note: In this example, the refreshDisplayAndSetNextUpdate() method is called whenever the screen needs to be updated. For more examples of when to call this method, see the AlwaysOnKotlin sample on GitHub.

Cancel the alarm

When the device switches to interactive mode, cancel the alarm in the onExitAmbient() method:

Kotlin

override fun onExitAmbient() {
    super.onExitAmbient()

    ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent)
}

Java

@Override
public void onExitAmbient() {
    super.onExitAmbient();
    ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent);
}

When the user exits or stops your activity, cancel the alarm in the onDestroy() method of your activity:

Kotlin

override fun onDestroy() {
    ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent)
    super.onDestroy()
}

Java

@Override
public void onDestroy() {
    ambientUpdateAlarmManager.cancel(ambientUpdatePendingIntent);
    super.onDestroy();
}