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

View file

@ -169,7 +169,63 @@
android:name=".plugins.general.wear.wearintegration.WatchUpdaterService" android:name=".plugins.general.wear.wearintegration.WatchUpdaterService"
android:exported="true"> android:exported="true">
<intent-filter> <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> </intent-filter>
</service> </service>
<service <service
@ -216,4 +272,4 @@
android:label="@string/pairing_information" /> android:label="@string/pairing_information" />
</application> </application>
</manifest> </manifest>

View file

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

View file

@ -13,40 +13,127 @@ import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable; import com.google.android.gms.wearable.Wearable;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/** /**
* Created by emmablack on 12/26/14. * Created by emmablack on 12/26/14.
*/ */
class SendToDataLayerThread extends AsyncTask<DataMap,Void,Void> { class SendToDataLayerThread extends AsyncTask<DataMap,Void,Void> {
private GoogleApiClient googleApiClient; private GoogleApiClient googleApiClient;
private static final String TAG = "SendDataThread"; private static final String TAG = "SendToDataLayerThread";
String path; 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) { SendToDataLayerThread(String path, GoogleApiClient pGoogleApiClient) {
// Log.d(TAG, logPrefix + "SendToDataLayerThread: " + path);
this.path = path; this.path = path;
googleApiClient = pGoogleApiClient; 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 @Override
protected Void doInBackground(DataMap... params) { protected Void doInBackground(DataMap... params) {
try { if (testlockup) {
final NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleApiClient).await(15, TimeUnit.SECONDS); try {
for (Node node : nodes.getNodes()) { Log.e(TAG, logPrefix + "WARNING RUNNING TEST LOCK UP CODE - NEVER FOR PRODUCTION");
for (DataMap dataMap : params) { Thread.sleep(1000000); // DEEEBBUUGGGG
PutDataMapRequest putDMR = PutDataMapRequest.create(path); } catch (Exception e) {
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");
}
}
} }
} 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; 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; 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.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.BatteryManager; import android.os.BatteryManager;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; 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.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient; 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.DataMap;
import com.google.android.gms.wearable.MessageEvent; 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.PutDataMapRequest;
import com.google.android.gms.wearable.PutDataRequest; import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable; 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.plugins.treatments.Treatment;
import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.PluginType;
import info.nightscout.androidaps.interfaces.TreatmentsInterface; 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.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.nsclient.data.NSDeviceStatus;
import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin; import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin;
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
import info.nightscout.androidaps.plugins.general.wear.ActionStringHandler; import info.nightscout.androidaps.plugins.general.wear.ActionStringHandler;
import info.nightscout.androidaps.plugins.general.wear.WearPlugin; 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.DecimalFormatter;
import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.utils.SP;
import info.nightscout.androidaps.utils.SafeParse; import info.nightscout.androidaps.utils.SafeParse;
import info.nightscout.androidaps.utils.ToastUtils; import info.nightscout.androidaps.utils.ToastUtils;
public class WatchUpdaterService extends WearableListenerService implements public class WatchUpdaterService extends WearableListenerService implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
public static final String ACTION_RESEND = WatchUpdaterService.class.getName().concat(".Resend"); 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_OPEN_SETTINGS = WatchUpdaterService.class.getName().concat(".OpenSettings");
public static final String ACTION_SEND_STATUS = WatchUpdaterService.class.getName().concat(".SendStatus"); 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; 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 @Override
public void onCreate() { public void onCreate() {
mPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); mPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
@ -112,24 +137,24 @@ public class WatchUpdaterService extends WearableListenerService implements
public void setSettings() { public void setSettings() {
wear_integration = WearPlugin.getPlugin().isEnabled(PluginType.GENERAL); wear_integration = WearPlugin.getPlugin().isEnabled(PluginType.GENERAL);
// Log.d(TAG, "WR: wear_integration=" + wear_integration);
if (wear_integration) { if (wear_integration) {
googleApiConnect(); googleApiConnect();
} }
} }
public void googleApiConnect() {
private void googleApiConnect() {
if (googleApiClient != null && (googleApiClient.isConnected() || googleApiClient.isConnecting())) { if (googleApiClient != null && (googleApiClient.isConnected() || googleApiClient.isConnecting())) {
googleApiClient.disconnect(); googleApiClient.disconnect();
} }
googleApiClient = new GoogleApiClient.Builder(this) googleApiClient = new GoogleApiClient.Builder(this).addConnectionCallbacks(this)
.addConnectionCallbacks(this) .addOnConnectionFailedListener(this).addApi(Wearable.API).build();
.addOnConnectionFailedListener(this)
.addApi(Wearable.API)
.build();
Wearable.MessageApi.addListener(googleApiClient, this); Wearable.MessageApi.addListener(googleApiClient, this);
if (googleApiClient.isConnected()) { if (googleApiClient.isConnected()) {
Log.d("WatchUpdater", "API client is connected"); log.debug(logPrefix + "API client is connected");
} else { } else {
// Log.d("WatchUpdater", logPrefix + "API client is not connected and is trying to connect");
googleApiClient.connect(); googleApiClient.connect();
} }
} }
@ -138,6 +163,8 @@ public class WatchUpdaterService extends WearableListenerService implements
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
String action = intent != null ? intent.getAction() : null; String action = intent != null ? intent.getAction() : null;
// Log.d(TAG, logPrefix + "onStartCommand: " + action);
if (wear_integration) { if (wear_integration) {
handler.post(() -> { handler.post(() -> {
if (googleApiClient.isConnected()) { 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 @Override
public void onConnected(Bundle connectionHint) { 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(); 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 @Override
public void onMessageReceived(MessageEvent event) { public void onMessageReceived(MessageEvent event) {
// Log.d(TAG, logPrefix + "onMessageRecieved: " + event);
if (wear_integration) { if (wear_integration) {
if (event != null && event.getPath().equals(WEARABLE_RESEND_PATH)) { if (event != null && event.getPath().equals(WEARABLE_RESEND_PATH)) {
resendData(); resendData();
@ -214,6 +291,7 @@ public class WatchUpdaterService extends WearableListenerService implements
private void sendData() { private void sendData() {
BgReading lastBG = DatabaseHelper.lastBg(); BgReading lastBG = DatabaseHelper.lastBg();
// Log.d(TAG, logPrefix + "LastBg=" + lastBG);
if (lastBG != null) { if (lastBG != null) {
GlucoseStatus glucoseStatus = GlucoseStatus.getGlucoseStatusData(); GlucoseStatus glucoseStatus = GlucoseStatus.getGlucoseStatusData();
@ -228,18 +306,19 @@ public class WatchUpdaterService extends WearableListenerService implements
return; return;
} }
new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient).execute(dataMap); executeTask(new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient), dataMap);
} }
} }
} }
private DataMap dataMapSingleBG(BgReading lastBG, GlucoseStatus glucoseStatus) { private DataMap dataMapSingleBG(BgReading lastBG, GlucoseStatus glucoseStatus) {
String units = ProfileFunctions.getInstance().getProfileUnits(); String units = ProfileFunctions.getInstance().getProfileUnits();
Double lowLine = SafeParse.stringToDouble(mPrefs.getString("low_mark", "0")); Double lowLine = SafeParse.stringToDouble(mPrefs.getString("low_mark", "0"));
Double highLine = SafeParse.stringToDouble(mPrefs.getString("high_mark", "0")); Double highLine = SafeParse.stringToDouble(mPrefs.getString("high_mark", "0"));
//convert to mg/dl // convert to mg/dl
if (!units.equals(Constants.MGDL)) { if (!units.equals(Constants.MGDL)) {
lowLine *= Constants.MMOLL_TO_MGDL; lowLine *= Constants.MMOLL_TO_MGDL;
highLine *= Constants.MMOLL_TO_MGDL; highLine *= Constants.MMOLL_TO_MGDL;
@ -287,7 +366,6 @@ public class WatchUpdaterService extends WearableListenerService implements
deltastring += "+"; deltastring += "+";
} else { } else {
deltastring += "-"; deltastring += "-";
} }
boolean detailed = SP.getBoolean("wear_detailed_delta", false); boolean detailed = SP.getBoolean("wear_detailed_delta", false);
@ -352,7 +430,7 @@ public class WatchUpdaterService extends WearableListenerService implements
} }
} }
entries.putDataMapArrayList("entries", dataMaps); entries.putDataMapArrayList("entries", dataMaps);
new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient).execute(entries); executeTask(new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient), entries);
} }
sendPreferences(); sendPreferences();
sendBasals(); sendBasals();
@ -501,7 +579,7 @@ public class WatchUpdaterService extends WearableListenerService implements
dm.putDataMapArrayList("boluses", boluses); dm.putDataMapArrayList("boluses", boluses);
dm.putDataMapArrayList("predictions", predictions); 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) { 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().putLong("timestamp", System.currentTimeMillis());
dataMapRequest.getDataMap().putString("openSettings", "openSettings"); dataMapRequest.getDataMap().putString("openSettings", "openSettings");
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest(); PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendNotification", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest); Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else { } else {
Log.e("OpenSettings", "No connection to wearable available!"); 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().putString("progressstatus", status);
dataMapRequest.getDataMap().putInt("progresspercent", progresspercent); dataMapRequest.getDataMap().putInt("progresspercent", progresspercent);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest(); PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendBolusProgress", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest); Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else { } else {
Log.e("BolusProgress", "No connection to wearable available!"); 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); log.debug("Requesting confirmation from wear: " + actionstring);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest(); PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendActionConfirmationRequest", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest); Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else { } else {
Log.e("confirmationRequest", "No connection to wearable available!"); 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); log.debug("Requesting confirmation from wear: " + actionstring);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest(); PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendChangeConfirmationRequest", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest); Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else { } else {
Log.e("changeConfirmRequest", "No connection to wearable available!"); 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); log.debug("Canceling notification on wear: " + actionstring);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest(); PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendCancelNotificationRequest", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest); Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else { } 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().putBoolean("showBgi", mPrefs.getBoolean("wear_showbgi", false));
dataMapRequest.getDataMap().putInt("batteryLevel", (phoneBattery >= 30) ? 1 : 0); dataMapRequest.getDataMap().putInt("batteryLevel", (phoneBattery >= 30) ? 1 : 0);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest(); PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendStatus", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest); Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else { } else {
Log.e("SendStatus", "No connection to wearable available!"); 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().putLong("timestamp", System.currentTimeMillis());
dataMapRequest.getDataMap().putBoolean("wearcontrol", wearcontrol); dataMapRequest.getDataMap().putBoolean("wearcontrol", wearcontrol);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest(); PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
debugData("sendPreferences", putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest); Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else { } else {
Log.e("SendStatus", "No connection to wearable available!"); 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 @NonNull
private String generateStatusString(Profile profile, String currentBasal, String iobSum, String iobDetail, String bgiString) { 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 { ext {
wearableVersion = "2.0.1" wearableVersion = "2.0.1"
playServicesWearable = "10.2.1"
} }
def generateGitBuild = { -> def generateGitBuild = { ->
@ -93,7 +94,7 @@ dependencies {
//compile "com.ustwo.android:clockwise-wearable:1.0.2" //compile "com.ustwo.android:clockwise-wearable:1.0.2"
compileOnly "com.google.android.wearable:wearable:${wearableVersion}" compileOnly "com.google.android.wearable:wearable:${wearableVersion}"
implementation "com.google.android.support: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(name:"ustwo-clockwise-debug", ext:"aar")
implementation "com.android.support:support-v4:27.0.1" implementation "com.android.support:support-v4:27.0.1"
implementation 'com.android.support:wear: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" /> <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
</intent-filter> </intent-filter>
</service> </service>
<service android:name=".data.ListenerService"> <service android:name=".data.ListenerService">
<intent-filter> <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> </intent-filter>
</service> </service>

View file

@ -1,5 +1,8 @@
package info.nightscout.androidaps.data; package info.nightscout.androidaps.data;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@ -7,16 +10,24 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.SystemClock; import android.os.SystemClock;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat; 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.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient; 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.DataEvent;
import com.google.android.gms.wearable.DataEventBuffer; import com.google.android.gms.wearable.DataEventBuffer;
import com.google.android.gms.wearable.DataMap; 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.AcceptActivity;
import info.nightscout.androidaps.interaction.actions.CPPActivity; import info.nightscout.androidaps.interaction.actions.CPPActivity;
import info.nightscout.androidaps.interaction.utils.SafeParse; import info.nightscout.androidaps.interaction.utils.SafeParse;
import info.nightscout.androidaps.interaction.utils.WearUtil;
/** /**
* Created by emmablack on 12/26/14. * Created by emmablack on 12/26/14.
*/ */
public class ListenerService extends WearableListenerService implements GoogleApiClient.ConnectionCallbacks, 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_DATA_PATH = "/nightscout_watch_data";
private static final String WEARABLE_RESEND_PATH = "/nightscout_watch_data_resend"; private static final String WEARABLE_RESEND_PATH = "/nightscout_watch_data_resend";
private static final String WEARABLE_CANCELBOLUS_PATH = "/nightscout_watch_cancel_bolus"; 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"; private static final String ACTION_RESEND_BULK = "com.dexdrip.stephenblack.nightwatch.RESEND_BULK_DATA";
GoogleApiClient googleApiClient; GoogleApiClient googleApiClient;
private long lastRequest = 0; private long lastRequest = 0;
private DismissThread confirmThread; private DismissThread confirmThread;
private DismissThread bolusprogressThread; 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> { public class DataRequester extends AsyncTask<Void, Void, Void> {
Context mContext; 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 @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
if (googleApiClient.isConnected()) { // Log.d(TAG, logPrefix + "DataRequester: doInBack: " + params);
if (System.currentTimeMillis() - lastRequest > 20 * 1000) { // enforce 20-second debounce period
lastRequest = System.currentTimeMillis();
NodeApi.GetConnectedNodesResult nodes = try {
Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
for (Node node : nodes.getNodes()) { forceGoogleApiConnect();
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), WEARABLE_RESEND_PATH, null); DataMap datamap;
}
if (isCancelled()) {
Log.d(TAG, "doInBackground CANCELLED programmatically");
return null;
} }
} else {
googleApiClient.blockingConnect(15, TimeUnit.SECONDS); if (googleApiClient != null) {
if (googleApiClient.isConnected()) { if (!googleApiClient.isConnected())
if (System.currentTimeMillis() - lastRequest > 20 * 1000) { // enforce 20-second debounce period 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(); lastRequest = System.currentTimeMillis();
NodeApi.GetConnectedNodesResult nodes = // NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(googleApiClient).await(); // Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
for (Node node : nodes.getNodes()) { if (localnode == null || (localnode != null && localnode.isEmpty()))
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), WEARABLE_RESEND_PATH, null); 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; 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> { public class BolusCancelTask extends AsyncTask<Void, Void, Void> {
Context mContext; Context mContext;
@ -119,6 +248,8 @@ public class ListenerService extends WearableListenerService implements GoogleAp
@Override @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
// Log.d(TAG, logPrefix + "BolusCancelTask: doInBack: " + params);
if (googleApiClient.isConnected()) { if (googleApiClient.isConnected()) {
NodeApi.GetConnectedNodesResult nodes = NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(googleApiClient).await(); Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
@ -154,6 +285,9 @@ public class ListenerService extends WearableListenerService implements GoogleAp
@Override @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
forceGoogleApiConnect();
if (googleApiClient.isConnected()) { if (googleApiClient.isConnected()) {
NodeApi.GetConnectedNodesResult nodes = NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(googleApiClient).await(); Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
@ -176,7 +310,7 @@ public class ListenerService extends WearableListenerService implements GoogleAp
} }
public void requestData() { public void requestData() {
new DataRequester(this).execute(); sendData(WEARABLE_RESEND_PATH, null);
} }
public void cancelBolus() { public void cancelBolus() {
@ -191,7 +325,73 @@ public class ListenerService extends WearableListenerService implements GoogleAp
new MessageActionTask(this, WEARABLE_INITIATE_ACTIONSTRING_PATH, actionstring).execute(); 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) googleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this) .addConnectionCallbacks(this)
.addOnConnectionFailedListener(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 @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
// Log.d(TAG, logPrefix + "onStartCommand: Intent: " + intent);
if (intent != null && ACTION_RESEND.equals(intent.getAction())) { if (intent != null && ACTION_RESEND.equals(intent.getAction())) {
googleApiConnect(); googleApiConnect();
requestData(); requestData();
@ -256,13 +473,16 @@ public class ListenerService extends WearableListenerService implements GoogleAp
public void onDataChanged(DataEventBuffer dataEvents) { public void onDataChanged(DataEventBuffer dataEvents) {
DataMap dataMap; DataMap dataMap;
// Log.d(TAG, logPrefix + "onDataChanged: DataEvents=" + dataEvents);
for (DataEvent event : dataEvents) { for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_CHANGED) { if (event.getType() == DataEvent.TYPE_CHANGED) {
String path = event.getDataItem().getUri().getPath(); String path = event.getDataItem().getUri().getPath();
//Log.d(TAG, "WR: onDataChanged: Path: " + path + ", EventDataItem=" + event.getDataItem());
if (path.equals(OPEN_SETTINGS)) { if (path.equals(OPEN_SETTINGS)) {
Intent intent = new Intent(this, AAPSPreferences.class); Intent intent = new Intent(this, AAPSPreferences.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@ -480,6 +700,21 @@ public class ListenerService extends WearableListenerService implements GoogleAp
@Override @Override
public void onConnected(Bundle bundle) { 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(); 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 @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
if (googleApiClient != null && googleApiClient.isConnected()) { if (googleApiClient != null && googleApiClient.isConnected()) {
googleApiClient.disconnect(); googleApiClient.disconnect();
} }
if (googleApiClient != null) { if (googleApiClient != null) {
Wearable.MessageApi.removeListener(googleApiClient, this); 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>