Some Wear OS apps are constantly visible to the user.
Wear OS devices running Android version 5.1 or higher allow apps to remain in the foreground while saving battery power. Wear OS apps can control what's displayed on the watch while the watch is in a low-power (ambient) mode. Wear apps that run in both the ambient and interactive modes are called always-on apps.
Such apps enable users who are jogging to glance at their watch for the distance covered and time elapsed. Some users record shopping lists and can quickly see the items on a list as they shop.
Making an app constantly visible has an impact on battery life, so you should carefully consider that impact when adding this feature to your app.
Important:
The 27.1.0 version of the Android Support Library enables a
new way to support ambient mode
that uses the AmbientModeSupport
class rather than
the WearableActivity
class.
You can decide whether you want to use the
new, preferred way to support ambient mode
or instead
extend the WearableActivity class.
Note:
The
AmbientMode
class has been deprecated and replaced with the
AmbientModeSupport
class.
Refer to the following related resources:
Configure your project
To support ambient mode, you must update your Android SDK and configure your development project. Follow these steps to make the necessary changes:
- Create or update your project based on the configurations on the Creating and Running 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
Using the
AmbientModeSupport
class to support ambient mode enables you to
benefit from:
-
The
Activity
subclasses in the Android Support Library, such asFragmentActivity
; functionality for fragments is available - Architecture components, which are lifecycle aware
- Better support for Google Sign-In
To use the
AmbientModeSupport
class, you extend one of the
FragmentActivity
subclasses or the FragmentActivity itself and implement a provider interface,
which in turn can be used to listen for ambient mode updates.
Note:
The AmbientModeSupport.attach(FragmentActivity)
method
attaches a headless fragment to the
FragmentActivity
class or subclass that you provide, and subsequent calls to
FragmentManager.getFragments()
return a reference to
this fragment (which is not intended to be used in any way).
The following describes the general use of the
AmbientModeSupport
class:
-
Create a subclass of one of the
FragmentActivity
classes. -
Implement the
AmbientCallbackProvider
interface, as in the example below. Override thegetAmbientCallback()
method to provide the callbacks needed for reacting to ambient events from the Android system. In Step 4, we 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(); } … }
-
In the
onCreate()
method, enable Ambient mode by callingAmbientModeSupport.attach(FragmentActivity)
. This method returns anAmbientModeSupport.AmbientController
. The controller allows you to check the ambient state outside of the callbacks; you may 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 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 } }
Be sure to review the AlwaysOn sample 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:
-
Create an activity that extends
WearableActivity
. -
In the
onCreate()
method of your activity, call thesetAmbientEnabled()
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 does not interact with your app for a period of time while it is displayed, or if the user covers the screen with their palm, 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. You should use a black background with minimal white graphics and text. To ease a user into the transition from interactive to ambient mode, try to maintain similar placement of items on the screen. For more information on presenting content on an ambient screen, see the Watch Faces for Wear OS design guide.
Note that when your app runs on a device without a hardware button, palming the screen does not switch an app into ambient mode. Rather, it causes the app to exit and the home screen to appear. This behavior is intended to ensure that users can exit apps gracefully. However, these devices still go to ambient mode when the screen times out.
Note: In ambient mode, disable any interactive elements on the screen, such as buttons. For more information on how to design user interactions for an always-on app, see the App Structure for Wear OS design guide.
When the activity switches to ambient mode, the system calls the
onEnterAmbient()
method in your wearable activity. 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 only overriding the
onUpdateAmbient()
method to update the screen once a minute in
ambient mode. If your app requires more frequent updates, take into
consideration that there is a trade-off between battery life and the
frequency of updates. To realize battery savings, updates should be no
more than once every 10 seconds. In practice, however, you should update
your app less frequently than that.
Update once a minute
In order to preserve battery power, most Wear apps should not frequently
update the screen while in ambient mode. We recommend designing your app
to update the screen once per minute while in this mode. The system
provides a callback method,
onUpdateAmbient()
, that allows you to update the screen at
this recommended frequency.
To update your app content, override the
onUpdateAmbient()
method in your wearable activity:
Kotlin
override fun onUpdateAmbient() { super.onUpdateAmbient() // Update the content }
Java
@Override public void onUpdateAmbient() { super.onUpdateAmbient(); // Update the content }
Update more frequently
It is possible, though not recommended, to update a Wear app in ambient mode more frequently
than once per minute. For apps that do require more frequent updates, you can 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.
- Schedule the next update when the activity switches to ambient mode or is currently in 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 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 pending intent 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(); } }; ... }
Now we must register and unregister the
broadcast receiver
in 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 when the activity is entering
ambient mode or when the activity is already in ambient mode by overriding the
onEnterAmbient()
method and the
onUpdateAmbient()
method:
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
AlwaysOn sample.
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(); }
Maintain backward-compatibility
Activities that support ambient mode automatically fall back to normal activities on Wear devices that are on Android versions prior to 5.1 (API level 22). No special app code is required to support devices on these versions of Android. When the device switches to ambient mode, the device returns to the home screen and exits your activity.
If your app should not be installed or updated on devices with Android versions prior to 5.1, update your manifest with the following:
<uses-library android:name="com.google.android.wearable" android:required="true" />