I have an Android Wear app which communicates accelerometer values to an Handheld app.
I want to be notified in the Handheld app when connection with the android wear app has just been lost (when the Android Wear app is closed, for instance).
How must I implement that ?
Thank you for your help
Devices connected in the wear-to-device network are called Nodes. You can use the Node API to determine when devices enter or leave the network (e.g. when a wear device becomes disconnected from a phone).
https://developers.google.com/android/reference/com/google/android/gms/wearable/NodeApi.NodeListener
You can also use the Node API to get a list of all connected devices (e.g all wear devices connected to a phone) at any given time
https://developers.google.com/android/reference/com/google/android/gms/wearable/NodeApi
You can easily do this by extends the WearableListenerService and override onConnectedNodes() method.
Wearable Side
public class DisconnectListenerService extends WearableListenerService implements GoogleApiClient.ConnectionCallbacks {
/* the capability that the phone app would provide */
private static final String CONNECTION_STATUS_CAPABILITY_NAME = "is_connection_lost";
private GoogleApiClient mGoogleApiClient;
#Override
public void onCreate() {
super.onCreate();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.build();
}
#Override
public void onConnectedNodes(List<Node> connectedNodes) {
if (mGoogleApiClient.isConnected()) {
updateStatus();
} else if (!mGoogleApiClient.isConnecting()) {
mGoogleApiClient.connect();
}
}
private void updateStatus() {
Wearable.CapabilityApi.getCapability(
mGoogleApiClient, CONNECTION_STATUS_CAPABILITY_NAME,
CapabilityApi.FILTER_REACHABLE).setResultCallback(
new ResultCallback<CapabilityApi.GetCapabilityResult>() {
#Override
public void onResult(CapabilityApi.GetCapabilityResult result) {
if (result.getStatus().isSuccess()) {
updateConnectionCapability(result.getCapability());
} else {
Log.e(TAG, "Failed to get capabilities, " + "status: " + result.getStatus().getStatusMessage());
}
}
});
}
private void updateConnectionCapability(CapabilityInfo capabilityInfo) {
Set<Node> connectedNodes = capabilityInfo.getNodes();
if (connectedNodes.isEmpty()) {
// The connection is lost !
} else {
for (Node node : connectedNodes) {
if (node.isNearby()) {
// The connection is OK !
}
}
}
}
#Override
public void onConnected(Bundle bundle) {
updateStatus();
}
#Override
public void onConnectionSuspended(int cause) {
}
#Override
public void onDestroy() {
if (mGoogleApiClient.isConnected() || mGoogleApiClient.isConnecting()) {
mGoogleApiClient.disconnect();
}
super.onDestroy();
}
}
Phone Side
create a xml file in values/ directory named wear.xml
<resources>
<string-array name="android_wear_capabilities">
<item>is_connection_lost</item>
</string-array>
</resources>
Related
I am trying to pull the data from Intel's Fossil Android based Smartwatch (BLE device) via Google Fit Android SDK. The BLE Scan seem to happen, pairing occurs but inside the result Callback it doesn't go to onDeviceFound (I can proceed from there if it reaches). It eventually times out within few seconds from the start of the scan.
Any help would be appreciated.
Thanks for the docs link. I did go through all of that thoroughly but it didn't help. This is my Class file and I can call startBleScan from my MainActivity, the BleScan seems to be working, but after that it doesn't proceed to go on to onDeviceFound method.
public class BlueToothDevicesManager {
private static final String TAG = "BlueToothDevicesManager";
private static final int REQUEST_BLUETOOTH = 1001;
private Main2Activity mMonitor;
private GoogleApiClient mClient;
public BlueToothDevicesManager(Main2Activity monitor, GoogleApiClient client) {
mMonitor = monitor;
mClient = client;
}
public void startBleScan() {
if (mClient.isConnected()) {
Log.i(TAG, "Google account is connected");
}
BleScanCallback callback = new BleScanCallback() {
#Override
public void onDeviceFound(BleDevice device) {
Log.i(TAG, "BLE Device Found: " + device.getName());
claimDevice(device);
}
#Override
public void onScanStopped() {
Log.i(TAG, "BLE scan stopped");
}
};
PendingResult result = Fitness.BleApi.startBleScan(mClient, new StartBleScanRequest.Builder()
.setDataTypes(DataType.TYPE_POWER_SAMPLE, DataType.TYPE_STEP_COUNT_CADENCE, DataType.TYPE_STEP_COUNT_DELTA, DataType.TYPE_SPEED, DataType.TYPE_ACTIVITY_SAMPLE, DataType.TYPE_DISTANCE_DELTA, DataType.TYPE_ACTIVITY_SEGMENT, DataType.TYPE_LOCATION_SAMPLE)
.setBleScanCallback(callback)
.build());
result.setResultCallback(new ResultCallback() {
#Override
public void onResult(#NonNull Result result) {
Status status = result.getStatus();
if (!status.isSuccess()) {
String a = status.getStatusCode() + "";
Log.i(TAG, a);
switch (status.getStatusCode()) {
case FitnessStatusCodes.DISABLED_BLUETOOTH:
try {
status.startResolutionForResult(mMonitor, REQUEST_BLUETOOTH);
} catch (SendIntentException e) {
Log.i(TAG, "SendIntentException: " + e.getMessage());
}
break;
}
Log.i(TAG, "BLE scan unsuccessful");
} else {
Log.i(TAG, "ble scan status message: " + status.getStatusMessage());
Log.i(TAG, "Ble scan successful: " + status.getResolution());
}
}
});
}
public void claimDevice(BleDevice device) {
//Stop ble scan
//Claim device
PendingResult<Status> pendingResult = Fitness.BleApi.claimBleDevice(mClient, device);
pendingResult.setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(#NonNull Status st) {
if (st.isSuccess()) {
Log.i(TAG, "Claimed device successfully");
} else {
Log.e(TAG, "Did not successfully claim device");
}
}
});
}
}
I have this activity:
public class AMLoginActivity extends Activity implements IAsyncResponse {
public static int finished;
private final String TAG = "AMLoginActivity";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
finished = 0;
Intent i = new Intent(AMLoginActivity.this, GDLoginActivity.class);
i.putExtra("response", this);
AMLoginActivity.this.startActivity(i);
}
public void processFinish(){
finished++;
Log.i(TAG, "Number finished: " + finished);
if(finished == 1){
Log.i(TAG, "All finished");
AMLoginActivity.this.finish();
Log.i(TAG, "Finish called");
}
}
Which calls the GDLoginActivity to login to google drive. It can be seen below:
public class GDLoginActivity extends Activity implements GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
private static final String TAG = "GDLoginActivity";
private static final int REQUEST_CODE_RESOLUTION = 3;
private static GoogleApiClient mGoogleApiClient;
#Override
protected void onResume() {
super.onResume();
if (mGoogleApiClient == null) {
// Create the API client and bind it to an instance variable.
// We use this instance as the callback for connection and connection
// failures.
// Since no account name is passed, the user is prompted to choose.
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
Log.i(TAG, "New GDClient created");
}
// Connect the client.
mGoogleApiClient.connect();
}
#Override
protected void onPause() {
if (mGoogleApiClient != null) {
mGoogleApiClient.disconnect();
}
super.onPause();
}
#Override
public void onConnected(Bundle connectionHint) {
Log.i(TAG, "API client connected.");
IAsyncResponse response = (IAsyncResponse) getIntent().getSerializableExtra("response");
Log.i(TAG, "Process finish");
response.processFinish();
finish();
}
#Override
public void onConnectionSuspended(int cause) {
Log.i(TAG, "GoogleApiClient connection suspended");
}
#Override
public void onConnectionFailed(ConnectionResult result) {
// Called whenever the API client fails to connect.
Log.i(TAG, "GoogleApiClient connection failed: " + result.toString());
if (!result.hasResolution()) {
// show the localized error dialog.
GoogleApiAvailability.getInstance().getErrorDialog(this, result.getErrorCode(), 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.
try {
result.startResolutionForResult(this, REQUEST_CODE_RESOLUTION);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Exception while starting resolution activity", e);
}
}
public static GoogleApiClient getGoogleApiClient(){
return mGoogleApiClient;
}
}
The issue is that when the GDLoginActivity connects and finish()'s, it should increment finish by 1 and and also finish the AMLoginActivity. It does increment by 1 and call finish() in processFinish(), but nothing happens and AMLoginActivity doesn't actually close (i.e. onDestroy() is never called), so i'm just left with a blank screen. If I remove GDLoginActivity and just call processFinish() instead, then AMLoginActivity finishes just fine, so I assume it has something to do with GDLoginActivity, but this is happening with other similar activities too. Any ideas?
Edit: Also if I hit the back button on the blank screen, then it calls the onDestroy() method of AMLoginActivity and goes to the activity I want, if that hints a clue at what is going on?
It looks like you're finishing them in a weird order. Try finishing the visible activity before you finish the one that started it.
#Override
public void onConnected(Bundle connectionHint) {
Log.i(TAG, "API client connected.");
IAsyncResponse response = (IAsyncResponse) getIntent().getSerializableExtra("response");
Log.i(TAG, "Process finish");
//finishes the previous activity
response.processFinish();
//finishes the visible activity
finish();
//try flipping this order ^
}
You could use startActivitForResult() if you're trying to finish one activity after another has finished.
I got a problem with the Google android wear Data Layer Api...
I already used it to call handheld activities from a wearable activity and it worked just fine. But now I want call a handheld activity from a watchface service...
I created a Google API Client and added wearableApi:
public void onCreate(SurfaceHolder holder) {
super.onCreate(holder);
setWatchFaceStyle(new WatchFaceStyle.Builder(MyWatchFace.this)
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
.setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
.setShowSystemUiTime(false)
.setAcceptsTapEvents(true)
.build());
mGoogleApiClient = new GoogleApiClient.Builder(MyWatchFace.this)
.addApi(Wearable.API)
.build();
mGoogleApiClient.connect();
Then I used these methods to send the data:
public void startActivity()
{
HashSet<String> results = new HashSet<String>();
Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
#Override
public void onResult(NodeApi.GetConnectedNodesResult getConnectedNodesResult) {
String handheld = getConnectedNodesResult.getNodes().get(0).getId();
Toast.makeText(MyWatchFace.this, getConnectedNodesResult.getNodes().get(0).getDisplayName()+": "+ handheld, Toast.LENGTH_SHORT).show();
sendStartActivityMessage(handheld, giveValueXYZ());
}
});
private void sendStartActivityMessage(String nodeId, String data) {
byte[] dataArray = data.getBytes();
Toast.makeText(MyWatchFace.this, "ActivityPath: "+ START_ACTIVITY_PATH, Toast.LENGTH_SHORT).show();
Wearable.MessageApi.sendMessage(
mGoogleApiClient, nodeId, START_ACTIVITY_PATH, dataArray).setResultCallback(
new ResultCallback<MessageApi.SendMessageResult>() {
#Override
public void onResult(MessageApi.SendMessageResult sendMessageResult) {
if (!sendMessageResult.getStatus().isSuccess()) {
Log.e(TAG, "Failed to send message with status code: "
+ sendMessageResult.getStatus().getStatusCode());
}
}
});
}
On the mobile site I used this code to listen for the messages:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_host_main);
context = getApplicationContext();
...
//Google Api
final GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API).build();
googleApiClient.connect();
Wearable.MessageApi.addListener(googleApiClient, this);
...
public void onMessageReceived(MessageEvent messageEvent) {
if (messageEvent.getPath().equals(START_ACTIVITY_PATH_WEARDATA)) {
Log.d(TAG, "getPath: " + messageEvent.getPath() + "|||getData: " + messageEvent.getData());
someActions();
}
All this works perfectly fine if I send the message from a wearable activity but does not even trigger the onMessageReceived(...) methode if I send a message from the watchface service...
Could there be a problem with my package names?
mobile :com.example.janik.xxx.xxx
watchface: de.WatchSmart.watch_smart_watch_face_service
How does the client know which Data Layer Api message is adressed to him?
I have created a mobile app with an Activity which sends the data to wear. Mobile App Side the code is as below
Please note I have deleted some code to reduce the number of lines.
public class MainActivity extends AppCompatActivity implements
GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
GoogleApiClient mGoogleClient;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Build a new GoogleApiClient for the the Wearable API
mGoogleClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
Button sendBtn = (Button) findViewById(R.id.send_btn);
sendBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mGoogleClient.isConnected()) {
Log.w("mobile-p", "connected.....sending data");
sendData();
}
}
});
}
// Connect to the data layer when the Activity starts
#Override
protected void onStart() {
super.onStart();
mGoogleClient.connect();
}
private void constructDataI temsForNotification() {
//I construct the dataItems here which needs to be passed to the wear
//and call the sendata.
sendData();
}
private void sendData() {
//Requires a new thread to avoid blocking the UI
if (mDataMapList != null && mDataMapList.size() > 0 ) {
new SendToDataLayerThread().start();
} else {
Log.w("mobile", "Nothing to notify");
}
}
#Override
public void onConnected(Bundle connectionHint) {
}
// Disconnect from the data layer when the Activity stops
#Override
protected void onStop() {
if (null != mGoogleClient && mGoogleClient.isConnected()) {
mGoogleClient.disconnect();
}
super.onStop();
}
// Placeholders for required connection callbacks
#Override
public void onConnectionSuspended(int cause) {
if (mGoogleClient != null) {
mGoogleClient.reconnect();
}
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) { }
//Use thread or asynctask.
class SendToDataLayerThread extends Thread {
public void run() {
// Construct a DataRequest and send over the data layer
PutDataMapRequest putDMR = PutDataMapRequest.create("/weardatapath");
putDMR.getDataMap().putDataMapArrayList("/weardatapath", mDataMapList);
PutDataRequest request = putDMR.asPutDataRequest();
DataApi.DataItemResult result = Wearable.DataApi.putDataItem(mGoogleClient, request).await();
if (result.getStatus().isSuccess()) {
for (DataMap mapItem : mDataMapList) {
Log.v("mobile-p", "DataMap: " + mapItem + " sent successfully to data layer ");
}
} else {
// Log an error
Log.v("mobile-p", "ERROR: failed to send DataMap to data layer");
}
}
}
}
On the Wear app side I have created a PhoneListenerService as below
public class PhoneListenerService extends WearableListenerService {
#Override
public void onDataChanged(DataEventBuffer dataEvents) {
DataMap dataMap;
for (DataEvent event : dataEvents) {
Log.v("Wear-W", "DataMap received on watch: " + DataMapItem.fromDataItem(event.getDataItem()).getDataMap());
// Check the data type
if (event.getType() == DataEvent.TYPE_CHANGED) {
// Check the data path
String path = event.getDataItem().getUri().getPath();
if (path.equals("/weardatapath")) {
DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem());
ArrayList<DataMap> dataItems = dataMapItem.getDataMap().getDataMapArrayList
("dataMapItems");
// Broadcast DataMap contents to wearable show in notification..
if (dataItems != null && dataItems.size() > 0) {
for (DataMap item : dataItems) {
........
}
}
}
}
}
}
}
The above code works fine. Whenever I send a data from the MainActivity from mobileApp I do recieve it on the Wear app in PhoneListenerService.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
From the received data I build notifications on the wear app and for each action taken on the notification I want to notify to the mobile App. To do this I created PhoneSyncService on the wear app which will notify the mobile App on the actions taken..
public class PhoneSyncService extends IntentService implements
GoogleApiClient.OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks{
private static final String "wearsrvc-w" = "PhoneSyncService-W";
public static final String ACTION_REPLY = "com.test.mobilewearsample.action.REPLY";
GoogleApiClient mGoogleClient;
public PhoneSyncService() {
super("PhoneSyncService");
}
#Override
public void onCreate() {
super.onCreate();
mGoogleClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
#Override
public void onDestroy() {
if (null != mGoogleClient && mGoogleClient.isConnected()) {
mGoogleClient.disconnect();
}
super.onDestroy();
}
#Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
Log.w("wearsrvc-w", "ACTION : "+action);
if (ACTION_REPLY.equals(action)) {
final String uId = intent.getStringExtra("dataId");
notifyHandheldDevice(uId);
}
}
}
private void notifyHandheldDevice(String uId) {
Log.v("wearsrvc-w", "in notify to handheld device :"+uId);
if (mGoogleClient.isConnected()) {
DataMap dataMap = new DataMap();
dataMap.putInt("uId", Integer.valueOf(uId));
PutDataMapRequest putDMR = PutDataMapRequest.create("/mobiledatapath");
putDMR.getDataMap().putAll(dataMap);
PutDataRequest request = putDMR.asPutDataRequest();
DataApi.DataItemResult result = Wearable.DataApi.putDataItem(mGoogleClient, request).await();
if (result.getStatus().isSuccess()) {
Log.v("wearsrvc-w" , "Wear DataMap: " + dataMap + " sent successfully to data layer ");
} else {
// Log an error
Log.v("wearsrvc-w" , "ERROR: failed to send DataMap to data layer");
}
}
Log.v("wearsrvc-w", "done notify to handheld device :"+uId);
}
#Override
public void onConnected(Bundle bundle) {
}
#Override
public void onConnectionSuspended(int i) {
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
}
}
To get the data sent from wear app to the mobile App I created a WearDataListenerService as well on the mobile app , below is the code.
public class WearDataListenerService extends WearableListenerService {
private static final String TAG = "WearDataLstrService-M";
#Override
public void onDataChanged(DataEventBuffer dataEvents) {
for (DataEvent event : dataEvents) {
Log.v(TAG, "DataMap received on mobile: " + DataMapItem.fromDataItem(event.getDataItem()).getDataMap());
// Check the data type
Log.v(TAG, "event type :"+event.getType());
if (event.getType() == DataEvent.TYPE_CHANGED) {
// Check the data path
String path = event.getDataItem().getUri().getPath();
if (path.equals("/mobiledatapath")) {
DataMap dataMap = DataMapItem.fromDataItem(event.getDataItem()).getDataMap();
int uId = dataMap.getInt("dataId");
Log.v(TAG, "action received for uId :"+uId);
}
}
}
}
}
When I did the above changes I started to see a strange issue. When I try to send the data from the mobile app to the wear app the data is received on the WearDataListenerService which is on the mobile app side only and is not being received at the wear app side.
Its like the data sent from mobile app is received on the mobile app WearableListenerService instead of the one on wear app.
Can someone please help me.
Your mobile listener service has the following line:
if (path.equals("/mobiledatapath")) {}
I think you are closing the block too early; did you mean to do:
if (path.equals("/mobiledatapath")) {
// rest of that code
}
Your mobile side is adding/changing data so ANY node can receive a callback, including the mobile device, so that is why your mobile device receives it (and because of your faulty if-clause, it is not filtered out). As for the other part, what is the payload that you are sending in the data (mDataMapList)? Is that the same thing across multiple tries or it changes each time? There are two possible causes:
Your payload is the same so there is no "change", hence onDataChanged is not called for a second time (adding a, say, timestamp to data can address that)
in Play Services 8.3+, some changes were made to batch the data sync across network and transfer them not immediately (can be delayed up to 20 minutes); if an app needs immediate sync, they need to set the urgent flag, see setUrgent(). On the mobile side, teh change can be seen immediately (it is not crossing wire) but on the wear side, it can take a while unless send as urgent.
When player take turn, i get notification in the notification bar instead of listening to it in my game while its running
Implement OnInvitationReceivedListener and OnTurnBasedMatchUpdateReceivedListener
In onConnected(Bundle connectionHint) i added Games.Invitations.registerInvitationListener(mGoogleApiClient, this) and Games.TurnBasedMultiplayer.registerMatchUpdateListener(mGoogleApiClient, this)
Override these methods
#Override
public void onTurnBasedMatchReceived(TurnBasedMatch match) {
Toast.makeText(this, "A match was updated.",Toast.LENGTH_LONG).show();
}
#Override
public void onTurnBasedMatchRemoved(String s) {
Toast.makeText(this, "A match was canceled.",Toast.LENGTH_LONG).show();
finish();
}
#Override
public void onInvitationReceived(Invitation invitation) {
Toast.makeText(this, "An invitation received.",Toast.LENGTH_LONG).show();
}
#Override
public void onInvitationRemoved(String s) {
Toast.makeText(this, "An invitation removed.",Toast.LENGTH_LONG).show();
}
I can't get the code to listen to invitations/notifications that were received.
If you are starting the game by clicking on a notification, the match is passed as the 'connectionHint' to onConnected(). You can check it with code like:
public void onConnected(Bundle connectionHint) {
Log.d(TAG, "onConnected(): Connection successful");
// Retrieve the TurnBasedMatch from the connectionHint
if (connectionHint != null) {
mTurnBasedMatch = connectionHint.getParcelable(Multiplayer.EXTRA_TURN_BASED_MATCH);
if (mTurnBasedMatch != null) {
if (mGoogleApiClient == null || !mGoogleApiClient.isConnected()) {
Log.d(TAG, "Warning: accessing TurnBasedMatch when not connected");
}
updateMatch(mTurnBasedMatch);
return;
}
}
// handle registering callbacks and updating status here....
}
The listeners are only needed if you want to handle these notifications in the application when the app is running, so you don't need to have the player go out to the notification bar, through play games, and back to your application.