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. 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 displays 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:
- Create or update your project based on the configurations on the Create and run a wearable app page.
- 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:
-
Create a subclass of
FragmentActivity
or one of its subclasses. -
Implement the
AmbientCallbackProvider
interface, as in the following example. Override thegetAmbientCallback()
method to provide the callbacks needed for reacting to ambient events from the Android system. (In a later step, you create a 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(); } ... }
-
In the
onCreate()
method, enable ambient mode by callingAmbientModeSupport.attach(FragmentActivity)
. This method returns anAmbientModeSupport.AmbientController
. The controller lets you check the ambient state outside of the callbacks. You might want to keep a reference to theAmbientModeSupport.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); } ... }
-
Create an inner class that extends the
AmbientCallback
class to act on ambient events. This class becomes the object returned from the method you created in step 2, as shown in the following example: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 using the
WearableActivity
class as follows:
-
Create an activity that extends
WearableActivity
. -
In the
onCreate()
method of your activity, call thesetAmbientEnabled()
method.
The following snippet shows how to enable ambient mode in an activity:
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 back 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 lets you update the screen with new information for the user, but you must carefully balance display updates against battery life. Consider updating the screen only 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 }
We do not recommend updating a Wear OS app in ambient mode more
frequently than once per minute. For apps that 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:
- Prepare the alarm manager.
- Set the frequency of the updates.
- Check whether the device is currently in ambient mode and schedule the next update when the activity switches to ambient mode.
- 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 behavior, declare your activity
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()
method, as shown in the following 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(); }