1

I'm building a watch face for Android wear. The watch face will occasionally ask the handheld for information, which will be used to update the watch face. Unfortunately, onDataChanged and onMessageReceived only occur the first time the watch face loads, and never again (I can switch to another watch face and back, and again it'll work once)

Most of the boilerplate is taken from examples I think. So in the watch face I have

@Override
    public void onCreate() {
        Log.d(TAG, "onCreate - Creating service...");
        super.onCreate();
        initGoogleApiClient();
    }

    private void initGoogleApiClient() {
        mApiClient = new GoogleApiClient.Builder(mContext)
                .addApi(Wearable.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build();
        Log.d(TAG, "Calling connect on mApiClient...");

        mApiClient.connect();
    }

@Override
    public void onMessageReceived(MessageEvent messageEvent) {
        Log.d(TAG, "onMessageReceived: " + messageEvent.getPath() + " " + messageEvent.getData());

        switch (messageEvent.getPath()) {
            case "case 1":
                    Log.d(TAG,"some data was sent to us...");
                if (!mApiClient.isConnected())
                    mApiClient.connect();
                break;
        }
    }

    @Override
    public void onConnected(@Nullable Bundle bundle) {
        Log.d(TAG,"onConnected - adding Listeners");
        Wearable.DataApi.addListener(mApiClient, this);
        Wearable.MessageApi.addListener(mApiClient, this);
    }

    @Override
    public void onConnectionSuspended(int i) {
        Log.d(TAG,"onConnection suspended");
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        Log.e(TAG,"onConnection failed");
    }

    @Override
    public void onDataChanged(DataEventBuffer dataEventBuffer) {
        Log.d(TAG, "onDataChanged");
        for (DataEvent event : dataEventBuffer) {
            if (event.getType() == DataEvent.TYPE_CHANGED && event.getDataItem().getUri().getPath().equals("/image")) {
                Log.d(TAG, "Matched TYPE_CHANGED and /image");
                DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem());
                Asset photoAsset = dataMapItem.getDataMap().getAsset("photo");
                loadBitmapFromAsset(photoAsset);
            }
        }
    }

On the handheld, logging shows that the data is being sent to the wearable the same way on both the first instance after the watch face being loaded (where it works) and on subsequent requests. The difference being on subsequent requests, none of the logging in the above code occurs, showing nothing is getting triggered.

Some handheld code for how the data is sent:

private void SendBitmap(final Bitmap bitmap) {
//snip - stuff to resize the bitmap
Log.d(TAG, "Resized image to " + cropped.getWidth() + "x" + cropped.getHeight());

final Bitmap croppedBitmap = cropped;

new Thread(new Runnable() {
    @Override
    public void run() {

        if (!nodeConnected || !mGoogleApiClient.isConnected())
        {
            Log.d(TAG,"No node connected. Attempting connection for 5 seconds...");
            mGoogleApiClient.blockingConnect(5, TimeUnit.SECONDS);
        }
        if (!nodeConnected)
        {
            Log.e(TAG, "Failed to connect to mGoogleApiClient within 5 seconds");
            return;
        }

        if (mGoogleApiClient.isConnected()) {
            Log.d(TAG, "Client is connected...");
            Asset asset = createAssetFromBitmap(croppedBitmap);
            PutDataMapRequest putDataMapRequest = PutDataMapRequest.create("/image").setUrgent();
            putDataMapRequest.getDataMap().clear(); //clear down current stuff
            putDataMapRequest.getDataMap().putAsset("photo", asset);
            putDataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
            PutDataRequest request = putDataMapRequest.asPutDataRequest().setUrgent();

            PendingResult<DataApi.DataItemResult> pendingResult =
                    Wearable.DataApi.putDataItem(mGoogleApiClient, request);

            Log.d(TAG,"DataItem is put");

            pendingResult.setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
                @Override
                public void onResult(DataApi.DataItemResult dataItemResult) {
                    Log.d(TAG, "Sending task data: " + dataItemResult.getStatus().isSuccess());
                }
            });
        }
        else {
            Log.w(TAG,"Client is NOT connected...");
        }
    }
}).start();


//send a message to trigger connection?
SendToDataLayerThread photoTrigger = new SendToDataLayerThread("case 1", "Photo sent...".getBytes());
photoTrigger.run();
Log.d(TAG, "Photo sent to the Google API");
}

private static Asset createAssetFromBitmap(Bitmap bitmap) {
final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteStream);
return Asset.createFromBytes(byteStream.toByteArray());
}

public class SendToDataLayerThread extends Thread {
    String path;
    byte[] message;

    // Constructor to send a message to the data layer
    SendToDataLayerThread(String p, byte[] msg) {
        path = p;
        message = msg;
    }

