Merge pull request #1688 from AdrianLxM/dev-wear

Dev wear
This commit is contained in:
Milos Kozak 2019-03-21 10:52:14 +01:00 committed by GitHub
commit 751e87ca5e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 694 additions and 66 deletions

View file

@ -68,6 +68,7 @@ android {
buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"'
buildConfigField "String", "HEAD", '"' + generateGitBuild() + '"'
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// if you change minSdkVersion to less than 11, you need to change executeTask for wear
ndk {
moduleName "BleCommandUtil"
@ -77,7 +78,7 @@ android {
// TODO remove once wear dependency com.google.android.gms:play-services-wearable:7.3.0
// has been upgraded (requiring significant code changes), which currently fails release
// build with a deprecation warning
abortOnError false
// abortOnError false
// (disabled entirely to avoid reports on the error, which would still be displayed
// and it's easy to overlook that it's ignored)
checkReleaseBuilds false
@ -194,7 +195,7 @@ dependencies {
implementation "org.slf4j:slf4j-api:1.7.12"
implementation "com.jjoe64:graphview:4.0.1"
implementation "com.joanzapata.iconify:android-iconify-fontawesome:2.1.1"
implementation "com.google.android.gms:play-services-wearable:7.5.0"
implementation 'com.google.android.gms:play-services-wearable:10.2.1'
implementation(name: "android-edittext-validator-v1.3.4-mod", ext: "aar")
implementation(name: "sightparser-release", ext: "aar")
implementation 'com.madgag.spongycastle:core:1.58.0.0'

View file

@ -169,7 +169,63 @@
android:name=".plugins.general.wear.wearintegration.WatchUpdaterService"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
<!-- <action android:name="com.google.android.gms.wearable.BIND_LISTENER" /> -->
<!-- listeners receive events that match the action and data filters -->
<action android:name="com.google.android.gms.wearable.CAPABILITY_CHANGED" />
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_data" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_data_resend" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_cancel_bolus" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_confirmactionstring" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_initiateactionstring" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/openwearsettings" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/sendstatustowear" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/sendpreferencestowear" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_basal" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_bolusprogress" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_actionconfirmationrequest" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_changeconfirmationrequest" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_cancelnotificationrequest" />
</intent-filter>
</service>
<service

View file

@ -2,6 +2,7 @@ package info.nightscout.androidaps.plugins.general.wear;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import com.squareup.otto.Subscribe;
@ -35,6 +36,8 @@ public class WearPlugin extends PluginBase {
private final Context ctx;
private static WearPlugin wearPlugin;
private static String TAG = "WearPlugin";
public static WearPlugin getPlugin() {
return wearPlugin;
@ -76,6 +79,9 @@ public class WearPlugin extends PluginBase {
}
private void sendDataToWatch(boolean status, boolean basals, boolean bgValue) {
//Log.d(TAG, "WR: WearPlugin:sendDataToWatch (status=" + status + ",basals=" + basals + ",bgValue=" + bgValue + ")");
if (isEnabled(getType())) { // only start service when this plugin is enabled
if (bgValue) {
@ -93,15 +99,20 @@ public class WearPlugin extends PluginBase {
}
void resendDataToWatch() {
//Log.d(TAG, "WR: WearPlugin:resendDataToWatch");
ctx.startService(new Intent(ctx, WatchUpdaterService.class).setAction(WatchUpdaterService.ACTION_RESEND));
}
void openSettings() {
//Log.d(TAG, "WR: WearPlugin:openSettings");
ctx.startService(new Intent(ctx, WatchUpdaterService.class).setAction(WatchUpdaterService.ACTION_OPEN_SETTINGS));
}
void requestNotificationCancel(String actionstring) {
Intent intent = new Intent(ctx, WatchUpdaterService.class).setAction(WatchUpdaterService.ACTION_CANCEL_NOTIFICATION);
//Log.d(TAG, "WR: WearPlugin:requestNotificationCancel");
Intent intent = new Intent(ctx, WatchUpdaterService.class)
.setAction(WatchUpdaterService.ACTION_CANCEL_NOTIFICATION);
intent.putExtra("actionstring", actionstring);
ctx.startService(intent);
}

View file

@ -13,40 +13,127 @@ import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by emmablack on 12/26/14.
*/
class SendToDataLayerThread extends AsyncTask<DataMap,Void,Void> {
private GoogleApiClient googleApiClient;
private static final String TAG = "SendDataThread";
String path;
private static final String TAG = "SendToDataLayerThread";
private String path;
private String logPrefix = ""; // "WR: ";
private static int concurrency = 0;
private static int state = 0;
private static final ReentrantLock lock = new ReentrantLock();
private static long lastlock = 0;
private static final boolean testlockup = false; // always false in production
SendToDataLayerThread(String path, GoogleApiClient pGoogleApiClient) {
// Log.d(TAG, logPrefix + "SendToDataLayerThread: " + path);
this.path = path;
googleApiClient = pGoogleApiClient;
}
@Override
protected void onPreExecute() {
concurrency++;
if ((concurrency > 12) || ((concurrency > 3 && (lastlock != 0) && (tsl() - lastlock) > 300000))) {
// error if 9 concurrent threads or lock held for >5 minutes with concurrency of 4
final String err = "Wear Integration deadlock detected!! " + ((lastlock != 0) ? "locked" : "") + " state:"
+ state + " @" + hourMinuteString(tsl());
// Home.toaststaticnext(err);
Log.e(TAG, logPrefix + err);
}
if (concurrency < 0)
Log.d(TAG, logPrefix + "Wear Integration impossible concurrency!!");
Log.d(TAG, logPrefix + "SendDataToLayerThread pre-execute concurrency: " + concurrency);
}
@Override
protected Void doInBackground(DataMap... params) {
if (testlockup) {
try {
final NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleApiClient).await(15, TimeUnit.SECONDS);
for (Node node : nodes.getNodes()) {
for (DataMap dataMap : params) {
PutDataMapRequest putDMR = PutDataMapRequest.create(path);
putDMR.getDataMap().putAll(dataMap);
PutDataRequest request = putDMR.asPutDataRequest();
DataApi.DataItemResult result = Wearable.DataApi.putDataItem(googleApiClient, request).await(15, TimeUnit.SECONDS);
if (result.getStatus().isSuccess()) {
Log.d(TAG, "DataMap: " + dataMap + " sent to: " + node.getDisplayName());
} else {
Log.d(TAG, "ERROR: failed to send DataMap");
}
}
}
Log.e(TAG, logPrefix + "WARNING RUNNING TEST LOCK UP CODE - NEVER FOR PRODUCTION");
Thread.sleep(1000000); // DEEEBBUUGGGG
} catch (Exception e) {
Log.e(TAG, "Got exception sending data to wear: " + e.toString());
}
}
sendToWear(params);
concurrency--;
Log.d(TAG, logPrefix + "SendDataToLayerThread post-execute concurrency: " + concurrency);
return null;
}
// Debug function to expose where it might be locking up
private synchronized void sendToWear(final DataMap... params) {
if (!lock.tryLock()) {
Log.d(TAG, logPrefix + "Concurrent access - waiting for thread unlock");
lock.lock(); // enforce single threading
Log.d(TAG, logPrefix + "Thread unlocked - proceeding");
}
lastlock = tsl();
try {
if (state != 0) {
Log.e(TAG, logPrefix + "WEAR STATE ERROR: state=" + state);
}
state = 1;
final NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleApiClient).await(15,
TimeUnit.SECONDS);
Log.d(TAG, logPrefix + "Nodes: " + nodes);
state = 2;
for (Node node : nodes.getNodes()) {
state = 3;
for (DataMap dataMap : params) {
state = 4;
PutDataMapRequest putDMR = PutDataMapRequest.create(path);
state = 5;
putDMR.getDataMap().putAll(dataMap);
putDMR.setUrgent();
state = 6;
PutDataRequest request = putDMR.asPutDataRequest();
state = 7;
DataApi.DataItemResult result = Wearable.DataApi.putDataItem(googleApiClient, request).await(15,
TimeUnit.SECONDS);
state = 8;
if (result.getStatus().isSuccess()) {
Log.d(TAG, logPrefix + "DataMap: " + dataMap + " sent to: " + node.getDisplayName());
} else {
Log.e(TAG, logPrefix + "ERROR: failed to send DataMap");
result = Wearable.DataApi.putDataItem(googleApiClient, request).await(30, TimeUnit.SECONDS);
if (result.getStatus().isSuccess()) {
Log.d(TAG, logPrefix + "DataMap retry: " + dataMap + " sent to: " + node.getDisplayName());
} else {
Log.e(TAG, logPrefix + "ERROR on retry: failed to send DataMap: "
+ result.getStatus().toString());
}
}
state = 9;
}
}
state = 0;
} catch (Exception e) {
Log.e(TAG, logPrefix + "Got exception in sendToWear: " + e.toString());
} finally {
lastlock = 0;
lock.unlock();
}
}
private static long tsl() {
return System.currentTimeMillis();
}
private static String hourMinuteString(long timestamp) {
return android.text.format.DateFormat.format("kk:mm", timestamp).toString();
}
}

View file

@ -1,9 +1,17 @@
package info.nightscout.androidaps.plugins.general.wear.wearintegration;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.Handler;
@ -14,8 +22,11 @@ import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.CapabilityApi;
import com.google.android.gms.wearable.CapabilityInfo;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable;
@ -42,21 +53,24 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorP
import info.nightscout.androidaps.plugins.treatments.Treatment;
import info.nightscout.androidaps.interfaces.PluginType;
import info.nightscout.androidaps.interfaces.TreatmentsInterface;
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin;
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions;
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus;
import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin;
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
import info.nightscout.androidaps.plugins.general.wear.ActionStringHandler;
import info.nightscout.androidaps.plugins.general.wear.WearPlugin;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin;
import info.nightscout.androidaps.plugins.treatments.Treatment;
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
import info.nightscout.androidaps.utils.DecimalFormatter;
import info.nightscout.androidaps.utils.SP;
import info.nightscout.androidaps.utils.SafeParse;
import info.nightscout.androidaps.utils.ToastUtils;
public class WatchUpdaterService extends WearableListenerService implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
public class WatchUpdaterService extends WearableListenerService implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
public static final String ACTION_RESEND = WatchUpdaterService.class.getName().concat(".Resend");
public static final String ACTION_OPEN_SETTINGS = WatchUpdaterService.class.getName().concat(".OpenSettings");
public static final String ACTION_SEND_STATUS = WatchUpdaterService.class.getName().concat(".SendStatus");
@ -91,6 +105,17 @@ public class WatchUpdaterService extends WearableListenerService implements
private Handler handler;
// Phone
private static final String CAPABILITY_PHONE_APP = "phone_app_sync_bgs";
private static final String MESSAGE_PATH_PHONE = "/phone_message_path";
// Wear
private static final String CAPABILITY_WEAR_APP = "wear_app_sync_bgs";
private static final String MESSAGE_PATH_WEAR = "/wear_message_path";
private String mWearNodeId = null;
private String localnode = null;
private String logPrefix = ""; // "WR: "
@Override
public void onCreate() {
mPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
@ -112,24 +137,24 @@ public class WatchUpdaterService extends WearableListenerService implements
public void setSettings() {
wear_integration = WearPlugin.getPlugin().isEnabled(PluginType.GENERAL);
// Log.d(TAG, "WR: wear_integration=" + wear_integration);
if (wear_integration) {
googleApiConnect();
}
}
public void googleApiConnect() {
private void googleApiConnect() {
if (googleApiClient != null && (googleApiClient.isConnected() || googleApiClient.isConnecting())) {
googleApiClient.disconnect();
}
googleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Wearable.API)
.build();
googleApiClient = new GoogleApiClient.Builder(this).addConnectionCallbacks(this)
.addOnConnectionFailedListener(this).addApi(Wearable.API).build();
Wearable.MessageApi.addListener(googleApiClient, this);
if (googleApiClient.isConnected()) {
Log.d("WatchUpdater", "API client is connected");
log.debug(logPrefix + "API client is connected");
} else {
// Log.d("WatchUpdater", logPrefix + "API client is not connected and is trying to connect");
googleApiClient.connect();
}
}
@ -138,6 +163,8 @@ public class WatchUpdaterService extends WearableListenerService implements
public int onStartCommand(Intent intent, int flags, int startId) {
String action = intent != null ? intent.getAction() : null;
// Log.d(TAG, logPrefix + "onStartCommand: " + action);
if (wear_integration) {
handler.post(() -> {
if (googleApiClient.isConnected()) {
@ -177,13 +204,63 @@ public class WatchUpdaterService extends WearableListenerService implements
}
private void updateWearSyncBgsCapability(CapabilityInfo capabilityInfo) {
Log.d("WatchUpdaterService", logPrefix + "CabilityInfo: " + capabilityInfo);
Set<Node> connectedNodes = capabilityInfo.getNodes();
mWearNodeId = pickBestNodeId(connectedNodes);
}
private String pickBestNodeId(Set<Node> nodes) {
String bestNodeId = null;
// Find a nearby node or pick one arbitrarily
for (Node node : nodes) {
if (node.isNearby()) {
return node.getId();
}
bestNodeId = node.getId();
}
return bestNodeId;
}
@Override
public void onConnected(Bundle connectionHint) {
CapabilityApi.CapabilityListener capabilityListener = capabilityInfo -> {
updateWearSyncBgsCapability(capabilityInfo);
// Log.d(TAG, logPrefix + "onConnected onCapabilityChanged mWearNodeID:" + mWearNodeId);
// new CheckWearableConnected().execute();
};
Wearable.CapabilityApi.addCapabilityListener(googleApiClient, capabilityListener, CAPABILITY_WEAR_APP);
sendData();
}
@Override
public void onPeerConnected(com.google.android.gms.wearable.Node peer) {// KS
super.onPeerConnected(peer);
String id = peer.getId();
String name = peer.getDisplayName();
// Log.d(TAG, logPrefix + "onPeerConnected peer name & ID: " + name + "|" + id);
}
@Override
public void onPeerDisconnected(com.google.android.gms.wearable.Node peer) {// KS
super.onPeerDisconnected(peer);
String id = peer.getId();
String name = peer.getDisplayName();
// Log.d(TAG, logPrefix + "onPeerDisconnected peer name & ID: " + name + "|" + id);
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
}
@Override
public void onMessageReceived(MessageEvent event) {
// Log.d(TAG, logPrefix + "onMessageRecieved: " + event);
if (wear_integration) {
if (event != null && event.getPath().equals(WEARABLE_RESEND_PATH)) {
resendData();
@ -214,6 +291,7 @@ public class WatchUpdaterService extends WearableListenerService implements
private void sendData() {
BgReading lastBG = DatabaseHelper.lastBg();
// Log.d(TAG, logPrefix + "LastBg=" + lastBG);
if (lastBG != null) {
GlucoseStatus glucoseStatus = GlucoseStatus.getGlucoseStatusData();
@ -228,11 +306,12 @@ public class WatchUpdaterService extends WearableListenerService implements
return;
}
new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient).execute(dataMap);
executeTask(new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient), dataMap);
}
}
}
private DataMap dataMapSingleBG(BgReading lastBG, GlucoseStatus glucoseStatus) {
String units = ProfileFunctions.getInstance().getProfileUnits();
@ -287,7 +366,6 @@ public class WatchUpdaterService extends WearableListenerService implements
deltastring += "+";
} else {
deltastring += "-";
}
boolean detailed = SP.getBoolean("wear_detailed_delta", false);
@ -352,7 +430,7 @@ public class WatchUpdaterService extends WearableListenerService implements
}
}
entries.putDataMapArrayList("entries", dataMaps);
new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient).execute(entries);
executeTask(new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient), entries);
}
sendPreferences();
sendBasals();
@ -501,7 +579,7 @@ public class WatchUpdaterService extends WearableListenerService implements
dm.putDataMapArrayList("boluses", boluses);
dm.putDataMapArrayList("predictions", predictions);
new SendToDataLayerThread(BASAL_DATA_PATH, googleApiClient).execute(dm);
executeTask(new SendToDataLayerThread(BASAL_DATA_PATH, googleApiClient), dm);
}
private DataMap tempDatamap(long startTime, double startBasal, long to, double toBasal, double amount) {
@ -548,6 +626,7 @@ public class WatchUpdaterService extends WearableListenerService implements
dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
dataMapRequest.getDataMap().putString("openSettings", "openSettings");
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendNotification", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else {
Log.e("OpenSettings", "No connection to wearable available!");
@ -563,6 +642,7 @@ public class WatchUpdaterService extends WearableListenerService implements
dataMapRequest.getDataMap().putString("progressstatus", status);
dataMapRequest.getDataMap().putInt("progresspercent", progresspercent);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendBolusProgress", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else {
Log.e("BolusProgress", "No connection to wearable available!");
@ -582,6 +662,7 @@ public class WatchUpdaterService extends WearableListenerService implements
log.debug("Requesting confirmation from wear: " + actionstring);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendActionConfirmationRequest", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else {
Log.e("confirmationRequest", "No connection to wearable available!");
@ -601,6 +682,7 @@ public class WatchUpdaterService extends WearableListenerService implements
log.debug("Requesting confirmation from wear: " + actionstring);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendChangeConfirmationRequest", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else {
Log.e("changeConfirmRequest", "No connection to wearable available!");
@ -618,9 +700,10 @@ public class WatchUpdaterService extends WearableListenerService implements
log.debug("Canceling notification on wear: " + actionstring);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendCancelNotificationRequest", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else {
Log.e("cancelNotificationRequest", "No connection to wearable available!");
Log.e("cancelNotificationReq", "No connection to wearable available!");
}
}
@ -683,6 +766,7 @@ public class WatchUpdaterService extends WearableListenerService implements
dataMapRequest.getDataMap().putBoolean("showBgi", mPrefs.getBoolean("wear_showbgi", false));
dataMapRequest.getDataMap().putInt("batteryLevel", (phoneBattery >= 30) ? 1 : 0);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendStatus", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else {
Log.e("SendStatus", "No connection to wearable available!");
@ -699,12 +783,29 @@ public class WatchUpdaterService extends WearableListenerService implements
dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
dataMapRequest.getDataMap().putBoolean("wearcontrol", wearcontrol);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendPreferences", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else {
Log.e("SendStatus", "No connection to wearable available!");
}
}
private void debugData(String source, Object data) {
// Log.d(TAG, "WR: " + source + " " + data);
}
private void executeTask(AsyncTask task, DataMap... parameters) {
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Object[])parameters);
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
// } else {
// task.execute();
// }
}
@NonNull
private String generateStatusString(Profile profile, String currentBasal, String iobSum, String iobDetail, String bgiString) {

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2015 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<string-array name="android_wear_capabilities">
<item>phone_app_sync_bgs</item>
</string-array>
</resources>

View file

@ -2,6 +2,7 @@ apply plugin: 'com.android.application'
ext {
wearableVersion = "2.0.1"
playServicesWearable = "10.2.1"
}
def generateGitBuild = { ->
@ -93,7 +94,7 @@ dependencies {
//compile "com.ustwo.android:clockwise-wearable:1.0.2"
compileOnly "com.google.android.wearable:wearable:${wearableVersion}"
implementation "com.google.android.support:wearable:${wearableVersion}"
implementation "com.google.android.gms:play-services-wearable:7.3.0"
implementation "com.google.android.gms:play-services-wearable:${playServicesWearable}"
implementation(name:"ustwo-clockwise-debug", ext:"aar")
implementation "com.android.support:support-v4:27.0.1"
implementation 'com.android.support:wear:27.0.1'

View file

@ -168,9 +168,66 @@
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
</intent-filter>
</service>
<service android:name=".data.ListenerService">
<intent-filter>
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
<!-- <action android:name="com.google.android.gms.wearable.BIND_LISTENER" /> -->
<!-- listeners receive events that match the action and data filters -->
<action android:name="com.google.android.gms.wearable.CAPABILITY_CHANGED" />
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_data" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_data_resend" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_cancel_bolus" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_confirmactionstring" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_initiateactionstring" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/openwearsettings" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/sendstatustowear" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/sendpreferencestowear" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_basal" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_bolusprogress" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_actionconfirmationrequest" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_changeconfirmationrequest" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_cancelnotificationrequest" />
</intent-filter>
</service>

View file

@ -1,5 +1,8 @@
package info.nightscout.androidaps.data;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@ -7,16 +10,24 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.CapabilityApi;
import com.google.android.gms.wearable.CapabilityInfo;
import com.google.android.gms.wearable.ChannelApi;
import com.google.android.gms.wearable.DataEvent;
import com.google.android.gms.wearable.DataEventBuffer;
import com.google.android.gms.wearable.DataMap;
@ -33,12 +44,15 @@ import info.nightscout.androidaps.R;
import info.nightscout.androidaps.interaction.actions.AcceptActivity;
import info.nightscout.androidaps.interaction.actions.CPPActivity;
import info.nightscout.androidaps.interaction.utils.SafeParse;
import info.nightscout.androidaps.interaction.utils.WearUtil;
/**
* Created by emmablack on 12/26/14.
*/
public class ListenerService extends WearableListenerService implements GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
GoogleApiClient.OnConnectionFailedListener, ChannelApi.ChannelListener {
private static final String WEARABLE_DATA_PATH = "/nightscout_watch_data";
private static final String WEARABLE_RESEND_PATH = "/nightscout_watch_data_resend";
private static final String WEARABLE_CANCELBOLUS_PATH = "/nightscout_watch_cancel_bolus";
@ -67,49 +81,164 @@ public class ListenerService extends WearableListenerService implements GoogleAp
private static final String ACTION_RESEND_BULK = "com.dexdrip.stephenblack.nightwatch.RESEND_BULK_DATA";
GoogleApiClient googleApiClient;
private long lastRequest = 0;
private DismissThread confirmThread;
private DismissThread bolusprogressThread;
private static final String TAG = "ListenerService";
private DataRequester mDataRequester = null;
private static final int GET_CAPABILITIES_TIMEOUT_MS = 5000;
// Phone
private static final String CAPABILITY_PHONE_APP = "phone_app_sync_bgs";
private static final String MESSAGE_PATH_PHONE = "/phone_message_path";
// Wear
private static final String CAPABILITY_WEAR_APP = "wear_app_sync_bgs";
private static final String MESSAGE_PATH_WEAR = "/wear_message_path";
private String mPhoneNodeId = null;
private String localnode = null;
private String logPrefix = ""; // "WR: "
public class DataRequester extends AsyncTask<Void, Void, Void> {
Context mContext;
String path;
byte[] payload;
DataRequester(Context context) {
mContext = context;
DataRequester(Context context, String thispath, byte[] thispayload) {
path = thispath;
payload = thispayload;
// Log.d(TAG, logPrefix + "DataRequester DataRequester: " + thispath + " lastRequest:" + lastRequest);
}
@Override
protected Void doInBackground(Void... params) {
if (googleApiClient.isConnected()) {
if (System.currentTimeMillis() - lastRequest > 20 * 1000) { // enforce 20-second debounce period
// Log.d(TAG, logPrefix + "DataRequester: doInBack: " + params);
try {
forceGoogleApiConnect();
DataMap datamap;
if (isCancelled()) {
Log.d(TAG, "doInBackground CANCELLED programmatically");
return null;
}
if (googleApiClient != null) {
if (!googleApiClient.isConnected())
googleApiClient.blockingConnect(15, TimeUnit.SECONDS);
}
// this code might not be needed in this way, but we need to see that later
if ((googleApiClient != null) && (googleApiClient.isConnected())) {
if ((System.currentTimeMillis() - lastRequest > 20 * 1000)) {
// enforce 20-second debounce period
lastRequest = System.currentTimeMillis();
NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
for (Node node : nodes.getNodes()) {
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), WEARABLE_RESEND_PATH, null);
// NodeApi.GetConnectedNodesResult nodes =
// Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
if (localnode == null || (localnode != null && localnode.isEmpty()))
setLocalNodeName();
CapabilityInfo capabilityInfo = getCapabilities();
int count = 0;
Node phoneNode = null;
if (capabilityInfo != null) {
phoneNode = updatePhoneSyncBgsCapability(capabilityInfo);
count = capabilityInfo.getNodes().size();
}
Log.d(TAG, "doInBackground connected. CapabilityApi.GetCapabilityResult mPhoneNodeID="
+ (phoneNode != null ? phoneNode.getId() : "") + " count=" + count + " localnode="
+ localnode);// KS
if (count > 0) {
for (Node node : capabilityInfo.getNodes()) {
// Log.d(TAG, "doInBackground path: " + path);
switch (path) {
// simple send as is payloads
case WEARABLE_RESEND_PATH:
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(),
WEARABLE_RESEND_PATH, null);
break;
case WEARABLE_DATA_PATH:
case WEARABLE_CANCELBOLUS_PATH:
case WEARABLE_CONFIRM_ACTIONSTRING_PATH:
case WEARABLE_INITIATE_ACTIONSTRING_PATH:
case OPEN_SETTINGS:
case NEW_STATUS_PATH:
case NEW_PREFERENCES_PATH:
case BASAL_DATA_PATH:
case BOLUS_PROGRESS_PATH:
case ACTION_CONFIRMATION_REQUEST_PATH:
case NEW_CHANGECONFIRMATIONREQUEST_PATH:
case ACTION_CANCELNOTIFICATION_REQUEST_PATH: {
Log.w(TAG, logPrefix + "Unhandled path");
// sendMessagePayload(node, path, path, payload);
}
default:// SYNC_ALL_DATA
// this fall through is messy and non-deterministic for new paths
}
}
} else {
googleApiClient.blockingConnect(15, TimeUnit.SECONDS);
if (googleApiClient.isConnected()) {
if (System.currentTimeMillis() - lastRequest > 20 * 1000) { // enforce 20-second debounce period
lastRequest = System.currentTimeMillis();
NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
for (Node node : nodes.getNodes()) {
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), WEARABLE_RESEND_PATH, null);
}
Log.d(TAG, logPrefix + "doInBackground connected but getConnectedNodes returns 0.");
}
} else {
// no resend
Log.d(TAG, logPrefix + "Inside the timeout, will not be executed");
}
} else {
Log.d(TAG, logPrefix + "Not connected for sending: api "
+ ((googleApiClient == null) ? "is NULL!" : "not null"));
if (googleApiClient != null) {
googleApiClient.connect();
} else {
googleApiConnect();
}
}
} catch (Exception ex) {
Log.e(TAG, logPrefix + "Error executing DataRequester in background. Exception: " + ex.getMessage());
}
return null;
}
}
public CapabilityInfo getCapabilities() {
CapabilityApi.GetCapabilityResult capabilityResult = Wearable.CapabilityApi.getCapability(googleApiClient,
CAPABILITY_PHONE_APP, CapabilityApi.FILTER_REACHABLE).await(GET_CAPABILITIES_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
if (!capabilityResult.getStatus().isSuccess()) {
Log.e(TAG, logPrefix + "doInBackground Failed to get capabilities, status: "
+ capabilityResult.getStatus().getStatusMessage());
return null;
}
return capabilityResult.getCapability();
}
public class BolusCancelTask extends AsyncTask<Void, Void, Void> {
Context mContext;
@ -119,6 +248,8 @@ public class ListenerService extends WearableListenerService implements GoogleAp
@Override
protected Void doInBackground(Void... params) {
// Log.d(TAG, logPrefix + "BolusCancelTask: doInBack: " + params);
if (googleApiClient.isConnected()) {
NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
@ -154,6 +285,9 @@ public class ListenerService extends WearableListenerService implements GoogleAp
@Override
protected Void doInBackground(Void... params) {
forceGoogleApiConnect();
if (googleApiClient.isConnected()) {
NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
@ -176,7 +310,7 @@ public class ListenerService extends WearableListenerService implements GoogleAp
}
public void requestData() {
new DataRequester(this).execute();
sendData(WEARABLE_RESEND_PATH, null);
}
public void cancelBolus() {
@ -191,7 +325,73 @@ public class ListenerService extends WearableListenerService implements GoogleAp
new MessageActionTask(this, WEARABLE_INITIATE_ACTIONSTRING_PATH, actionstring).execute();
}
public void googleApiConnect() {
private Node updatePhoneSyncBgsCapability(CapabilityInfo capabilityInfo) {
// Log.d(TAG, "CapabilityInfo: " + capabilityInfo);
Set<Node> connectedNodes = capabilityInfo.getNodes();
return pickBestNode(connectedNodes);
// mPhoneNodeId = pickBestNodeId(connectedNodes);
}
private Node pickBestNode(Set<Node> nodes) {
Node bestNode = null;
// Find a nearby node or pick one arbitrarily
for (Node node : nodes) {
if (node.isNearby()) {
return node;
}
bestNode = node;
}
return bestNode;
}
private synchronized void sendData(String path, byte[] payload) {
// Log.d(TAG, "WR: sendData: path: " + path + ", payload=" + payload);
if (path == null)
return;
if (mDataRequester != null) {
// Log.d(TAG, logPrefix + "sendData DataRequester != null lastRequest:" +
// WearUtil.dateTimeText(lastRequest));
if (mDataRequester.getStatus() != AsyncTask.Status.FINISHED) {
// Log.d(TAG, logPrefix + "sendData Should be canceled? Let run 'til finished.");
// mDataRequester.cancel(true);
}
}
Log.d(TAG, logPrefix + "sendData: execute lastRequest:" + WearUtil.dateTimeText(lastRequest));
mDataRequester = (DataRequester)new DataRequester(this, path, payload).execute();
// executeTask(mDataRequester);
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Log.d(TAG, "sendData SDK < M call execute lastRequest:" + WearUtil.dateTimeText(lastRequest));
// mDataRequester = (DataRequester) new DataRequester(this, path, payload).execute();
// } else {
// Log.d(TAG, "sendData SDK >= M call executeOnExecutor lastRequest:" + WearUtil.dateTimeText(lastRequest));
// // TODO xdrip executor
// mDataRequester = (DataRequester) new DataRequester(this, path, payload).executeOnExecutor(xdrip.executor);
// }
}
private void googleApiConnect() {
if (googleApiClient != null) {
// Remove old listener(s)
try {
Wearable.ChannelApi.removeListener(googleApiClient, this);
} catch (Exception e) {
//
}
try {
Wearable.MessageApi.removeListener(googleApiClient, this);
} catch (Exception e) {
//
}
}
googleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
@ -201,8 +401,25 @@ public class ListenerService extends WearableListenerService implements GoogleAp
}
private void forceGoogleApiConnect() {
if ((googleApiClient != null && !googleApiClient.isConnected() && !googleApiClient.isConnecting()) || googleApiClient == null) {
try {
Log.d(TAG, "forceGoogleApiConnect: forcing google api reconnection");
googleApiConnect();
Thread.sleep(2000);
} catch (InterruptedException e) {
//
}
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// Log.d(TAG, logPrefix + "onStartCommand: Intent: " + intent);
if (intent != null && ACTION_RESEND.equals(intent.getAction())) {
googleApiConnect();
requestData();
@ -256,13 +473,16 @@ public class ListenerService extends WearableListenerService implements GoogleAp
public void onDataChanged(DataEventBuffer dataEvents) {
DataMap dataMap;
// Log.d(TAG, logPrefix + "onDataChanged: DataEvents=" + dataEvents);
for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_CHANGED) {
String path = event.getDataItem().getUri().getPath();
//Log.d(TAG, "WR: onDataChanged: Path: " + path + ", EventDataItem=" + event.getDataItem());
if (path.equals(OPEN_SETTINGS)) {
Intent intent = new Intent(this, AAPSPreferences.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@ -480,6 +700,21 @@ public class ListenerService extends WearableListenerService implements GoogleAp
@Override
public void onConnected(Bundle bundle) {
// Log.d(TAG, logPrefix + "onConnected call requestData");
CapabilityApi.CapabilityListener capabilityListener = new CapabilityApi.CapabilityListener() {
@Override
public void onCapabilityChanged(CapabilityInfo capabilityInfo) {
updatePhoneSyncBgsCapability(capabilityInfo);
Log.d(TAG, logPrefix + "onConnected onCapabilityChanged mPhoneNodeID:" + mPhoneNodeId
+ ", Capability: " + capabilityInfo);
}
};
Wearable.CapabilityApi.addCapabilityListener(googleApiClient, capabilityListener, CAPABILITY_PHONE_APP);
Wearable.ChannelApi.addListener(googleApiClient, this);
requestData();
}
@ -493,14 +728,38 @@ public class ListenerService extends WearableListenerService implements GoogleAp
}
private void setLocalNodeName() {
forceGoogleApiConnect();
PendingResult<NodeApi.GetLocalNodeResult> result = Wearable.NodeApi.getLocalNode(googleApiClient);
result.setResultCallback(new ResultCallback<NodeApi.GetLocalNodeResult>() {
@Override
public void onResult(NodeApi.GetLocalNodeResult getLocalNodeResult) {
if (!getLocalNodeResult.getStatus().isSuccess()) {
Log.e(TAG, "ERROR: failed to getLocalNode Status="
+ getLocalNodeResult.getStatus().getStatusMessage());
} else {
Log.d(TAG, "getLocalNode Status=: " + getLocalNodeResult.getStatus().getStatusMessage());
Node getnode = getLocalNodeResult.getNode();
localnode = getnode != null ? getnode.getDisplayName() + "|" + getnode.getId() : "";
Log.d(TAG, "setLocalNodeName. localnode=" + localnode);
}
}
});
}
@Override
public void onDestroy() {
super.onDestroy();
if (googleApiClient != null && googleApiClient.isConnected()) {
googleApiClient.disconnect();
}
if (googleApiClient != null) {
Wearable.MessageApi.removeListener(googleApiClient, this);
Wearable.ChannelApi.removeListener(googleApiClient, this);
}
}
}

View file

@ -0,0 +1,19 @@
package info.nightscout.androidaps.interaction.utils;
import java.time.LocalDateTime;
import java.util.Date;
/**
* Created by andy on 3/5/19.
*/
public class WearUtil {
public static String dateTimeText(long timeInMs) {
Date d = new Date(timeInMs);
return "" + d.getDay() + "." + d.getMonth() + "." + d.getYear() + " " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();
}
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2015 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<string-array name="android_wear_capabilities">
<item>phone_app_sync_bgs</item>
</string-array>
</resources>