Reputation: 53806
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
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