blue-sky
blue-sky

Reputation: 53806

Sending String from watch to phone

I'm attempting to send a string from wearable to mobile device using code below. This implementation is based on https://github.com/twotoasters/Wear-MessageApiDemo/

Case there is an issue with the time delay in connecting to the device I've increased CONNECTION_TIME_OUT_MS from 100 to 2000 (milliseconds).

To the mobile manifest I add :

<service
    android:name=".ListenerService" >
    <intent-filter>
        <action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
    </intent-filter>
</service>

instead of

<service
    android:name=".ListenerService" >
    <intent-filter>
        <action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
    </intent-filter>
</service>

as com.google.android.gms.wearable.BIND_LISTENER is deprecated

The code compiles but the message is not received by phone.

The method

private void showToast(String message) {

        Log.d(TAG, "received message : " + message);

    }

Should fire within the listenerService when a message is received.

The issue is a message is never received. Have I implemented the message api correctly ?

API version : 23

Source :

Mobile component

Kick off listenerService :

----------------------------------- MainActivity.onCreate ---------------

  @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new ListenerService();

}

Define the Listener service to listen for messages

----------------------------------- ListenerService ------------------

import android.util.Log;
import android.widget.TextView;

import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.WearableListenerService;

public class ListenerService extends WearableListenerService {

    private static final String TAG = "ListenerService";

    TextView mTextView;

    @Override
    public void onMessageReceived(MessageEvent messageEvent) {
        MainActivity.mTextView.setText("got message");
        showToast(messageEvent.getPath());
    }

    private void showToast(String message) {

        Log.d(TAG, "received message : " + message);

    }
}

Define the service in the manifest

----------------------------------- AndroidManifest.xml ----------------

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.runner">

    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.BODY_SENSORS"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.GPS_PROVIDER" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".ListenerService" >
            <intent-filter>
                <action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
            </intent-filter>
        </service>

    </application>

</manifest>

Wear component

MainActivity :

package common;

import android.content.Context;
import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.wearable.activity.WearableActivity;
import android.support.wearable.view.BoxInsetLayout;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

public class MainActivity extends WearableActivity {

    private static final long CONNECTION_TIME_OUT_MS = 2000;
    private static final String MESSAGE = "Hello Wear!";

    private GoogleApiClient client;
    private String nodeId;

    private static final String TAG = "MainActivity";

    private BoxInsetLayout mContainerView;


    /**
     * Initializes the GoogleApiClient and gets the Node ID of the connected device.
     */
    private void initApi() {
        client = getGoogleApiClient(this);
        retrieveDeviceNode();
    }

    /**
     * Returns a GoogleApiClient that can access the Wear API.
     * @param context
     * @return A GoogleApiClient that can make calls to the Wear API
     */
    private GoogleApiClient getGoogleApiClient(Context context) {
        return new GoogleApiClient.Builder(context)
                .addApi(Wearable.API)
                .build();
    }

    /**
     * Connects to the GoogleApiClient and retrieves the connected device's Node ID. If there are
     * multiple connected devices, the first Node ID is returned.
     */
    private void retrieveDeviceNode() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                client.blockingConnect(CONNECTION_TIME_OUT_MS, TimeUnit.MILLISECONDS);
                NodeApi.GetConnectedNodesResult result =
                        Wearable.NodeApi.getConnectedNodes(client).await();
                List<Node> nodes = result.getNodes();
                if (nodes.size() > 0) {
                    Log.d(TAG, "nodeId "+nodeId);
                    nodeId = nodes.get(0).getId();
                }
                client.disconnect();
            }
        }).start();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initApi();
        sendToast();

    }


    /**
     * Sends a message to the connected mobile device, telling it to show a Toast.
     */
    private void sendToast() {
        if (nodeId != null) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    client.blockingConnect(CONNECTION_TIME_OUT_MS, TimeUnit.MILLISECONDS);
                    Wearable.MessageApi.sendMessage(client, nodeId, MESSAGE, null);
                    client.disconnect();
                }
            }).start();
        }
    }

}

Update :

Here is the class added to mobile module to listen for received messages :

package com.receivers;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class MessageListener extends BroadcastReceiver {

    private static final String TAG = "MessageListener";

    @Override
    public void onReceive(Context context, Intent intent) {
        String str = intent.getAction();
        Log.i(TAG, "onReceive triggered : "+str);
    }
}

Config of MessageListener in AndroidManifest.xml :

    <receiver android:name="com.receivers.MessageListener">
        <intent-filter>
            <action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
        </intent-filter>
    </receiver>

I've tried setting a breakpoint at line String str = intent.getAction(); but onReceive method does not appear to be invoked.

Within the wear module the method onNodeFound() does appear to send the message correctly as this line Wearable.MessageApi.sendMessage(googleApiClient, nodeId, MESSAGE_PATH, "Hello Wear!".getBytes(Charset.forName("UTF-8"))); is being invoked. Have i setup the MessageListener correctly ?

Update 2 :

ReceiverActivity :

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;

public class ReceiverActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
                new IntentFilter("custom-event-name"));

    }

    private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // Get extra data included in the Intent
            String message = intent.getStringExtra("EXTRA_MESSAGE");
            Log.d("receiver", "Got message: " + message);
        }
    };

    @Override
    protected void onDestroy() {
        LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
        super.onDestroy();
    }

}

Within the ListenerService , method onMessageReceived is being fired and here trying to broadcast the message :

@Override
public void onMessageReceived(MessageEvent messageEvent) {
    super.onMessageReceived(messageEvent);
    Log.d("tester", "received a message from wear: " + new String(messageEvent.getData()));

    final String message = new String(messageEvent.getData());
    final Intent messageIntent = new Intent();
    messageIntent.putExtra("EXTRA_MESSAGE", message); // define your extra
    LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent);

}

