With Wear OS by Google, a watch can communicate with a network directly, without access to an Android or iOS phone. This direct network access replaces the use (in Wear 1.x) of the Data Layer API for connecting to a network.
Network access
Wear OS apps can make network requests. When a watch has a Bluetooth connection to a phone, the watch's network traffic generally is proxied through the phone. But when a phone is unavailable, Wi-Fi and cellular networks are used, depending on the hardware. The Wear platform handles transitions between networks.
You can use protocols such as HTTP, TCP, and UDP. However, the android.webkit APIs (including the CookieManager class) are not available. You can use cookies by reading and writing headers on requests and responses.
Additionally, we recommend using the following:
- The JobScheduler API for asynchronous jobs, including polling at regular intervals (described below)
- Multi-networking APIs if you need to connect to specific network types; see Multiple Network Connections
High-bandwidth network access
The Wear OS platform manages network connectivity with the goal of providing the best overall user experience. The platform chooses the default, active network by balancing two factors:
- The need for long battery life
- The need for network bandwidth
When battery preservation is prioritized, the active network may have insufficient bandwidth to do network tasks that require high bandwidth, such as transporting large files or streaming media.
This section provides guidance on using the ConnectivityManager class to ensure that requisite network bandwidth is available for your app. For general information about fine-grained control over network resources, see Managing Network Usage.
Acquire a high-bandwidth network
On Wear OS, do not assume a high-bandwidth network always is available. For use cases that require high-bandwidth network access, such as transporting large files or streaming media, we recommend the following steps:
- Check for an active network, and if there is one, check its bandwidth.
- If there isn't an active network, or its bandwidth is insufficient, request access to an unmetered Wi-Fi or cellular network.
You can use the ConnectivityManager class to check if an active network exists and has enough bandwidth:
Kotlin
const val MIN_BANDWIDTH_KBPS = 320 connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val bandwidth: Int = connectivityManager.activeNetwork?.let { activeNetwork -> connectivityManager.getNetworkCapabilities(activeNetwork).linkDownstreamBandwidthKbps } ?: -1 when { bandwidth < 0 -> { // No active network } bandwidth in (0 until MIN_BANDWIDTH_KBPS) -> { // Request a high-bandwidth network } else -> { // You already are on a high-bandwidth network, so start your network request } }
Java
int MIN_BANDWIDTH_KBPS = 320; connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); Network activeNetwork = connectivityManager.getActiveNetwork(); if (activeNetwork != null) { int bandwidth = connectivityManager.getNetworkCapabilities(activeNetwork).getLinkDownstreamBandwidthKbps(); if (bandwidth < MIN_BANDWIDTH_KBPS) { // Request a high-bandwidth network } else { // You already are on a high-bandwidth network, so start your network request } } else { // No active network }
You can request an unmetered, high-bandwidth network using the
ConnectivityManager. With a single network request, you can request
an unmetered Wi-Fi or cellular network. When the network is ready (e.g.,
the device's Wi-Fi radio connects to a saved network), the
onAvailable()
method of your NetworkCallback
instance is called. If a suitable network is not found, the
onAvailable()
method is not called. Therefore, you should
time-out your request manually; see Waiting for Network Availability.
Kotlin
networkCallback = object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { if (bindProcessToNetwork(network)) { // socket connections will now use this network } else { // app doesn't have android.permission.INTERNET permission } } } val request: NetworkRequest = NetworkRequest.Builder().run { addTransportType(NetworkCapabilities.TRANSPORT_WIFI) addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) build() } connectivityManager.requestNetwork(request, networkCallback)
Java
networkCallback = new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(Network network) { if (bindProcessToNetwork(network)) { // socket connections will now use this network } else { // app doesn't have android.permission.INTERNET permission } } }; NetworkRequest request = new NetworkRequest.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .build(); connectivityManager.requestNetwork(request, networkCallback);
Release the network
When your app no longer needs the high-bandwidth network, you must release the network using the ConnectivityManager class to ensure that the platform can resume management of network access.
Kotlin
connectivityManager.bindProcessToNetwork(null) connectivityManager.unregisterNetworkCallback(networkCallback)
Java
connectivityManager.bindProcessToNetwork(null); connectivityManager.unregisterNetworkCallback(networkCallback);
To optimize battery consumption, a network connection should only remain
registered for the duration of the activity. Therefore, you should
consider releasing the network in your activity's onStop()
method.
Wait for network availability
Acquiring a network may not be instantaneous because a watch's Wi-Fi or
cellular radio may be off to preserve battery. Additionally, if a watch
cannot connect to a network, the onAvailable()
method of
your NetworkCallback
instance is not called. Therefore, you
should time-out the request after a predetermined length of time and
release any associated resources.
Kotlin
const val MESSAGE_CONNECTIVITY_TIMEOUT = 1 const val NETWORK_CONNECTIVITY_TIMEOUT_MS: Long = 10000 ... handler = MyHandler() networkCallback = object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { handler.removeMessages(MESSAGE_CONNECTIVITY_TIMEOUT) ... } } connectivityManager.requestNetwork(request, networkCallback) handler.sendMessageDelayed( handler.obtainMessage(MESSAGE_CONNECTIVITY_TIMEOUT), NETWORK_CONNECTIVITY_TIMEOUT_MS ) ... // Don't make this an inner class otherwise there is the potential to leak the parent class private class MyHandler : Handler() { override fun handleMessage(msg: Message) { when (msg.what) { MESSAGE_CONNECTIVITY_TIMEOUT -> { // unregister the network } } } }
Java
int MESSAGE_CONNECTIVITY_TIMEOUT = 1; long NETWORK_CONNECTIVITY_TIMEOUT_MS = 10000; handler = new MyHandler(); networkCallback = new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(Network network) { handler.removeMessages(MESSAGE_CONNECTIVITY_TIMEOUT); ... } }; connectivityManager.requestNetwork(request, networkCallback); handler.sendMessageDelayed( handler.obtainMessage(MESSAGE_CONNECTIVITY_TIMEOUT), NETWORK_CONNECTIVITY_TIMEOUT_MS); ... // This needs to be static to avoid potentially leaking the parent class private static class MyHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_CONNECTIVITY_TIMEOUT: // unregister the network break; } } }
Monitor the network state
The NetworkCallback
interface has methods for monitoring
state changes to the bound network, such as bandwidth changes and
connectivity loss.
Kotlin
networkCallback = object : ConnectivityManager.NetworkCallback() { override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { val bandwidth: Int = networkCapabilities.linkDownstreamBandwidthKbps if (bandwidth < MIN_BANDWIDTH_KBPS) { // handle insufficient network bandwidth } } override fun onLost(network: Network) { // handle network loss } }
Java
networkCallback = ConnectivityManager.NetworkCallback { @Override public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { int bandwidth = networkCapabilities.getLinkDownstreamBandwidthKbps(); if (bandwidth < MIN_BANDWIDTH_KBPS) { // handle insufficient network bandwidth } } @Override public void onLost(Network network) { // handle network loss } }
Launch the Wi-Fi settings activity
When requesting a Wi-Fi network, the system tries to connect to a saved
network if a saved network was configured and is in range. However, if no
saved Wi-Fi network is available, the onAvailable()
callback
method of your NetworkCallback
instance is never called. If
you are using a Handler
to time-out the network request, you
can direct the user to add a Wi-Fi network when the timeout has occurred.
You can send the user directly to the activity for adding a Wi-Fi network
using the following intent:
Kotlin
context.startActivity(Intent("com.google.android.clockwork.settings.connectivity.wifi.ADD_NETWORK_SETTINGS"))
Java
context.startActivity(new Intent("com.google.android.clockwork.settings.connectivity.wifi.ADD_NETWORK_SETTINGS"));
To launch the settings activity, your app must have the following
permission: android.permission.CHANGE_WIFI_STATE
User interface considerations
If your app requires a connection to a new Wi-Fi network for a high-bandwidth operation, ensure that the reason for connecting is clear to the user before you launch the Wi-Fi settings. Only request that a user add a new Wi-Fi network when the high-bandwidth network is required. Do not block a user from accessing app features that do not require a high-bandwidth network.
Figure 1 shows, for example, a music app. The app should allow a user to browse music and only require the user to add a new Wi-Fi network if they want to download or stream music.

Figure 1. A music app flow for downloading music.
If your app requires a high-bandwidth network before it can operate, you should present a clear rationale to the user before requiring the user to add a new Wi-Fi network. Also, for long-running network operations, such as downloading a user's media playlist, you should present a progress indicator with a description of what operation is being performed.
Figure 2 shows the music app in a streaming-music flow. If a user wants to stream music and a high-bandwidth network is needed, the app should clearly explain why a new Wi-Fi network is needed, before taking the user to Wi-Fi settings.

Figure 2. A music app flow for streaming music.
Cloud messaging
For sending notifications, apps can directly use Firebase Cloud Messaging (FCM), which replaces Google Cloud Messaging (GCM). FCM is supported on Wear 2.0. GCM is not supported on Wear 2.0.
No APIs for network access or FCM are specific to Wear OS. Refer to the existing documentation about connecting to a network and cloud messaging.
FCM works well with Doze and is the recommended way to send notifications to a watch.
Provide for messages from FCM by collecting a registration token for a device when your Wear app runs. Then include the token as part of the destination when your server sends messages to the FCM REST endpoint. FCM sends messages to the device identified by the token.
An FCM message is in JSON format and can include one or both of the following payloads:
- Notification payload. When a notification payload is received by a watch, the data is displayed to a user directly in the notification stream. When the user taps the notification, your app is launched.
- Data payload. The payload has a set of custom key/value pairs. The payload and is delivered as data to your Wear app.
For more information and examples of payloads, see About FCM Messages.
By default, notifications are bridged (shared) from a phone app to a watch. If you have a standalone Wear app and a corresponding phone app, duplicate notifications can occur. For example, the same notification from FCM, received by both a phone and a watch, could be displayed by both devices independently.
Use background services
To ensure that background tasks are correctly executed, they must account for Doze. In Android 6.0, Doze and App Standby improved battery life.
Doze is enhanced in Android Nougat and Wear OS. When a screen turns off or enters ambient mode for a long enough time, a subset of Doze can occur and background tasks may be deferred for certain periods. Later, when a device is stationary for an extended time, regular Doze occurs.
You should schedule jobs with the JobScheduler API, which enables your app to register for Doze-safe code execution. When scheduling jobs, you can select constraints such as periodic execution and the need for connectivity or device charging. Configure jobs in a way that does not adversely impact battery life. Jobs should use a JobInfo.Builder object to provide constraints and metadata, e.g. with one or more of the following methods for a task:
- To schedule a task that requires networking, use
setRequiredNetworkType(int networkType)
, specifyingNETWORK_TYPE_ANY
orNETWORK_TYPE_UNMETERED
; note thatNETWORK_TYPE_UNMETERED
is for large data transfers whileNETWORK_TYPE_ANY
is for small transfers - To schedule a task while charging, use
setRequiresCharging(boolean requiresCharging)
- To specify that a device is idle for a task, use
setRequiresDeviceIdle(boolean requiresDeviceIdle)
; this method is useful for lower-priority background work or synchronization, especially when used withsetRequiresCharging
Note that some low-bandwidth networks, such as Bluetooth LE, are considered metered.
Schedule with constraints
You can schedule a task that requires constraints. In the example below,
a JobScheduler
object activates MyJobService
when the following constraints are met:
- Unmetered networking
- Device charging
You can use the builder method setExtras
to attach a bundle
of app-specific metadata to the job request. When your job executes, this
bundle is provided to your job service. Note the MY_JOB_ID
value passed to the JobInfo.Builder
constructor. This
MY_JOB_ID
value is an app-provided identifier. Subsequent
calls to cancel, and subsequent jobs created with that same value, will
update the existing job:
Kotlin
JobInfo.Builder(MY_JOB_ID, ComponentName(this, MyJobService::class.java)) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setRequiresCharging(true) .setExtras(extras) .build() .also { jobInfo -> (getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler) .schedule(jobInfo) }
Java
JobInfo jobInfo = new JobInfo.Builder(MY_JOB_ID, new ComponentName(this, MyJobService.class)) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setRequiresCharging(true) .setExtras(extras) .build(); ((JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE)) .schedule(jobInfo);
Below is an implementation of
JobService to handle the job above. When the job executes, a
JobParameters
object is passed into the
onStartJob
method. The JobParameters
object
enables you to get the job ID value along with any extras bundle provided
when scheduling the job. The onStartJob
method is called on
the main application thread, and therefore any expensive logic should be
run from a separate thread. In the example, an AsyncTask
is
used to run code in the background. When work is complete, you would call
the jobFinished
method to notify JobScheduler
that the task is done:
Kotlin
private class MyJobService : JobService() { override fun onStartJob(params: JobParameters): Boolean { JobAsyncTask().execute(params) return true } private class JobAsyncTask : AsyncTask<...>() { ... } }
Java
public class MyJobService extends JobService { @Override public boolean onStartJob(JobParameters params) { new JobAsyncTask().execute(params); return true; } private class JobAsyncTask extends AsyncTask