    public void run() {
        Wearable.NodeApi.getConnectedNodes(mGoogleApiClient)
                .setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
                    @Override
                    public void onResult(NodeApi.GetConnectedNodesResult nodes) {
                        for (final Node node : nodes.getNodes()) {
                            Wearable.MessageApi.sendMessage(mGoogleApiClient, node.getId(), path, message).setResultCallback(new ResultCallback<MessageApi.SendMessageResult>() {
                                @Override
                                public void onResult(MessageApi.SendMessageResult sendMessageResult) {
                                    if (sendMessageResult.getStatus().isSuccess()) {
                                        Log.d(TAG, "Message: {" + message + "} sent to: " + node.getDisplayName());
                                    } else {
                                        // Log an error
                                        Log.d(TAG, "ERROR: failed to send Message");
                                    }
                                }
                            });
                        }
                    }
                });
    }
}

Things I've tried, based on other posts:

  • using setUrgent()
  • in addition to sending my image, sending a timestamp to make the data "unique"
  • sending a message to "wake up" the connection on the wearable

I'm pretty stumped at this point. I'm sure I'm missing something obvious but I'm not sure what to try next...

James
  • 999
  • 2
  • 11
  • 22
  • Hello James. I have exactly the same problem. Any news related to this ? I am investigating... The only thing I found so far is when you change the URI, the `onDataChanged()` is called again. Look likes *changing data without changing the URI* is not considered as a "data change". – Benoit Jan 27 '17 at 16:40
  • I did get it working after a fair few changes. I'll post an Answer shortly with some clues! – James Jan 27 '17 at 21:06

1 Answers1

1

I got it working eventually after a bunch of changes. On the wear app I ended up with:

  • in DataLayerListener.java:

    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        Log.d(TAG, "onDataChanged");
    
        GoogleApiClient mApiClient = new GoogleApiClient.Builder(this)
                .addApi(Wearable.API)
                .build();
    
        ConnectionResult result = mApiClient.blockingConnect(250, TimeUnit.MILLISECONDS);
        if (!result.isSuccess()) {
            Log.e(TAG, "Failed to connect to mApiClient when receiving image...");
        }
    
        for (DataEvent event : dataEvents) {
            if (event.getType() == DataEvent.TYPE_CHANGED && event.getDataItem().getUri().getPath().equals("/myapp")) {
                Log.d(TAG, "Matched TYPE_CHANGED and /myapp");
                DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem());
                Asset photoAsset = dataMapItem.getDataMap().getAsset("photo");
                InputStream assetInputStream = Wearable.DataApi.getFdForAsset(mApiClient, photoAsset).await().getInputStream();
    
                Bitmap background = BitmapFactory.decodeStream(assetInputStream);
                Intent intent = new Intent("background");
                intent.putExtra("message", background);
                LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
            }
        }
    }
    

In the manifest:

    <service
        android:name=".MyWatchFace"
        android:label="@string/my_watch_face_name"
        android:permission="android.permission.BIND_WALLPAPER">
        <meta-data ... />
        <meta-data ... />
        <intent-filter>
            <action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
            <action android:name="android.service.wallpaper.WallpaperService" />
            <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
        </intent-filter>
    </service>
    <service android:name=".DataLayerListener">
        <intent-filter>
            <action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
            <action android:name="com.google.android.gms.wearable.CAPABILITY_CHANGED" />
            <action android:name="com.google.android.gms.wearable.CHANNEL_EVENT" />
            <action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
            <data
                android:host="*"
                android:path="/myapp"
                android:pathPrefix="/myapp"
                android:scheme="wear" />
        </intent-filter>
    </service>

The watch face itself registers BroadcastReceivers to handle the messages broadcast from the DataLayerListener.

I think it was the manifest changes that were the important bit... Hopefully that gives a clue if you're stuck with the same problem

Benoit
  • 5,118
  • 2
  • 24
  • 43
James
  • 999
  • 2
  • 11
  • 22
  • Thanks for the post, James. But that did not helped... Finally I discovered what's wrong: I copy/pasted code from [loadBitmapFromAsset(Asset asset)](https://developer.android.com/training/wearables/data-layer/assets.html#ReceiveAsset), and that method does a `mGoogleApiClient.disconnect();` - in my case the reason why onDataChanged() was not called on subsequent changes. My guess is that it's an error... – Benoit Jan 30 '17 at 10:19
  • Hmm, I may have seen similar at some point too as I recall adding lots of code to reconnect things at some point in the past... Maybe one day I'll pull out a proper working template code so I don't have all the same problems next time! – James Jan 30 '17 at 14:27