Starting the activity in AndroidManifest.xml :

<activity android:name=".ReceiverActivity">
</activity>

But ReceiverActivity does not appear to receive message, is ReceiverActivity setup correctly ?

Update 3 :

As per comment to start the activity I add :

    Intent intent = new Intent(this, ReceiverActivity.class);
    startActivity(intent);

to MainActivity.onCreate :

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        Intent intent = new Intent(this, ReceiverActivity.class);
        startActivity(intent);
        ......

Upvotes: 1

Views: 2413

Answers (1)

Yaroslav Mytkalyk
Yaroslav Mytkalyk

Reputation: 17105

new ListenerService();

This is not how you start any Service. This just creates a Servie instance that will do nothing and will be collected after onCreate() exits.

This Service will be started by the system when you'll receive a message. You only need to define it in the manifest. Also, you might need to define a path for messages you receive, like

<service android:name=".ListenerService" >
    <intent-filter>
        <action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
        <data android:scheme="wear" android:host="*" android:pathPrefix="/message"/>
    </intent-filter>
</service>

That's all you need for your Service to be set up. Also, keep in mind that in order to receive messages your Wear app and Handheld app should have the same package name (applicationId). Double check you don't have mismatching applicationId for flavors or buildTypes. So if you have applicationId in build.gradle, make sure they match for both wear and handhelp app projects

defaultConfig {
    applicationId "com.runner"

About updating UI:

@Override
public void onMessageReceived(MessageEvent messageEvent) {
    MainActivity.mTextView.setText("got message");
}

This is not a way to interact a Service with Activity. Activity might or might not be running when Service runs. Only update Activtity UI from Activity. If you need to show user a message, use a BroadcastReceiver or an Observer.

Note that onMessageReceived() will not be ran on Main UI thread, so use a Handler before showing a Toast there.

So if you want to pass the Message from this Service to Activity, one of the ways is like

@Override
public void onMessageReceived(MessageEvent messageEvent) {
    final byte[] data = messsageEvent.getData();
    if (data != null) {
        final String message = new String(data, Charset.forName("UTF-8"));
        final Intent messageIntent = new Intent("custom-event-name");
        intent.putExtra(EXTRA_MESSAGE, message); // define your extra
        LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent); 
        // Register BroadcastReiver from LocalBroadcastManager in your Activity to receive this broadcast
    }
}

Or if you want to start Activity if it's not running, you need a different approach:

<activity
    android:name=".ReceiverActivity"
    android:launchMode="singleTop"/>

Service:

@Override
public void onMessageReceived(MessageEvent messageEvent) {
    final byte[] data = messsageEvent.getData();
    if (data != null) {
        final String message = new String(data, Charset.forName("UTF-8"));
        final Intent activityIntent = new Intent(this, ReceiverActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.putExtra("EXTRA_MESSAGE", message);
        startActivity(intent);
    }
}

// In this case, in Activity, if it's explicitly started, you don't need a BroadcastReceiver
// Instead, you can get the extra from Activity Intent

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    handleIntent(getIntent());
}

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    handleIntent(intent);
}

private void handleIntent(Intent intent) {
    String message = intent.getStringExtra("EXTRA_MESSAGE");
}

In Wear component

initApi();
sendToast();

you use different threads that may run simultaneously so when sendToast() runs you may actually have nodeId not resolved yet.

What I suggest doing is connecting GoogleApiClient in onCreate() with a Listener. Once the client connects, start getting the node. You don't need to spawn your own threads, the API is asynchronous if you use setResultCallback() instead of await()

Edit 14/02/2018: as Rajesh mentioned in the comments, Wearable.API is deprecated. The answer below refers to old API, which were new on the time of writing. I am leaving the old answer as is, but I don't have time to investigate how to do this with new APIs.

private static final String MESSAGE_PATH = "/message";

private GoogleApiClient googleApiClient;

@Override
protected void onCerate(Bundle state) {
    super.onCreate(state);
    googleApiClient = getGoogleApiClient(this);
    googleApiClient.connect();
}

@Override
protected void onDestroy() {
    super.onDestroy();
    googleApiClient.disconnect();
}

private GoogleApiClient getGoogleApiClient(Context context) {
    return new GoogleApiClient.Builder(context)
            .addApi(Wearable.API)
            .addConnectionCallbacks(mConnectionCallbacks)
            .build();
}

private void findNodes() {
     Wearable.NodeApi.getConnectedNodes(googleApiClient).setResultCallback(
            new ResultCallback<NodeApi.GetConnectedNodesResult>() {
                @Override
                public void onResult(
                        @NonNull final NodeApi.GetConnectedNodesResult getConnectedNodesResult) {
                    List<Node> nodes = result.getNodes();
                    if (nodes != null && !nodes.isEmpty()) {
                        nodeId = nodes.get(0).getId();
                        Log.d(TAG, "nodeId "+ nodeId);
                        onNodeFound();
                    }
                }
            });
}

private void onNodeFound() {
    if (nodeId != null) {
        // Now you have your node, send a message, make sure the path starts like the path in manifest
        // What you thought is a message is actually a path, and the actual message is the byte array.
        // You may concat your message in path though, but keep in mind you will have to parse the string then
        Wearable.MessageApi.sendMessage(client, nodeId, MESSAGE_PATH, "Hello Wear!".getBytes(Charset.forName("UTF-8")));
    }
}

private final GoogleApiClient.ConnectionCallbacks mConnectionCallbacks
        = new GoogleApiClient.ConnectionCallbacks() {

    @Override
    public void onConnected(@Nullable final Bundle bundle) {
        findNodes();
    }

    @Override
    public void onConnectionSuspended(final int i) {
    }
};

Upvotes: 2

Related Questions