On both sides changed BIND_LISTENER in manifest and added a lot of logging, some might be commented out.

On AAPS side:
- added some capabilites
- fully refactored SendToDataLayerThread (copied from xdrip)
- updated code
- all tasks are now executed on Executor (and not by itself) - main problem that some data was not sent

On Wear side:
- added capabilities and refactored code in ListenerService (by looking at xdrip code)
This commit is contained in:
Andy Rozman 2019-03-06 23:38:18 +00:00
parent 056ad650cd
commit 6e500c1e75
9 changed files with 547 additions and 83 deletions

View file

@ -68,6 +68,7 @@ android {
buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"'
buildConfigField "String", "HEAD", '"' + generateGitBuild() + '"'
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// if you change minSdkVersion to less than 11, you need to change executeTask for wear
ndk {
moduleName "BleCommandUtil"
@ -194,7 +195,7 @@ dependencies {
implementation "org.slf4j:slf4j-api:1.7.12"
implementation "com.jjoe64:graphview:4.0.1"
implementation "com.joanzapata.iconify:android-iconify-fontawesome:2.1.1"
implementation "com.google.android.gms:play-services-wearable:7.5.0"
implementation 'com.google.android.gms:play-services-wearable:10.2.1'
implementation(name: "android-edittext-validator-v1.3.4-mod", ext: "aar")
implementation(name: "sightparser-release", ext: "aar")
implementation 'com.madgag.spongycastle:core:1.58.0.0'

View file

@ -135,6 +135,7 @@
</intent-filter>
</receiver>
<!--
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
@ -143,7 +144,7 @@
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
</provider> -->
<!-- Service processing incomming data -->
<service
@ -169,7 +170,63 @@
android:name=".plugins.general.wear.wearintegration.WatchUpdaterService"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
<!-- <action android:name="com.google.android.gms.wearable.BIND_LISTENER" /> -->
<!-- listeners receive events that match the action and data filters -->
<action android:name="com.google.android.gms.wearable.CAPABILITY_CHANGED" />
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_data" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_data_resend" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_cancel_bolus" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_confirmactionstring" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_initiateactionstring" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/openwearsettings" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/sendstatustowear" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/sendpreferencestowear" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_basal" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_bolusprogress" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_actionconfirmationrequest" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_changeconfirmationrequest" />
<data
android:scheme="wear"
android:host="*"
android:pathPrefix="/nightscout_watch_cancelnotificationrequest" />
</intent-filter>
</service>
<service

View file

@ -2,6 +2,7 @@ package info.nightscout.androidaps.plugins.general.wear;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import com.squareup.otto.Subscribe;
@ -35,6 +36,8 @@ public class WearPlugin extends PluginBase {
private final Context ctx;
private static WearPlugin wearPlugin;
private static String TAG = "WearPlugin";
public static WearPlugin getPlugin() {
return wearPlugin;
@ -76,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);
}

View file

@ -19,34 +19,120 @@ import java.util.concurrent.TimeUnit;
*/
class SendToDataLayerThread extends AsyncTask<DataMap,Void,Void> {
private GoogleApiClient googleApiClient;
private static final String TAG = "SendDataThread";
String path;
private static final String TAG = "SendToDataLayerThread";
private String path;
private String logPrefix = ""; // "WR: ";
private static int concurrency = 0;
private static int state = 0;
private static final ReentrantLock lock = new ReentrantLock();
private static long lastlock = 0;
private static final boolean testlockup = false; // always false in production
SendToDataLayerThread(String path, GoogleApiClient pGoogleApiClient) {
// Log.d(TAG, logPrefix + "SendToDataLayerThread: " + path);
this.path = path;
googleApiClient = pGoogleApiClient;
}
@Override
protected void onPreExecute() {
concurrency++;
if ((concurrency > 12) || ((concurrency > 3 && (lastlock != 0) && (tsl() - lastlock) > 300000))) {
// error if 9 concurrent threads or lock held for >5 minutes with concurrency of 4
final String err = "Wear Integration deadlock detected!! " + ((lastlock != 0) ? "locked" : "") + " state:"
+ state + " @" + hourMinuteString(tsl());
// Home.toaststaticnext(err);
Log.e(TAG, logPrefix + err);
}
if (concurrency < 0)
Log.d(TAG, logPrefix + "Wear Integration impossible concurrency!!");
Log.d(TAG, logPrefix + "SendDataToLayerThread pre-execute concurrency: " + concurrency);
}
@Override
protected Void doInBackground(DataMap... params) {
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();
}
}

View file

@ -1,9 +1,17 @@
package info.nightscout.androidaps.plugins.general.wear.wearintegration;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.Handler;
@ -14,8 +22,11 @@ import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.CapabilityApi;
import com.google.android.gms.wearable.CapabilityInfo;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable;
@ -42,21 +53,24 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorP
import info.nightscout.androidaps.plugins.treatments.Treatment;
import info.nightscout.androidaps.interfaces.PluginType;
import info.nightscout.androidaps.interfaces.TreatmentsInterface;
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin;
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions;
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus;
import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin;
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
import info.nightscout.androidaps.plugins.general.wear.ActionStringHandler;
import info.nightscout.androidaps.plugins.general.wear.WearPlugin;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin;
import info.nightscout.androidaps.plugins.treatments.Treatment;
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
import info.nightscout.androidaps.utils.DecimalFormatter;
import info.nightscout.androidaps.utils.SP;
import info.nightscout.androidaps.utils.SafeParse;
import info.nightscout.androidaps.utils.ToastUtils;
public class WatchUpdaterService extends WearableListenerService implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
public class WatchUpdaterService extends WearableListenerService implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
public static final String ACTION_RESEND = WatchUpdaterService.class.getName().concat(".Resend");
public static final String ACTION_OPEN_SETTINGS = WatchUpdaterService.class.getName().concat(".OpenSettings");
public static final String ACTION_SEND_STATUS = WatchUpdaterService.class.getName().concat(".SendStatus");
@ -91,6 +105,17 @@ public class WatchUpdaterService extends WearableListenerService implements
private Handler handler;
// Phone
private static final String CAPABILITY_PHONE_APP = "phone_app_sync_bgs";
private static final String MESSAGE_PATH_PHONE = "/phone_message_path";
// Wear
private static final String CAPABILITY_WEAR_APP = "wear_app_sync_bgs";
private static final String MESSAGE_PATH_WEAR = "/wear_message_path";
private String mWearNodeId = null;
private String localnode = null;
private String logPrefix = ""; // "WR: "
@Override
public void onCreate() {
mPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
@ -112,24 +137,24 @@ public class WatchUpdaterService extends WearableListenerService implements
public void setSettings() {
wear_integration = WearPlugin.getPlugin().isEnabled(PluginType.GENERAL);
// Log.d(TAG, "WR: wear_integration=" + wear_integration);
if (wear_integration) {
googleApiConnect();
}
}
public void googleApiConnect() {
private void googleApiConnect() {
if (googleApiClient != null && (googleApiClient.isConnected() || googleApiClient.isConnecting())) {
googleApiClient.disconnect();
}
googleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Wearable.API)
.build();
googleApiClient = new GoogleApiClient.Builder(this).addConnectionCallbacks(this)
.addOnConnectionFailedListener(this).addApi(Wearable.API).build();
Wearable.MessageApi.addListener(googleApiClient, this);
if (googleApiClient.isConnected()) {
Log.d("WatchUpdater", "API client is connected");
Log.d(TAG, logPrefix + "API client is connected");
} else {
// Log.d("WatchUpdater", logPrefix + "API client is not connected and is trying to connect");
googleApiClient.connect();
}
}
@ -138,6 +163,8 @@ public class WatchUpdaterService extends WearableListenerService implements
public int onStartCommand(Intent intent, int flags, int startId) {
String action = intent != null ? intent.getAction() : null;
// Log.d(TAG, logPrefix + "onStartCommand: " + action);
if (wear_integration) {
handler.post(() -> {
if (googleApiClient.isConnected()) {
@ -177,13 +204,63 @@ public class WatchUpdaterService extends WearableListenerService implements
}
private void updateWearSyncBgsCapability(CapabilityInfo capabilityInfo) {
Log.d("WatchUpdaterService", logPrefix + "CabilityInfo: " + capabilityInfo);
Set<Node> connectedNodes = capabilityInfo.getNodes();
mWearNodeId = pickBestNodeId(connectedNodes);
}
private String pickBestNodeId(Set<Node> nodes) {
String bestNodeId = null;
// Find a nearby node or pick one arbitrarily
for (Node node : nodes) {
if (node.isNearby()) {
return node.getId();
}
bestNodeId = node.getId();
}
return bestNodeId;
}
@Override
public void onConnected(Bundle connectionHint) {
CapabilityApi.CapabilityListener capabilityListener = capabilityInfo -> {
updateWearSyncBgsCapability(capabilityInfo);
// Log.d(TAG, logPrefix + "onConnected onCapabilityChanged mWearNodeID:" + mWearNodeId);
// new CheckWearableConnected().execute();
};
Wearable.CapabilityApi.addCapabilityListener(googleApiClient, capabilityListener, CAPABILITY_WEAR_APP);
sendData();
}
@Override
public void onPeerConnected(com.google.android.gms.wearable.Node peer) {// KS
super.onPeerConnected(peer);
String id = peer.getId();
String name = peer.getDisplayName();
// Log.d(TAG, logPrefix + "onPeerConnected peer name & ID: " + name + "|" + id);
}
@Override
public void onPeerDisconnected(com.google.android.gms.wearable.Node peer) {// KS
super.onPeerDisconnected(peer);
String id = peer.getId();
String name = peer.getDisplayName();
// Log.d(TAG, logPrefix + "onPeerDisconnected peer name & ID: " + name + "|" + id);
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
}
@Override
public void onMessageReceived(MessageEvent event) {
// Log.d(TAG, logPrefix + "onMessageRecieved: " + event);
if (wear_integration) {
if (event != null && event.getPath().equals(WEARABLE_RESEND_PATH)) {
resendData();
@ -214,6 +291,7 @@ public class WatchUpdaterService extends WearableListenerService implements
private void sendData() {
BgReading lastBG = DatabaseHelper.lastBg();
// Log.d(TAG, logPrefix + "LastBg=" + lastBG);
if (lastBG != null) {
GlucoseStatus glucoseStatus = GlucoseStatus.getGlucoseStatusData();
@ -228,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,6 +700,7 @@ 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!");
@ -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, parameters);
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
// } else {
// task.execute();
// }
}
@NonNull
private String generateStatusString(Profile profile, String currentBasal, String iobSum, String iobDetail, String bgiString) {

View file

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

View file

@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
ext {
wearableVersion = "2.0.1"
playServicesWearable = "9.4.0"
playServicesWearable = "10.2.1"
}
def generateGitBuild = { ->

View file

@ -1,5 +1,8 @@
package info.nightscout.androidaps.data;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@ -14,11 +17,16 @@ 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;
@ -81,57 +89,156 @@ public class ListenerService extends WearableListenerService implements GoogleAp
private static final String TAG = "ListenerService";
private DataRequester mDataRequester = null;
private static final int GET_CAPABILITIES_TIMEOUT_MS = 5000;
// Phone
private static final String CAPABILITY_PHONE_APP = "phone_app_sync_bgs";
private static final String MESSAGE_PATH_PHONE = "/phone_message_path";
// Wear
private static final String CAPABILITY_WEAR_APP = "wear_app_sync_bgs";
private static final String MESSAGE_PATH_WEAR = "/wear_message_path";
private String mPhoneNodeId = null;
private String localnode = null;
private String logPrefix = ""; // "WR: "
public class DataRequester extends AsyncTask<Void, Void, Void> {
Context mContext;
String path;
byte[] payload;
// DataRequester(Context context) {
// mContext = context;
// }
DataRequester(Context context, String thispath, byte[] thispayload) {
path = thispath;
payload = thispayload;
Log.d(TAG, "DataRequester DataRequester: " + thispath + " lastRequest:" + lastRequest);
// 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<Void, Void, Void> {
Context mContext;
@ -141,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();
@ -217,29 +326,54 @@ public class ListenerService extends WearableListenerService implements GoogleAp
}
private synchronized void sendData(String path, byte[] payload) {
if (path == null) return;
if (mDataRequester != null) {
Log.d(TAG, "sendData DataRequester != null lastRequest:" + WearUtil.dateTimeText(lastRequest));
if (mDataRequester.getStatus() != AsyncTask.Status.FINISHED) {
Log.d(TAG, "sendData Should be canceled? Let run 'til finished.");
//mDataRequester.cancel(true);
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);
}
//mDataRequester = null;
}
Log.d(TAG, "sendData: execute lastRequest:" + WearUtil.dateTimeText(lastRequest));
mDataRequester = (DataRequester) new DataRequester(this, path, payload).execute();
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);
// }
// 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);
// }
}
@ -283,6 +417,9 @@ public class ListenerService extends WearableListenerService implements GoogleAp
@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();
@ -336,6 +473,7 @@ 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) {
@ -343,7 +481,7 @@ public class ListenerService extends WearableListenerService implements GoogleAp
String path = event.getDataItem().getUri().getPath();
Log.d(TAG, "Path: {}" + path + ", Event=" + event);
//Log.d(TAG, "WR: onDataChanged: Path: " + path + ", EventDataItem=" + event.getDataItem());
if (path.equals(OPEN_SETTINGS)) {
Intent intent = new Intent(this, AAPSPreferences.class);
@ -562,7 +700,19 @@ public class ListenerService extends WearableListenerService implements GoogleAp
@Override
public void onConnected(Bundle bundle) {
Log.d(TAG, "onConnected call requestData");
// 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();
@ -578,6 +728,28 @@ public class ListenerService extends WearableListenerService implements GoogleAp
}
private void setLocalNodeName() {
forceGoogleApiConnect();
PendingResult<NodeApi.GetLocalNodeResult> result = Wearable.NodeApi.getLocalNode(googleApiClient);
result.setResultCallback(new ResultCallback<NodeApi.GetLocalNodeResult>() {
@Override
public void onResult(NodeApi.GetLocalNodeResult getLocalNodeResult) {
if (!getLocalNodeResult.getStatus().isSuccess()) {
Log.e(TAG, "ERROR: failed to getLocalNode Status="
+ getLocalNodeResult.getStatus().getStatusMessage());
} else {
Log.d(TAG, "getLocalNode Status=: " + getLocalNodeResult.getStatus().getStatusMessage());
Node getnode = getLocalNodeResult.getNode();
localnode = getnode != null ? getnode.getDisplayName() + "|" + getnode.getId() : "";
Log.d(TAG, "setLocalNodeName. localnode=" + localnode);
}
}
});
}
@Override
public void onDestroy() {
super.onDestroy();

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>