diff --git a/README.md b/README.md
index d1e1e8a47c..4409852b76 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
# AndroidAPS
+ddd
+
* Check the wiki: http://wiki.androidaps.org
* Everyone who’s been looping with AndroidAPS needs to fill out the form after 3 days of looping https://docs.google.com/forms/d/14KcMjlINPMJHVt28MDRupa4sz4DDIooI4SrW0P3HSN8/viewform?c=0&w=1
diff --git a/app/build.gradle b/app/build.gradle
index b7ea47204b..8b50ad30ed 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2ec11e5a33..dc9201d86c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -169,7 +169,63 @@
android:name=".plugins.general.wear.wearintegration.WatchUpdaterService"
android:exported="true">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearPlugin.java
index ca24c4ed9e..e3545d88bf 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearPlugin.java
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearPlugin.java
@@ -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,7 +79,10 @@ public class WearPlugin extends PluginBase {
}
private void sendDataToWatch(boolean status, boolean basals, boolean bgValue) {
- if (isEnabled(getType())) { //only start service when this plugin is enabled
+
+ //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) {
ctx.startService(new Intent(ctx, WatchUpdaterService.class));
@@ -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);
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/SendToDataLayerThread.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/SendToDataLayerThread.java
index d935bafec6..aa6498563d 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/SendToDataLayerThread.java
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/SendToDataLayerThread.java
@@ -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 {
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) {
- 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");
- }
- }
+ if (testlockup) {
+ try {
+ Log.e(TAG, logPrefix + "WARNING RUNNING TEST LOCK UP CODE - NEVER FOR PRODUCTION");
+ Thread.sleep(1000000); // DEEEBBUUGGGG
+ } catch (Exception e) {
}
- } 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();
+ }
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java
index 12dc116895..52dbeed75c 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java
@@ -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 connectedNodes = capabilityInfo.getNodes();
+ mWearNodeId = pickBestNodeId(connectedNodes);
+ }
+
+
+ private String pickBestNodeId(Set 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,18 +306,19 @@ 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();
Double lowLine = SafeParse.stringToDouble(mPrefs.getString("low_mark", "0"));
Double highLine = SafeParse.stringToDouble(mPrefs.getString("high_mark", "0"));
- //convert to mg/dl
+ // convert to mg/dl
if (!units.equals(Constants.MGDL)) {
lowLine *= Constants.MMOLL_TO_MGDL;
highLine *= Constants.MMOLL_TO_MGDL;
@@ -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) {
diff --git a/app/src/main/res/values/wear.xml b/app/src/main/res/values/wear.xml
new file mode 100644
index 0000000000..41df8ef193
--- /dev/null
+++ b/app/src/main/res/values/wear.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ - phone_app_sync_bgs
+
+
\ No newline at end of file
diff --git a/wear/build.gradle b/wear/build.gradle
index be11cdbb5b..2a35c591b6 100644
--- a/wear/build.gradle
+++ b/wear/build.gradle
@@ -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'
diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml
index e6750c515d..f04e072bc7 100644
--- a/wear/src/main/AndroidManifest.xml
+++ b/wear/src/main/AndroidManifest.xml
@@ -168,9 +168,66 @@
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java b/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java
index 52dfc97ad1..094b8aad76 100644
--- a/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java
+++ b/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java
@@ -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 {
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
- lastRequest = System.currentTimeMillis();
+ // Log.d(TAG, logPrefix + "DataRequester: doInBack: " + params);
- NodeApi.GetConnectedNodesResult nodes =
- Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
- for (Node node : nodes.getNodes()) {
- Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), WEARABLE_RESEND_PATH, null);
- }
+ try {
+
+ forceGoogleApiConnect();
+ DataMap datamap;
+
+ if (isCancelled()) {
+ Log.d(TAG, "doInBackground CANCELLED programmatically");
+ return null;
}
- } else {
- googleApiClient.blockingConnect(15, TimeUnit.SECONDS);
- if (googleApiClient.isConnected()) {
- if (System.currentTimeMillis() - lastRequest > 20 * 1000) { // enforce 20-second debounce period
+
+ 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 {
+
+ 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 {
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 connectedNodes = capabilityInfo.getNodes();
+ return pickBestNode(connectedNodes);
+ // mPhoneNodeId = pickBestNodeId(connectedNodes);
+ }
+
+
+ private Node pickBestNode(Set 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 result = Wearable.NodeApi.getLocalNode(googleApiClient);
+ result.setResultCallback(new ResultCallback() {
+
+ @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);
}
- }
+ }
}
diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/WearUtil.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/WearUtil.java
new file mode 100644
index 0000000000..ca63749888
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/WearUtil.java
@@ -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();
+ }
+
+
+}
diff --git a/wear/src/main/res/values/wear.xml b/wear/src/main/res/values/wear.xml
new file mode 100644
index 0000000000..41df8ef193
--- /dev/null
+++ b/wear/src/main/res/values/wear.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ - phone_app_sync_bgs
+
+
\ No newline at end of file