Transfer Wear OS data to a new mobile device

When users set up a Wear OS device, they connect the Wear OS device to a particular mobile device. The user might later decide to get a new mobile device and connect their existing Wear OS device to this new mobile device. Some data related to a Wear OS device is stored on the currently-connected mobile device.

Starting in Wear OS 4, when users connect to a new mobile device, they can transfer Wear OS data to the new mobile device. Data is synced automatically when it's transferred.

When the user requests a transfer, the Wearable Data Layer delivers DataItem objects, originally stored on one mobile device, to the other mobile device. This allows a seamless experience for users of your app.

This document describes how you can configure your Wear OS app, and its companion mobile app, to support this scenario.

Preparation

The data transfer process handles DataItem objects differently, depending on which app owns the data:

Objects owned by the Wear OS app
These objects are preserved on the Wear OS device.
Objects owned by the mobile app

These objects are archived on the old device. The system then packages the archived data into a DataItemBuffer object and delivers this data to the mobile app that's installed on the new mobile device.

Immediately after the archive is delivered, the Wearable Data Layer invokes the onNodeMigrated() listener, similarly to how your app is notified when data is written by the Wear OS device.

Preserve transferred data

It's your app's responsibility to preserve the transferred DataItem objects. Shortly after the data is delivered to the new mobile device, the archive is deleted off of the old device.

Make sure each of the following conditions is true:

  1. Your app is installed on both mobile devices that are involved in the transfer.
  2. The mobile apps, installed on each mobile device, have package signatures that match.

Otherwise, the archived DataItem objects aren't delivered and are instead discarded.

Receive data from the old mobile device

To receive data on the new mobile device that was archived on the old mobile device, your mobile app must implement the onNodeMigrated() callback, part of the WearableListenerService class. To do so, complete the following steps:

  1. In your mobile app's build file, include a dependency on the latest version of the wearable library in Google Play services:

    dependencies {
        ...
        implementation 'com.google.android.gms:play-services-wearable:18.2.0'
    }
  2. Declare and export the WearableListenerService in your app's manifest file:

    <service
    android:name=".MyWearableListenerService"
    android:exported="true">
    <intent-filter>
        ...
        <action android:name="com.google.android.gms.wearable.NODE_MIGRATED" />
        <data android:scheme="wear" />
    </intent-filter>
    </service>
    
  3. Create a service class which extends WearableListenerService and overrides onNodeMigrated().

    Kotlin

    class MyWearableListenerService : WearableListenerService() {
        val dataClient: DataClient = Wearable.getDataClient(this)
    
        private fun shouldHandleDataItem(nodeId: String,
                                        dataItem: DataItem): Boolean {
            // Your logic here
            return dataItem.uri.path?.startsWith("/my_feature_path/") == true
        }
    
        private fun handleDataItem(nodeId: String, dataItem: DataItem) {
            val data = dataItem.data ?: return
            val path = dataItem.uri.path ?: return
            // Your logic here
            if (data.toString().startsWith("Please restore")) {
                dataClient.putDataItem(
                    PutDataRequest.create(path).setData(data)
                )
            }
        }
    
        override fun onNodeMigrated(nodeId: String, archive: DataItemBuffer) {
            val dataItemsToHandle = mutableListOf<DataItem>()
    
            for (dataItem in archive) {
                if (shouldHandleDataItem(nodeId, dataItem)) {
                    dataItemsToHandle.add(dataItem.freeze())
                }
            }
    
            // Callback stops automatically after 20 seconds of data processing.
            // If you think you need more time, delegate to a coroutine or thread.
            runBlocking {
                for (dataItem in dataItemsToHandle) {
                    handleDataItem(nodeId, dataItem)
                }
            }
        }
    }

    Java

    public class MyWearableListenerService extends WearableListenerService {
        private final DataClient dataClient = Wearable.getDataClient(this);
    
        private boolean shouldHandleDataItem(String nodeId, DataItem dataItem) {
            // Your logic here
            return Objects.requireNonNull(dataItem.getUri().getPath())
                    .startsWith("/my_feature_path/");
        }
    
        private Task<DataItem> handleDataItem(String nodeId, DataItem dataItem) {
            byte[] data = dataItem.getData();
            String path = dataItem.getUri().getPath();
            // Your logic here
            if (data != null && path != null && Arrays.toString(data)
                    .startsWith("Please restore")) {
                assert path != null;
                return dataClient.putDataItem(
                            PutDataRequest.create(path).setData(data));
        }
    
        @Override
        public void onNodeMigrated(@NonNull String nodeId, DataItemBuffer archive) {
            List<DataItem> dataItemsToHandle = new ArrayList<>();
    
            for (DataItem dataItem : archive) {
                if (shouldHandleDataItem(nodeId, dataItem)) {
                    dataItemsToHandle.add(dataItem.freeze());
                }
            }
    
            for (dataItem in dataItemsToHandle) {
                handleDataItem(nodeId, dataItem);
            }
    
            // Callback stops automatically after 20 seconds of data processing.
            // If you think you need more time, delegate to another thread.
        }
    }