Always-on apps and system ambient mode

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 system ambient mode. The app is resumed after the user interacts with the watch if any of the following conditions are met:

  • The user interaction occurs within a certain timeframe (before timeout).
  • The app declares and has started an ongoing activity.

For specific use cases—for example, a user wanting to see heart rate and pace during a run—you can also control what displays in system 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. (Only needed on Wear OS 4 or lower) Add the WAKE_LOCK permission to the Android manifest file:
<uses-permission android:name="android.permission.WAKE_LOCK" android:maxSdkVersion="33"/>

Enable always-on mode

Starting from Wear OS 6, apps with targetSdkVersion set to 36 or above are always-on by default. These apps remain visible during system ambient mode for a limited time period without any configuration. If your app's targetSdkVersion is lower than 36, or if your app needs to run on Wear OS 5 or lower, use the AmbientLifecycleObserver class to make your app always-on.

React to ambient mode events using the AmbientLifecycleObserver class

Apps can also use the AmbientLifecycleObserver class to directly react to ambient mode events:

  1. Implement the AmbientLifecycleObserver.AmbientLifecycleCallback interface, as in the following example. At this stage, the methods are empty, but later in the guide provides details of what changes you should ensure you are making to the visualization for entering and exiting ambient mode.

    Kotlin

    val ambientCallback = object : AmbientLifecycleObserver.AmbientLifecycleCallback {
        override fun onEnterAmbient(ambientDetails: AmbientLifecycleObserver.AmbientDetails) {
        // ... Called when moving from interactive mode into ambient mode.
        }
    
        override fun onExitAmbient() {
        // ... Called when leaving ambient mode, back into interactive mode.
        }
    
        override fun onUpdateAmbient() {
        // ... Called by the system in order to allow the app to periodically
        // update the display while in ambient mode. Typically the system will
        // call this every 60 seconds.
        }
    }
  2. Create an AmbientLifecycleObserver and register the observer. Typically, this would be used in onCreate() or the top-level composable if using Compose for Wear OS, to allow the always-on behavior to be enabled throughout the lifecycle of the activity.

    Kotlin

    private val ambientObserver = AmbientLifecycleObserver(activity, callback)
    
    override fun onCreate(savedInstanceState: Bundle) {
      super.onCreate(savedInstanceState)
      lifecycle.addObserver(observer)
    
      // ...
    }
  3. Remove the observer, by calling removeObserver(), when the always-on behavior is no longer required. For example, you might call this method in the onDestroy() method of your activity.

Update time text using the TimeText widget

Starting from Wear OS 6, the TimeText widget is ambient-mode aware. If your app only needs to update a time text every minute during ambient mode, you can just use the TimeText widget without using the AmbientLifecycleObserver.

Always-on apps can move to the background

Starting in Wear OS 5, the system moves always-on apps to the background after they're visible in ambient mode for a certain period of time. Users can configure the timeout in system settings.

If your always-on app displays information about an ongoing user task—such as music playback or a workout session—you might want to keep the ongoing activity visible until the task ends. To do so, use the Ongoing Activity API to post an ongoing notification that is linked to your always-on activity.

In order for the system to recognize the ongoing activity, the ongoing notification's touch intent must point to your always-on activity, as shown in the following code snippet:

// Create a pending intent that point to your always-on activity
val touchIntent =
    PendingIntent.getActivity(
        context,
        0,
        Intent(context, MyAlwaysOnActivity::class.java),
        PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
    )

val notificationBuilder =
    NotificationCompat.Builder(this, CHANNEL_ID)
    // ...
    .setOngoing(true)

val ongoingActivity =
    OngoingActivity.Builder(
        applicationContext, NOTIFICATION_ID, notificationBuilder
    )
    // ...
    .setTouchIntent(touchIntent)
    .build()

ongoingActivity.apply(applicationContext)

notificationManager.notify(
    NOTIFICATION_ID,
    notificationBuilder.build()
)

Modify the user experience in ambient mode

By default, when implementing always-on, the screen doesn't change its appearance when the watch enters ambient mode. You can modify this behavior by overriding the methods in the AmbientLifecycleCallback.

To help conserve power, do the following:

  • Illuminate fewer pixels. Consider showing only critical information in ambient mode, and provide more detail when the user enters interactive mode.
  • Keep at least 85% of the screen black, remove fills, and use outlines for buttons and large icons.
  • Avoid displaying extraneous information, such as non-functional branding and background images.
  • Keep elements in the same position across active and always-on modes, and always show the time.
  • Adjust any content for less-frequent updates. For example, show timers to the nearest minute instead of the nearest second.
  • Remove or show placeholder UI for alphanumeric content that updates frequently, such as distance or time.
  • Remove progress indicators that update frequently, such as for countdown rings and media sessions.
  • When entering always on mode, if the user had previously been on a configuration or settings screen in your app, consider showing a more relevant screen in your app instead.
  • In the AmbientDetails object passed to onEnterAmbient():
    • If deviceHasLowBitAmbient is set, disable anti-aliasing where possible.
    • If burnInProtectionRequired is set, shift the visualization around periodically, and avoid solid white areas.
  • Avoid running continuous animation during ambient mode. Starting in Wear OS 5.1, animators might stop running during ambient mode.

Checklist for uninterrupted display

There might be situations where you want maximum control over the display as the device moves through different states, for example when a workout app wants to avoid the watchface appearing on the display during a workout. In these cases, do the following:

  1. Implement the AmbientLifecycleObserver.AmbientLifecycleCallback interface.
  2. Create a new low-powered layout for use when the device is in system ambient mode.
  3. For the duration of the workout, implement an ongoing activity.

For an example of how this can be achieved, check out the compose-based Exercise sample on GitHub, which makes use of the AmbientAware composable from the Horologist library.