I am working on the udacity wearable course and unable to get my wearable emulator to send dataEvents to the wearable device.
On both the handheld and the wearable I have services that extend the WearableListenerService (WLS). The handheld version is currently started via a startService call in the activity, the wearable service is started in the watchface service also with startService, both services can be seen as started.
The device WLS successfully makes a call to the content provider and attempts at sending the data to the wearable, but putDataItem resultCallback is never called.
The wearable seems to be paired with my phone as I receive various notifications on it from my phone, so the setup is good. Both the handheld and wearable modules have the service added to the manifest with the required intent-filter, and with logging I can see they are both starting up as expected.
I am following the docs, but I must be missing something.
Thanks for any help.
Handheld service
public class WeatherDataService extends WearableListenerService implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
private static final String TAG = "HandheldService";
private GoogleApiClient mGoogleClientApi;
#Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "initializing");
mGoogleClientApi = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
mGoogleClientApi.connect();
}
#Override
public void onPeerConnected(Node peer) {
super.onPeerConnected(peer);
Log.d(TAG, "onPeerConnected: " + peer.getDisplayName());
String[] temps = getCurrentTemps();
if (temps != null && temps.length == 2) {
Log.d(TAG, String.format("onPeerConnected: temps %s %s", temps[0], temps[1]));
notifyWearables(mGoogleClientApi, temps[0], temps[1]);
}
}
private void notifyWearables(GoogleApiClient client, String low, String high) {
Log.d(TAG, String.format("notifyWearables: %s %s", low, high));
PutDataMapRequest map = PutDataMapRequest.create("/weather");
map.getDataMap().putString("tempLow", low);
map.getDataMap().putString("tempHigh", high);
PutDataRequest request = map.asPutDataRequest();
Wearable.DataApi.putDataItem(client, request).setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
#Override
public void onResult(DataApi.DataItemResult result) {
Log.d(TAG, String.format("onResult, %s", result.getStatus().getStatusMessage()));
if (!result.getStatus().isSuccess()) {
Log.d(TAG, "onResult: Failed to send data");
}
}
});
...
}
Wearable service
public class WeatherDataService extends WearableListenerService {
private static final String TAG = "Wearable:Service";
#Override
public void onCreate() {
super.onCreate();
// this is called
Log.d(TAG, "onCreate");
}
#Override
public void onDataChanged(DataEventBuffer dataEvents) {
// NEVER makes it here
Log.d(TAG, "onDataChanged: ");
for (DataEvent dataEvent : dataEvents) {
Log.d(TAG, "onDataChanged: " + dataEvent.getDataItem().getUri().getPath());
if (dataEvent.getType() == DataEvent.TYPE_CHANGED) {
Log.d(TAG, "onDataChanged: TYPE_CHANGED");
DataMap dataMap = DataMapItem.fromDataItem(dataEvent.getDataItem()).getDataMap();
String path = dataEvent.getDataItem().getUri().getPath();
if (path.equals("/weather")) {
Log.d(TAG, "onDataChanged: /weather");
String tempLow = dataMap.getString("tempLow");
String tempHigh = dataMap.getString("tempHigh");
Log.d(TAG, "onDataChanged: " + tempLow + " " + tempHigh);
}
}
}
}
}
Update
I was missing the mGoogleApiClient.connect() method call. The putDataItem resultCallback is now being called, unforunately the wearable device's onDataChanged event is not being called.
onDataChanged
doesn't call because you doesn't change any data that sent to wear every time(it's call only when the data really did change), try to send different data and it will work, and make sure to connect your mGoogleClientApi
in onStrart();
It turned out there was a couple things wrong with things.
The first was what #mahmoud mentioned, although I missed it the first time I read it, in that mGoogleClientApi.connect() needed to be called. When #mahmoud said connect to the client in onStart() I didn't read that as call the .connect() method.
The second things that was wrong was that the manifest package attributes did not match for each the modules. I thought they needed the same parent namespaces.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.sunshine.app">
Related
I have a question regarding Android Wear: Wearable.DataApi.putDataItem:
My App sends some data from the mobile to the wear by using:
dataToWearRequest = PutDataRequest.create("/image");
dataToWearRequest.setData(compressedPictureAsByteArray);
Wearable.DataApi.putDataItem(mGoogleApiClient, dataToWearRequest);
The wear is receiving the data correctly by using an activity which implements DataApi.DataListener:
public void onDataChanged(DataEventBuffer dataEvents) {
for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_CHANGED &&
event.getDataItem().getUri().getPath().equals("/image")) {
DataItem dataItem = event.getDataItem();
byte content[] = dataItem.getData();
if (mDebug) System.out.println("onDataChanged: receiver: content.length="+ content.length);
}
}
}
But my mobile app has also a WearableListenerService which is desired to receive messages from the wear. The WearableListenerService class itself also implements the DataApi.DataListener (as I said: on the mobile). In this Service the onDataChanged method also receives the data packet which was sent to the wear. It is implemented very simple (in my case because I dont need data packets from the wear):
public void onDataChanged(DataEventBuffer dataEvents) {
//super.onDataChanged(dataEvents);
for (DataEvent event : dataEvents) {
System.out.println("WEAR Data changed: "+event.getDataItem().getUri().toString());
}
dataEvents.close();
}
The corresponding manifest entry for this mobile service looks as usual:
<service android:name=".RemoteControlWearService">
<intent-filter>
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
</intent-filter>
</service>
I have tried to set an intent-filter in the manifest file of the mobile with an unknown path just to prevent it from receiving the /image data. But that did not help.
I am asking the question because the mobile indeed duplicates the data packet sent to the wear to be able to call onDataReceived. Inside the Wearable.DataApi.putDataItem seems to be a memory leak: when you send many images to the wear the memory consumption of the mobile is increasing and never garbage collected (that are native buffers). I want to reduce the memory consumption of the mobile app by preventing the sending of the unneeded parcel to the local service.
Does anybody knows how I could prevent my local mobile service from receiving the data packet directed to the wear only?
You can retrieve the local node id see here, then compare it to the one that triggered the DataEvent by calling dataItem.getUri().getHost(). The best way to compare would be by calling equals()on the value returned by dataItem.getUri().getHost() and put the localNodeId as parameter.
You can see my implementation below:
#Override
public void onDataChanged(DataEventBuffer dataEvents) {
String localNodeId = NodeUtils.getLocalNodeId(this);
for (DataEvent dataEvent : dataEvents) {
if (dataEvent.getType() == DataEvent.TYPE_CHANGED) {
DataItem dataItem = dataEvent.getDataItem();
String host = dataItem.getUri().getHost();
if (host.equals(localNodeId)) continue; // Ignore local changes
if (dataItem.getUri().getPath().equals(THE_DATA_PATH_I_WANT)) {
DataMap dataMap = DataMapItem.fromDataItem(dataItem).getDataMap();
// Do something with my DataMap
}
}
}
}
with the code used to get the node id
public class NodeUtils {
#Nullable
public static String getLocalNodeId(#NonNull Context context) {
GoogleApiClient googleApiClient = new GoogleApiClient.Builder(context)
.addApi(Wearable.API)
.build();
ConnectionResult connectionResult = googleApiClient.blockingConnect(30, TimeUnit.SECONDS);
return connectionResult.isSuccess() ? getLocalNodeId(googleApiClient) : null;
}
#Nullable
public static String getLocalNodeId(#NonNull GoogleApiClient googleApiClient) {
NodeApi.GetLocalNodeResult nodeResult =
Wearable.NodeApi.getLocalNode(googleApiClient).await(10, TimeUnit.SECONDS);
if (nodeResult.getStatus().isSuccess()) {
return nodeResult.getNode().getId();
} else {
Timber.wtf("couldn't get local node? status: %s", nodeResult.getStatus());
return null;
}
}
}
I'm following the same steps described here (the Google Fit client connection part is working fine).
final DataType dataType=TYPE_STEP_COUNT_DELTA;
DataSourcesRequest requestData = new DataSourcesRequest.Builder()
.setDataTypes(dataType) // At least one datatype must be specified.
.build();
Fitness.SensorsApi.findDataSources(mClient, requestData)
.setResultCallback(new ResultCallback<DataSourcesResult>() {
#Override
public void onResult(DataSourcesResult dataSourcesResult) {
Log.i(TAG, "Result: " + dataSourcesResult.getDataSources().size() + " sources "
+ dataSourcesResult.getStatus().toString());
for (DataSource dataSource : dataSourcesResult.getDataSources()) {
Log.i(TAG, "Data source found: " + dataSource.toString());
Log.i(TAG, "Data Source type: " + dataSource.getDataType().getName());
}
}
});
When I ask for data sources I get only one result which is the smartphone. If I add a listener then I really get data so it's working.
However it is also connected to an Android Wear smartwatch Gear Live with Android Wear app on the phone. Google Fit is installed in both of them but I'd like to get data from the smartwatch.
In the official guide I read
The Sensors API provides access to raw sensor data streams from
sensors available on the Android device and from sensors available in
companion devices, such as wearables.
This code is running on the smartphone so I think it would be right to expect data sources from companion smartwatch too. But it's like invisible to my phone application. Am I doing something wrong?
EDIT:
public class MainActivity extends AppCompatActivity {
private final static String TAG = "main_mobile";
private static final int REQUEST_OAUTH = 1;
private final static String DATE_FORMAT = "yyyy.MM.dd HH:mm:ss";
private static final String AUTH_PENDING = "auth_state_pending";
private boolean authInProgress = false;
private GoogleApiClient mClient = null;
private final static DataType dataType = TYPE_STEP_COUNT_DELTA;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
authInProgress = savedInstanceState.getBoolean(AUTH_PENDING);
}
mClient = new GoogleApiClient.Builder(this)
.addApi(Fitness.SENSORS_API)
.addApi(Fitness.RECORDING_API)
.addApi(Fitness.HISTORY_API)
.addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE))
.addConnectionCallbacks(connectionCallbacks)
.addOnConnectionFailedListener(connectionFailCallbacks)
.build();
}
private void initFitness() {
DataSourcesRequest requestData = new DataSourcesRequest.Builder()
.setDataTypes(dataType)
.build();
Fitness.SensorsApi.findDataSources(mClient, requestData)
.setResultCallback(new ResultCallback<DataSourcesResult>() {
#Override
public void onResult(DataSourcesResult dataSourcesResult) {
Log.i(TAG, "Result: " + dataSourcesResult.getDataSources().size() + " sources " + dataSourcesResult.getStatus().toString());
for (DataSource dataSource : dataSourcesResult.getDataSources()) {
Log.i(TAG, "\nData source found: \n\t" + dataSource.toString() + "\n\tType: " + dataSource.getDataType().getName());
}
}
});
}
#Override
protected void onStart() {
super.onStart();
Log.i(TAG, "Connecting...");
mClient.connect();
}
#Override
protected void onStop() {
super.onStop();
if (mClient.isConnected()) {
mClient.disconnect();
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(AUTH_PENDING, authInProgress);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_OAUTH) {
authInProgress = false;
if (resultCode == RESULT_OK) {
// Make sure the app is not already connected or attempting to connect
if (!mClient.isConnecting() && !mClient.isConnected()) {
mClient.connect();
}
}
}
}
GoogleApiClient.ConnectionCallbacks connectionCallbacks = new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(Bundle bundle) {
Log.i(TAG, "Connected!!!");
// Now you can make calls to the Fitness APIs.
// Put application specific code here.
initFitness();
}
#Override
public void onConnectionSuspended(int i) {
// If your connection to the sensor gets lost at some point,
// you'll be able to determine the reason and react to it here.
if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) {
Log.i(TAG, "Connection lost. Cause: Network Lost.");
} else if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) {
Log.i(TAG, "Connection lost. Reason: Service Disconnected");
}
}
};
GoogleApiClient.OnConnectionFailedListener connectionFailCallbacks = new GoogleApiClient.OnConnectionFailedListener() {
// Called whenever the API client fails to connect.
#Override
public void onConnectionFailed(ConnectionResult result) {
Log.i(TAG, "Connection failed. Cause: " + result.toString());
if (!result.hasResolution()) {
// Show the localized error dialog
GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), MainActivity.this, 0).show();
return;
}
// The failure has a resolution. Resolve it.
// Called typically when the app is not yet authorized, and an
// authorization dialog is displayed to the user.
if (!authInProgress) {
try {
Log.i(TAG, "Attempting to resolve failed connection");
authInProgress = true;
result.startResolutionForResult(MainActivity.this, REQUEST_OAUTH);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Exception while starting resolution activity", e);
}
}
}
};
}
I have not tried any of this.
It seems as though the Samsung Gear Live Sensors are not supported out of the box, but you might be able to make it work via software sensors:
Your Gear Live
As said in this SO answer,
The Samsung Gear Live watch does not advertise itself as a BLE heart
rate monitor and therefore does not make the heart rate data
available via the normal Bluetooth Low Energy API or the Google
Fit API which is built upon it.
Supported Sensors
As said in the official docs,
Google Fit includes support for sensors on the mobile device and
Bluetooth Low Energy sensors paired with the device. Google Fit lets
developers implement support for other sensors and expose them as
software sensors in Android apps. Sensors supported by Google Fit are
available to Android apps as data source objects.
Possible Solution
It seems possible to implement additional software sensors.
(Copied template for this is at the bottom of the post, because it is lengthy).
You would get the data on the wearable following get-heart-rate-from-sensor-samsung-gear-live.
Template (from https://developers.google.com/fit/android/new-sensors)
Add this to your manifest file:
<service android:name="com.example.MySensorService"
android:process=":sensor">
<intent-filter>
<action android:name="com.google.android.gms.fitness.service.FitnessSensorService"/>
<!-- include at least one mimeType filter for the supported data types -->
<data android:mimeType="vnd.google.fitness.data_type/com.google.heart_rate.bpm"/>
</intent-filter>
</service>
and flesh this Service out:
import com.google.android.gms.common.*;
import com.google.android.gms.common.api.*;
import com.google.android.gms.fitness.*;
import com.google.android.gms.fitness.data.*;
import com.google.android.gms.fitness.service.*;
...
public class MySensorService extends FitnessSensorService {
#Override
public void onCreate() {
super.onCreate();
// 1. Initialize your software sensor(s).
// 2. Create DataSource representations of your software sensor(s).
// 3. Initialize some data structure to keep track of a registration for each sensor.
}
#Override
protected List<DataSource> onFindDataSources(List<DataType> dataTypes) {
// 1. Find which of your software sensors provide the data types requested.
// 2. Return those as a list of DataSource objects.
}
#Override
protected boolean onRegister(FitnessSensorServiceRequest request) {
// 1. Determine which sensor to register with request.getDataSource().
// 2. If a registration for this sensor already exists, replace it with this one.
// 3. Keep (or update) a reference to the request object.
// 4. Configure your sensor according to the request parameters.
// 5. When the sensor has new data, deliver it to the platform by calling
// request.getDispatcher().publish(List<DataPoint> dataPoints)
}
#Override
protected boolean onUnregister(DataSource dataSource) {
// 1. Configure this sensor to stop delivering data to the platform
// 2. Discard the reference to the registration request object
}
}
Wifi P2P service discovery is not behaving as expected. I am seeing intermittent issues where the DNSSD listeners are not called always and hence I have no clue of nearby devices running the same app. I am using the following two APIs - one to register a service to be discovered by other devices and the other to discover the nearby services running on other devices. Any idea if I am doing anything wrong here or is there some specific sequence of other android API calls that need to be made before I call these APIs to ensure that the listeners are always called whenever there is a new service registered or even if a service is registered before we call the API to discover the local services.
API to register a local service:
private void registerService() {
Map<String, String> values = new HashMap<String, String>();
values.put("name", "Steve");
values.put("port", "8080");
WifiP2pServiceInfo srvcInfo = WifiP2pDnsSdServiceInfo.newInstance(mMyDevice.deviceName, "_http._tcp", values);
manager.addLocalService(channel, srvcInfo, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
Toast.makeText(WiFiDirectActivity.this, "Local service added successfully",
Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(int reasonCode) {
Toast.makeText(WiFiDirectActivity.this, "Local service addition failed : " + reasonCode,
Toast.LENGTH_SHORT).show();
}
});
}
API to discover local services:
public void discoverService() {
manager.clearServiceRequests(channel, null);
DnsSdTxtRecordListener txtListener = new DnsSdTxtRecordListener() {
#Override
/* Callback includes:
* fullDomain: full domain name: e.g "printer._ipp._tcp.local."
* record: TXT record data as a map of key/value pairs.
* device: The device running the advertised service.
*/
public void onDnsSdTxtRecordAvailable(String fullDomain, Map record, WifiP2pDevice device) {
Log.d(TAG, "DnsSdTxtRecord available -" + record.toString());
}
};
DnsSdServiceResponseListener servListener = new DnsSdServiceResponseListener() {
#Override
public void onDnsSdServiceAvailable(String instanceName, String registrationType, WifiP2pDevice resourceType) {
Log.d(TAG, "onBonjourServiceAvailable " + instanceName);
}
};
manager.setDnsSdResponseListeners(channel, servListener, txtListener);
WifiP2pDnsSdServiceRequest serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
manager.addServiceRequest(channel, serviceRequest, new ActionListener() {
#Override
public void onSuccess() {
// Success!
Log.d(TAG, "addServiceRequest success");
}
#Override
public void onFailure(int code) {
// Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY
Log.d(TAG, "addServiceRequest failure with code " + code);
}
});
manager.discoverServices(channel, new ActionListener() {
#Override
public void onSuccess() {
// Success!
Log.d(TAG, "discoverServices success");
}
#Override
public void onFailure(int code) {
// Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY
if (code == WifiP2pManager.P2P_UNSUPPORTED) {
Log.d(TAG, "P2P isn't supported on this device.");
} else {
Log.d(TAG, "discoverServices failure");
}
}
});
}
Note: manager & channel are initialized as
WifiP2pManager manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
Channel channel = manager.initialize(this, getMainLooper(), null);
WifiP2p (in general):
Some time ago I was developing an application with a pretty complex network connectivity system based on WifiP2p with Service Broadcasting/Discovery. And based on that experience I already wrote few posts here on SO about how difficult, wearing and problematic that is. Here are two of them (they are quite full of the inside knowledge I acquired about WifiP2p with Service Discovery, and WifiP2p itself):
Why is discovering peers for Android WifiDirect so unreliable
Wi-fi P2P. Inform all peers available of some event
I would advise you to read both of my answers (even though they are focused a bit more on the WifiP2p itself). They should give you some perspective on the things you should be looking for when working with the WifiP2p Service Discovery.
I can easily say that if you want to build an efficient, relatively reliable and robust WifiP2p connection system (especially with Service Discovery), you will have to work your ass off.
WifiP2p Service Discovery:
To better answer your exact question, I will tell you what I did (different from you) to make my Service Discovery work pretty reliably.
1. Broadcasting Service:
First of all: before registering your Service (with addLocalService method) you should use the WifiP2pManager's clearLocalServices method. And it is important, that you should only call addLocalService if the listener passed in the clearLocalServices returned with the onSuccess callback.
Although this sets up the broadcasting pretty nicely, I found that other nodes were not always able to detect the broadcasted service (especially when those nodes weren't already actively detecting services at the moment of registering your local Service - but they "joined" later). I couldn't find a way to fix this issue 100% reliably. And believe me I was trying probably everything WifiP2p-related. And no, the clearLocalServices-addLocalService sequence wasn't really giving satisfying results. Or more so: doing something different was working much better. What I decided to do, was after I successfully added local service (onSuccess callback from addLocalService), I started a Thread that would periodically call WifiP2pManager's method discoverPeers. That seemed to be forcing to rebroadcast all the service information.
So... basically the base of your broadcasting code should look more-less like this (bare in mind that every single piece of code I will post here is stripped-off of all "checks" if the network connectivity system is in the right state, you should design them yourself to fit your solution the best):
public void startBroadcastingService(){
mWifiP2pManager.clearLocalServices(mWifiP2pChannel, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
mWifiP2pManager.addLocalService(mWifiP2pChannel, mWifiP2pServiceInfo,
new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
// service broadcasting started
mServiceBroadcastingHandler
.postDelayed(mServiceBroadcastingRunnable,
SERVICE_BROADCASTING_INTERVAL);
}
#Override
public void onFailure(int error) {
// react to failure of adding the local service
}
});
}
#Override
public void onFailure(int error) {
// react to failure of clearing the local services
}
});
}
where the mServiceBroadcastingRunnable should be:
private Runnable mServiceBroadcastingRunnable = new Runnable() {
#Override
public void run() {
mWifiP2pManager.discoverPeers(mWifiP2pChannel, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
}
#Override
public void onFailure(int error) {
}
});
mServiceBroadcastingHandler
.postDelayed(mServiceBroadcastingRunnable, SERVICE_BROADCASTING_INTERVAL);
}
};
2. Discovering Service:
For the discovering of your service I used similar approach. Both with the setting up the discovering, and with trying to force "rediscovery" of services.
Setting up was performed with the sequence of the following three WifiP2pManager's methods:
removeServiceRequest, addServiceRequest, discoverServices
They were called in this exact order and a particular method (second or the third one to be exact) has been called only after the previous one had "returned" with the onSuccess callback.
The rediscovery of services was being performed with the intuitive method (just by repeating the mentioned sequence: removeServiceRequest -> addServiceRequest -> discoverServices).
The base of my code looked more-less like this (to start Service Discovery I would first call prepareServiceDiscovery() and then startServiceDiscovery()):
public void prepareServiceDiscovery() {
mWifiP2pManager.setDnsSdResponseListeners(mWifiP2pChannel,
new WifiP2pManager.DnsSdServiceResponseListener() {
#Override
public void onDnsSdServiceAvailable(String instanceName,
String registrationType, WifiP2pDevice srcDevice) {
// do all the things you need to do with detected service
}
}, new WifiP2pManager.DnsSdTxtRecordListener() {
#Override
public void onDnsSdTxtRecordAvailable(
String fullDomainName, Map<String, String> record,
WifiP2pDevice device) {
// do all the things you need to do with detailed information about detected service
}
});
mWifiP2pServiceRequest = WifiP2pDnsSdServiceRequest.newInstance();
}
private void startServiceDiscovery() {
mWifiP2pManager.removeServiceRequest(mWifiP2pChannel, mWifiP2pServiceRequest,
new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
mWifiP2pManager.addServiceRequest(mWifiP2pChannel, mWifiP2pServiceRequest,
new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
mWifiP2pManager.discoverServices(mWifiP2pChannel,
new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
//service discovery started
mServiceDiscoveringHandler.postDelayed(
mServiceDiscoveringRunnable,
SERVICE_DISCOVERING_INTERVAL);
}
#Override
public void onFailure(int error) {
// react to failure of starting service discovery
}
});
}
#Override
public void onFailure(int error) {
// react to failure of adding service request
}
});
}
#Override
public void onFailure(int reason) {
// react to failure of removing service request
}
});
}
the mServiceDiscoveringRunnable was just:
private Runnable mServiceDiscoveringRunnable = new Runnable() {
#Override
public void run() {
startServiceDiscovery();
}
};
All this made my system work quite well. It wasn't perfect yet, but with the lack of documentation on this subject I think I couldn't do much more to improve it.
If you test this approach, be sure to tell me how it works for you (or if it works for you ;) ).
if the problem is the detection of the service i believe that crearing group is the best way to make the device and service detectable but the if created group in the all devices then you cannot connect in direct.
but as wifi network.
i do it every day and it works.
I am working on android wear app using Eclipse IDE.I am using same package names for wear app and mobile app and i am packing wearable app manually according to google documentation.Everything is working fine.it is installed on Android wear emulator using usb debugging with phone.
My problem is when i am sending a message to wearable using following code
List<Node> nodeList=getNodes();
for(Node node : nodeList) {
Log.v(" ", "telling " + node.getId() );
PendingResult<MessageApi.SendMessageResult> result = Wearable.MessageApi.sendMessage(
mGoogleApiClient,
node.getId(),
START_ACTIVITY_PATH,
null
);
result.setResultCallback(new ResultCallback<MessageApi.SendMessageResult>() {
#Override
public void onResult(MessageApi.SendMessageResult sendMessageResult) {
Log.v(" ", "Phone: " + sendMessageResult.getStatus().getStatusMessage());
}
});
}
the OnPeerConnected method is running when devices are peered but OnMessageReceived never called in WearableListenerService.This is my WearableListenerService code:
public class DataLayerListenerService extends WearableListenerService {
private static final String TAG = "DataLayerSample";
private static final String START_ACTIVITY_PATH = "/start/MainActivity";
private static final String DATA_ITEM_RECEIVED_PATH = "/data-item-received";
private static final String LOG_TAG = "log";
#Override
public void onPeerConnected(Node peer) {
super.onPeerConnected(peer);
String id = peer.getId();
String name = peer.getDisplayName();
Log.d(LOG_TAG, "Connected peer name & ID: " + name + "|" + id);
}
#Override
public void onDataChanged(DataEventBuffer dataEvents) {
System.out.println("Recevive message3");
}
#Override
public void onMessageReceived(MessageEvent messageEvent) {
System.out.println("service watch message1");
if (messageEvent.getPath().equals(START_ACTIVITY_PATH)) {
System.out.println("service watch message2");
Intent startIntent = new Intent(this, MainActivity.class);
startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(startIntent);
}
}
}
Also a warning message in Logcat always appears :
app does not match record's app key: AppKey[com.myapp,c3f31717fa35401056c20a2798907f1232efa75e] != AppKey[com.myapp,f36e726eefc7e528db26a1c25f6fbf2f93dacd70]
If app key for both apps should be same then how can i create same app key for both the apps.
Any help is highly appreciated,
Thanks.
The error message you have:
app does not match record's app key:
AppKey[com.myapp,c3f31717fa35401056c20a2798907f1232efa75e] !=
AppKey[com.myapp,f36e726eefc7e528db26a1c25f6fbf2f93dacd70]
Indicated that your apps are signed with the different keys.
Package names of phone and wearable apps are the same - that is good, but they also need to share the same signature. This is the reason why messages cannot be delivered - wearable apps are recognized as "part of the same app" based on the package name and signature.
Please make sure that you have both apps signed with the same key. If you are testing the autoinstallation feature please make sure to uninstall the debug version of wearable app from watch emulator.
I had the same error, my fault was that the "wear" module's package name was not the same as the app's.
BAD:
[module: app] es.voghdev.myapp
[module: wear] es.voghdev.myapp.wear
GOOD:
[module: app] es.voghdev.myapp
[module: wear] es.voghdev.myapp
Made me waste so much time!! >:-(
Use an asyntask to send messages as they will block the ui thread. Also you need to call the await method. To get the apps to have the same key, you need to use build variants with gradle.
public class SendMessageTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... voids) {
NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(apiClient).await();
for (Node node : nodes.getNodes()) {
Wearable.MessageApi
.sendMessage(apiClient, node.getId(), "/start/MainActivity", null)
.await();
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
Toast.makeText(MainActivity.this, "Message Sent", Toast.LENGTH_SHORT).show();
}
}
I currently have an android wear watchface developed. I however would now like to create a settings section on the host app that would allow the user to customize the watchface. I am new to the android development so I am curious on the correct way to do this.
Is there a way to update a sharedpreference on the host and then push or sync that with the sharedpreference on the wear device? Or is there a totally different way I should be looking at this?
You can use the DataApi or MessageApi to sync your watchface configuration between Phone and Watch devices.
Please take a look at the documentation and choose the one more appropriate to your needs:
https://developer.android.com/training/wearables/data-layer/index.html
https://developer.android.com/training/wearables/data-layer/data-items.html
https://developer.android.com/training/wearables/data-layer/messages.html
Here is an example with the use of DataApi.
Everything pushed to the DataApi is shared between devices and available of both of them. You can change this data on both sides and the other side will be notified about such change immediately (when devices are connected to each other). You can also read this data at any moment (for example when user will choose your watchface on the Watch - the configuration data will be already waiting for you there).
On the phone side:
public class WatchfaceConfigActivity extends Activity {
private GoogleApiClient mGoogleApiClient;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(new ConnectionCallbacks() {
#Override
public void onConnected(Bundle connectionHint) {
}
#Override
public void onConnectionSuspended(int cause) {
}
})
.addOnConnectionFailedListener(new OnConnectionFailedListener() {
#Override
public void onConnectionFailed(ConnectionResult result) {
}
})
.addApi(Wearable.API)
.build();
mGoogleApiClient.connect();
}
and every time you want to sync new fconfiguration with the Android Wear device you have to put a DataRequest via Wearable DataApi:
private void syncConfiguration() {
if(mGoogleApiClient==null)
return;
final PutDataMapRequest putRequest = PutDataMapRequest.create("/CONFIG");
final DataMap map = putRequest.getDataMap();
map.putInt("mode", 1);
map.putInt("color", Color.RED);
map.putString("string_example", "MyWatchface");
Wearable.DataApi.putDataItem(mGoogleApiClient, putRequest.asPutDataRequest());
}
}
On the Watch side:
You need to create a class that extends WearableListenerService:
public class DataLayerListenerService extends WearableListenerService {
#Override
public void onDataChanged(DataEventBuffer dataEvents) {
super.onDataChanged(dataEvents);
final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents);
for(DataEvent event : events) {
final Uri uri = event.getDataItem().getUri();
final String path = uri!=null ? uri.getPath() : null;
if("/CONFIG".equals(path)) {
final DataMap map = DataMapItem.fromDataItem(event.getDataItem()).getDataMap();
// read your values from map:
int mode = map.getInt("mode");
int color = map.getInt("color");
String stringExample = map.getString("string_example");
}
}
}
}
and declare it in your AndroidManifest:
<service android:name=".DataLayerListenerService" >
<intent-filter>
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
</intent-filter>
</service>
Notice that this is only an example of usage. Maybe (instead of registering an instance of WearableListenerService) there will be better for you to create an instance of mGoogleApiClient inside your Watchface directly and add a DataListener there:
Wearable.DataApi.addListener(mGoogleApiClient, new DataListener() {
#Override
public void onDataChanged(DataEventBuffer dataEvents) {
// read config here and update the watchface
}
});
Maybe you don't need shared data - then you can communicate using MessageApi and send messages only when new configuration is saved or then watch wants to read current configuration from phone.
There isn't a shared preferences across the mobile and wear modules per se, but you can send messages and/or update assets that a listener will detect. For example, whenever you change a preference on the phone, you could also send a message to the watch using the Message API. On the watch, you should implement a WearableListenerService with an onMessageReceived method, in which you can parse the message and take an appropriate action, such as setting a preference on the watch.
Check out the Android Developers training guide: https://developer.android.com/training/wearables/data-layer/index.html