diff --git a/icons/battery-burnin/battery-charging-wireless-10-burnin.svg b/icons/battery-burnin/battery-charging-wireless-10-burnin.svg
new file mode 100644
index 0000000000..2888abf209
--- /dev/null
+++ b/icons/battery-burnin/battery-charging-wireless-10-burnin.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/icons/battery-burnin/battery-charging-wireless-20-burnin.svg b/icons/battery-burnin/battery-charging-wireless-20-burnin.svg
new file mode 100644
index 0000000000..77d52751d7
--- /dev/null
+++ b/icons/battery-burnin/battery-charging-wireless-20-burnin.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/icons/battery-burnin/battery-charging-wireless-30-burnin.svg b/icons/battery-burnin/battery-charging-wireless-30-burnin.svg
new file mode 100644
index 0000000000..9ffa356436
--- /dev/null
+++ b/icons/battery-burnin/battery-charging-wireless-30-burnin.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/icons/battery-burnin/battery-charging-wireless-40-burnin.svg b/icons/battery-burnin/battery-charging-wireless-40-burnin.svg
new file mode 100644
index 0000000000..d9ccc46027
--- /dev/null
+++ b/icons/battery-burnin/battery-charging-wireless-40-burnin.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/icons/battery-burnin/battery-charging-wireless-50-burnin.svg b/icons/battery-burnin/battery-charging-wireless-50-burnin.svg
new file mode 100644
index 0000000000..4d4905046c
--- /dev/null
+++ b/icons/battery-burnin/battery-charging-wireless-50-burnin.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/icons/battery-burnin/battery-charging-wireless-60-burnin.svg b/icons/battery-burnin/battery-charging-wireless-60-burnin.svg
new file mode 100644
index 0000000000..e3c4ba5c44
--- /dev/null
+++ b/icons/battery-burnin/battery-charging-wireless-60-burnin.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/icons/battery-burnin/battery-charging-wireless-70-burnin.svg b/icons/battery-burnin/battery-charging-wireless-70-burnin.svg
new file mode 100644
index 0000000000..fe26b5e93d
--- /dev/null
+++ b/icons/battery-burnin/battery-charging-wireless-70-burnin.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/icons/battery-burnin/battery-charging-wireless-80-burnin.svg b/icons/battery-burnin/battery-charging-wireless-80-burnin.svg
new file mode 100644
index 0000000000..c949afb1c8
--- /dev/null
+++ b/icons/battery-burnin/battery-charging-wireless-80-burnin.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/icons/battery-burnin/battery-charging-wireless-90-burnin.svg b/icons/battery-burnin/battery-charging-wireless-90-burnin.svg
new file mode 100644
index 0000000000..13677dc328
--- /dev/null
+++ b/icons/battery-burnin/battery-charging-wireless-90-burnin.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/icons/battery-burnin/battery-charging-wireless-burnin.svg b/icons/battery-burnin/battery-charging-wireless-burnin.svg
new file mode 100644
index 0000000000..f074b89d34
--- /dev/null
+++ b/icons/battery-burnin/battery-charging-wireless-burnin.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/icons/battery-burnin/battery-unknown-burnin.svg b/icons/battery-burnin/battery-unknown-burnin.svg
new file mode 100644
index 0000000000..b0cc3bba40
--- /dev/null
+++ b/icons/battery-burnin/battery-unknown-burnin.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/icons/battery-source/mask-burnin-battery-raw.svg b/icons/battery-source/mask-burnin-battery-raw.svg
new file mode 100644
index 0000000000..e2ab79f2a1
--- /dev/null
+++ b/icons/battery-source/mask-burnin-battery-raw.svg
@@ -0,0 +1,55 @@
+
+
diff --git a/icons/battery-source/mask-burnin-battery.svg b/icons/battery-source/mask-burnin-battery.svg
new file mode 100644
index 0000000000..09be5da8a9
--- /dev/null
+++ b/icons/battery-source/mask-burnin-battery.svg
@@ -0,0 +1,223 @@
+
+
diff --git a/icons/battery/battery-charging-wireless-10.svg b/icons/battery/battery-charging-wireless-10.svg
new file mode 100644
index 0000000000..d6dbd7febc
--- /dev/null
+++ b/icons/battery/battery-charging-wireless-10.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/battery/battery-charging-wireless-20.svg b/icons/battery/battery-charging-wireless-20.svg
new file mode 100644
index 0000000000..9e4badedc9
--- /dev/null
+++ b/icons/battery/battery-charging-wireless-20.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/battery/battery-charging-wireless-30.svg b/icons/battery/battery-charging-wireless-30.svg
new file mode 100644
index 0000000000..7da87ce966
--- /dev/null
+++ b/icons/battery/battery-charging-wireless-30.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/battery/battery-charging-wireless-40.svg b/icons/battery/battery-charging-wireless-40.svg
new file mode 100644
index 0000000000..b9aaad2b0f
--- /dev/null
+++ b/icons/battery/battery-charging-wireless-40.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/battery/battery-charging-wireless-50.svg b/icons/battery/battery-charging-wireless-50.svg
new file mode 100644
index 0000000000..705a61c55b
--- /dev/null
+++ b/icons/battery/battery-charging-wireless-50.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/battery/battery-charging-wireless-60.svg b/icons/battery/battery-charging-wireless-60.svg
new file mode 100644
index 0000000000..b2cd9f7734
--- /dev/null
+++ b/icons/battery/battery-charging-wireless-60.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/battery/battery-charging-wireless-70.svg b/icons/battery/battery-charging-wireless-70.svg
new file mode 100644
index 0000000000..608a404882
--- /dev/null
+++ b/icons/battery/battery-charging-wireless-70.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/battery/battery-charging-wireless-80.svg b/icons/battery/battery-charging-wireless-80.svg
new file mode 100644
index 0000000000..c604743cc3
--- /dev/null
+++ b/icons/battery/battery-charging-wireless-80.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/battery/battery-charging-wireless-90.svg b/icons/battery/battery-charging-wireless-90.svg
new file mode 100644
index 0000000000..246886ad08
--- /dev/null
+++ b/icons/battery/battery-charging-wireless-90.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/battery/battery-charging-wireless.svg b/icons/battery/battery-charging-wireless.svg
new file mode 100644
index 0000000000..b36143c4c2
--- /dev/null
+++ b/icons/battery/battery-charging-wireless.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/battery/battery-outline.svg b/icons/battery/battery-outline.svg
new file mode 100644
index 0000000000..e05e71b288
--- /dev/null
+++ b/icons/battery/battery-outline.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/battery/battery-unknown.svg b/icons/battery/battery-unknown.svg
new file mode 100644
index 0000000000..8e117be5cb
--- /dev/null
+++ b/icons/battery/battery-unknown.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/complications-source/ic_br_cob_iob_orig.svg b/icons/complications-source/ic_br_cob_iob_orig.svg
new file mode 100644
index 0000000000..f650e12d99
--- /dev/null
+++ b/icons/complications-source/ic_br_cob_iob_orig.svg
@@ -0,0 +1,93 @@
+
+
+
+
diff --git a/icons/complications-source/ic_cob_detailed_orig.svg b/icons/complications-source/ic_cob_detailed_orig.svg
new file mode 100644
index 0000000000..e599774c5d
--- /dev/null
+++ b/icons/complications-source/ic_cob_detailed_orig.svg
@@ -0,0 +1,83 @@
+
+
+
+
diff --git a/icons/complications-source/ic_cob_iob_orig.svg b/icons/complications-source/ic_cob_iob_orig.svg
new file mode 100644
index 0000000000..98c1764554
--- /dev/null
+++ b/icons/complications-source/ic_cob_iob_orig.svg
@@ -0,0 +1,83 @@
+
+
+
+
diff --git a/icons/complications-source/ic_ins_burnin_orig.svg b/icons/complications-source/ic_ins_burnin_orig.svg
new file mode 100644
index 0000000000..c5bcdfe0ff
--- /dev/null
+++ b/icons/complications-source/ic_ins_burnin_orig.svg
@@ -0,0 +1,57 @@
+
+
diff --git a/icons/complications-source/ic_ins_orig.svg b/icons/complications-source/ic_ins_orig.svg
new file mode 100644
index 0000000000..7601867f58
--- /dev/null
+++ b/icons/complications-source/ic_ins_orig.svg
@@ -0,0 +1,56 @@
+
+
diff --git a/icons/complications-source/ic_iob_detailed_orig.svg b/icons/complications-source/ic_iob_detailed_orig.svg
new file mode 100644
index 0000000000..3479a78565
--- /dev/null
+++ b/icons/complications-source/ic_iob_detailed_orig.svg
@@ -0,0 +1,88 @@
+
+
+
+
diff --git a/icons/complications/ic_aaps_full.svg b/icons/complications/ic_aaps_full.svg
new file mode 100644
index 0000000000..4e005a8228
--- /dev/null
+++ b/icons/complications/ic_aaps_full.svg
@@ -0,0 +1,36 @@
+
+
diff --git a/icons/complications/ic_basal.svg b/icons/complications/ic_basal.svg
new file mode 100644
index 0000000000..0491265177
--- /dev/null
+++ b/icons/complications/ic_basal.svg
@@ -0,0 +1,35 @@
+
+
diff --git a/icons/complications/ic_br_cob_iob.svg b/icons/complications/ic_br_cob_iob.svg
new file mode 100644
index 0000000000..b340c42187
--- /dev/null
+++ b/icons/complications/ic_br_cob_iob.svg
@@ -0,0 +1,40 @@
+
+
diff --git a/icons/complications/ic_carbs.svg b/icons/complications/ic_carbs.svg
new file mode 100644
index 0000000000..0785741d47
--- /dev/null
+++ b/icons/complications/ic_carbs.svg
@@ -0,0 +1,35 @@
+
+
diff --git a/icons/complications/ic_cob_detailed.svg b/icons/complications/ic_cob_detailed.svg
new file mode 100644
index 0000000000..5132260c3c
--- /dev/null
+++ b/icons/complications/ic_cob_detailed.svg
@@ -0,0 +1,36 @@
+
+
diff --git a/icons/complications/ic_cob_iob.svg b/icons/complications/ic_cob_iob.svg
new file mode 100644
index 0000000000..81a2e9250f
--- /dev/null
+++ b/icons/complications/ic_cob_iob.svg
@@ -0,0 +1,36 @@
+
+
diff --git a/icons/complications/ic_ins.svg b/icons/complications/ic_ins.svg
new file mode 100644
index 0000000000..e5d1b3e147
--- /dev/null
+++ b/icons/complications/ic_ins.svg
@@ -0,0 +1,31 @@
+
+
diff --git a/icons/complications/ic_ins_burnin.svg b/icons/complications/ic_ins_burnin.svg
new file mode 100644
index 0000000000..623db41e5b
--- /dev/null
+++ b/icons/complications/ic_ins_burnin.svg
@@ -0,0 +1,31 @@
+
+
diff --git a/icons/complications/ic_iob_detailed.svg b/icons/complications/ic_iob_detailed.svg
new file mode 100644
index 0000000000..8bd9aabc0e
--- /dev/null
+++ b/icons/complications/ic_iob_detailed.svg
@@ -0,0 +1,36 @@
+
+
diff --git a/icons/complications/ic_sgv.svg b/icons/complications/ic_sgv.svg
new file mode 100644
index 0000000000..1dc46d403b
--- /dev/null
+++ b/icons/complications/ic_sgv.svg
@@ -0,0 +1,35 @@
+
+
diff --git a/wear/build.gradle b/wear/build.gradle
index 46161f6a5a..3a2277e2fe 100644
--- a/wear/build.gradle
+++ b/wear/build.gradle
@@ -1,4 +1,18 @@
+buildscript {
+ repositories {
+ jcenter()
+ }
+
+ dependencies {
+ classpath 'com.dicedmelon.gradle:jacoco-android:0.1.4'
+ }
+}
apply plugin: 'com.android.application'
+apply plugin: 'jacoco-android'
+
+jacoco {
+ toolVersion = "0.8.3"
+}
ext {
wearableVersion = "2.4.0"
@@ -93,6 +107,10 @@ dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
//implementation files("libs/hellocharts-library-1.5.5.jar")
//compile "com.ustwo.android:clockwise-wearable:1.0.2"
+
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.legacy:legacy-support-v13:1.0.0'
+
compileOnly "com.google.android.wearable:wearable:${wearableVersion}"
implementation "com.google.android.support:wearable:${wearableVersion}"
implementation "com.google.android.gms:play-services-wearable:${playServicesWearable}"
@@ -105,7 +123,16 @@ dependencies {
testImplementation "junit:junit:4.12"
testImplementation "org.json:json:20140107"
- testImplementation "org.mockito:mockito-core:2.8.47"
+ testImplementation ("org.mockito:mockito-core:2.8.47") {
+ exclude group: 'net.bytebuddy', module: 'byte-buddy'
+ exclude group: 'net.bytebuddy', module: 'byte-buddy-android'
+ exclude group: 'net.bytebuddy', module: 'byte-buddy-agent'
+ }
+ // to fix org.mockito:mockito-core dependency issues, fixed in mockito 3+
+ testImplementation 'net.bytebuddy:byte-buddy:1.8.22'
+ testImplementation 'net.bytebuddy:byte-buddy-android:1.8.22'
+ testImplementation 'net.bytebuddy:byte-buddy-agent:1.8.22'
+
testImplementation "org.powermock:powermock-api-mockito2:${powermockVersion}"
testImplementation "org.powermock:powermock-module-junit4-rule-agent:${powermockVersion}"
testImplementation "org.powermock:powermock-module-junit4-rule:${powermockVersion}"
diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml
index 874a9c8763..1097be80be 100644
--- a/wear/src/main/AndroidManifest.xml
+++ b/wear/src/main/AndroidManifest.xml
@@ -10,6 +10,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wear/src/main/assets/watch_dark.jpg b/wear/src/main/assets/watch_dark.jpg
new file mode 100644
index 0000000000..54cd4dc1dc
Binary files /dev/null and b/wear/src/main/assets/watch_dark.jpg differ
diff --git a/wear/src/main/assets/watch_gray.jpg b/wear/src/main/assets/watch_gray.jpg
new file mode 100644
index 0000000000..2cb3778b6b
Binary files /dev/null and b/wear/src/main/assets/watch_gray.jpg differ
diff --git a/wear/src/main/assets/watch_light.jpg b/wear/src/main/assets/watch_light.jpg
new file mode 100644
index 0000000000..a8183304d6
Binary files /dev/null and b/wear/src/main/assets/watch_light.jpg differ
diff --git a/wear/src/main/java/info/nightscout/androidaps/aaps.java b/wear/src/main/java/info/nightscout/androidaps/aaps.java
new file mode 100644
index 0000000000..f05f032f2e
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/aaps.java
@@ -0,0 +1,75 @@
+package info.nightscout.androidaps;
+
+import android.annotation.SuppressLint;
+import android.app.Application;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.preference.PreferenceManager;
+
+import androidx.annotation.StringRes;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import info.nightscout.androidaps.interaction.utils.Persistence;
+
+/**
+ * Created for xDrip+ by Emma Black on 3/21/15.
+ * Adapted for AAPS by dlvoy 2019-11-06.
+ */
+
+public class aaps extends Application implements SharedPreferences.OnSharedPreferenceChangeListener {
+
+ @SuppressLint("StaticFieldLeak")
+ private static Context context;
+ private static Boolean unicodeComplications = true;
+ private static String complicationTapAction = "default";
+
+ @Override
+ public void onCreate() {
+ aaps.context = getApplicationContext();
+ SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+ sharedPrefs.registerOnSharedPreferenceChangeListener(this);
+ updatePrefs(sharedPrefs);
+ super.onCreate();
+ }
+
+ private void updatePrefs(SharedPreferences sharedPrefs) {
+ unicodeComplications = sharedPrefs.getBoolean("complication_unicode", true);
+ complicationTapAction = sharedPrefs.getString("complication_tap_action", "default");
+ }
+
+ public static Context getAppContext() {
+ return aaps.context;
+ }
+
+ private static boolean isWear2OrAbove() {
+ return Build.VERSION.SDK_INT > 23;
+ }
+
+ public static String gs(@StringRes final int id) {
+ return getAppContext().getString(id);
+ }
+
+ public static String gs(@StringRes final int id, String... args) {
+ return getAppContext().getString(id, (Object[]) args);
+ }
+
+ public static Boolean areComplicationsUnicode() {
+ return unicodeComplications;
+ }
+
+ public static String getComplicationTapAction() {
+ return complicationTapAction;
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+ updatePrefs(sharedPrefs);
+
+ // we trigger update on Complications
+ Intent messageIntent = new Intent();
+ messageIntent.setAction(Intent.ACTION_SEND);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent);
+ }
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/BaseComplicationProviderService.java b/wear/src/main/java/info/nightscout/androidaps/complications/BaseComplicationProviderService.java
new file mode 100644
index 0000000000..49f8f82837
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/complications/BaseComplicationProviderService.java
@@ -0,0 +1,411 @@
+package info.nightscout.androidaps.complications;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.drawable.Icon;
+import android.support.wearable.complications.ComplicationData;
+import android.support.wearable.complications.ComplicationManager;
+import android.support.wearable.complications.ComplicationProviderService;
+import android.support.wearable.complications.ComplicationText;
+import android.support.wearable.complications.ProviderUpdateRequester;
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.aaps;
+import info.nightscout.androidaps.data.RawDisplayData;
+import info.nightscout.androidaps.data.ListenerService;
+import info.nightscout.androidaps.interaction.utils.Constants;
+import info.nightscout.androidaps.interaction.utils.DisplayFormat;
+import info.nightscout.androidaps.interaction.utils.Inevitable;
+import info.nightscout.androidaps.interaction.utils.Persistence;
+import info.nightscout.androidaps.interaction.utils.WearUtil;
+
+/**
+ * Base class for all complications
+ *
+ * Created by dlvoy on 2019-11-12
+ */
+public abstract class BaseComplicationProviderService extends ComplicationProviderService {
+
+ private static final String TAG = BaseComplicationProviderService.class.getSimpleName();
+
+ private static final String KEY_COMPLICATIONS = "complications";
+ private static final String KEY_LAST_SHOWN_SINCE_VALUE = "lastSince";
+ private static final String KEY_STALE_REPORTED = "staleReported";
+ private static final String TASK_ID_REFRESH_COMPLICATION = "refresh-complication";
+
+
+ private LocalBroadcastManager localBroadcastManager;
+ private MessageReceiver messageReceiver;
+
+ public static void turnOff() {
+ Log.d(TAG, "TURNING OFF all active complications");
+ final Persistence persistence = new Persistence();
+ persistence.putString(KEY_COMPLICATIONS, "");
+ }
+
+ //==============================================================================================
+ // ABSTRACT COMPLICATION INTERFACE
+ //==============================================================================================
+
+ public abstract ComplicationData buildComplicationData(int dataType, RawDisplayData raw, PendingIntent complicationPendingIntent);
+ public abstract String getProviderCanonicalName();
+
+ public ComplicationAction getComplicationAction() { return ComplicationAction.MENU; };
+
+ //----------------------------------------------------------------------------------------------
+ // DEFAULT BEHAVIOURS
+ //----------------------------------------------------------------------------------------------
+
+ public ComplicationData buildNoSyncComplicationData(int dataType,
+ RawDisplayData raw,
+ PendingIntent complicationPendingIntent,
+ PendingIntent exceptionalPendingIntent,
+ long since) {
+
+
+ final ComplicationData.Builder builder = new ComplicationData.Builder(dataType);
+ if (dataType != ComplicationData.TYPE_LARGE_IMAGE) {
+ builder.setIcon(Icon.createWithResource(this, R.drawable.ic_sync_alert));
+ }
+
+ if (dataType == ComplicationData.TYPE_RANGED_VALUE) {
+ builder.setMinValue(0);
+ builder.setMaxValue(100);
+ builder.setValue(0);
+ }
+
+ switch (dataType) {
+ case ComplicationData.TYPE_ICON:
+ case ComplicationData.TYPE_SHORT_TEXT:
+ case ComplicationData.TYPE_RANGED_VALUE:
+ if (since > 0) {
+ builder.setShortText(ComplicationText.plainText(DisplayFormat.shortTimeSince(since) + " old"));
+ } else {
+ builder.setShortText(ComplicationText.plainText("!err!"));
+ }
+ break;
+ case ComplicationData.TYPE_LONG_TEXT:
+ builder.setLongTitle(ComplicationText.plainText(aaps.gs(R.string.label_warning_sync)));
+ if (since > 0) {
+ builder.setLongText(ComplicationText.plainText(String.format(aaps.gs(R.string.label_warning_since), DisplayFormat.shortTimeSince(since))));
+ } else {
+ builder.setLongText(ComplicationText.plainText(aaps.gs(R.string.label_warning_sync_aaps)));
+ }
+ break;
+ case ComplicationData.TYPE_LARGE_IMAGE:
+ return buildComplicationData(dataType, raw, complicationPendingIntent);
+ default:
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "Unexpected complication type " + dataType);
+ }
+ break;
+ }
+
+ builder.setTapAction(exceptionalPendingIntent);
+ return builder.build();
+ }
+
+ public ComplicationData buildOutdatedComplicationData(int dataType,
+ RawDisplayData raw,
+ PendingIntent complicationPendingIntent,
+ PendingIntent exceptionalPendingIntent,
+ long since) {
+
+ final ComplicationData.Builder builder = new ComplicationData.Builder(dataType);
+ if (dataType != ComplicationData.TYPE_LARGE_IMAGE) {
+ builder.setIcon(Icon.createWithResource(this, R.drawable.ic_alert));
+ builder.setBurnInProtectionIcon(Icon.createWithResource(this, R.drawable.ic_alert_burnin));
+ }
+
+ if (dataType == ComplicationData.TYPE_RANGED_VALUE) {
+ builder.setMinValue(0);
+ builder.setMaxValue(100);
+ builder.setValue(0);
+ }
+
+ switch (dataType) {
+ case ComplicationData.TYPE_ICON:
+ case ComplicationData.TYPE_SHORT_TEXT:
+ case ComplicationData.TYPE_RANGED_VALUE:
+ if (since > 0) {
+ builder.setShortText(ComplicationText.plainText(DisplayFormat.shortTimeSince(since) + " old"));
+ } else {
+ builder.setShortText(ComplicationText.plainText("!old!"));
+ }
+ break;
+ case ComplicationData.TYPE_LONG_TEXT:
+ builder.setLongTitle(ComplicationText.plainText(aaps.gs(R.string.label_warning_old)));
+ if (since > 0) {
+ builder.setLongText(ComplicationText.plainText(String.format(aaps.gs(R.string.label_warning_since), DisplayFormat.shortTimeSince(since))));
+ } else {
+ builder.setLongText(ComplicationText.plainText(aaps.gs(R.string.label_warning_sync_aaps)));
+ }
+ break;
+ case ComplicationData.TYPE_LARGE_IMAGE:
+ return buildComplicationData(dataType, raw, complicationPendingIntent);
+ default:
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "Unexpected complication type " + dataType);
+ }
+ break;
+ }
+
+ builder.setTapAction(exceptionalPendingIntent);
+ return builder.build();
+ }
+
+ /**
+ * If Complication depend on "since" field and need to be updated every minute or not
+ * and need only update when new DisplayRawData arrive
+ */
+ protected boolean usesSinceField() {
+ return false;
+ }
+
+ //==============================================================================================
+ // COMPLICATION LIFECYCLE
+ //==============================================================================================
+
+ /*
+ * Called when a complication has been activated. The method is for any one-time
+ * (per complication) set-up.
+ *
+ * You can continue sending data for the active complicationId until onComplicationDeactivated()
+ * is called.
+ */
+ @Override
+ public void onComplicationActivated(
+ int complicationId, int dataType, ComplicationManager complicationManager) {
+ Log.d(TAG, "onComplicationActivated(): " + complicationId + " of kind: "+getProviderCanonicalName());
+
+ Persistence persistence = new Persistence();
+ persistence.putString("complication_"+complicationId, getProviderCanonicalName());
+ persistence.putBoolean("complication_"+complicationId+"_since", usesSinceField());
+ persistence.addToSet(KEY_COMPLICATIONS, "complication_"+complicationId);
+
+ IntentFilter messageFilter = new IntentFilter(Intent.ACTION_SEND);
+
+ messageReceiver = new BaseComplicationProviderService.MessageReceiver();
+ localBroadcastManager = LocalBroadcastManager.getInstance(this);
+ localBroadcastManager.registerReceiver(messageReceiver, messageFilter);
+
+ ListenerService.requestData(this);
+ checkIfUpdateNeeded();
+ }
+
+ /*
+ * Called when the complication needs updated data from your provider. There are four scenarios
+ * when this will happen:
+ *
+ * 1. An active watch face complication is changed to use this provider
+ * 2. A complication using this provider becomes active
+ * 3. The period of time you specified in the manifest has elapsed (UPDATE_PERIOD_SECONDS)
+ * 4. You triggered an update from your own class via the
+ * ProviderUpdateRequester.requestUpdate() method.
+ */
+ @Override
+ public void onComplicationUpdate(
+ int complicationId, int dataType, ComplicationManager complicationManager) {
+ Log.d(TAG, "onComplicationUpdate() id: " + complicationId + " of class: "+getProviderCanonicalName());
+
+ // Create Tap Action so that the user can checkIfUpdateNeeded an update by tapping the complication.
+ final ComponentName thisProvider = new ComponentName(this, getProviderCanonicalName());
+
+ // We pass the complication id, so we can only update the specific complication tapped.
+ final PendingIntent complicationPendingIntent =
+ ComplicationTapBroadcastReceiver.getTapActionIntent(
+ aaps.getAppContext(), thisProvider, complicationId, getComplicationAction());
+
+ final Persistence persistence = new Persistence();
+
+ final RawDisplayData raw = new RawDisplayData();
+ raw.updateForComplicationsFromPersistence(persistence);
+ Log.d(TAG, "Complication data: " + raw.toDebugString());
+
+ // store what is currently rendered in 'SGV since' field, to detect if it was changed and need update
+ persistence.putString(KEY_LAST_SHOWN_SINCE_VALUE, DisplayFormat.shortTimeSince(raw.datetime));
+
+ // by each render we clear stale flag to ensure it is re-rendered at next refresh detection round
+ persistence.putBoolean(KEY_STALE_REPORTED, false);
+
+ ComplicationData complicationData;
+
+ if (WearUtil.msSince(persistence.whenDataUpdated()) > Constants.STALE_MS) {
+ // no new data arrived - probably configuration or connection error
+ final PendingIntent infoToast = ComplicationTapBroadcastReceiver.getTapWarningSinceIntent(
+ aaps.getAppContext(), thisProvider, complicationId, ComplicationAction.WARNING_SYNC, persistence.whenDataUpdated());
+ complicationData = buildNoSyncComplicationData(dataType, raw, complicationPendingIntent, infoToast, persistence.whenDataUpdated());
+ } else if (WearUtil.msSince(raw.datetime) > Constants.STALE_MS) {
+ // data arriving from phone AAPS, but it is outdated (uploader/NS/xDrip/Sensor error)
+ final PendingIntent infoToast = ComplicationTapBroadcastReceiver.getTapWarningSinceIntent(
+ aaps.getAppContext(), thisProvider, complicationId, ComplicationAction.WARNING_OLD, raw.datetime);
+ complicationData = buildOutdatedComplicationData(dataType, raw, complicationPendingIntent, infoToast, raw.datetime);
+ } else {
+ // data is up-to-date, we can render standard complication
+ complicationData = buildComplicationData(dataType, raw, complicationPendingIntent);
+ }
+
+ if (complicationData != null) {
+ complicationManager.updateComplicationData(complicationId, complicationData);
+ } else {
+ // If no data is sent, we still need to inform the ComplicationManager, so the update
+ // job can finish and the wake lock isn't held any longer than necessary.
+ complicationManager.noUpdateRequired(complicationId);
+ }
+ }
+
+ /*
+ * Called when the complication has been deactivated.
+ */
+ @Override
+ public void onComplicationDeactivated(int complicationId) {
+ Log.d(TAG, "onComplicationDeactivated(): " + complicationId);
+
+ Persistence persistence = new Persistence();
+ persistence.removeFromSet(KEY_COMPLICATIONS, "complication_"+complicationId);
+
+ if (localBroadcastManager != null && messageReceiver != null) {
+ localBroadcastManager.unregisterReceiver(messageReceiver);
+ }
+ Inevitable.kill(TASK_ID_REFRESH_COMPLICATION);
+ }
+
+ //==============================================================================================
+ // UPDATE AND REFRESH LOGIC
+ //==============================================================================================
+
+ /*
+ * Schedule check for field update
+ */
+ public static void checkIfUpdateNeeded() {
+
+ Persistence p = new Persistence();
+
+ Log.d(TAG, "Pending check if update needed - "+p.getString(KEY_COMPLICATIONS, ""));
+
+ Inevitable.task(TASK_ID_REFRESH_COMPLICATION, 15 * Constants.SECOND_IN_MS, () -> {
+ if (WearUtil.isBelowRateLimit("complication-checkIfUpdateNeeded", 5)) {
+ Log.d(TAG, "Checking if update needed");
+ requestUpdateIfSinceChanged();
+ // We reschedule need for check - to make sure next check will Inevitable go in next 15s
+ checkIfUpdateNeeded();
+ }
+ });
+
+ }
+
+ /*
+ * Check if displayed since field (field that shows how old, in minutes, is reading)
+ * is up-to-date or need to be changed (a minute or more elapsed)
+ */
+ private static void requestUpdateIfSinceChanged() {
+ final Persistence persistence = new Persistence();
+
+ final RawDisplayData raw = new RawDisplayData();
+ raw.updateForComplicationsFromPersistence(persistence);
+
+ final String lastSince = persistence.getString(KEY_LAST_SHOWN_SINCE_VALUE, "-");
+ final String calcSince = DisplayFormat.shortTimeSince(raw.datetime);
+ final boolean isStale = (WearUtil.msSince(persistence.whenDataUpdated()) > Constants.STALE_MS)
+ ||(WearUtil.msSince(raw.datetime) > Constants.STALE_MS);
+
+ final boolean staleWasRefreshed = persistence.getBoolean(KEY_STALE_REPORTED, false);
+ final boolean sinceWasChanged = !lastSince.equals(calcSince);
+
+ if (sinceWasChanged|| (isStale && !staleWasRefreshed)) {
+ persistence.putString(KEY_LAST_SHOWN_SINCE_VALUE, calcSince);
+ persistence.putBoolean(KEY_STALE_REPORTED, isStale);
+
+ Log.d(TAG, "Detected refresh of time needed! Reason: "
+ + (isStale ? "- stale detected": "")
+ + (sinceWasChanged ? "- since changed from: "+lastSince+" to: "+calcSince : ""));
+
+ if (isStale) {
+ // all complications should update to show offline/old warning
+ requestUpdate(getActiveProviderClasses());
+ } else {
+ // ... but only some require update due to 'since' field change
+ requestUpdate(getSinceDependingProviderClasses());
+ }
+ }
+ }
+
+ /*
+ * Request update for specified list of providers
+ */
+ private static void requestUpdate(Set providers) {
+ for (String provider: providers) {
+ Log.d(TAG, "Pending update of "+provider);
+ // We wait with updating allowing all request, from various sources, to arrive
+ Inevitable.task("update-req-"+provider, 700, () -> {
+ if (WearUtil.isBelowRateLimit("update-req-"+provider, 2)) {
+ Log.d(TAG, "Requesting update of "+provider);
+ final ComponentName componentName = new ComponentName(aaps.getAppContext(), provider);
+ final ProviderUpdateRequester providerUpdateRequester = new ProviderUpdateRequester(aaps.getAppContext(), componentName);
+ providerUpdateRequester.requestUpdateAll();
+ }
+ });
+ }
+ }
+
+ /*
+ * List all Complication providing classes that have active (registered) providers
+ */
+ private static Set getActiveProviderClasses() {
+ Persistence persistence = new Persistence();
+ Set providers = new HashSet<>();
+ Set complications = persistence.getSetOf(KEY_COMPLICATIONS);
+ for (String complication: complications) {
+ final String providerClass = persistence.getString(complication, "");
+ if (providerClass.length() > 0) {
+ providers.add(providerClass);
+ }
+ }
+ return providers;
+ }
+
+ /*
+ * List all Complication providing classes that have active (registered) providers
+ * and additionally they depend on "since" field
+ * == they need to be updated not only on data broadcasts, but every minute or so
+ */
+ private static Set getSinceDependingProviderClasses() {
+ Persistence persistence = new Persistence();
+ Set providers = new HashSet<>();
+ Set complications = persistence.getSetOf(KEY_COMPLICATIONS);
+ for (String complication: complications) {
+ final String providerClass = persistence.getString(complication, "");
+ final boolean dependOnSince = persistence.getBoolean(complication+"_since", false);
+ if ((providerClass.length() > 0)&&(dependOnSince)) {
+ providers.add(providerClass);
+ }
+ }
+ return providers;
+ }
+
+ /*
+ * Listen to broadcast --> new data was stored by ListenerService to Persistence
+ */
+ public class MessageReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Set complications = Persistence.setOf(KEY_COMPLICATIONS);
+ if (complications.size() > 0) {
+ checkIfUpdateNeeded();
+ // We request all active providers
+ requestUpdate(getActiveProviderClasses());
+ }
+ }
+ }
+
+
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/BrCobIobComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/BrCobIobComplication.java
new file mode 100644
index 0000000000..ec862f4f47
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/complications/BrCobIobComplication.java
@@ -0,0 +1,49 @@
+package info.nightscout.androidaps.complications;
+
+import android.app.PendingIntent;
+import android.support.wearable.complications.ComplicationData;
+import android.support.wearable.complications.ComplicationText;
+import android.util.Log;
+
+import info.nightscout.androidaps.data.RawDisplayData;
+import info.nightscout.androidaps.interaction.utils.DisplayFormat;
+import info.nightscout.androidaps.interaction.utils.SmallestDoubleString;
+
+import static info.nightscout.androidaps.interaction.utils.DisplayFormat.MAX_FIELD_LEN_SHORT;
+import static info.nightscout.androidaps.interaction.utils.DisplayFormat.MIN_FIELD_LEN_COB;
+import static info.nightscout.androidaps.interaction.utils.DisplayFormat.MIN_FIELD_LEN_IOB;
+
+/*
+ * Created by dlvoy on 2019-11-12
+ */
+public class BrCobIobComplication extends BaseComplicationProviderService {
+
+ private static final String TAG = BrCobIobComplication.class.getSimpleName();
+
+ public ComplicationData buildComplicationData(int dataType, RawDisplayData raw, PendingIntent complicationPendingIntent) {
+
+ ComplicationData complicationData = null;
+
+ if (dataType == ComplicationData.TYPE_SHORT_TEXT) {
+ final String cob = new SmallestDoubleString(raw.sCOB2, SmallestDoubleString.Units.USE).minimise(MIN_FIELD_LEN_COB);
+ final String iob = new SmallestDoubleString(raw.sIOB1, SmallestDoubleString.Units.USE).minimise(Math.max(MIN_FIELD_LEN_IOB, (MAX_FIELD_LEN_SHORT -1) - cob.length()));
+
+ final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
+ .setShortText(ComplicationText.plainText(DisplayFormat.basalRateSymbol()+raw.sBasalRate))
+ .setShortTitle(ComplicationText.plainText(cob + " " + iob))
+ .setTapAction(complicationPendingIntent);
+
+ complicationData = builder.build();
+ } else {
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "Unexpected complication type " + dataType);
+ }
+ }
+ return complicationData;
+ }
+
+ @Override
+ public String getProviderCanonicalName() {
+ return BrCobIobComplication.class.getCanonicalName();
+ }
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/CobDetailedComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/CobDetailedComplication.java
new file mode 100644
index 0000000000..350ec9b0ec
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/complications/CobDetailedComplication.java
@@ -0,0 +1,52 @@
+package info.nightscout.androidaps.complications;
+
+import android.app.PendingIntent;
+import android.support.wearable.complications.ComplicationData;
+import android.support.wearable.complications.ComplicationText;
+import android.util.Log;
+
+import info.nightscout.androidaps.data.RawDisplayData;
+import info.nightscout.androidaps.interaction.utils.DisplayFormat;
+import info.nightscout.androidaps.interaction.utils.Pair;
+
+/*
+ * Created by dlvoy on 2019-11-12
+ */
+public class CobDetailedComplication extends BaseComplicationProviderService {
+
+ private static final String TAG = CobDetailedComplication.class.getSimpleName();
+
+ public ComplicationData buildComplicationData(int dataType, RawDisplayData raw, PendingIntent complicationPendingIntent) {
+
+ ComplicationData complicationData = null;
+
+ if (dataType == ComplicationData.TYPE_SHORT_TEXT) {
+
+ Pair cob = DisplayFormat.detailedCob(raw);
+ final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
+ .setShortText(ComplicationText.plainText(cob.first))
+ .setTapAction(complicationPendingIntent);
+
+ if (cob.second.length() > 0) {
+ builder.setShortTitle(ComplicationText.plainText(cob.second));
+ }
+
+ complicationData = builder.build();
+ } else {
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "Unexpected complication type " + dataType);
+ }
+ }
+ return complicationData;
+ }
+
+ @Override
+ public String getProviderCanonicalName() {
+ return CobDetailedComplication.class.getCanonicalName();
+ }
+
+ @Override
+ public ComplicationAction getComplicationAction() {
+ return ComplicationAction.WIZARD;
+ };
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/CobIconComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/CobIconComplication.java
new file mode 100644
index 0000000000..e42f46e3a4
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/complications/CobIconComplication.java
@@ -0,0 +1,51 @@
+package info.nightscout.androidaps.complications;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.support.wearable.complications.ComplicationData;
+import android.support.wearable.complications.ComplicationText;
+import android.util.Log;
+
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.data.RawDisplayData;
+
+/*
+ * Created by dlvoy on 2019-11-12
+ */
+public class CobIconComplication extends BaseComplicationProviderService {
+
+ private static final String TAG = CobIconComplication.class.getSimpleName();
+
+ public ComplicationData buildComplicationData(int dataType, RawDisplayData raw, PendingIntent complicationPendingIntent) {
+
+ ComplicationData complicationData = null;
+
+ if (dataType == ComplicationData.TYPE_SHORT_TEXT) {
+ final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
+ .setShortText(ComplicationText.plainText(raw.sCOB2))
+ .setIcon(
+ Icon.createWithResource(
+ this, R.drawable.ic_carbs))
+ .setBurnInProtectionIcon(
+ Icon.createWithResource(
+ this, R.drawable.ic_carbs))
+ .setTapAction(complicationPendingIntent);
+
+ complicationData = builder.build();
+ } else {
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "Unexpected complication type " + dataType);
+ }
+ }
+ return complicationData;
+ }
+
+ @Override
+ public String getProviderCanonicalName() {
+ return CobIconComplication.class.getCanonicalName();
+ }
+
+ public ComplicationAction getComplicationAction() {
+ return ComplicationAction.WIZARD;
+ };
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/CobIobComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/CobIobComplication.java
new file mode 100644
index 0000000000..31ea4dc5f4
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/complications/CobIobComplication.java
@@ -0,0 +1,46 @@
+package info.nightscout.androidaps.complications;
+
+import android.app.PendingIntent;
+import android.support.wearable.complications.ComplicationData;
+import android.support.wearable.complications.ComplicationText;
+import android.util.Log;
+
+import info.nightscout.androidaps.data.RawDisplayData;
+import info.nightscout.androidaps.interaction.utils.SmallestDoubleString;
+
+import static info.nightscout.androidaps.interaction.utils.DisplayFormat.MAX_FIELD_LEN_SHORT;
+
+/*
+ * Created by dlvoy on 2019-11-12
+ */
+public class CobIobComplication extends BaseComplicationProviderService {
+
+ private static final String TAG = CobIobComplication.class.getSimpleName();
+
+ public ComplicationData buildComplicationData(int dataType, RawDisplayData raw, PendingIntent complicationPendingIntent) {
+
+ ComplicationData complicationData = null;
+
+ if (dataType == ComplicationData.TYPE_SHORT_TEXT) {
+ final String cob = raw.sCOB2;
+ final String iob = new SmallestDoubleString(raw.sIOB1, SmallestDoubleString.Units.USE).minimise(MAX_FIELD_LEN_SHORT);
+
+ final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
+ .setShortText(ComplicationText.plainText(cob))
+ .setShortTitle(ComplicationText.plainText(iob))
+ .setTapAction(complicationPendingIntent);
+
+ complicationData = builder.build();
+ } else {
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "Unexpected complication type " + dataType);
+ }
+ }
+ return complicationData;
+ }
+
+ @Override
+ public String getProviderCanonicalName() {
+ return CobIobComplication.class.getCanonicalName();
+ }
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/ComplicationAction.java b/wear/src/main/java/info/nightscout/androidaps/complications/ComplicationAction.java
new file mode 100644
index 0000000000..9ca1e9c456
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/complications/ComplicationAction.java
@@ -0,0 +1,12 @@
+package info.nightscout.androidaps.complications;
+
+public enum ComplicationAction {
+ NONE,
+ MENU,
+ WIZARD,
+ BOLUS,
+ ECARB,
+ STATUS,
+ WARNING_SYNC,
+ WARNING_OLD
+}
\ No newline at end of file
diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/ComplicationTapBroadcastReceiver.java b/wear/src/main/java/info/nightscout/androidaps/complications/ComplicationTapBroadcastReceiver.java
new file mode 100644
index 0000000000..61dc8d4dde
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/complications/ComplicationTapBroadcastReceiver.java
@@ -0,0 +1,167 @@
+package info.nightscout.androidaps.complications;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.wearable.complications.ProviderUpdateRequester;
+import android.util.Log;
+import android.widget.Toast;
+
+import androidx.annotation.StringRes;
+
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.aaps;
+import info.nightscout.androidaps.interaction.actions.BolusActivity;
+import info.nightscout.androidaps.interaction.actions.ECarbActivity;
+import info.nightscout.androidaps.interaction.actions.WizardActivity;
+import info.nightscout.androidaps.interaction.menus.MainMenuActivity;
+import info.nightscout.androidaps.interaction.menus.StatusMenuActivity;
+import info.nightscout.androidaps.interaction.utils.Constants;
+import info.nightscout.androidaps.interaction.utils.DisplayFormat;
+import info.nightscout.androidaps.interaction.utils.WearUtil;
+
+/*
+ * Created by dlvoy on 2019-11-12
+ */
+public class ComplicationTapBroadcastReceiver extends BroadcastReceiver {
+
+ private static final String TAG = ComplicationTapBroadcastReceiver.class.getSimpleName();
+
+ private static final String EXTRA_PROVIDER_COMPONENT =
+ "info.nightscout.androidaps.complications.action.PROVIDER_COMPONENT";
+ private static final String EXTRA_COMPLICATION_ID =
+ "info.nightscout.androidaps.complications.action.COMPLICATION_ID";
+ private static final String EXTRA_COMPLICATION_ACTION =
+ "info.nightscout.androidaps.complications.action.COMPLICATION_ACTION";
+ private static final String EXTRA_COMPLICATION_SINCE =
+ "info.nightscout.androidaps.complications.action.COMPLICATION_SINCE";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Bundle extras = intent.getExtras();
+ ComponentName provider = extras.getParcelable(EXTRA_PROVIDER_COMPONENT);
+ int complicationId = extras.getInt(EXTRA_COMPLICATION_ID);
+ String complicationAction = extras.getString(EXTRA_COMPLICATION_ACTION, ComplicationAction.MENU.toString());
+
+ ComplicationAction action = ComplicationAction.MENU;
+ try {
+ action = ComplicationAction.valueOf(ComplicationAction.class, complicationAction);
+ } catch (IllegalArgumentException | NullPointerException ex) {
+ // but how?
+ Log.e(TAG, "Cannot interpret complication action: "+complicationAction);
+ }
+
+ action = remapActionWithUserPreferences(action);
+
+ // Request an update for the complication that has just been tapped.
+ ProviderUpdateRequester requester = new ProviderUpdateRequester(context, provider);
+ requester.requestUpdate(complicationId);
+
+ Intent intentOpen = null;
+
+ switch (action) {
+ case NONE:
+ // do nothing
+ return;
+ case WIZARD:
+ intentOpen = new Intent(aaps.getAppContext(), WizardActivity.class);
+ break;
+ case BOLUS:
+ intentOpen = new Intent(aaps.getAppContext(), BolusActivity.class);
+ break;
+ case ECARB:
+ intentOpen = new Intent(aaps.getAppContext(), ECarbActivity.class);
+ break;
+ case STATUS:
+ intentOpen = new Intent(aaps.getAppContext(), StatusMenuActivity.class);
+ break;
+ case WARNING_OLD:
+ case WARNING_SYNC:
+ long oneAndHalfMinuteAgo = WearUtil.timestamp() - (Constants.MINUTE_IN_MS+Constants.SECOND_IN_MS * 30);
+ long since = extras.getLong(EXTRA_COMPLICATION_SINCE, oneAndHalfMinuteAgo);
+ @StringRes int labelId = (action == ComplicationAction.WARNING_SYNC) ?
+ R.string.msg_warning_sync : R.string.msg_warning_old;
+ String msg = String.format(aaps.gs(labelId), DisplayFormat.shortTimeSince(since));
+ Toast.makeText(aaps.getAppContext(), msg, Toast.LENGTH_LONG).show();
+ break;
+ case MENU:
+ default:
+ intentOpen = new Intent(aaps.getAppContext(), MainMenuActivity.class);
+ }
+
+ if (intentOpen != null) {
+ // Perform intent - open dialog
+ intentOpen.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ aaps.getAppContext().startActivity(intentOpen);
+ }
+ }
+
+ private ComplicationAction remapActionWithUserPreferences(ComplicationAction originalAction) {
+ final String userPrefAction = aaps.getComplicationTapAction();
+ switch (originalAction) {
+ case WARNING_OLD:
+ case WARNING_SYNC:
+ // warnings cannot be reconfigured by user
+ return originalAction;
+ default:
+ switch (userPrefAction) {
+ case "menu":
+ return ComplicationAction.MENU;
+ case "wizard":
+ return ComplicationAction.WIZARD;
+ case "bolus":
+ return ComplicationAction.BOLUS;
+ case "ecarb":
+ return ComplicationAction.ECARB;
+ case "status":
+ return ComplicationAction.STATUS;
+ case "none":
+ return ComplicationAction.NONE;
+ case "default":
+ default:
+ return originalAction;
+ }
+ }
+ }
+
+ /**
+ * Returns a pending intent, suitable for use as a tap intent, that causes a complication to be
+ * toggled and updated.
+ */
+ static PendingIntent getTapActionIntent(
+ Context context, ComponentName provider, int complicationId, ComplicationAction action) {
+ Intent intent = new Intent(context, ComplicationTapBroadcastReceiver.class);
+ intent.putExtra(EXTRA_PROVIDER_COMPONENT, provider);
+ intent.putExtra(EXTRA_COMPLICATION_ID, complicationId);
+ intent.putExtra(EXTRA_COMPLICATION_ACTION, action.toString());
+
+
+ // Pass complicationId as the requestCode to ensure that different complications get
+ // different intents.
+ return PendingIntent.getBroadcast(
+ context, complicationId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ /**
+ * Returns a pending intent, suitable for use as a tap intent, that causes a complication to be
+ * toggled and updated.
+ */
+ static PendingIntent getTapWarningSinceIntent(
+ Context context, ComponentName provider, int complicationId, ComplicationAction action, long since) {
+ Intent intent = new Intent(context, ComplicationTapBroadcastReceiver.class);
+ intent.putExtra(EXTRA_PROVIDER_COMPONENT, provider);
+ intent.putExtra(EXTRA_COMPLICATION_ID, complicationId);
+ intent.putExtra(EXTRA_COMPLICATION_ACTION, action.toString());
+ intent.putExtra(EXTRA_COMPLICATION_SINCE, since);
+
+
+ // Pass complicationId as the requestCode to ensure that different complications get
+ // different intents.
+ return PendingIntent.getBroadcast(
+ context, complicationId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/IobDetailedComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/IobDetailedComplication.java
new file mode 100644
index 0000000000..db1d67a66c
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/complications/IobDetailedComplication.java
@@ -0,0 +1,52 @@
+package info.nightscout.androidaps.complications;
+
+import android.app.PendingIntent;
+import android.support.wearable.complications.ComplicationData;
+import android.support.wearable.complications.ComplicationText;
+import android.util.Log;
+
+import info.nightscout.androidaps.data.RawDisplayData;
+import info.nightscout.androidaps.interaction.utils.DisplayFormat;
+import info.nightscout.androidaps.interaction.utils.Pair;
+
+/*
+ * Created by dlvoy on 2019-11-12
+ */
+public class IobDetailedComplication extends BaseComplicationProviderService {
+
+ private static final String TAG = IobDetailedComplication.class.getSimpleName();
+
+ public ComplicationData buildComplicationData(int dataType, RawDisplayData raw, PendingIntent complicationPendingIntent) {
+
+ ComplicationData complicationData = null;
+
+ if (dataType == ComplicationData.TYPE_SHORT_TEXT) {
+
+ Pair iob = DisplayFormat.detailedIob(raw);
+ final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
+ .setShortText(ComplicationText.plainText(iob.first))
+ .setTapAction(complicationPendingIntent);
+
+ if (iob.second.length() > 0) {
+ builder.setShortTitle(ComplicationText.plainText(iob.second));
+ }
+
+ complicationData = builder.build();
+ } else {
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "Unexpected complication type " + dataType);
+ }
+ }
+ return complicationData;
+ }
+
+ @Override
+ public String getProviderCanonicalName() {
+ return IobDetailedComplication.class.getCanonicalName();
+ }
+
+ @Override
+ public ComplicationAction getComplicationAction() {
+ return ComplicationAction.BOLUS;
+ };
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/IobIconComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/IobIconComplication.java
new file mode 100644
index 0000000000..1dca0f2e4d
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/complications/IobIconComplication.java
@@ -0,0 +1,56 @@
+package info.nightscout.androidaps.complications;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.support.wearable.complications.ComplicationData;
+import android.support.wearable.complications.ComplicationText;
+import android.util.Log;
+
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.data.RawDisplayData;
+import info.nightscout.androidaps.interaction.utils.SmallestDoubleString;
+
+import static info.nightscout.androidaps.interaction.utils.DisplayFormat.MAX_FIELD_LEN_SHORT;
+
+/*
+ * Created by dlvoy on 2019-11-12
+ */
+public class IobIconComplication extends BaseComplicationProviderService {
+
+ private static final String TAG = IobIconComplication.class.getSimpleName();
+
+ public ComplicationData buildComplicationData(int dataType, RawDisplayData raw, PendingIntent complicationPendingIntent) {
+
+ ComplicationData complicationData = null;
+
+ if (dataType == ComplicationData.TYPE_SHORT_TEXT) {
+ final String iob = new SmallestDoubleString(raw.sIOB1, SmallestDoubleString.Units.USE).minimise(MAX_FIELD_LEN_SHORT);
+
+ final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
+ .setShortText(ComplicationText.plainText(iob))
+ .setIcon(Icon.createWithResource(
+ this, R.drawable.ic_ins))
+ .setBurnInProtectionIcon(
+ Icon.createWithResource(
+ this, R.drawable.ic_ins_burnin))
+ .setTapAction(complicationPendingIntent);
+
+ complicationData = builder.build();
+ } else {
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "Unexpected complication type " + dataType);
+ }
+ }
+ return complicationData;
+ }
+
+ @Override
+ public String getProviderCanonicalName() {
+ return IobIconComplication.class.getCanonicalName();
+ }
+
+ @Override
+ public ComplicationAction getComplicationAction() {
+ return ComplicationAction.BOLUS;
+ };
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/LongStatusComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/LongStatusComplication.java
new file mode 100644
index 0000000000..f18ad21662
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/complications/LongStatusComplication.java
@@ -0,0 +1,52 @@
+package info.nightscout.androidaps.complications;
+
+import android.app.PendingIntent;
+import android.support.wearable.complications.ComplicationData;
+import android.support.wearable.complications.ComplicationText;
+import android.util.Log;
+
+import info.nightscout.androidaps.data.RawDisplayData;
+import info.nightscout.androidaps.interaction.utils.DisplayFormat;
+
+/*
+ * Created by dlvoy on 2019-11-12
+ */
+public class LongStatusComplication extends BaseComplicationProviderService {
+
+ private static final String TAG = LongStatusComplication.class.getSimpleName();
+
+ public ComplicationData buildComplicationData(int dataType, RawDisplayData raw, PendingIntent complicationPendingIntent) {
+
+ ComplicationData complicationData = null;
+
+ switch (dataType) {
+ case ComplicationData.TYPE_LONG_TEXT:
+
+ final String glucoseLine = DisplayFormat.longGlucoseLine(raw);
+ final String detailsLine = DisplayFormat.longDetailsLine(raw);
+
+ final ComplicationData.Builder builderLong = new ComplicationData.Builder(ComplicationData.TYPE_LONG_TEXT)
+ .setLongTitle(ComplicationText.plainText(glucoseLine))
+ .setLongText(ComplicationText.plainText(detailsLine))
+ .setTapAction(complicationPendingIntent);
+ complicationData = builderLong.build();
+
+ break;
+ default:
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "Unexpected complication type " + dataType);
+ }
+ }
+ return complicationData;
+ }
+
+ @Override
+ public String getProviderCanonicalName() {
+ return LongStatusComplication.class.getCanonicalName();
+ }
+
+ @Override
+ protected boolean usesSinceField() {
+ return true;
+ }
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/LongStatusFlippedComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/LongStatusFlippedComplication.java
new file mode 100644
index 0000000000..7c0b730c76
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/complications/LongStatusFlippedComplication.java
@@ -0,0 +1,52 @@
+package info.nightscout.androidaps.complications;
+
+import android.app.PendingIntent;
+import android.support.wearable.complications.ComplicationData;
+import android.support.wearable.complications.ComplicationText;
+import android.util.Log;
+
+import info.nightscout.androidaps.data.RawDisplayData;
+import info.nightscout.androidaps.interaction.utils.DisplayFormat;
+
+/*
+ * Created by dlvoy on 2019-11-12
+ */
+public class LongStatusFlippedComplication extends BaseComplicationProviderService {
+
+ private static final String TAG = LongStatusFlippedComplication.class.getSimpleName();
+
+ public ComplicationData buildComplicationData(int dataType, RawDisplayData raw, PendingIntent complicationPendingIntent) {
+
+ ComplicationData complicationData = null;
+
+ switch (dataType) {
+ case ComplicationData.TYPE_LONG_TEXT:
+
+ final String glucoseLine = DisplayFormat.longGlucoseLine(raw);
+ final String detailsLine = DisplayFormat.longDetailsLine(raw);
+
+ final ComplicationData.Builder builderLong = new ComplicationData.Builder(ComplicationData.TYPE_LONG_TEXT)
+ .setLongTitle(ComplicationText.plainText(detailsLine))
+ .setLongText(ComplicationText.plainText(glucoseLine))
+ .setTapAction(complicationPendingIntent);
+ complicationData = builderLong.build();
+
+ break;
+ default:
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "Unexpected complication type " + dataType);
+ }
+ }
+ return complicationData;
+ }
+
+ @Override
+ public String getProviderCanonicalName() {
+ return LongStatusFlippedComplication.class.getCanonicalName();
+ }
+
+ @Override
+ protected boolean usesSinceField() {
+ return true;
+ }
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/SgvComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/SgvComplication.java
new file mode 100644
index 0000000000..0296f8bab6
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/complications/SgvComplication.java
@@ -0,0 +1,48 @@
+package info.nightscout.androidaps.complications;
+
+import android.app.PendingIntent;
+import android.support.wearable.complications.ComplicationData;
+import android.support.wearable.complications.ComplicationText;
+import android.util.Log;
+
+import info.nightscout.androidaps.data.RawDisplayData;
+import info.nightscout.androidaps.interaction.utils.DisplayFormat;
+
+/*
+ * Created by dlvoy on 2019-11-12
+ */
+public class SgvComplication extends BaseComplicationProviderService {
+
+ private static final String TAG = SgvComplication.class.getSimpleName();
+
+ public ComplicationData buildComplicationData(int dataType, RawDisplayData raw, PendingIntent complicationPendingIntent) {
+
+ ComplicationData complicationData = null;
+
+ switch (dataType) {
+ case ComplicationData.TYPE_SHORT_TEXT:
+ final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
+ .setShortText(ComplicationText.plainText(raw.sSgv + raw.sDirection))
+ .setShortTitle(ComplicationText.plainText(DisplayFormat.shortTrend(raw)))
+ .setTapAction(complicationPendingIntent);
+
+ complicationData = builder.build();
+ break;
+ default:
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "Unexpected complication type " + dataType);
+ }
+ }
+ return complicationData;
+ }
+
+ @Override
+ public String getProviderCanonicalName() {
+ return SgvComplication.class.getCanonicalName();
+ }
+
+ @Override
+ protected boolean usesSinceField() {
+ return true;
+ }
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/UploaderBattery.java b/wear/src/main/java/info/nightscout/androidaps/complications/UploaderBattery.java
new file mode 100644
index 0000000000..a5fcedacdd
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/complications/UploaderBattery.java
@@ -0,0 +1,114 @@
+package info.nightscout.androidaps.complications;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.support.wearable.complications.ComplicationData;
+import android.support.wearable.complications.ComplicationText;
+import android.util.Log;
+
+import androidx.annotation.DrawableRes;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.data.RawDisplayData;
+
+/*
+ * Created by dlvoy on 2019-11-12
+ */
+public class UploaderBattery extends BaseComplicationProviderService {
+
+ private static final String TAG = UploaderBattery.class.getSimpleName();
+
+ public ComplicationData buildComplicationData(int dataType, RawDisplayData raw, PendingIntent complicationPendingIntent) {
+
+ ComplicationData complicationData = null;
+
+ @DrawableRes int batteryIcon = R.drawable.ic_battery_unknown;
+ @DrawableRes int burnInBatteryIcon = R.drawable.ic_battery_unknown_burnin;
+ int level = 0;
+ String levelStr = "???";
+
+ if (raw.sUploaderBattery.matches("^[0-9]+$")) {
+ try {
+ level = Integer.parseInt(raw.sUploaderBattery);
+ level = Math.max(Math.min(level, 100), 0);
+ levelStr = level + "%";
+ int iconNo = (int)Math.floor(level / 10.0);
+ if (level > 95) {
+ iconNo = 10;
+ }
+ switch (iconNo) {
+ case 10: batteryIcon = R.drawable.ic_battery_charging_wireless; break;
+ case 9: batteryIcon = R.drawable.ic_battery_charging_wireless_90; break;
+ case 8: batteryIcon = R.drawable.ic_battery_charging_wireless_80; break;
+ case 7: batteryIcon = R.drawable.ic_battery_charging_wireless_70; break;
+ case 6: batteryIcon = R.drawable.ic_battery_charging_wireless_60; break;
+ case 5: batteryIcon = R.drawable.ic_battery_charging_wireless_50; break;
+ case 4: batteryIcon = R.drawable.ic_battery_charging_wireless_40; break;
+ case 3: batteryIcon = R.drawable.ic_battery_charging_wireless_30; break;
+ case 2: batteryIcon = R.drawable.ic_battery_charging_wireless_20; break;
+ case 1: batteryIcon = R.drawable.ic_battery_charging_wireless_10; break;
+ case 0: batteryIcon = R.drawable.ic_battery_alert_variant_outline; break;
+ default: batteryIcon = R.drawable.ic_battery_charging_wireless_outline;
+ }
+
+ switch (iconNo) {
+ case 10: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_burnin; break;
+ case 9: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_90_burnin; break;
+ case 8: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_80_burnin; break;
+ case 7: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_70_burnin; break;
+ case 6: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_60_burnin; break;
+ case 5: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_50_burnin; break;
+ case 4: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_40_burnin; break;
+ case 3: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_30_burnin; break;
+ case 2: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_20_burnin; break;
+ case 1: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_10_burnin; break;
+ case 0: burnInBatteryIcon = R.drawable.ic_battery_alert_variant_outline; break;
+ default: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_outline;
+ }
+
+
+ } catch (NumberFormatException ex){
+ Log.e(TAG, "Cannot parse battery level of: " + raw.sUploaderBattery);
+ }
+ }
+
+ if (dataType == ComplicationData.TYPE_RANGED_VALUE) {
+ final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_RANGED_VALUE)
+ .setMinValue(0)
+ .setMaxValue(100)
+ .setValue(level)
+ .setShortText(ComplicationText.plainText(levelStr))
+ .setIcon(Icon.createWithResource(this, batteryIcon))
+ .setBurnInProtectionIcon(Icon.createWithResource(this, burnInBatteryIcon))
+ .setTapAction(complicationPendingIntent);
+ complicationData = builder.build();
+ } else if (dataType == ComplicationData.TYPE_SHORT_TEXT) {
+ final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
+ .setShortText(ComplicationText.plainText(levelStr))
+ .setIcon(Icon.createWithResource(this, batteryIcon))
+ .setBurnInProtectionIcon(Icon.createWithResource(this, burnInBatteryIcon))
+ .setTapAction(complicationPendingIntent);
+ complicationData = builder.build();
+ } else if (dataType == ComplicationData.TYPE_ICON) {
+ final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_ICON)
+ .setIcon(Icon.createWithResource(this, batteryIcon))
+ .setBurnInProtectionIcon(Icon.createWithResource(this, burnInBatteryIcon))
+ .setTapAction(complicationPendingIntent);
+ complicationData = builder.build();
+ } else {
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "Unexpected complication type " + dataType);
+ }
+ }
+ return complicationData;
+ }
+
+ @Override
+ public String getProviderCanonicalName() {
+ return UploaderBattery.class.getCanonicalName();
+ }
+
+ @Override
+ public ComplicationAction getComplicationAction() {
+ return ComplicationAction.STATUS;
+ };
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperComplication.java
new file mode 100644
index 0000000000..293b1331ca
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperComplication.java
@@ -0,0 +1,61 @@
+package info.nightscout.androidaps.complications;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
+import android.support.wearable.complications.ComplicationData;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import info.nightscout.androidaps.aaps;
+import info.nightscout.androidaps.data.RawDisplayData;
+
+/*
+ * Created by dlvoy on 2019-11-12
+ */
+public abstract class WallpaperComplication extends BaseComplicationProviderService {
+
+ public abstract String getWallpaperAssetsFileName();
+
+ private static final String TAG = WallpaperComplication.class.getSimpleName();
+
+ public ComplicationData buildComplicationData(int dataType, RawDisplayData raw, PendingIntent complicationPendingIntent) {
+
+ ComplicationData complicationData = null;
+
+ if (dataType == ComplicationData.TYPE_LARGE_IMAGE) {
+
+ DisplayMetrics metrics = new DisplayMetrics();
+ WindowManager windowManager = (WindowManager) aaps.getAppContext()
+ .getSystemService(Context.WINDOW_SERVICE);
+ windowManager.getDefaultDisplay().getMetrics(metrics);
+ int width = metrics.widthPixels;
+ int height = metrics.heightPixels;
+
+ final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_LARGE_IMAGE);
+
+ AssetManager assetManager = getAssets();
+ try (InputStream istr = assetManager.open(getWallpaperAssetsFileName())) {
+ Bitmap bitmap = BitmapFactory.decodeStream(istr);
+ Bitmap scaled = Bitmap.createScaledBitmap(bitmap, width, height, true);
+ builder.setLargeImage(Icon.createWithBitmap(scaled));
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot read wallpaper asset: "+e.getMessage(), e);
+ }
+
+ complicationData = builder.build();
+ } else {
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "Unexpected complication type " + dataType);
+ }
+ }
+ return complicationData;
+ }
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperDarkComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperDarkComplication.java
new file mode 100644
index 0000000000..8c84e1d8c3
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperDarkComplication.java
@@ -0,0 +1,22 @@
+package info.nightscout.androidaps.complications;
+
+/*
+ * Created by dlvoy on 2019-11-12
+ */
+public class WallpaperDarkComplication extends WallpaperComplication {
+
+ @Override
+ public String getWallpaperAssetsFileName() {
+ return "watch_dark.jpg";
+ }
+
+ @Override
+ public String getProviderCanonicalName() {
+ return WallpaperDarkComplication.class.getCanonicalName();
+ }
+
+ @Override
+ public ComplicationAction getComplicationAction() {
+ return ComplicationAction.NONE;
+ };
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperGrayComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperGrayComplication.java
new file mode 100644
index 0000000000..bec047f323
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperGrayComplication.java
@@ -0,0 +1,22 @@
+package info.nightscout.androidaps.complications;
+
+/*
+ * Created by dlvoy on 2019-11-12
+ */
+public class WallpaperGrayComplication extends WallpaperComplication {
+
+ @Override
+ public String getWallpaperAssetsFileName() {
+ return "watch_gray.jpg";
+ }
+
+ @Override
+ public String getProviderCanonicalName() {
+ return WallpaperGrayComplication.class.getCanonicalName();
+ }
+
+ @Override
+ public ComplicationAction getComplicationAction() {
+ return ComplicationAction.NONE;
+ };
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperLightComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperLightComplication.java
new file mode 100644
index 0000000000..2d2bbf6f14
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperLightComplication.java
@@ -0,0 +1,22 @@
+package info.nightscout.androidaps.complications;
+
+/*
+ * Created by dlvoy on 2019-11-12
+ */
+public class WallpaperLightComplication extends WallpaperComplication {
+
+ @Override
+ public String getWallpaperAssetsFileName() {
+ return "watch_light.jpg";
+ }
+
+ @Override
+ public String getProviderCanonicalName() {
+ return WallpaperLightComplication.class.getCanonicalName();
+ }
+
+ @Override
+ public ComplicationAction getComplicationAction() {
+ return ComplicationAction.NONE;
+ };
+}
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 83c9ad58b3..9e018516ba 100644
--- a/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java
+++ b/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java
@@ -40,6 +40,7 @@ import info.nightscout.androidaps.interaction.AAPSPreferences;
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.Persistence;
import info.nightscout.androidaps.interaction.utils.SafeParse;
import info.nightscout.androidaps.interaction.utils.WearUtil;
@@ -512,12 +513,14 @@ public class ListenerService extends WearableListenerService implements GoogleAp
Intent messageIntent = new Intent();
messageIntent.setAction(Intent.ACTION_SEND);
messageIntent.putExtra("status", dataMap.toBundle());
+ Persistence.storeDataMap(RawDisplayData.STATUS_PERSISTENCE_KEY, dataMap);
LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent);
} else if (path.equals(BASAL_DATA_PATH)){
dataMap = DataMapItem.fromDataItem(event.getDataItem()).getDataMap();
Intent messageIntent = new Intent();
messageIntent.setAction(Intent.ACTION_SEND);
messageIntent.putExtra("basals", dataMap.toBundle());
+ Persistence.storeDataMap(RawDisplayData.BASALS_PERSISTENCE_KEY, dataMap);
LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent);
} else if (path.equals(NEW_PREFERENCES_PATH)){
dataMap = DataMapItem.fromDataItem(event.getDataItem()).getDataMap();
@@ -541,6 +544,7 @@ public class ListenerService extends WearableListenerService implements GoogleAp
Intent messageIntent = new Intent();
messageIntent.setAction(Intent.ACTION_SEND);
messageIntent.putExtra("data", dataMap.toBundle());
+ Persistence.storeDataMap(RawDisplayData.DATA_PERSISTENCE_KEY, dataMap);
LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent);
}
}
diff --git a/wear/src/main/java/info/nightscout/androidaps/data/RawDisplayData.java b/wear/src/main/java/info/nightscout/androidaps/data/RawDisplayData.java
new file mode 100644
index 0000000000..d4e3580561
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/data/RawDisplayData.java
@@ -0,0 +1,272 @@
+package info.nightscout.androidaps.data;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.PowerManager;
+
+import com.google.android.gms.wearable.DataMap;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import info.nightscout.androidaps.interaction.utils.Constants;
+import info.nightscout.androidaps.interaction.utils.Persistence;
+import info.nightscout.androidaps.interaction.utils.WearUtil;
+
+/**
+ * Holds bunch of data model variables and lists that arrive from phone app and are due to be
+ * displayed on watchface and complications. Keeping them together makes code cleaner and allows
+ * passing it to complications via persistence layer.
+ *
+ * Created by dlvoy on 2019-11-12
+ */
+public class RawDisplayData {
+
+ static final String DATA_PERSISTENCE_KEY = "raw_data";
+ static final String BASALS_PERSISTENCE_KEY = "raw_basals";
+ static final String STATUS_PERSISTENCE_KEY = "raw_status";
+
+ // data bundle
+ public long sgvLevel = 0;
+ public long datetime;
+ public String sSgv = "---";
+ public String sDirection = "--";
+ public String sDelta = "--";
+ public String sAvgDelta = "--";
+ public String sUnits = "-";
+
+ // status bundle
+ public String sBasalRate = "-.--U/h";
+ public String sUploaderBattery = "--";
+ public String sRigBattery = "--";
+ public boolean detailedIOB = false;
+ public String sIOB1 = "IOB";
+ public String sIOB2 = "-.--";
+ public String sCOB1 = "Carb";
+ public String sCOB2= "--g";
+ public String sBgi = "--";
+ public boolean showBGI = false;
+ public String externalStatusString = "no status";
+ public int batteryLevel = 1;
+ public long openApsStatus = -1;
+
+ // basals bundle
+ public ArrayList bgDataList = new ArrayList<>();
+ public ArrayList tempWatchDataList = new ArrayList<>();
+ public ArrayList basalWatchDataList = new ArrayList<>();
+ public ArrayList bolusWatchDataList = new ArrayList<>();
+ public ArrayList predictionList = new ArrayList<>();
+
+ public String toDebugString() {
+ return "DisplayRawData{" +
+ "sgvLevel=" + sgvLevel +
+ ", datetime=" + datetime +
+ ", sSgv='" + sSgv + '\'' +
+ ", sDirection='" + sDirection + '\'' +
+ ", sDelta='" + sDelta + '\'' +
+ ", sAvgDelta='" + sAvgDelta + '\'' +
+ ", sUnits='" + sUnits + '\'' +
+ ", sBasalRate='" + sBasalRate + '\'' +
+ ", sUploaderBattery='" + sUploaderBattery + '\'' +
+ ", sRigBattery='" + sRigBattery + '\'' +
+ ", detailedIOB=" + detailedIOB +
+ ", sIOB1='" + sIOB1 + '\'' +
+ ", sIOB2='" + sIOB2 + '\'' +
+ ", sCOB1='" + sCOB1 + '\'' +
+ ", sCOB2='" + sCOB2 + '\'' +
+ ", sBgi='" + sBgi + '\'' +
+ ", showBGI=" + showBGI +
+ ", externalStatusString='" + externalStatusString + '\'' +
+ ", batteryLevel=" + batteryLevel +
+ ", openApsStatus=" + openApsStatus +
+ ", bgDataList size=" + bgDataList.size() +
+ ", tempWatchDataList size=" + tempWatchDataList.size() +
+ ", basalWatchDataList size=" + basalWatchDataList.size() +
+ ", bolusWatchDataLis size=" + bolusWatchDataList.size() +
+ ", predictionList size=" + predictionList.size() +
+ '}';
+ }
+
+ public void updateFromPersistence(Persistence persistence) {
+
+ DataMap dataMapData = persistence.getDataMap(DATA_PERSISTENCE_KEY);
+ if (dataMapData != null) {
+ updateData(dataMapData);
+ }
+ DataMap dataMapStatus = persistence.getDataMap(STATUS_PERSISTENCE_KEY);
+ if (dataMapStatus != null) {
+ updateStatus(dataMapStatus);
+ }
+ DataMap dataMapBasals = persistence.getDataMap(BASALS_PERSISTENCE_KEY);
+ if (dataMapBasals != null) {
+ updateBasals(dataMapBasals);
+ }
+ }
+
+ /*
+ * Since complications do not need Basals, we skip them for performance
+ */
+ public void updateForComplicationsFromPersistence(Persistence persistence) {
+
+ DataMap dataMapData = persistence.getDataMap(DATA_PERSISTENCE_KEY);
+ if (dataMapData != null) {
+ updateData(dataMapData);
+ }
+ DataMap dataMapStatus = persistence.getDataMap(STATUS_PERSISTENCE_KEY);
+ if (dataMapStatus != null) {
+ updateStatus(dataMapStatus);
+ }
+ }
+
+ public DataMap updateDataFromMessage(Intent intent, PowerManager.WakeLock wakeLock) {
+ Bundle bundle = intent.getBundleExtra("data");
+ if (bundle != null) {
+ DataMap dataMap = WearUtil.bundleToDataMap(bundle);
+ updateData(dataMap);
+ return dataMap;
+ }
+ return null;
+ }
+
+ private void updateData(DataMap dataMap) {
+ WearUtil.getWakeLock("readingPrefs", 50);
+ sgvLevel = dataMap.getLong("sgvLevel");
+ datetime = dataMap.getLong("timestamp");
+ sSgv = dataMap.getString("sgvString");
+ sDirection = dataMap.getString("slopeArrow");
+ sDelta = dataMap.getString("delta");
+ sAvgDelta = dataMap.getString("avgDelta");
+ sUnits = dataMap.getString("glucoseUnits");
+ }
+
+ public DataMap updateStatusFromMessage(Intent intent, PowerManager.WakeLock wakeLock) {
+ Bundle bundle = intent.getBundleExtra("status");
+ if (bundle != null) {
+ DataMap dataMap = WearUtil.bundleToDataMap(bundle);
+ updateStatus(dataMap);
+ return dataMap;
+ }
+ return null;
+ }
+
+ private void updateStatus(DataMap dataMap) {
+ WearUtil.getWakeLock("readingPrefs", 50);
+ sBasalRate = dataMap.getString("currentBasal");
+ sUploaderBattery = dataMap.getString("battery");
+ sRigBattery = dataMap.getString("rigBattery");
+ detailedIOB = dataMap.getBoolean("detailedIob");
+ sIOB1 = dataMap.getString("iobSum") + "U";
+ sIOB2 = dataMap.getString("iobDetail");
+ sCOB1 = "Carb";
+ sCOB2 = dataMap.getString("cob");
+ sBgi = dataMap.getString("bgi");
+ showBGI = dataMap.getBoolean("showBgi");
+ externalStatusString = dataMap.getString("externalStatusString");
+ batteryLevel = dataMap.getInt("batteryLevel");
+ openApsStatus = dataMap.getLong("openApsStatus");
+ }
+
+ public DataMap updateBasalsFromMessage(Intent intent, PowerManager.WakeLock wakeLock) {
+ Bundle bundle = intent.getBundleExtra("basals");
+ if (bundle != null) {
+ DataMap dataMap = WearUtil.bundleToDataMap(bundle);
+ updateBasals(dataMap);
+ return dataMap;
+ }
+ return null;
+ }
+
+ private void updateBasals(DataMap dataMap) {
+ WearUtil.getWakeLock("readingPrefs", 500);
+ loadBasalsAndTemps(dataMap);
+ }
+
+ private void loadBasalsAndTemps(DataMap dataMap) {
+ ArrayList temps = dataMap.getDataMapArrayList("temps");
+ if (temps != null) {
+ tempWatchDataList = new ArrayList<>();
+ for (DataMap temp : temps) {
+ TempWatchData twd = new TempWatchData();
+ twd.startTime = temp.getLong("starttime");
+ twd.startBasal = temp.getDouble("startBasal");
+ twd.endTime = temp.getLong("endtime");
+ twd.endBasal = temp.getDouble("endbasal");
+ twd.amount = temp.getDouble("amount");
+ tempWatchDataList.add(twd);
+ }
+ }
+ ArrayList basals = dataMap.getDataMapArrayList("basals");
+ if (basals != null) {
+ basalWatchDataList = new ArrayList<>();
+ for (DataMap basal : basals) {
+ BasalWatchData bwd = new BasalWatchData();
+ bwd.startTime = basal.getLong("starttime");
+ bwd.endTime = basal.getLong("endtime");
+ bwd.amount = basal.getDouble("amount");
+ basalWatchDataList.add(bwd);
+ }
+ }
+ ArrayList boluses = dataMap.getDataMapArrayList("boluses");
+ if (boluses != null) {
+ bolusWatchDataList = new ArrayList<>();
+ for (DataMap bolus : boluses) {
+ BolusWatchData bwd = new BolusWatchData();
+ bwd.date = bolus.getLong("date");
+ bwd.bolus = bolus.getDouble("bolus");
+ bwd.carbs = bolus.getDouble("carbs");
+ bwd.isSMB = bolus.getBoolean("isSMB");
+ bwd.isValid = bolus.getBoolean("isValid");
+ bolusWatchDataList.add(bwd);
+ }
+ }
+ ArrayList predictions = dataMap.getDataMapArrayList("predictions");
+ if (boluses != null) {
+ predictionList = new ArrayList<>();
+ for (DataMap prediction : predictions) {
+ BgWatchData bwd = new BgWatchData();
+ bwd.timestamp = prediction.getLong("timestamp");
+ bwd.sgv = prediction.getDouble("sgv");
+ bwd.color = prediction.getInt("color");
+ predictionList.add(bwd);
+ }
+ }
+ }
+
+ public void addToWatchSet(DataMap dataMap) {
+ ArrayList entries = dataMap.getDataMapArrayList("entries");
+ if (entries != null) {
+ bgDataList = new ArrayList<>();
+ for (DataMap entry : entries) {
+ double sgv = entry.getDouble("sgvDouble");
+ double high = entry.getDouble("high");
+ double low = entry.getDouble("low");
+ long timestamp = entry.getLong("timestamp");
+ int color = entry.getInt("color", 0);
+ bgDataList.add(new BgWatchData(sgv, high, low, timestamp, color));
+ }
+ } else {
+ double sgv = dataMap.getDouble("sgvDouble");
+ double high = dataMap.getDouble("high");
+ double low = dataMap.getDouble("low");
+ long timestamp = dataMap.getLong("timestamp");
+ int color = dataMap.getInt("color", 0);
+
+ final int size = bgDataList.size();
+ if (size > 0) {
+ if (bgDataList.get(size - 1).timestamp == timestamp)
+ return; // Ignore duplicates.
+ }
+
+ bgDataList.add(new BgWatchData(sgv, high, low, timestamp, color));
+ }
+
+ // We use iterator instead for-loop because we iterate and remove on the go
+ Iterator itr = bgDataList.iterator();
+ while (itr.hasNext()) {
+ BgWatchData entry = (BgWatchData)itr.next();
+ if (entry.timestamp < (WearUtil.timestamp() - (Constants.HOUR_IN_MS * 5))) {
+ itr.remove(); //Get rid of anything more than 5 hours old
+ }
+ }
+ }
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Constants.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Constants.java
new file mode 100644
index 0000000000..2af3ef34b6
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Constants.java
@@ -0,0 +1,14 @@
+package info.nightscout.androidaps.interaction.utils;
+
+public class Constants {
+
+ public static final long SECOND_IN_MS = 1000;
+ public static final long MINUTE_IN_MS = 60000;
+ public static final long HOUR_IN_MS = 3600000;
+ public static final long DAY_IN_MS = 86400000;
+ public static final long WEEK_IN_MS = DAY_IN_MS * 7;
+ public static final long MONTH_IN_MS = DAY_IN_MS * 30;
+
+ public static final long STALE_MS = Constants.MINUTE_IN_MS * 12;
+
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/DisplayFormat.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/DisplayFormat.java
new file mode 100644
index 0000000000..f2cf06671e
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/DisplayFormat.java
@@ -0,0 +1,143 @@
+package info.nightscout.androidaps.interaction.utils;
+
+import info.nightscout.androidaps.aaps;
+import info.nightscout.androidaps.data.RawDisplayData;
+
+public class DisplayFormat {
+
+ /**
+ * Maximal and minimal lengths of fields/labels shown in complications, in characters
+ * For MAX values - above that WearOS and watch faces may start ellipsize (...) contents
+ * For MIN values - this is minimal length that can hold legible data
+ */
+ public static final int MAX_FIELD_LEN_LONG = 22; // this is found out empirical, for TYPE_LONG_TEXT
+ public static final int MAX_FIELD_LEN_SHORT = 7; // according to Wear OS docs for TYPE_SHORT_TEXT
+ public static final int MIN_FIELD_LEN_COB = 3; // since carbs are usually 0..99g
+ public static final int MIN_FIELD_LEN_IOB = 3; // IoB can range from like .1U to 99U
+
+ public static String deltaSymbol() {
+ return aaps.areComplicationsUnicode() ? "\u0394" : "";
+ }
+
+ public static String verticalSeparatorSymbol() {
+ return aaps.areComplicationsUnicode() ? "\u205E" : "|";
+ }
+
+ public static String basalRateSymbol() {
+ return aaps.areComplicationsUnicode() ? "\u238D\u2006" : "";
+ }
+
+ public static String shortTimeSince(final long refTime) {
+
+ long deltaTimeMs = WearUtil.msSince(refTime);
+
+ if (deltaTimeMs < Constants.MINUTE_IN_MS) {
+ return "0'";
+ } else if (deltaTimeMs < Constants.HOUR_IN_MS) {
+ int minutes = (int) (deltaTimeMs / Constants.MINUTE_IN_MS);
+ return minutes + "'";
+ } else if (deltaTimeMs < Constants.DAY_IN_MS) {
+ int hours = (int) (deltaTimeMs / Constants.HOUR_IN_MS);
+ return hours + "h";
+ } else {
+ int days = (int) (deltaTimeMs / Constants.DAY_IN_MS);
+ if (days < 7) {
+ return days + "d";
+ } else {
+ int weeks = days / 7;
+ return weeks + "w";
+ }
+ }
+ }
+
+ public static String shortTrend(final RawDisplayData raw) {
+ String minutes = "--";
+ if (raw.datetime > 0) {
+ minutes = shortTimeSince(raw.datetime);
+ }
+
+ if (minutes.length() + raw.sDelta.length() + deltaSymbol().length() + 1 <= MAX_FIELD_LEN_SHORT) {
+ return minutes + " " + deltaSymbol() + raw.sDelta;
+ }
+
+ // that only optimizes obvious things like 0 before . or at end, + at beginning
+ String delta = (new SmallestDoubleString(raw.sDelta)).minimise(MAX_FIELD_LEN_SHORT -1);
+ if (minutes.length() + delta.length() + deltaSymbol().length() + 1 <= MAX_FIELD_LEN_SHORT) {
+ return minutes + " " + deltaSymbol() + delta;
+ }
+
+ String shortDelta = (new SmallestDoubleString(raw.sDelta)).minimise(MAX_FIELD_LEN_SHORT -(1+minutes.length()));
+
+ return minutes + " " + shortDelta;
+ }
+
+ public static String longGlucoseLine(final RawDisplayData raw) {
+ return raw.sSgv + raw.sDirection + " " + deltaSymbol() + (new SmallestDoubleString(raw.sDelta)).minimise(8) + " (" + shortTimeSince(raw.datetime) + ")";
+ }
+
+ public static String longDetailsLine(final RawDisplayData raw) {
+
+ final String SEP_LONG = " " + verticalSeparatorSymbol() + " ";
+ final String SEP_SHORT = " " + verticalSeparatorSymbol() + " ";
+ final int SEP_SHORT_LEN = SEP_SHORT.length();
+ final String SEP_MIN = " ";
+
+ String line = raw.sCOB2 + SEP_LONG + raw.sIOB1 + SEP_LONG + basalRateSymbol()+raw.sBasalRate;
+ if (line.length() <= MAX_FIELD_LEN_LONG) {
+ return line;
+ }
+ line = raw.sCOB2 + SEP_SHORT + raw.sIOB1 + SEP_SHORT + raw.sBasalRate;
+ if (line.length() <= MAX_FIELD_LEN_LONG) {
+ return line;
+ }
+
+ int remainingMax = MAX_FIELD_LEN_LONG - (raw.sCOB2.length() + raw.sBasalRate.length() + SEP_SHORT_LEN*2);
+ final String smallestIoB = new SmallestDoubleString(raw.sIOB1, SmallestDoubleString.Units.USE).minimise(Math.max(MIN_FIELD_LEN_IOB, remainingMax));
+ line = raw.sCOB2 + SEP_SHORT + smallestIoB + SEP_SHORT + raw.sBasalRate;
+ if (line.length() <= MAX_FIELD_LEN_LONG) {
+ return line;
+ }
+
+ remainingMax = MAX_FIELD_LEN_LONG - (smallestIoB.length() + raw.sBasalRate.length() + SEP_SHORT_LEN*2);
+ final String simplifiedCob = new SmallestDoubleString(raw.sCOB2, SmallestDoubleString.Units.USE).minimise(Math.max(MIN_FIELD_LEN_COB, remainingMax));
+
+ line = simplifiedCob + SEP_SHORT + smallestIoB + SEP_SHORT + raw.sBasalRate;
+ if (line.length() <= MAX_FIELD_LEN_LONG) {
+ return line;
+ }
+
+ line = simplifiedCob + SEP_MIN + smallestIoB + SEP_MIN + raw.sBasalRate;
+
+ return line;
+ }
+
+ public static Pair detailedIob(RawDisplayData raw) {
+ final String iob1 = new SmallestDoubleString(raw.sIOB1, SmallestDoubleString.Units.USE).minimise(MAX_FIELD_LEN_SHORT);
+ String iob2 = "";
+ if (raw.sIOB2.contains("|")) {
+ String[] iobs = raw.sIOB2.replace("(", "").replace(")", "").split("\\|");
+
+ String iobBolus = new SmallestDoubleString(iobs[0]).minimise(MIN_FIELD_LEN_IOB);
+ if (iobBolus.trim().length() == 0) {
+ iobBolus = "--";
+ }
+ String iobBasal = new SmallestDoubleString(iobs[1]).minimise((MAX_FIELD_LEN_SHORT -1) - Math.max(MIN_FIELD_LEN_IOB, iobBolus.length()));
+ if (iobBasal.trim().length() == 0) {
+ iobBasal = "--";
+ }
+ iob2 = iobBolus+" "+iobBasal;
+ }
+ return Pair.create(iob1, iob2);
+ }
+
+ public static Pair detailedCob(final RawDisplayData raw) {
+ SmallestDoubleString cobMini = new SmallestDoubleString(raw.sCOB2, SmallestDoubleString.Units.USE);
+
+ String cob2 = "";
+ if (cobMini.getExtra().length() > 0) {
+ cob2 = cobMini.getExtra() + cobMini.getUnits();
+ }
+ final String cob1 = cobMini.minimise(MAX_FIELD_LEN_SHORT);
+ return Pair.create(cob1, cob2);
+ }
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Inevitable.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Inevitable.java
new file mode 100644
index 0000000000..54361b6678
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Inevitable.java
@@ -0,0 +1,118 @@
+package info.nightscout.androidaps.interaction.utils;
+
+import android.os.PowerManager;
+import android.util.Log;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import info.nightscout.androidaps.BuildConfig;
+
+/**
+ * Created for xDrip by jamorham on 07/03/2018
+ * Adapted for AAPS by dlvoy on 2019-11-11
+ *
+ * Tasks which are fired from events can be scheduled here and only execute when they become idle
+ * and are not being rescheduled within their wait window.
+ *
+ */
+
+public class Inevitable {
+
+ private static final String TAG = Inevitable.class.getSimpleName();
+ private static final int MAX_QUEUE_TIME = (int) Constants.MINUTE_IN_MS * 6;
+ private static final boolean debug = BuildConfig.DEBUG;
+
+ private static final ConcurrentHashMap tasks = new ConcurrentHashMap<>();
+
+ public static synchronized void task(final String id, long idle_for, Runnable runnable) {
+ if (idle_for > MAX_QUEUE_TIME) {
+ throw new RuntimeException(id + " Requested time: " + idle_for + " beyond max queue time");
+ }
+ final Task task = tasks.get(id);
+ if (task != null) {
+ // if it already exists then extend the time
+ task.extendTime(idle_for);
+
+ if (debug)
+ Log.d(TAG, "Extending time for: " + id + " to " + WearUtil.dateTimeText(task.when));
+ } else {
+ // otherwise create new task
+ if (runnable == null) return; // extension only if already exists
+ tasks.put(id, new Task(id, idle_for, runnable));
+
+ if (debug) {
+ Log.d(TAG, "Creating task: " + id + " due: " + WearUtil.dateTimeText(tasks.get(id).when));
+ }
+
+ // create a thread to wait and execute in background
+ final Thread t = new Thread(() -> {
+ final PowerManager.WakeLock wl = WearUtil.getWakeLock(id, MAX_QUEUE_TIME + 5000);
+ try {
+ boolean running = true;
+ // wait for task to be due or killed
+ while (running) {
+ WearUtil.threadSleep(500);
+ final Task thisTask = tasks.get(id);
+ running = thisTask != null && !thisTask.poll();
+ }
+ } finally {
+ WearUtil.releaseWakeLock(wl);
+ }
+ });
+ t.setPriority(Thread.MIN_PRIORITY);
+ t.start();
+ }
+ }
+
+ public static synchronized void stackableTask(String id, long idle_for, Runnable runnable) {
+ int stack = 0;
+ while (tasks.get(id = id + "-" + stack) != null) {
+ stack++;
+ }
+ if (stack > 0) {
+ Log.d(TAG, "Task stacked to: " + id);
+ }
+ task(id, idle_for, runnable);
+ }
+
+ public static void kill(final String id) {
+ tasks.remove(id);
+ }
+
+ public static boolean waiting(final String id) {
+ return tasks.containsKey(id);
+ }
+
+ private static class Task {
+ private long when;
+ private final Runnable what;
+ private final String id;
+
+ Task(String id, long offset, Runnable what) {
+ this.what = what;
+ this.id = id;
+ extendTime(offset);
+ }
+
+ public void extendTime(long offset) {
+ this.when = WearUtil.timestamp() + offset;
+ }
+
+ public boolean poll() {
+ final long till = WearUtil.msTill(when);
+ if (till < 1) {
+ if (debug) Log.d(TAG, "Executing task! " + this.id);
+ tasks.remove(this.id); // early remove to allow overlapping scheduling
+ what.run();
+ return true;
+ } else if (till > MAX_QUEUE_TIME) {
+ Log.wtf(TAG, "Task: " + this.id + " In queue too long: " + till);
+ tasks.remove(this.id);
+ return true;
+ }
+ return false;
+ }
+
+ }
+
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Pair.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Pair.java
new file mode 100644
index 0000000000..4207cbd743
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Pair.java
@@ -0,0 +1,43 @@
+package info.nightscout.androidaps.interaction.utils;
+
+import java.util.Objects;
+
+/**
+ * Same as android Pair, but clean room java class - does not require Android SDK for tests
+ */
+public class Pair {
+
+ public final F first;
+ public final S second;
+
+ public Pair(F first, S second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ public static Pair create(F f, S s) {
+ return new Pair<>(f, s);
+ }
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (o instanceof Pair) {
+ return ((Pair) o).first.equals(first) && ((Pair) o).second.equals(second);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "First: \""+first.toString()+"\" Second: \""+second.toString()+"\"";
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(first, second);
+ }
+
+}
+
+
diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Persistence.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Persistence.java
new file mode 100644
index 0000000000..36c0ae76ee
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Persistence.java
@@ -0,0 +1,96 @@
+package info.nightscout.androidaps.interaction.utils;
+
+import android.content.SharedPreferences;
+import android.util.Base64;
+
+import androidx.annotation.Nullable;
+
+import com.google.android.gms.wearable.DataMap;
+
+import java.util.Set;
+
+import info.nightscout.androidaps.aaps;
+
+/**
+ * Created by dlvoy on 2019-11-12
+ */
+public class Persistence {
+
+ final SharedPreferences preferences;
+ public static final String COMPLICATION_PROVIDER_PREFERENCES_FILE_KEY =
+ "info.nightscout.androidaps.complications.COMPLICATION_PROVIDER_PREFERENCES_FILE_KEY";
+
+ public Persistence() {
+ preferences = aaps.getAppContext().getSharedPreferences(COMPLICATION_PROVIDER_PREFERENCES_FILE_KEY, 0);
+ }
+
+ @Nullable
+ public DataMap getDataMap(String key) {
+ if (preferences.contains(key)) {
+ final String rawB64Data = preferences.getString(key, null);
+ byte[] rawData = Base64.decode(rawB64Data, Base64.DEFAULT);
+ try {
+ return DataMap.fromByteArray(rawData);
+ } catch (IllegalArgumentException ex) {
+ // Should never happen, and if it happen - we ignore and fallback to null
+ }
+ }
+ return null;
+ }
+
+ public void putDataMap(String key, DataMap dataMap) {
+ preferences.edit().putString(key, Base64.encodeToString(dataMap.toByteArray(), Base64.DEFAULT)).apply();
+ }
+
+ public String getString(String key, String defaultValue) {
+ return preferences.getString(key, defaultValue);
+ }
+
+ public void putString(String key, String value) {
+ preferences.edit().putString(key, value).apply();
+ }
+
+ public boolean getBoolean(String key, boolean defaultValue) {
+ return preferences.getBoolean(key, defaultValue);
+ }
+
+ public void putBoolean(String key, boolean value) {
+ preferences.edit().putBoolean(key, value).apply();
+ }
+
+ public long whenDataUpdated() {
+ return preferences.getLong("data_updated_at", 0);
+ }
+
+ private void markDataUpdated() {
+ preferences.edit().putLong("data_updated_at", WearUtil.timestamp()).apply();
+ }
+
+ public Set getSetOf(String key) {
+ return WearUtil.explodeSet(getString(key, ""), "|");
+ }
+
+ public void addToSet(String key, String value) {
+ final Set set = WearUtil.explodeSet(getString(key, ""), "|");
+ set.add(value);
+ putString(key, WearUtil.joinSet(set, "|"));
+ }
+
+ public void removeFromSet(String key, String value) {
+ final Set set = WearUtil.explodeSet(getString(key, ""), "|");
+ set.remove(value);
+ putString(key, WearUtil.joinSet(set, "|"));
+ }
+
+ public static void storeDataMap(String key, DataMap dataMap) {
+ Persistence p = new Persistence();
+ p.putDataMap(key, dataMap);
+ p.markDataUpdated();
+ }
+
+ public static Set setOf(String key) {
+ Persistence p = new Persistence();
+ return p.getSetOf(key);
+ }
+
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/SmallestDoubleString.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/SmallestDoubleString.java
new file mode 100644
index 0000000000..8361478976
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/SmallestDoubleString.java
@@ -0,0 +1,135 @@
+package info.nightscout.androidaps.interaction.utils;
+
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper to minimise various floating point values, with or without unit, to fit into specified
+ * and limited size, scarifying precision (rounding up) and extra characters like leading zero,
+ * following zero(s) in fractional part, extra plus sign etc.
+ *
+ * Created by dlvoy on 2019-11-12
+ */
+public class SmallestDoubleString {
+
+ private String sign = "";
+ private String decimal = "";
+ private String separator = "";
+ private String fractional = "";
+ private String extra = "";
+ private String units = "";
+
+ private final Units withUnits;
+
+ public enum Units {
+ SKIP,
+ USE
+ }
+
+ private static Pattern pattern = Pattern.compile("^([+-]?)([0-9]*)([,.]?)([0-9]*)(\\([^)]*\\))?(.*?)$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE );
+
+ public SmallestDoubleString(String inputString) {
+ this(inputString, Units.SKIP);
+ }
+
+ public SmallestDoubleString(String inputString, Units withUnits) {
+ Matcher matcher = pattern.matcher(inputString);
+ matcher.matches();
+
+ sign = matcher.group(1);
+ decimal = matcher.group(2);
+ separator = matcher.group(3);
+ fractional = matcher.group(4);
+ units = matcher.group(6);
+
+ if (fractional == null || fractional.length() == 0) {
+ separator = "";
+ fractional = "";
+ }
+ if (decimal == null || decimal.length() == 0) {
+ decimal = "";
+ }
+ if (separator == null || separator.length() == 0) {
+ separator = "";
+ }
+ if (sign == null || sign.length() == 0) {
+ sign = "";
+ }
+
+ final String extraCandidate = matcher.group(5);
+ if (extraCandidate != null && extraCandidate.length() > 2) {
+ extra = extraCandidate.substring(1, extraCandidate.length()-1);
+ }
+
+ if (units != null) {
+ units = units.trim();
+ }
+
+ this.withUnits = withUnits;
+ }
+
+ public String minimise(int maxSize) {
+ final String originalSeparator = separator;
+
+ if (Integer.parseInt("0"+fractional) == 0) {
+ separator = "";
+ fractional = "";
+ }
+ if (Integer.parseInt("0"+decimal) == 0 && (fractional.length() >0)) {
+ decimal = "";
+ }
+ if (currentLen() <= maxSize)
+ return toString();
+
+ if (sign.equals("+")) {
+ sign = "";
+ }
+ if (currentLen() <= maxSize) {
+ return toString();
+ }
+
+ while ((fractional.length() > 1)&&(fractional.charAt(fractional.length()-1) == '0')) {
+ fractional = fractional.substring(0, fractional.length()-1);
+ }
+ if (currentLen() <= maxSize) {
+ return toString();
+ }
+
+ if (fractional.length() > 0) {
+ int remainingForFraction = maxSize-currentLen()+fractional.length();
+ String formatCandidate = "#";
+ if (remainingForFraction>=1) {
+ formatCandidate = "#."+("#######".substring(0, remainingForFraction));
+ }
+ DecimalFormat df = new DecimalFormat(formatCandidate);
+ df.setRoundingMode(RoundingMode.HALF_UP);
+
+ final String decimalSup = (decimal.length() > 0) ? decimal : "0";
+ String result = sign + df.format(Double.parseDouble(decimalSup+"."+fractional)).replace(",", originalSeparator).replace(".", originalSeparator) +
+ ((withUnits == Units.USE) ? units : "");
+ return (decimal.length() > 0) ? result : result.substring(1);
+ }
+ return toString();
+ }
+
+ private int currentLen() {
+ return sign.length() + decimal.length() + separator.length() + fractional.length() +
+ ((withUnits == Units.USE) ? units.length() : 0);
+ }
+
+ @Override
+ public String toString() {
+ return sign+decimal+separator+fractional +
+ ((withUnits == Units.USE) ? units : "");
+ }
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public String getUnits() { return units; }
+
+
+}
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
index ca63749888..f8415bdd4a 100644
--- a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/WearUtil.java
+++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/WearUtil.java
@@ -1,19 +1,134 @@
package info.nightscout.androidaps.interaction.utils;
-import java.time.LocalDateTime;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.google.android.gms.wearable.DataMap;
+
import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import info.nightscout.androidaps.aaps;
/**
* Created by andy on 3/5/19.
+ * Adapted by dlvoy on 2019-11-06 using code from jamorham JoH class
*/
public class WearUtil {
+ private final static boolean debug_wakelocks = false;
+ private static final Map rateLimits = new HashMap();
+ private static final String TAG = WearUtil.class.getName();
+
+ //==============================================================================================
+ // Time related util methods
+ //==============================================================================================
public static String dateTimeText(long timeInMs) {
Date d = new Date(timeInMs);
return "" + d.getDay() + "." + d.getMonth() + "." + d.getYear() + " " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();
}
+ public static long timestamp() {
+ return System.currentTimeMillis();
+ }
+ public static long msSince(long when) {
+ return (timestamp() - when);
+ }
+
+ public static long msTill(long when) {
+ return (when - timestamp());
+ }
+
+ //==============================================================================================
+ // Thread and power management utils
+ //==============================================================================================
+
+ // return true if below rate limit
+ public static synchronized boolean isBelowRateLimit(String named, int onceForSeconds) {
+ // check if over limit
+ if ((rateLimits.containsKey(named)) && (timestamp() - rateLimits.get(named) < (onceForSeconds * 1000))) {
+ Log.d(TAG, named + " rate limited to one for " + onceForSeconds + " seconds");
+ return false;
+ }
+ // not over limit
+ rateLimits.put(named, timestamp());
+ return true;
+ }
+
+ public static PowerManager.WakeLock getWakeLock(final String name, int millis) {
+ final PowerManager pm = (PowerManager) aaps.getAppContext().getSystemService(Context.POWER_SERVICE);
+ PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AAPS::"+name);
+ wl.acquire(millis);
+ if (debug_wakelocks) Log.d(TAG, "getWakeLock: " + name + " " + wl.toString());
+ return wl;
+ }
+
+ public static void releaseWakeLock(PowerManager.WakeLock wl) {
+ if (debug_wakelocks) Log.d(TAG, "releaseWakeLock: " + wl.toString());
+ if (wl == null) return;
+ if (wl.isHeld()) wl.release();
+ }
+
+ public static void startActivity(Class c) {
+ aaps.getAppContext().startActivity(getStartActivityIntent(c));
+ }
+
+ public static Intent getStartActivityIntent(Class c) {
+ return new Intent(aaps.getAppContext(), c).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+
+ public static void threadSleep(long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ // we simply ignore if sleep was interrupted
+ }
+ }
+
+ public static String joinSet(Set set, String separator) {
+ StringBuilder sb = new StringBuilder();
+ int i = 0;
+ for (String item : set) {
+ final String itemToAdd = item.trim();
+ if (itemToAdd.length() > 0) {
+ if (i > 0) {
+ sb.append(separator);
+ }
+ i++;
+ sb.append(itemToAdd);
+ }
+ }
+ return sb.toString();
+ }
+
+ public static Set explodeSet(String joined, String separator) {
+ // special RegEx literal \\Q starts sequence we escape, \\E ends is
+ // we use it to escape separator for use in RegEx
+ String[] items = joined.split("\\Q"+separator+"\\E");
+ Set set = new HashSet<>();
+ for (String item : items) {
+ final String itemToAdd = item.trim();
+ if (itemToAdd.length() > 0) {
+ set.add(itemToAdd);
+ }
+ }
+ return set;
+ }
+
+ /**
+ * Taken out to helper method to allow testing
+ */
+ public static DataMap bundleToDataMap(Bundle bundle) {
+ return DataMap.fromBundle(bundle);
+ }
}
diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/BaseWatchFace.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/BaseWatchFace.java
index 229eb64f1b..1d1f5758e8 100644
--- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/BaseWatchFace.java
+++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/BaseWatchFace.java
@@ -10,7 +10,6 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
-import android.os.Bundle;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
@@ -33,20 +32,18 @@ import com.ustwo.clockwise.common.WatchFaceTime;
import com.ustwo.clockwise.common.WatchShape;
import java.text.SimpleDateFormat;
-import java.util.ArrayList;
import java.util.Date;
-import info.nightscout.androidaps.data.BasalWatchData;
-import info.nightscout.androidaps.data.BgWatchData;
-import info.nightscout.androidaps.data.BolusWatchData;
+import info.nightscout.androidaps.complications.BaseComplicationProviderService;
+import info.nightscout.androidaps.data.RawDisplayData;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.R;
-import info.nightscout.androidaps.data.TempWatchData;
import lecho.lib.hellocharts.view.LineChartView;
/**
* Created by emmablack on 12/29/14.
* Updated by andrew-warrington on 02-Jan-2018.
+ * Refactored by dlvoy on 2019-11-2019
*/
public abstract class BaseWatchFace extends WatchFace implements SharedPreferences.OnSharedPreferenceChangeListener {
@@ -54,13 +51,10 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
public static final long[] vibratePattern = {0,400,300,400,300,400};
public TextView mTime, mSgv, mDirection, mTimestamp, mUploaderBattery, mRigBattery, mDelta, mAvgDelta, mStatus, mBasalRate, mIOB1, mIOB2, mCOB1, mCOB2, mBgi, mLoop, mDay, mMonth, isAAPSv2, mHighLight, mLowLight;
public ImageView mGlucoseDial, mDeltaGauge, mHourHand, mMinuteHand;
- public long datetime;
public RelativeLayout mRelativeLayout;
public LinearLayout mLinearLayout, mLinearLayout2, mDate, mChartTap, mMainMenuTap;
- public long sgvLevel = 0;
public int ageLevel = 1;
public int loopLevel = 1;
- public int batteryLevel = 1;
public int highColor = Color.YELLOW;
public int lowColor = Color.RED;
public int midColor = Color.WHITE;
@@ -74,11 +68,9 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
public int pointSize = 2;
public BgGraphBuilder bgGraphBuilder;
public LineChartView chart;
- public ArrayList bgDataList = new ArrayList<>();
- public ArrayList tempWatchDataList = new ArrayList<>();
- public ArrayList basalWatchDataList = new ArrayList<>();
- public ArrayList bolusWatchDataList = new ArrayList<>();
- public ArrayList predictionList = new ArrayList<>();
+
+
+ public RawDisplayData rawData = new RawDisplayData();
public PowerManager.WakeLock wakeLock;
// related endTime manual layout
@@ -90,26 +82,9 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
protected SharedPreferences sharedPrefs;
- public boolean detailedIOB = false;
- public boolean showBGI = false;
public boolean forceSquareCanvas = false; //set to true by the Steampunk watch face.
- public long openApsStatus;
- public String externalStatusString = "no status";
- public String sSgv = "---";
- public String sDirection = "--";
- public String sUploaderBattery = "--";
- public String sRigBattery = "--";
- public String sDelta = "--";
- public String sAvgDelta = "--";
- public String sBasalRate = "-.--U/h";
- public String sIOB1 = "IOB";
- public String sIOB2 = "-.--";
- public String sCOB1 = "Carb";
- public String sCOB2 = "--g";
- public String sBgi = "--";
public String sMinute = "0";
public String sHour = "0";
- public String sUnits = "-";
@Override
public void onCreate() {
@@ -126,6 +101,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
}
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
sharedPrefs.registerOnSharedPreferenceChangeListener(this);
+
+ BaseComplicationProviderService.turnOff();
}
@Override
@@ -197,11 +174,11 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
}
public double timeSince() {
- return System.currentTimeMillis() - datetime;
+ return System.currentTimeMillis() - rawData.datetime;
}
public String readingAge(boolean shortString) {
- if (datetime == 0) { return shortString?"--'":"-- Minute ago"; }
+ if (rawData.datetime == 0) { return shortString?"--'":"-- Minute ago"; }
int minutesAgo = (int) Math.floor(timeSince()/(1000*60));
if (minutesAgo == 1) {
return minutesAgo + (shortString?"'":" Minute ago");
@@ -266,50 +243,20 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
@Override
public void onReceive(Context context, Intent intent) {
- Bundle bundle = intent.getBundleExtra("data");
- if (layoutSet && bundle != null) {
- DataMap dataMap = DataMap.fromBundle(bundle);
- wakeLock.acquire(50);
- sgvLevel = dataMap.getLong("sgvLevel");
- datetime = dataMap.getLong("timestamp");
- sSgv = dataMap.getString("sgvString");
- sDirection = dataMap.getString("slopeArrow");
- sDelta = dataMap.getString("delta");
- sAvgDelta = dataMap.getString("avgDelta");
- sUnits = dataMap.getString("glucoseUnits");
- if (chart != null) {
- addToWatchSet(dataMap);
+ if (layoutSet) {
+ final DataMap dataMap = rawData.updateDataFromMessage(intent, wakeLock);
+ if (chart != null && dataMap != null) {
+ rawData.addToWatchSet(dataMap);
setupCharts();
}
- }
-
- bundle = intent.getBundleExtra("status");
- if (layoutSet && bundle != null) {
- DataMap dataMap = DataMap.fromBundle(bundle);
- wakeLock.acquire(50);
- sBasalRate = dataMap.getString("currentBasal");
- sUploaderBattery = dataMap.getString("battery");
- sRigBattery = dataMap.getString("rigBattery");
- detailedIOB = dataMap.getBoolean("detailedIob");
- sIOB1 = dataMap.getString("iobSum") + "U";
- sIOB2 = dataMap.getString("iobDetail");
- sCOB1 = "Carb";
- sCOB2 = dataMap.getString("cob");
- sBgi = dataMap.getString("bgi");
- showBGI = dataMap.getBoolean("showBgi");
- externalStatusString = dataMap.getString("externalStatusString");
- batteryLevel = dataMap.getInt("batteryLevel");
- openApsStatus = dataMap.getLong("openApsStatus");
+ rawData.updateStatusFromMessage(intent, wakeLock);
}
setDataFields();
setColor();
- bundle = intent.getBundleExtra("basals");
- if (layoutSet && bundle != null) {
- DataMap dataMap = DataMap.fromBundle(bundle);
- wakeLock.acquire(500);
- loadBasalsAndTemps(dataMap);
+ if (layoutSet) {
+ rawData.updateBasalsFromMessage(intent, wakeLock);
}
mRelativeLayout.measure(specW, specH);
@@ -328,7 +275,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
if (mSgv != null) {
if (sharedPrefs.getBoolean("showBG", true)) {
- mSgv.setText(sSgv);
+ mSgv.setText(rawData.sSgv);
mSgv.setVisibility(View.VISIBLE);
} else {
//leave the textview there but invisible, as a height holder for the empty space above the white line
@@ -341,7 +288,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
if (mDirection != null) {
if (sharedPrefs.getBoolean("show_direction", true)) {
- mDirection.setText(sDirection);
+ mDirection.setText(rawData.sDirection);
mDirection.setVisibility(View.VISIBLE);
} else {
mDirection.setVisibility(View.GONE);
@@ -350,7 +297,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
if (mDelta != null) {
if (sharedPrefs.getBoolean("showDelta", true)) {
- mDelta.setText(sDelta);
+ mDelta.setText(rawData.sDelta);
mDelta.setVisibility(View.VISIBLE);
} else {
mDelta.setVisibility(View.GONE);
@@ -359,7 +306,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
if (mAvgDelta != null) {
if (sharedPrefs.getBoolean("showAvgDelta", true)) {
- mAvgDelta.setText(sAvgDelta);
+ mAvgDelta.setText(rawData.sAvgDelta);
mAvgDelta.setVisibility(View.VISIBLE);
} else {
mAvgDelta.setVisibility(View.GONE);
@@ -367,7 +314,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
}
if (mCOB1 != null && mCOB2 != null) {
- mCOB2.setText(sCOB2);
+ mCOB2.setText(rawData.sCOB2);
if (sharedPrefs.getBoolean("show_cob", true)) {
mCOB1.setVisibility(View.VISIBLE);
mCOB2.setVisibility(View.VISIBLE);
@@ -377,7 +324,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
}
//deal with cases where there is only the value shown for COB, and not the label
} else if (mCOB2 != null) {
- mCOB2.setText(sCOB2);
+ mCOB2.setText(rawData.sCOB2);
if (sharedPrefs.getBoolean("show_cob", true)) {
mCOB2.setVisibility(View.VISIBLE);
} else {
@@ -389,12 +336,12 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
if (sharedPrefs.getBoolean("show_iob", true)) {
mIOB1.setVisibility(View.VISIBLE);
mIOB2.setVisibility(View.VISIBLE);
- if (detailedIOB) {
- mIOB1.setText(sIOB1);
- mIOB2.setText(sIOB2);
+ if (rawData.detailedIOB) {
+ mIOB1.setText(rawData.sIOB1);
+ mIOB2.setText(rawData.sIOB2);
} else {
mIOB1.setText("IOB");
- mIOB2.setText(sIOB1);
+ mIOB2.setText(rawData.sIOB1);
}
} else {
mIOB1.setVisibility(View.GONE);
@@ -404,10 +351,10 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
} else if (mIOB2 != null) {
if (sharedPrefs.getBoolean("show_iob", true)) {
mIOB2.setVisibility(View.VISIBLE);
- if (detailedIOB) {
- mIOB2.setText(sIOB2);
+ if (rawData.detailedIOB) {
+ mIOB2.setText(rawData.sIOB2);
} else {
- mIOB2.setText(sIOB1);
+ mIOB2.setText(rawData.sIOB1);
}
} else {
mIOB2.setText("");
@@ -434,13 +381,13 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
if (mUploaderBattery != null) {
if (sharedPrefs.getBoolean("show_uploader_battery", true)) {
if (isAAPSv2 != null) {
- mUploaderBattery.setText(sUploaderBattery + "%");
+ mUploaderBattery.setText(rawData.sUploaderBattery + "%");
mUploaderBattery.setVisibility(View.VISIBLE);
} else {
if (sharedPrefs.getBoolean("showExternalStatus", true)) {
- mUploaderBattery.setText("U: " + sUploaderBattery + "%");
+ mUploaderBattery.setText("U: " + rawData.sUploaderBattery + "%");
} else {
- mUploaderBattery.setText("Uploader: " + sUploaderBattery + "%");
+ mUploaderBattery.setText("Uploader: " + rawData.sUploaderBattery + "%");
}
}
} else {
@@ -450,7 +397,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
if (mRigBattery != null) {
if (sharedPrefs.getBoolean("show_rig_battery", false)) {
- mRigBattery.setText(sRigBattery);
+ mRigBattery.setText(rawData.sRigBattery);
mRigBattery.setVisibility(View.VISIBLE);
} else {
mRigBattery.setVisibility(View.GONE);
@@ -459,7 +406,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
if (mBasalRate != null) {
if (sharedPrefs.getBoolean("show_temp_basal", true)) {
- mBasalRate.setText(sBasalRate);
+ mBasalRate.setText(rawData.sBasalRate);
mBasalRate.setVisibility(View.VISIBLE);
} else {
mBasalRate.setVisibility(View.GONE);
@@ -467,8 +414,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
}
if (mBgi != null) {
- if (showBGI) {
- mBgi.setText(sBgi);
+ if (rawData.showBGI) {
+ mBgi.setText(rawData.sBgi);
mBgi.setVisibility(View.VISIBLE);
} else {
mBgi.setVisibility(View.GONE);
@@ -477,7 +424,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
if (mStatus != null) {
if (sharedPrefs.getBoolean("showExternalStatus", true)) {
- mStatus.setText(externalStatusString);
+ mStatus.setText(rawData.externalStatusString);
mStatus.setVisibility(View.VISIBLE);
} else {
mStatus.setVisibility(View.GONE);
@@ -487,8 +434,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
if (mLoop != null) {
if (sharedPrefs.getBoolean("showExternalStatus", true)) {
mLoop.setVisibility(View.VISIBLE);
- if (openApsStatus != -1) {
- int mins = (int) ((System.currentTimeMillis() - openApsStatus) / 1000 / 60);
+ if (rawData.openApsStatus != -1) {
+ int mins = (int) ((System.currentTimeMillis() - rawData.openApsStatus) / 1000 / 60);
mLoop.setText(mins + "'");
if (mins > 14) {
loopLevel = 0;
@@ -594,50 +541,13 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
}
}
- public void addToWatchSet(DataMap dataMap) {
-
- ArrayList entries = dataMap.getDataMapArrayList("entries");
- if (entries != null) {
- bgDataList = new ArrayList();
- for (DataMap entry : entries) {
- double sgv = entry.getDouble("sgvDouble");
- double high = entry.getDouble("high");
- double low = entry.getDouble("low");
- long timestamp = entry.getLong("timestamp");
- int color = entry.getInt("color", 0);
- bgDataList.add(new BgWatchData(sgv, high, low, timestamp, color));
- }
- } else {
- double sgv = dataMap.getDouble("sgvDouble");
- double high = dataMap.getDouble("high");
- double low = dataMap.getDouble("low");
- long timestamp = dataMap.getLong("timestamp");
- int color = dataMap.getInt("color", 0);
-
- final int size = bgDataList.size();
- if (size > 0) {
- if (bgDataList.get(size - 1).timestamp == timestamp)
- return; // Ignore duplicates.
- }
-
- bgDataList.add(new BgWatchData(sgv, high, low, timestamp, color));
- }
-
- for (int i = 0; i < bgDataList.size(); i++) {
- if (bgDataList.get(i).timestamp < (System.currentTimeMillis() - (1000 * 60 * 60 * 5))) {
- bgDataList.remove(i); //Get rid of anything more than 5 hours old
- break;
- }
- }
- }
-
public void setupCharts() {
- if(bgDataList.size() > 0) { //Dont crash things just because we dont have values, people dont like crashy things
+ if(rawData.bgDataList.size() > 0) { //Dont crash things just because we dont have values, people dont like crashy things
int timeframe = Integer.parseInt(sharedPrefs.getString("chart_timeframe", "3"));
if (lowResMode) {
- bgGraphBuilder = new BgGraphBuilder(getApplicationContext(), bgDataList, predictionList, tempWatchDataList, basalWatchDataList, bolusWatchDataList, pointSize, midColor, gridColor, basalBackgroundColor, basalCenterColor, bolusColor, Color.GREEN, timeframe);
+ bgGraphBuilder = new BgGraphBuilder(getApplicationContext(), rawData, pointSize, midColor, gridColor, basalBackgroundColor, basalCenterColor, bolusColor, Color.GREEN, timeframe);
} else {
- bgGraphBuilder = new BgGraphBuilder(getApplicationContext(), bgDataList,predictionList, tempWatchDataList, basalWatchDataList, bolusWatchDataList, pointSize, highColor, lowColor, midColor, gridColor, basalBackgroundColor, basalCenterColor, bolusColor, Color.GREEN, timeframe);
+ bgGraphBuilder = new BgGraphBuilder(getApplicationContext(), rawData, pointSize, highColor, lowColor, midColor, gridColor, basalBackgroundColor, basalCenterColor, bolusColor, Color.GREEN, timeframe);
}
chart.setLineChartData(bgGraphBuilder.lineData());
@@ -646,54 +556,4 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen
}
}
- private void loadBasalsAndTemps(DataMap dataMap) {
- ArrayList temps = dataMap.getDataMapArrayList("temps");
- if (temps != null) {
- tempWatchDataList = new ArrayList<>();
- for (DataMap temp : temps) {
- TempWatchData twd = new TempWatchData();
- twd.startTime = temp.getLong("starttime");
- twd.startBasal = temp.getDouble("startBasal");
- twd.endTime = temp.getLong("endtime");
- twd.endBasal = temp.getDouble("endbasal");
- twd.amount = temp.getDouble("amount");
- tempWatchDataList.add(twd);
- }
- }
- ArrayList basals = dataMap.getDataMapArrayList("basals");
- if (basals != null) {
- basalWatchDataList = new ArrayList<>();
- for (DataMap basal : basals) {
- BasalWatchData bwd = new BasalWatchData();
- bwd.startTime = basal.getLong("starttime");
- bwd.endTime = basal.getLong("endtime");
- bwd.amount = basal.getDouble("amount");
- basalWatchDataList.add(bwd);
- }
- }
- ArrayList boluses = dataMap.getDataMapArrayList("boluses");
- if (boluses != null) {
- bolusWatchDataList = new ArrayList<>();
- for (DataMap bolus : boluses) {
- BolusWatchData bwd = new BolusWatchData();
- bwd.date = bolus.getLong("date");
- bwd.bolus = bolus.getDouble("bolus");
- bwd.carbs = bolus.getDouble("carbs");
- bwd.isSMB = bolus.getBoolean("isSMB");
- bwd.isValid = bolus.getBoolean("isValid");
- bolusWatchDataList.add(bwd);
- }
- }
- ArrayList predictions = dataMap.getDataMapArrayList("predictions");
- if (boluses != null) {
- predictionList = new ArrayList<>();
- for (DataMap prediction : predictions) {
- BgWatchData bwd = new BgWatchData();
- bwd.timestamp = prediction.getLong("timestamp");
- bwd.sgv = prediction.getDouble("sgv");
- bwd.color = prediction.getInt("color");
- predictionList.add(bwd);
- }
- }
- }
}
diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/BgGraphBuilder.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/BgGraphBuilder.java
index 4a401489d9..ee17d91e76 100644
--- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/BgGraphBuilder.java
+++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/BgGraphBuilder.java
@@ -1,7 +1,6 @@
package info.nightscout.androidaps.watchfaces;
import android.content.Context;
-import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.preference.PreferenceManager;
import android.text.format.DateFormat;
@@ -18,6 +17,7 @@ import java.util.TimeZone;
import info.nightscout.androidaps.data.BasalWatchData;
import info.nightscout.androidaps.data.BgWatchData;
import info.nightscout.androidaps.data.BolusWatchData;
+import info.nightscout.androidaps.data.RawDisplayData;
import info.nightscout.androidaps.data.TempWatchData;
import lecho.lib.hellocharts.model.Axis;
import lecho.lib.hellocharts.model.AxisValue;
@@ -115,6 +115,42 @@ public class BgGraphBuilder {
this.end_time = (predictionEndTime>end_time)?predictionEndTime:end_time;
}
+ public BgGraphBuilder(Context context, RawDisplayData raw, int aPointSize, int aHighColor, int aLowColor, int aMidColor, int gridColour, int basalBackgroundColor, int basalCenterColor, int bolusInvalidColor, int carbsColor, int timespan) {
+ this(context,
+ raw.bgDataList,
+ raw.predictionList,
+ raw.tempWatchDataList,
+ raw.basalWatchDataList,
+ raw.bolusWatchDataList,
+ aPointSize,
+ aHighColor,
+ aLowColor,
+ aMidColor,
+ gridColour,
+ basalBackgroundColor,
+ basalCenterColor,
+ bolusInvalidColor,
+ carbsColor,
+ timespan);
+ }
+
+ public BgGraphBuilder(Context context, RawDisplayData raw, int aPointSize, int aMidColor, int gridColour, int basalBackgroundColor, int basalCenterColor, int bolusInvalidColor, int carbsColor, int timespan) {
+ this(context,
+ raw.bgDataList,
+ raw.predictionList,
+ raw.tempWatchDataList,
+ raw.basalWatchDataList,
+ raw.bolusWatchDataList,
+ aPointSize,
+ aMidColor,
+ gridColour,
+ basalBackgroundColor,
+ basalCenterColor,
+ bolusInvalidColor,
+ carbsColor,
+ timespan);
+ }
+
public LineChartData lineData() {
LineChartData lineData = new LineChartData(defaultLines());
lineData.setAxisYLeft(yAxis());
diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Cockpit.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Cockpit.java
index b949e3832a..31d293929a 100644
--- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Cockpit.java
+++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Cockpit.java
@@ -48,13 +48,13 @@ public class Cockpit extends BaseWatchFace {
setTextSizes();
if (mHighLight != null && mLowLight != null) {
- if (sgvLevel == 1) {
+ if (rawData.sgvLevel == 1) {
mHighLight.setBackgroundResource(R.drawable.airplane_led_yellow_lit);
mLowLight.setBackgroundResource(R.drawable.airplane_led_grey_unlit);
- } else if (sgvLevel == 0) {
+ } else if (rawData.sgvLevel == 0) {
mHighLight.setBackgroundResource(R.drawable.airplane_led_grey_unlit);
mLowLight.setBackgroundResource(R.drawable.airplane_led_grey_unlit);
- } else if (sgvLevel == -1) {
+ } else if (rawData.sgvLevel == -1) {
mHighLight.setBackgroundResource(R.drawable.airplane_led_grey_unlit);
mLowLight.setBackgroundResource(R.drawable.airplane_led_red_lit);
}
@@ -82,7 +82,7 @@ public class Cockpit extends BaseWatchFace {
protected void setTextSizes() {
if (mIOB2 != null) {
- if (detailedIOB) {
+ if (rawData.detailedIOB) {
if (bIsRound) {
mIOB2.setTextSize(10);
} else {
diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home.java
index 430a3feb47..209e6214e7 100644
--- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home.java
+++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home.java
@@ -68,15 +68,15 @@ public class Home extends BaseWatchFace {
mLinearLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_statusView));
mTime.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_mTime));
mRelativeLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_background));
- if (sgvLevel == 1) {
+ if (rawData.sgvLevel == 1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
- } else if (sgvLevel == 0) {
+ } else if (rawData.sgvLevel == 0) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
- } else if (sgvLevel == -1) {
+ } else if (rawData.sgvLevel == -1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
@@ -88,7 +88,7 @@ public class Home extends BaseWatchFace {
mTimestamp.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_TimestampOld));
}
- if (batteryLevel == 1) {
+ if (rawData.batteryLevel == 1) {
mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_uploaderBattery));
} else {
mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_uploaderBatteryEmpty));
@@ -133,15 +133,15 @@ public class Home extends BaseWatchFace {
if (getCurrentWatchMode() == WatchMode.INTERACTIVE) {
mLinearLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.light_stripe_background));
mRelativeLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.light_background));
- if (sgvLevel == 1) {
+ if (rawData.sgvLevel == 1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
- } else if (sgvLevel == 0) {
+ } else if (rawData.sgvLevel == 0) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
- } else if (sgvLevel == -1) {
+ } else if (rawData.sgvLevel == -1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
@@ -153,7 +153,7 @@ public class Home extends BaseWatchFace {
mTimestamp.setTextColor(Color.RED);
}
- if (batteryLevel == 1) {
+ if (rawData.batteryLevel == 1) {
mUploaderBattery.setTextColor(Color.WHITE);
} else {
mUploaderBattery.setTextColor(Color.RED);
diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home2.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home2.java
index 41c9fd3029..fbc69f3c1a 100644
--- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home2.java
+++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home2.java
@@ -78,13 +78,13 @@ public class Home2 extends BaseWatchFace {
setTextSizes();
- if (sgvLevel == 1) {
+ if (rawData.sgvLevel == 1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
- } else if (sgvLevel == 0) {
+ } else if (rawData.sgvLevel == 0) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
- } else if (sgvLevel == -1) {
+ } else if (rawData.sgvLevel == -1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
}
@@ -95,7 +95,7 @@ public class Home2 extends BaseWatchFace {
mTimestamp.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_TimestampOld));
}
- if (batteryLevel == 1) {
+ if (rawData.batteryLevel == 1) {
mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_uploaderBattery));
} else {
mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_uploaderBatteryEmpty));
@@ -176,13 +176,13 @@ public class Home2 extends BaseWatchFace {
setTextSizes();
- if (sgvLevel == 1) {
+ if (rawData.sgvLevel == 1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
- } else if (sgvLevel == 0) {
+ } else if (rawData.sgvLevel == 0) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
- } else if (sgvLevel == -1) {
+ } else if (rawData.sgvLevel == -1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
}
@@ -193,7 +193,7 @@ public class Home2 extends BaseWatchFace {
mTimestamp.setTextColor(Color.RED);
}
- if (batteryLevel == 1) {
+ if (rawData.batteryLevel == 1) {
mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
} else {
mUploaderBattery.setTextColor(Color.RED);
@@ -229,7 +229,7 @@ public class Home2 extends BaseWatchFace {
if (mIOB1 != null && mIOB2 != null) {
- if (detailedIOB) {
+ if (rawData.detailedIOB) {
mIOB1.setTextSize(14);
mIOB2.setTextSize(10);
} else {
diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/LargeHome.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/LargeHome.java
index ffb8dce3ad..75b7d995dd 100644
--- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/LargeHome.java
+++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/LargeHome.java
@@ -52,15 +52,15 @@ public class LargeHome extends BaseWatchFace {
mLinearLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_mLinearLayout));
mTime.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_mTime));
mRelativeLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_background));
- if (sgvLevel == 1) {
+ if (rawData.sgvLevel == 1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
- } else if (sgvLevel == 0) {
+ } else if (rawData.sgvLevel == 0) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
- } else if (sgvLevel == -1) {
+ } else if (rawData.sgvLevel == -1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
@@ -72,7 +72,7 @@ public class LargeHome extends BaseWatchFace {
mTimestamp.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_TimestampOld));
}
- if (batteryLevel == 1) {
+ if (rawData.batteryLevel == 1) {
mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_uploaderBattery));
} else {
mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_uploaderBatteryEmpty));
@@ -86,15 +86,15 @@ public class LargeHome extends BaseWatchFace {
if (getCurrentWatchMode() == WatchMode.INTERACTIVE) {
mLinearLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.light_stripe_background));
mRelativeLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.light_background));
- if (sgvLevel == 1) {
+ if (rawData.sgvLevel == 1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
- } else if (sgvLevel == 0) {
+ } else if (rawData.sgvLevel == 0) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
- } else if (sgvLevel == -1) {
+ } else if (rawData.sgvLevel == -1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
@@ -106,7 +106,7 @@ public class LargeHome extends BaseWatchFace {
mTimestamp.setTextColor(Color.RED);
}
- if (batteryLevel == 1) {
+ if (rawData.batteryLevel == 1) {
mUploaderBattery.setTextColor(Color.WHITE);
} else {
mUploaderBattery.setTextColor(Color.RED);
@@ -116,15 +116,15 @@ public class LargeHome extends BaseWatchFace {
} else {
mRelativeLayout.setBackgroundColor(Color.BLACK);
mLinearLayout.setBackgroundColor(Color.LTGRAY);
- if (sgvLevel == 1) {
+ if (rawData.sgvLevel == 1) {
mSgv.setTextColor(Color.YELLOW);
mDirection.setTextColor(Color.YELLOW);
mDelta.setTextColor(Color.YELLOW);
- } else if (sgvLevel == 0) {
+ } else if (rawData.sgvLevel == 0) {
mSgv.setTextColor(Color.WHITE);
mDirection.setTextColor(Color.WHITE);
mDelta.setTextColor(Color.WHITE);
- } else if (sgvLevel == -1) {
+ } else if (rawData.sgvLevel == -1) {
mSgv.setTextColor(Color.RED);
mDirection.setTextColor(Color.RED);
mDelta.setTextColor(Color.RED);
diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Steampunk.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Steampunk.java
index c42aff1518..a600290a04 100644
--- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Steampunk.java
+++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Steampunk.java
@@ -83,24 +83,24 @@ public class Steampunk extends BaseWatchFace {
}
}
- if (!sSgv.equals("---")) {
+ if (!rawData.sSgv.equals("---")) {
float rotationAngle = 0f; //by default, show ? on the dial (? is at 0 degrees on the dial)
- if (!sUnits.equals("-")) {
+ if (!rawData.sUnits.equals("-")) {
//ensure the glucose dial is the correct units
- if (sUnits.equals("mmol")) {
+ if (rawData.sUnits.equals("mmol")) {
mGlucoseDial.setImageResource(R.drawable.steampunk_dial_mmol);
} else {
mGlucoseDial.setImageResource(R.drawable.steampunk_dial_mgdl);
}
//convert the Sgv to degrees of rotation
- if (sUnits.equals("mmol")) {
- rotationAngle = Float.valueOf(sSgv) * 18f; //convert to mg/dL, which is equivalent to degrees
+ if (rawData.sUnits.equals("mmol")) {
+ rotationAngle = Float.valueOf(rawData.sSgv) * 18f; //convert to mg/dL, which is equivalent to degrees
} else {
- rotationAngle = Float.valueOf(sSgv); //if glucose a value is received, use it to determine the amount of rotation of the dial.
+ rotationAngle = Float.valueOf(rawData.sSgv); //if glucose a value is received, use it to determine the amount of rotation of the dial.
}
}
@@ -122,36 +122,36 @@ public class Steampunk extends BaseWatchFace {
//set the delta gauge and rotate the delta pointer
float deltaIsNegative = 1f; //by default go clockwise
- if (!sAvgDelta.equals("--")) { //if a legitimate delta value is received, then...
- if (sAvgDelta.substring(0,1).equals("-")) deltaIsNegative = -1f; //if the delta is negative, go counter-clockwise
+ if (!rawData.sAvgDelta.equals("--")) { //if a legitimate delta value is received, then...
+ if (rawData.sAvgDelta.substring(0,1).equals("-")) deltaIsNegative = -1f; //if the delta is negative, go counter-clockwise
//ensure the delta gauge is the right units and granularity
- if (!sUnits.equals("-")) {
- if (sUnits.equals("mmol")) {
+ if (!rawData.sUnits.equals("-")) {
+ if (rawData.sUnits.equals("mmol")) {
if (sharedPrefs.getString("delta_granularity", "2").equals("1")) { //low
mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mmol_10);
- deltaRotationAngle = (Float.valueOf(sAvgDelta.substring(1)) * 30f); //get rid of the sign so it can be converted to float.
+ deltaRotationAngle = (Float.valueOf(rawData.sAvgDelta.substring(1)) * 30f); //get rid of the sign so it can be converted to float.
}
if (sharedPrefs.getString("delta_granularity", "2").equals("2")) { //medium
mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mmol_05);
- deltaRotationAngle = (Float.valueOf(sAvgDelta.substring(1)) * 60f); //get rid of the sign so it can be converted to float.
+ deltaRotationAngle = (Float.valueOf(rawData.sAvgDelta.substring(1)) * 60f); //get rid of the sign so it can be converted to float.
}
if (sharedPrefs.getString("delta_granularity", "2").equals("3")) { //high
mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mmol_03);
- deltaRotationAngle = (Float.valueOf(sAvgDelta.substring(1)) * 100f); //get rid of the sign so it can be converted to float.
+ deltaRotationAngle = (Float.valueOf(rawData.sAvgDelta.substring(1)) * 100f); //get rid of the sign so it can be converted to float.
}
} else {
if (sharedPrefs.getString("delta_granularity", "2").equals("1")) { //low
mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mgdl_20);
- deltaRotationAngle = (Float.valueOf(sAvgDelta.substring(1)) * 1.5f); //get rid of the sign so it can be converted to float.
+ deltaRotationAngle = (Float.valueOf(rawData.sAvgDelta.substring(1)) * 1.5f); //get rid of the sign so it can be converted to float.
}
if (sharedPrefs.getString("delta_granularity", "2").equals("2")) { //medium
mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mgdl_10);
- deltaRotationAngle = (Float.valueOf(sAvgDelta.substring(1)) * 3f); //get rid of the sign so it can be converted to float.
+ deltaRotationAngle = (Float.valueOf(rawData.sAvgDelta.substring(1)) * 3f); //get rid of the sign so it can be converted to float.
}
if (sharedPrefs.getString("delta_granularity", "2").equals("3")) { //high
mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mgdl_5);
- deltaRotationAngle = (Float.valueOf(sAvgDelta.substring(1)) * 6f); //get rid of the sign so it can be converted to float.
+ deltaRotationAngle = (Float.valueOf(rawData.sAvgDelta.substring(1)) * 6f); //get rid of the sign so it can be converted to float.
}
}
}
@@ -213,7 +213,7 @@ public class Steampunk extends BaseWatchFace {
//top row. large font unless text too big (i.e. detailedIOB)
mCOB2.setTextSize(fontLarge);
mBasalRate.setTextSize(fontLarge);
- if (sIOB2.length() < 7) {
+ if (rawData.sIOB2.length() < 7) {
mIOB2.setTextSize(fontLarge);
} else {
mIOB2.setTextSize(fontSmall);
diff --git a/wear/src/main/res/drawable/ic_aaps_dark.xml b/wear/src/main/res/drawable/ic_aaps_dark.xml
new file mode 100644
index 0000000000..4126fec9d9
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_aaps_dark.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/wear/src/main/res/drawable/ic_aaps_full.xml b/wear/src/main/res/drawable/ic_aaps_full.xml
new file mode 100644
index 0000000000..cf9716870c
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_aaps_full.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/wear/src/main/res/drawable/ic_aaps_gray.xml b/wear/src/main/res/drawable/ic_aaps_gray.xml
new file mode 100644
index 0000000000..1797ca3c9a
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_aaps_gray.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/wear/src/main/res/drawable/ic_aaps_light.xml b/wear/src/main/res/drawable/ic_aaps_light.xml
new file mode 100644
index 0000000000..cf9716870c
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_aaps_light.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/wear/src/main/res/drawable/ic_alert.xml b/wear/src/main/res/drawable/ic_alert.xml
new file mode 100644
index 0000000000..d95c322d47
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_alert.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_alert_burnin.xml b/wear/src/main/res/drawable/ic_alert_burnin.xml
new file mode 100644
index 0000000000..6f7bd23ef0
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_alert_burnin.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_battery_alert_variant_outline.xml b/wear/src/main/res/drawable/ic_battery_alert_variant_outline.xml
new file mode 100644
index 0000000000..516ae49c17
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_alert_variant_outline.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless.xml
new file mode 100644
index 0000000000..bd6bee5fb1
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_10.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_10.xml
new file mode 100644
index 0000000000..932c4e5930
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_10.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_10_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_10_burnin.xml
new file mode 100644
index 0000000000..a47508a1a9
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_10_burnin.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_20.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_20.xml
new file mode 100644
index 0000000000..00af480fcd
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_20.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_20_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_20_burnin.xml
new file mode 100644
index 0000000000..e08ff88448
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_20_burnin.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_30.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_30.xml
new file mode 100644
index 0000000000..592d0f380c
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_30.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_30_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_30_burnin.xml
new file mode 100644
index 0000000000..eb36861925
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_30_burnin.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_40.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_40.xml
new file mode 100644
index 0000000000..f94f85a69b
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_40.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_40_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_40_burnin.xml
new file mode 100644
index 0000000000..8980db2391
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_40_burnin.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_50.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_50.xml
new file mode 100644
index 0000000000..f6a4148589
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_50.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_50_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_50_burnin.xml
new file mode 100644
index 0000000000..9a9ee645dc
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_50_burnin.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_60.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_60.xml
new file mode 100644
index 0000000000..29192af0ed
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_60.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_60_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_60_burnin.xml
new file mode 100644
index 0000000000..d5191601a6
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_60_burnin.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_70.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_70.xml
new file mode 100644
index 0000000000..02c0ae7f58
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_70.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_70_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_70_burnin.xml
new file mode 100644
index 0000000000..9efc928672
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_70_burnin.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_80.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_80.xml
new file mode 100644
index 0000000000..21e44f7105
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_80.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_80_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_80_burnin.xml
new file mode 100644
index 0000000000..9f212fad87
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_80_burnin.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_90.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_90.xml
new file mode 100644
index 0000000000..1004964eb2
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_90.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_90_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_90_burnin.xml
new file mode 100644
index 0000000000..b6b371fa20
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_90_burnin.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_burnin.xml
new file mode 100644
index 0000000000..fd11b20ac9
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_burnin.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_outline.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_outline.xml
new file mode 100644
index 0000000000..5fa535e117
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_outline.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_battery_outline.xml b/wear/src/main/res/drawable/ic_battery_outline.xml
new file mode 100644
index 0000000000..c066289e18
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_outline.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_battery_unknown.xml b/wear/src/main/res/drawable/ic_battery_unknown.xml
new file mode 100644
index 0000000000..0ac1d8a740
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_unknown.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/drawable/ic_battery_unknown_burnin.xml b/wear/src/main/res/drawable/ic_battery_unknown_burnin.xml
new file mode 100644
index 0000000000..e6271cce70
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_battery_unknown_burnin.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_br_cob_iob.xml b/wear/src/main/res/drawable/ic_br_cob_iob.xml
new file mode 100644
index 0000000000..39ab3af490
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_br_cob_iob.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_carbs.xml b/wear/src/main/res/drawable/ic_carbs.xml
new file mode 100644
index 0000000000..a1ac753064
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_carbs.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_cob_detailed.xml b/wear/src/main/res/drawable/ic_cob_detailed.xml
new file mode 100644
index 0000000000..fc92be1910
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_cob_detailed.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_cob_iob.xml b/wear/src/main/res/drawable/ic_cob_iob.xml
new file mode 100644
index 0000000000..f029cf0d49
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_cob_iob.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_ins.xml b/wear/src/main/res/drawable/ic_ins.xml
new file mode 100644
index 0000000000..9fbb294190
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_ins.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_ins_burnin.xml b/wear/src/main/res/drawable/ic_ins_burnin.xml
new file mode 100644
index 0000000000..8a3a16c64b
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_ins_burnin.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_iob_detailed.xml b/wear/src/main/res/drawable/ic_iob_detailed.xml
new file mode 100644
index 0000000000..95974f2fed
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_iob_detailed.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_sgv.xml b/wear/src/main/res/drawable/ic_sgv.xml
new file mode 100644
index 0000000000..e7aefdd542
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_sgv.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/wear/src/main/res/drawable/ic_sync_alert.xml b/wear/src/main/res/drawable/ic_sync_alert.xml
new file mode 100644
index 0000000000..bd4ca18d5f
--- /dev/null
+++ b/wear/src/main/res/drawable/ic_sync_alert.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/wear/src/main/res/values/strings.xml b/wear/src/main/res/values/strings.xml
index d50cb015e6..ca24978853 100644
--- a/wear/src/main/res/values/strings.xml
+++ b/wear/src/main/res/values/strings.xml
@@ -46,6 +46,25 @@
- 4
+
+ - Default
+ - Menu
+ - Wizard
+ - Bolus
+ - eCarb
+ - Status
+ - None
+
+
+
+ - default
+ - menu
+ - wizard
+ - bolus
+ - ecarb
+ - status
+ - none
+
AAPS
AAPS(Large)
@@ -53,4 +72,13 @@
AAPS(NoChart)
AAPS(Circle)
+
+ No data!
+ Old data!
+ Since %1$s
+ Sync with AAPS!
+
+ No data received since %1$s! Check if AAPS on the phone sends data to watch
+ AAPS data is %1$s old! Check your sensor, xDrip+, NS, AAPS config or other!
+
diff --git a/wear/src/main/res/xml/preferences.xml b/wear/src/main/res/xml/preferences.xml
index 0befaddfd0..aa8a3f6ba2 100644
--- a/wear/src/main/res/xml/preferences.xml
+++ b/wear/src/main/res/xml/preferences.xml
@@ -209,6 +209,20 @@
android:title="Wizard Percentage"
app:wear_iconOff="@drawable/settings_off"
app:wear_iconOn="@drawable/settings_on"/>
+
+
set = new HashSet<>();
+
+ // THEN
+ assertFalse(set.contains(inserted));
+ set.add(inserted);
+ assertTrue(set.contains(inserted));
+ }
+
+ /**
+ * BgWatchData has BIZARRE equals - only timestamp and color are checked!
+ */
+ @Test
+ public void bgWatchDataEqualsTest() {
+ // GIVEN
+ BgWatchData item1 = new BgWatchData(
+ 88.0, 160.0, 90.0,
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS, 1
+ );
+
+ BgWatchData item2sameTimeSameColor = new BgWatchData(
+ 123.0, 190, 90.0,
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS, 1
+ );
+
+ BgWatchData item3sameTimeSameDiffColor = new BgWatchData(
+ 96.0, 190, 90.0,
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS, 0
+ );
+ BgWatchData item4differentTime = new BgWatchData(
+ 88.0, 160.0, 90.0,
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*2, 1
+ );
+
+ // THEN
+ assertEquals(item1, item2sameTimeSameColor);
+ assertNotEquals(item1, item3sameTimeSameDiffColor);
+ assertNotEquals(item1, item4differentTime);
+
+ assertFalse(item1.equals("aa bbb"));
+ }
+
+ /**
+ * BgWatchData is ordered by timestamp, reverse order
+ */
+ @Test
+ public void bgWatchDataCompareTest() {
+ // GIVEN
+ BgWatchData item1 = new BgWatchData(
+ 85, 160.0, 90.0,
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*2, 1
+ );
+
+ BgWatchData item2 = new BgWatchData(
+ 80, 190, 90.0,
+ WearUtilMocker.REF_NOW, 1
+ );
+
+ BgWatchData item3 = new BgWatchData(
+ 80, 190, 50.0,
+ WearUtilMocker.REF_NOW + Constants.MINUTE_IN_MS*5, 0
+ );
+
+ BgWatchData item4 = new BgWatchData(
+ 160, 140, 70.0,
+ WearUtilMocker.REF_NOW, 0
+ );
+
+ // THEN
+ assertThat(item2, lessThan(item1));
+ assertThat(item2, greaterThan(item3));
+ assertThat(item2, comparesEqualTo(item4));
+ }
+}
diff --git a/wear/src/test/java/info/nightscout/androidaps/data/RawDataSgvDisplayDataTest.java b/wear/src/test/java/info/nightscout/androidaps/data/RawDataSgvDisplayDataTest.java
new file mode 100644
index 0000000000..fa73a0272e
--- /dev/null
+++ b/wear/src/test/java/info/nightscout/androidaps/data/RawDataSgvDisplayDataTest.java
@@ -0,0 +1,148 @@
+package info.nightscout.androidaps.data;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.google.android.gms.wearable.DataMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import info.nightscout.androidaps.aaps;
+import info.nightscout.androidaps.interaction.utils.Constants;
+import info.nightscout.androidaps.interaction.utils.Persistence;
+import info.nightscout.androidaps.interaction.utils.WearUtil;
+import info.nightscout.androidaps.testing.mockers.AAPSMocker;
+import info.nightscout.androidaps.testing.mockers.AndroidMocker;
+import info.nightscout.androidaps.testing.mockers.WearUtilMocker;
+import info.nightscout.androidaps.testing.mocks.BundleMock;
+import info.nightscout.androidaps.testing.mocks.IntentMock;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest( { WearUtil.class, Log.class, SharedPreferences.class, Context.class, aaps.class, android.util.Base64.class, Intent.class } )
+public class RawDataSgvDisplayDataTest {
+
+ @Before
+ public void mock() throws Exception {
+ AAPSMocker.prepareMock();
+ AAPSMocker.resetMockedSharedPrefs();
+ AndroidMocker.mockBase64();
+ WearUtilMocker.prepareMockNoReal();
+ }
+
+ //==============================================================================================
+ // SGV DATA
+ //==============================================================================================
+
+ private DataMap dataMapForData() {
+ DataMap dataMap = new DataMap();
+ dataMap.putLong("sgvLevel", 1L);
+ dataMap.putLong("timestamp", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS);
+ dataMap.putString("sgvString", "106");
+ dataMap.putString("slopeArrow", "↗");
+ dataMap.putString("delta", "5.4");
+ dataMap.putString("avgDelta", "3.7");
+ dataMap.putString("glucoseUnits", "mg/dl");
+ return dataMap;
+ }
+
+ private void assertDataEmpty(RawDisplayData newRaw) {
+ assertThat(newRaw.sgvLevel, is(0L));
+ assertThat(newRaw.datetime, is(0L));
+ assertThat(newRaw.sSgv, is("---"));
+ assertThat(newRaw.sDirection, is("--"));
+ assertThat(newRaw.sDelta, is("--"));
+ assertThat(newRaw.sAvgDelta, is("--"));
+ assertThat(newRaw.sUnits, is("-"));
+ }
+
+ private void assertDataOk(RawDisplayData newRaw) {
+ assertThat(newRaw.sgvLevel, is(1L));
+ assertThat(newRaw.datetime, is(WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS));
+ assertThat(newRaw.sSgv, is("106"));
+ assertThat(newRaw.sDirection, is("↗"));
+ assertThat(newRaw.sDelta, is("5.4"));
+ assertThat(newRaw.sAvgDelta, is("3.7"));
+ assertThat(newRaw.sUnits, is("mg/dl"));
+ }
+
+ @Test
+ public void updateDataFromEmptyPersistenceTest() {
+ // GIVEN
+ Persistence persistence = new Persistence();
+ RawDisplayData newRaw = new RawDisplayData();
+
+ // WHEN
+ newRaw.updateFromPersistence(persistence);
+
+ // THEN
+ assertDataEmpty(newRaw);
+ }
+
+ @Test
+ public void updateDataFromPersistenceTest() {
+ // GIVEN
+ Persistence persistence = new Persistence();
+ RawDisplayData newRaw = new RawDisplayData();
+
+ // WHEN
+ Persistence.storeDataMap(RawDisplayData.DATA_PERSISTENCE_KEY, dataMapForData());
+ newRaw.updateFromPersistence(persistence);
+
+ // THEN
+ assertDataOk(newRaw);
+ }
+
+ @Test
+ public void partialUpdateDataFromPersistenceTest() {
+ // GIVEN
+ Persistence persistence = new Persistence();
+ RawDisplayData newRaw = new RawDisplayData();
+
+ // WHEN
+ Persistence.storeDataMap(RawDisplayData.DATA_PERSISTENCE_KEY, dataMapForData());
+ newRaw.updateForComplicationsFromPersistence(persistence);
+
+ // THEN
+ assertDataOk(newRaw);
+ }
+
+ @Test
+ public void updateDataFromMessageTest() {
+ // GIVEN
+ Intent intent = IntentMock.mock();
+ Bundle bundle = BundleMock.mock(dataMapForData());
+
+ intent.putExtra("data", bundle);
+ RawDisplayData newRaw = new RawDisplayData();
+
+ // WHEN
+ newRaw.updateDataFromMessage(intent, null);
+
+ // THEN
+ assertDataOk(newRaw);
+ }
+
+ @Test
+ public void updateDataFromEmptyMessageTest() {
+ // GIVEN
+ Intent intent = IntentMock.mock();
+ RawDisplayData newRaw = new RawDisplayData();
+
+ // WHEN
+ newRaw.updateDataFromMessage(intent, null);
+
+ // THEN
+ assertDataEmpty(newRaw);
+ }
+
+}
diff --git a/wear/src/test/java/info/nightscout/androidaps/data/RawDisplayDataBasalsTest.java b/wear/src/test/java/info/nightscout/androidaps/data/RawDisplayDataBasalsTest.java
new file mode 100644
index 0000000000..1245d0fbcb
--- /dev/null
+++ b/wear/src/test/java/info/nightscout/androidaps/data/RawDisplayDataBasalsTest.java
@@ -0,0 +1,265 @@
+package info.nightscout.androidaps.data;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.google.android.gms.wearable.DataMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.util.ArrayList;
+
+import info.nightscout.androidaps.aaps;
+import info.nightscout.androidaps.interaction.utils.Constants;
+import info.nightscout.androidaps.interaction.utils.Persistence;
+import info.nightscout.androidaps.interaction.utils.WearUtil;
+import info.nightscout.androidaps.testing.mockers.AAPSMocker;
+import info.nightscout.androidaps.testing.mockers.AndroidMocker;
+import info.nightscout.androidaps.testing.mockers.WearUtilMocker;
+import info.nightscout.androidaps.testing.mocks.BundleMock;
+import info.nightscout.androidaps.testing.mocks.IntentMock;
+import info.nightscout.androidaps.testing.utils.BasalWatchDataExt;
+import info.nightscout.androidaps.testing.utils.BgWatchDataExt;
+import info.nightscout.androidaps.testing.utils.BolusWatchDataExt;
+import info.nightscout.androidaps.testing.utils.TempWatchDataExt;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest( { WearUtil.class, Log.class, SharedPreferences.class, Context.class, aaps.class, android.util.Base64.class, Intent.class } )
+public class RawDisplayDataBasalsTest {
+
+ @Before
+ public void mock() throws Exception {
+ AAPSMocker.prepareMock();
+ AAPSMocker.resetMockedSharedPrefs();
+ AndroidMocker.mockBase64();
+ WearUtilMocker.prepareMockNoReal();
+ }
+
+ //==============================================================================================
+ // BASALS for chart
+ //==============================================================================================
+
+ private DataMap dataMapForBasals() {
+
+ DataMap dataMap = new DataMap();
+
+ ArrayList temps = new ArrayList<>();
+ DataMap temp = new DataMap();
+ temp.putLong("starttime", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*20);
+ temp.putDouble("startBasal", 1.5);
+ temp.putLong("endtime", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*10);
+ temp.putDouble("endbasal", 1.5);
+ temp.putDouble("amount", 1.8);
+ temps.add(temp);
+
+ DataMap temp2 = new DataMap();
+ temp2.putLong("starttime", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*10);
+ temp2.putDouble("startBasal", 1.3);
+ temp2.putLong("endtime", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*2);
+ temp2.putDouble("endbasal", 1.3);
+ temp2.putDouble("amount", 2.3);
+ temps.add(temp2);
+ dataMap.putDataMapArrayList("temps", temps);
+
+ ArrayList basals = new ArrayList<>();
+ DataMap basal = new DataMap();
+ basal.putLong("starttime", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*20);
+ basal.putLong("endtime", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*2);
+ basal.putDouble("amount", 1.2);
+ basals.add(basal);
+ dataMap.putDataMapArrayList("basals", basals);
+
+ ArrayList boluses = new ArrayList<>();
+ DataMap bolus = new DataMap();
+ bolus.putLong("date", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*17);
+ bolus.putDouble("bolus", 5.5);
+ bolus.putDouble("carbs", 20.0);
+ bolus.putBoolean("isSMB", false);
+ bolus.putBoolean("isValid", true);
+ boluses.add(bolus);
+
+ DataMap bolus2 = new DataMap();
+ bolus2.putLong("date", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*11);
+ bolus2.putDouble("bolus", 3.0);
+ bolus2.putDouble("carbs", 0.0);
+ bolus2.putBoolean("isSMB", false);
+ bolus2.putBoolean("isValid", true);
+ boluses.add(bolus2);
+
+ DataMap bolus3 = new DataMap();
+ bolus3.putLong("date", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*3);
+ bolus3.putDouble("bolus", 0.0);
+ bolus3.putDouble("carbs", 15.0);
+ bolus3.putBoolean("isSMB", true);
+ bolus3.putBoolean("isValid", false);
+ boluses.add(bolus3);
+
+ dataMap.putDataMapArrayList("boluses", boluses);
+
+ ArrayList predictions = new ArrayList<>();
+ for (int i=0; i<10; i++) {
+ DataMap prediction = new DataMap();
+ prediction.putLong("timestamp", WearUtilMocker.REF_NOW + Constants.MINUTE_IN_MS*i);
+ prediction.putDouble("sgv", 160-4*i);
+ prediction.putInt("color", 0);
+ predictions.add(prediction);
+ }
+ dataMap.putDataMapArrayList("predictions", predictions);
+
+ return dataMap;
+ }
+
+ private void assertBasalsEmpty(RawDisplayData newRaw) {
+ assertThat(newRaw.tempWatchDataList.size(), is(0));
+ assertThat(newRaw.basalWatchDataList.size(), is(0));
+ assertThat(newRaw.bolusWatchDataList.size(), is(0));
+ assertThat(newRaw.predictionList.size(), is(0));
+ }
+
+ private void assertBasalsOk(RawDisplayData newRaw) {
+ assertThat(newRaw.tempWatchDataList.size(), is(2));
+ assertThat(newRaw.basalWatchDataList.size(), is(1));
+ assertThat(newRaw.bolusWatchDataList.size(), is(3));
+ assertThat(newRaw.predictionList.size(), is(10));
+
+ assertThat(new TempWatchDataExt(newRaw.tempWatchDataList.get(0)), is(TempWatchDataExt.build(
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*20,
+ 1.5,
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*10,
+ 1.5,
+ 1.8
+ )));
+
+ assertThat(new TempWatchDataExt(newRaw.tempWatchDataList.get(1)), is(TempWatchDataExt.build(
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*10,
+ 1.3,
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*2,
+ 1.3,
+ 2.3
+ )));
+
+ assertThat(new BasalWatchDataExt(newRaw.basalWatchDataList.get(0)), is(BasalWatchDataExt.build(
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*20,
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*2,
+ 1.2
+ )));
+
+ assertThat(new BolusWatchDataExt(newRaw.bolusWatchDataList.get(0)), is(BolusWatchDataExt.build(
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*17,
+ 5.5,
+ 20,
+ false,
+ true
+ )));
+
+ assertThat(new BolusWatchDataExt(newRaw.bolusWatchDataList.get(1)), is(BolusWatchDataExt.build(
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*11,
+ 3,
+ 0,
+ false,
+ true
+ )));
+
+ assertThat(new BolusWatchDataExt(newRaw.bolusWatchDataList.get(2)), is(BolusWatchDataExt.build(
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*3,
+ 0,
+ 15,
+ true,
+ false
+ )));
+
+
+ assertThat(new BgWatchDataExt(newRaw.predictionList.get(3)), is(BgWatchDataExt.build(
+ 160-4*3,
+ WearUtilMocker.REF_NOW + Constants.MINUTE_IN_MS*3,
+ 0
+ )));
+
+ assertThat(new BgWatchDataExt(newRaw.predictionList.get(7)), is(BgWatchDataExt.build(
+ 160-4*7,
+ WearUtilMocker.REF_NOW + Constants.MINUTE_IN_MS*7,
+ 0
+ )));
+ }
+
+ @Test
+ public void updateBasalsFromEmptyPersistenceTest() {
+ // GIVEN
+ Persistence persistence = new Persistence();
+ RawDisplayData newRaw = new RawDisplayData();
+
+ // WHEN
+ newRaw.updateFromPersistence(persistence);
+
+ // THEN
+ assertBasalsEmpty(newRaw);
+ }
+
+ @Test
+ public void updateBasalsFromPersistenceTest() {
+ // GIVEN
+ Persistence persistence = new Persistence();
+ RawDisplayData newRaw = new RawDisplayData();
+
+ // WHEN
+ Persistence.storeDataMap(RawDisplayData.BASALS_PERSISTENCE_KEY, dataMapForBasals());
+ newRaw.updateFromPersistence(persistence);
+
+ // THEN
+ assertBasalsOk(newRaw);
+ }
+
+ @Test
+ public void partialUpdateBasalsFromPersistenceTest() {
+ // GIVEN
+ Persistence persistence = new Persistence();
+ RawDisplayData newRaw = new RawDisplayData();
+
+ // WHEN
+ Persistence.storeDataMap(RawDisplayData.BASALS_PERSISTENCE_KEY, dataMapForBasals());
+ newRaw.updateForComplicationsFromPersistence(persistence);
+
+ // THEN
+ assertBasalsEmpty(newRaw);
+ }
+
+ @Test
+ public void updateBasalsFromMessageTest() {
+ // GIVEN
+ Intent intent = IntentMock.mock();
+ Bundle bundle = BundleMock.mock(dataMapForBasals());
+
+ intent.putExtra("basals", bundle);
+ RawDisplayData newRaw = new RawDisplayData();
+
+ // WHEN
+ newRaw.updateBasalsFromMessage(intent, null);
+
+ // THEN
+ assertBasalsOk(newRaw);
+ }
+
+ @Test
+ public void updateBasalsFromEmptyMessageTest() {
+ // GIVEN
+ Intent intent = IntentMock.mock();
+ RawDisplayData newRaw = new RawDisplayData();
+
+ // WHEN
+ newRaw.updateBasalsFromMessage(intent, null);
+
+ // THEN
+ assertBasalsEmpty(newRaw);
+ }
+
+}
diff --git a/wear/src/test/java/info/nightscout/androidaps/data/RawDisplayDataBgEntriesTest.java b/wear/src/test/java/info/nightscout/androidaps/data/RawDisplayDataBgEntriesTest.java
new file mode 100644
index 0000000000..c200288213
--- /dev/null
+++ b/wear/src/test/java/info/nightscout/androidaps/data/RawDisplayDataBgEntriesTest.java
@@ -0,0 +1,146 @@
+package info.nightscout.androidaps.data;
+
+import com.google.android.gms.wearable.DataMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.util.ArrayList;
+
+import info.nightscout.androidaps.interaction.utils.Constants;
+import info.nightscout.androidaps.interaction.utils.WearUtil;
+import info.nightscout.androidaps.testing.mockers.WearUtilMocker;
+import info.nightscout.androidaps.testing.utils.BgWatchDataExt;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest( { WearUtil.class } )
+public class RawDisplayDataBgEntriesTest {
+
+ @Before
+ public void mock() throws Exception {
+ WearUtilMocker.prepareMockNoReal();
+ }
+
+ //==============================================================================================
+ // ENTRIES for chart
+ //==============================================================================================
+
+ private DataMap dataMapForEntries() {
+
+ DataMap dataMap = new DataMap();
+ ArrayList entries = new ArrayList<>();
+ for (int i=0; i<12; i++) {
+ DataMap entry = new DataMap();
+ entry.putLong("timestamp", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*4*(16-i));
+ entry.putDouble("sgvDouble", 145.0-5*i);
+ entry.putDouble("high", 170.0);
+ entry.putDouble("low", 80.0);
+ entry.putInt("color", 0);
+ entries.add(entry);
+ }
+ dataMap.putDataMapArrayList("entries", entries);
+
+ return dataMap;
+ }
+
+ private DataMap dataMapForEntries(long timestamp, double sgv) {
+ DataMap entry = new DataMap();
+ entry.putLong("timestamp", timestamp);
+ entry.putDouble("sgvDouble", sgv);
+ entry.putDouble("high", 160.0);
+ entry.putDouble("low", 90.0);
+ entry.putInt("color", 1);
+ return entry;
+ }
+
+ @Test
+ public void addToWatchSetTest() {
+ // GIVEN
+ RawDisplayData newRaw = new RawDisplayData();
+ DataMap multipleEntries = dataMapForEntries();
+ DataMap singleEntry1 = dataMapForEntries(WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*4*2,92);
+ DataMap singleEntry2 = dataMapForEntries(WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*4*1,88);
+
+ // WHEN, THEN
+ // add list
+ newRaw.addToWatchSet(multipleEntries);
+ assertThat(newRaw.bgDataList.size(), is(12));
+
+ assertThat(new BgWatchDataExt(newRaw.bgDataList.get(5)),
+ is(new BgWatchDataExt(new BgWatchData(
+ 120.0, 170.0, 80.0,
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*4*(16-5), 0
+ ))));
+
+ assertThat(new BgWatchDataExt(newRaw.bgDataList.get(11)),
+ is(new BgWatchDataExt(new BgWatchData(
+ 90.0, 170.0, 80.0,
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*4*(16-11), 0
+ ))));
+
+ // add single entries
+ newRaw.addToWatchSet(singleEntry1);
+ newRaw.addToWatchSet(singleEntry2);
+ assertThat(newRaw.bgDataList.size(), is(14));
+
+ assertThat(new BgWatchDataExt(newRaw.bgDataList.get(12)),
+ is(new BgWatchDataExt(new BgWatchData(
+ 92.0, 160.0, 90.0,
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*4*2, 1
+ ))));
+ assertThat(new BgWatchDataExt(newRaw.bgDataList.get(13)),
+ is(new BgWatchDataExt(new BgWatchData(
+ 88.0, 160.0, 90.0,
+ WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*4*1, 1
+ ))));
+
+ // ignore duplicates
+ newRaw.addToWatchSet(singleEntry2);
+ assertThat(newRaw.bgDataList.size(), is(14));
+ }
+
+ @Test
+ public void addToWatchSetCleanupOldTest() {
+ RawDisplayData newRaw = new RawDisplayData();
+
+ newRaw.addToWatchSet(dataMapForEntries(WearUtil.timestamp(),125));
+ assertThat(newRaw.bgDataList.size(), is(1));
+
+ WearUtilMocker.progressClock(Constants.HOUR_IN_MS*2);
+ newRaw.addToWatchSet(dataMapForEntries(WearUtil.timestamp(),140));
+ assertThat(newRaw.bgDataList.size(), is(2));
+
+ WearUtilMocker.progressClock(Constants.HOUR_IN_MS*1);
+ newRaw.addToWatchSet(dataMapForEntries(WearUtil.timestamp(),150));
+ WearUtilMocker.progressClock(Constants.HOUR_IN_MS*1 +Constants.MINUTE_IN_MS*30);
+ newRaw.addToWatchSet(dataMapForEntries(WearUtil.timestamp(),101));
+ assertThat(newRaw.bgDataList.size(), is(4));
+
+ WearUtilMocker.progressClock(Constants.MINUTE_IN_MS*30);
+ newRaw.addToWatchSet(dataMapForEntries(WearUtil.timestamp(),90));
+ assertThat(newRaw.bgDataList.size(), is(5));
+
+ WearUtilMocker.progressClock(Constants.HOUR_IN_MS*1 +Constants.MINUTE_IN_MS*30);
+ newRaw.addToWatchSet(dataMapForEntries(WearUtil.timestamp(),80));
+ assertThat(newRaw.bgDataList.size(), is(5));
+
+ WearUtilMocker.progressClock(Constants.HOUR_IN_MS*4);
+ newRaw.addToWatchSet(dataMapForEntries(WearUtil.timestamp(),92));
+ assertThat(newRaw.bgDataList.size(), is(2));
+
+ WearUtilMocker.progressClock(Constants.HOUR_IN_MS*5 +Constants.MINUTE_IN_MS*30);
+ newRaw.addToWatchSet(dataMapForEntries(WearUtil.timestamp(),107));
+ assertThat(newRaw.bgDataList.size(), is(1));
+
+ WearUtilMocker.progressClock(Constants.HOUR_IN_MS*6 +Constants.MINUTE_IN_MS*30);
+ newRaw.addToWatchSet(dataMapForEntries(WearUtil.timestamp()-Constants.HOUR_IN_MS*6,138));
+ assertThat(newRaw.bgDataList.size(), is(0));
+ }
+
+}
diff --git a/wear/src/test/java/info/nightscout/androidaps/data/RawDisplayDataStatusTest.java b/wear/src/test/java/info/nightscout/androidaps/data/RawDisplayDataStatusTest.java
new file mode 100644
index 0000000000..da0daea608
--- /dev/null
+++ b/wear/src/test/java/info/nightscout/androidaps/data/RawDisplayDataStatusTest.java
@@ -0,0 +1,176 @@
+package info.nightscout.androidaps.data;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.google.android.gms.wearable.DataMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import info.nightscout.androidaps.aaps;
+import info.nightscout.androidaps.interaction.utils.Constants;
+import info.nightscout.androidaps.interaction.utils.Persistence;
+import info.nightscout.androidaps.interaction.utils.WearUtil;
+import info.nightscout.androidaps.testing.mockers.AAPSMocker;
+import info.nightscout.androidaps.testing.mockers.AndroidMocker;
+import info.nightscout.androidaps.testing.mockers.RawDataMocker;
+import info.nightscout.androidaps.testing.mockers.WearUtilMocker;
+import info.nightscout.androidaps.testing.mocks.BundleMock;
+import info.nightscout.androidaps.testing.mocks.IntentMock;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest( { WearUtil.class, Log.class, SharedPreferences.class, Context.class, aaps.class, android.util.Base64.class, Intent.class } )
+public class RawDisplayDataStatusTest {
+
+ @Before
+ public void mock() throws Exception {
+ AAPSMocker.prepareMock();
+ AAPSMocker.resetMockedSharedPrefs();
+ AndroidMocker.mockBase64();
+ WearUtilMocker.prepareMockNoReal();
+ }
+
+ @Test
+ public void toDebugStringTest() {
+ RawDisplayData raw = RawDataMocker.rawDelta(5, "1.5");
+ raw.externalStatusString = "placeholder-here";
+
+ assertThat(raw.datetime, is(WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*5));
+ assertThat(raw.toDebugString(), containsString("placeholder-here"));
+ }
+
+ //==============================================================================================
+ // STATUS
+ //==============================================================================================
+
+ private DataMap dataMapForStatus() {
+ DataMap dataMap = new DataMap();
+ dataMap.putString("currentBasal", "120%");
+ dataMap.putString("battery", "76");
+ dataMap.putString("rigBattery", "40%");
+ dataMap.putBoolean("detailedIob", true);
+ dataMap.putString("iobSum", "12.5") ;
+ dataMap.putString("iobDetail","(11,2|1,3)");
+ dataMap.putString("cob","5(10)g");
+ dataMap.putString("bgi", "13");
+ dataMap.putBoolean("showBgi", false);
+ dataMap.putString("externalStatusString", "");
+ dataMap.putInt("batteryLevel", 1);
+ dataMap.putLong("openApsStatus", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*2);
+ return dataMap;
+ }
+
+ private void assertStatusEmpty(RawDisplayData newRaw) {
+ assertThat(newRaw.sBasalRate, is("-.--U/h"));
+ assertThat(newRaw.sUploaderBattery, is("--"));
+ assertThat(newRaw.sRigBattery, is("--"));
+ assertThat(newRaw.detailedIOB, is(false));
+ assertThat(newRaw.sIOB1, is("IOB"));
+ assertThat(newRaw.sIOB2, is("-.--"));
+ assertThat(newRaw.sCOB1, is("Carb"));
+ assertThat(newRaw.sCOB2, is("--g"));
+ assertThat(newRaw.sBgi, is("--"));
+ assertThat(newRaw.showBGI, is(false));
+ assertThat(newRaw.externalStatusString, is("no status"));
+ assertThat(newRaw.batteryLevel, is(1));
+ assertThat(newRaw.openApsStatus, is(-1L));
+ }
+
+ private void assertStatusOk(RawDisplayData newRaw) {
+ assertThat(newRaw.sBasalRate, is("120%"));
+ assertThat(newRaw.sUploaderBattery, is("76"));
+ assertThat(newRaw.sRigBattery, is("40%"));
+ assertThat(newRaw.detailedIOB, is(true));
+ assertThat(newRaw.sIOB1, is("12.5U"));
+ assertThat(newRaw.sIOB2, is("(11,2|1,3)"));
+ assertThat(newRaw.sCOB1, is("Carb"));
+ assertThat(newRaw.sCOB2, is("5(10)g"));
+ assertThat(newRaw.sBgi, is("13"));
+ assertThat(newRaw.showBGI, is(false));
+ assertThat(newRaw.externalStatusString, is(""));
+ assertThat(newRaw.batteryLevel, is(1));
+ assertThat(newRaw.openApsStatus, is(WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS*2));
+ }
+
+ @Test
+ public void updateStatusFromEmptyPersistenceTest() {
+ // GIVEN
+ Persistence persistence = new Persistence();
+ RawDisplayData newRaw = new RawDisplayData();
+
+ // WHEN
+ newRaw.updateFromPersistence(persistence);
+
+ // THEN
+ assertStatusEmpty(newRaw);
+ }
+
+ @Test
+ public void updateStatusFromPersistenceTest() {
+ // GIVEN
+ Persistence persistence = new Persistence();
+ RawDisplayData newRaw = new RawDisplayData();
+
+ // WHEN
+ Persistence.storeDataMap(RawDisplayData.STATUS_PERSISTENCE_KEY, dataMapForStatus());
+ newRaw.updateFromPersistence(persistence);
+
+ // THEN
+ assertStatusOk(newRaw);
+ }
+
+ @Test
+ public void partialUpdateStatusFromPersistenceTest() {
+ // GIVEN
+ Persistence persistence = new Persistence();
+ RawDisplayData newRaw = new RawDisplayData();
+
+ // WHEN
+ Persistence.storeDataMap(RawDisplayData.STATUS_PERSISTENCE_KEY, dataMapForStatus());
+ newRaw.updateForComplicationsFromPersistence(persistence);
+
+ // THEN
+ assertStatusOk(newRaw);
+ }
+
+ @Test
+ public void updateStatusFromMessageTest() {
+ // GIVEN
+ Intent intent = IntentMock.mock();
+ Bundle bundle = BundleMock.mock(dataMapForStatus());
+
+ intent.putExtra("status", bundle);
+ RawDisplayData newRaw = new RawDisplayData();
+
+ // WHEN
+ newRaw.updateStatusFromMessage(intent, null);
+
+ // THEN
+ assertStatusOk(newRaw);
+ }
+
+ @Test
+ public void updateStatusFromEmptyMessageTest() {
+ // GIVEN
+ Intent intent = IntentMock.mock();
+ RawDisplayData newRaw = new RawDisplayData();
+
+ // WHEN
+ newRaw.updateStatusFromMessage(intent, null);
+
+ // THEN
+ assertStatusEmpty(newRaw);
+ }
+
+}
diff --git a/wear/src/test/java/info/nightscout/androidaps/interaction/utils/DisplayFormatTest.java b/wear/src/test/java/info/nightscout/androidaps/interaction/utils/DisplayFormatTest.java
new file mode 100644
index 0000000000..98fb4fd11b
--- /dev/null
+++ b/wear/src/test/java/info/nightscout/androidaps/interaction/utils/DisplayFormatTest.java
@@ -0,0 +1,204 @@
+package info.nightscout.androidaps.interaction.utils;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import info.nightscout.androidaps.aaps;
+import info.nightscout.androidaps.data.RawDisplayData;
+import info.nightscout.androidaps.testing.mockers.AAPSMocker;
+import info.nightscout.androidaps.testing.mockers.WearUtilMocker;
+
+import static info.nightscout.androidaps.testing.mockers.RawDataMocker.rawCob;
+import static info.nightscout.androidaps.testing.mockers.RawDataMocker.rawCobIobBr;
+import static info.nightscout.androidaps.testing.mockers.RawDataMocker.rawDelta;
+import static info.nightscout.androidaps.testing.mockers.RawDataMocker.rawIob;
+import static info.nightscout.androidaps.testing.mockers.RawDataMocker.rawSgv;
+import static info.nightscout.androidaps.testing.mockers.WearUtilMocker.backInTime;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * This test covers DisplayFormat class (directly)
+ * but also SmallestDoubleString - due to carefully chosen input data to format
+ */
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest( { WearUtil.class, Log.class, SharedPreferences.class, Context.class, aaps.class } )
+public class DisplayFormatTest {
+
+ @Before
+ public void mock() throws Exception {
+ WearUtilMocker.prepareMock();
+ AAPSMocker.prepareMock();
+ AAPSMocker.resetMockedSharedPrefs();
+ }
+
+ @Test
+ public void shortTimeSinceTest() {
+
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,0,0,0)), is("0'"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,0,0,5)), is("0'"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,0,0,55)), is("0'"));
+
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,0,1,0)), is("1'"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,0,1,59)), is("1'"));
+
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,0,2,0)), is("2'"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,0,3,0)), is("3'"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,0,4,0)), is("4'"));
+
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,0,10,0)), is("10'"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,0,30,0)), is("30'"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,0,59,0)), is("59'"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,0,59,59)), is("59'"));
+
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,1,0,0)), is("1h"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,1,30,0)), is("1h"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,1,59,59)), is("1h"));
+
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,2,0,0)), is("2h"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,3,0,0)), is("3h"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,4,0,0)), is("4h"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,5,0,0)), is("5h"));
+
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,12,0,0)), is("12h"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,18,0,0)), is("18h"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(0,23,59,59)), is("23h"));
+
+ assertThat(DisplayFormat.shortTimeSince(backInTime(1,0,0,0)), is("1d"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(1,12,0,0)), is("1d"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(1,23,59,59)), is("1d"));
+
+ assertThat(DisplayFormat.shortTimeSince(backInTime(2,0,0,0)), is("2d"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(3,0,0,0)), is("3d"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(4,0,0,0)), is("4d"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(5,0,0,0)), is("5d"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(6,0,0,0)), is("6d"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(6,23,59,59)), is("6d"));
+
+ assertThat(DisplayFormat.shortTimeSince(backInTime(7,0,0,0)), is("1w"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(8,0,0,0)), is("1w"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(9,0,0,0)), is("1w"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(13,23,59,59)), is("1w"));
+
+ assertThat(DisplayFormat.shortTimeSince(backInTime(14,0,0,0)), is("2w"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(21,0,0,0)), is("3w"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(28,0,0,0)), is("4w"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(31,0,0,0)), is("4w"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(32,0,0,0)), is("4w"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(35,0,0,0)), is("5w"));
+
+ assertThat(DisplayFormat.shortTimeSince(backInTime(100,0,0,0)), is("14w"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(200,0,0,0)), is("28w"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(365,0,0,0)), is("52w"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(366,0,0,0)), is("52w"));
+ assertThat(DisplayFormat.shortTimeSince(backInTime(367,0,0,0)), is("52w"));
+ }
+
+ @Test
+ public void shortTrendTest() {
+ RawDisplayData raw = new RawDisplayData();
+ assertThat(DisplayFormat.shortTrend(raw), is("-- Δ--"));
+
+ raw.datetime = backInTime(0, 0, 2, 0);
+ assertThat(DisplayFormat.shortTrend(raw), is("2' Δ--"));
+
+ AAPSMocker.setMockedUnicodeComplicationsOn(true);
+
+ // shortening
+ assertThat(DisplayFormat.shortTrend(rawDelta(2, "1.2")), is("2' Δ1.2"));
+ assertThat(DisplayFormat.shortTrend(rawDelta(11,"1.2")), is("11' 1.2"));
+ assertThat(DisplayFormat.shortTrend(rawDelta(12,"0.7")), is("12' Δ.7"));
+ assertThat(DisplayFormat.shortTrend(rawDelta(10,"1.0")), is("10' Δ1"));
+ assertThat(DisplayFormat.shortTrend(rawDelta(14,"-5.0")), is("14' Δ-5"));
+ assertThat(DisplayFormat.shortTrend(rawDelta(13,"-5.1")), is("13' -5"));
+ assertThat(DisplayFormat.shortTrend(rawDelta(15,"0.87")), is("15' .87"));
+ assertThat(DisplayFormat.shortTrend(rawDelta(10,"-1.78")), is("10' -2"));
+ assertThat(DisplayFormat.shortTrend(rawDelta(3, "2.549")), is("3' 2.55"));
+ assertThat(DisplayFormat.shortTrend(rawDelta(1, "-1.563")), is("1' -1.6"));
+
+ // preserving separator
+ assertThat(DisplayFormat.shortTrend(rawDelta(2, "1,2")), is("2' Δ1,2"));
+ assertThat(DisplayFormat.shortTrend(rawDelta(15,"0,87")), is("15' ,87"));
+ assertThat(DisplayFormat.shortTrend(rawDelta(3, "+2,549")), is("3' 2,55"));
+ assertThat(DisplayFormat.shortTrend(rawDelta(1, "-1,563")), is("1' -1,6"));
+
+ // UTF-off mode - without delta symbol
+ AAPSMocker.setMockedUnicodeComplicationsOn(false);
+ assertThat(DisplayFormat.shortTrend(rawDelta(2, "1.2")), is("2' 1.2"));
+ assertThat(DisplayFormat.shortTrend(rawDelta(12,"0.7")), is("12' 0.7"));
+ assertThat(DisplayFormat.shortTrend(rawDelta(10,"1.0")), is("10' 1.0"));
+ assertThat(DisplayFormat.shortTrend(rawDelta(14,"-5.0")), is("14' -5"));
+ }
+
+ @Test
+ public void longGlucoseLine() {
+ assertThat(DisplayFormat.longGlucoseLine(rawSgv("125",2, "1.2")), is("125→ Δ1.2 (2')"));
+ assertThat(DisplayFormat.longGlucoseLine(rawSgv("97",11, "5.2")), is("97↗ Δ5.2 (11')"));
+ assertThat(DisplayFormat.longGlucoseLine(rawSgv("110",12,"0.7")), is("110→ Δ.7 (12')"));
+ assertThat(DisplayFormat.longGlucoseLine(rawSgv("65",10,"7.0")), is("65↗ Δ7 (10')"));
+ assertThat(DisplayFormat.longGlucoseLine(rawSgv("215",14,"-5.0")), is("215↘ Δ-5 (14')"));
+ assertThat(DisplayFormat.longGlucoseLine(rawSgv("8.3",13,"-5.1")), is("8.3↘ Δ-5.1 (13')"));
+ assertThat(DisplayFormat.longGlucoseLine(rawSgv("6.8",15,"10.83")), is("6.8↑ Δ10.83 (15')"));
+ assertThat(DisplayFormat.longGlucoseLine(rawSgv("13.2",10,"-11.78")), is("13.2↓ Δ-11.78 (10')"));
+ assertThat(DisplayFormat.longGlucoseLine(rawSgv("3.9",3, "2.549")), is("3.9→ Δ2.549 (3')"));
+ assertThat(DisplayFormat.longGlucoseLine(rawSgv("11.1",1, "-15.563")), is("11.1↓ Δ-15.563 (1')"));
+ }
+
+ @Test
+ public void longDetailsLineTest() {
+ AAPSMocker.setMockedUnicodeComplicationsOn(true);
+ assertThat(DisplayFormat.longDetailsLine(rawCobIobBr("0g", "0U", "3.5U/h")), is("0g ⁞ 0U ⁞ ⎍ 3.5U/h"));
+ assertThat(DisplayFormat.longDetailsLine(rawCobIobBr("50g", "7.56U", "0%")), is("50g ⁞ 7.56U ⁞ ⎍ 0%"));
+ assertThat(DisplayFormat.longDetailsLine(rawCobIobBr("12g", "3.23U", "120%")), is("12g ⁞ 3.23U ⁞ 120%"));
+ assertThat(DisplayFormat.longDetailsLine(rawCobIobBr("2(40)g", "-1.5U", "0.55U/h")), is("2(40)g ⁞ -2U ⁞ 0.55U/h"));
+ assertThat(DisplayFormat.longDetailsLine(rawCobIobBr("0(24)g", "0.05U", "160%")), is("0(24)g ⁞ 0.05U ⁞ 160%"));
+ assertThat(DisplayFormat.longDetailsLine(rawCobIobBr("47g", "13.87U", "220%")), is("47g ⁞ 13.87U ⁞ 220%"));
+ assertThat(DisplayFormat.longDetailsLine(rawCobIobBr("13(5)g", "5.90U", "300%")), is("13(5)g ⁞ 5.90U ⁞ 300%"));
+ assertThat(DisplayFormat.longDetailsLine(rawCobIobBr("11(50)g", "0U", "70%")), is("11(50)g ⁞ 0U ⁞ 70%"));
+ assertThat(DisplayFormat.longDetailsLine(rawCobIobBr("7g", "0.54U", "30%")), is("7g ⁞ 0.54U ⁞ ⎍ 30%"));
+ assertThat(DisplayFormat.longDetailsLine(rawCobIobBr("19(38)g", "35.545U", "12.9U/h")), is("19g ⁞ 36U ⁞ 12.9U/h"));
+ assertThat(DisplayFormat.longDetailsLine(rawCobIobBr("100(1)g", "12.345U", "6.98647U/h")), is("100g 12U 6.98647U/h"));
+
+ AAPSMocker.setMockedUnicodeComplicationsOn(false);
+ assertThat(DisplayFormat.longDetailsLine(rawCobIobBr("0g", "0U", "3.5U/h")), is("0g | 0U | 3.5U/h"));
+ assertThat(DisplayFormat.longDetailsLine(rawCobIobBr("50g", "7.56U", "0%")), is("50g | 7.56U | 0%"));
+ assertThat(DisplayFormat.longDetailsLine(rawCobIobBr("12g", "3.23U", "120%")), is("12g | 3.23U | 120%"));
+ assertThat(DisplayFormat.longDetailsLine(rawCobIobBr("7g", "0.54U", "30%")), is("7g | 0.54U | 30%"));
+ assertThat(DisplayFormat.longDetailsLine(rawCobIobBr("19(38)g", "35.545U", "12.9U/h")), is("19g | 36U | 12.9U/h"));
+ }
+
+ @Test
+ public void detailedIobTest() {
+ assertThat(DisplayFormat.detailedIob(rawIob("-1.29U", "(0,910|-2,20)")), is(Pair.create("-1.29U", ",91 -2")));
+ assertThat(DisplayFormat.detailedIob(rawIob("3.50U", "")), is(Pair.create("3.50U", "")));
+ assertThat(DisplayFormat.detailedIob(rawIob("12.5U", "(+1,4|-4.78)")), is(Pair.create("12.5U", "1,4 -5")));
+ assertThat(DisplayFormat.detailedIob(rawIob("0.67U", "some junks")), is(Pair.create(".67U", "")));
+ assertThat(DisplayFormat.detailedIob(rawIob("-11.0U", "(broken|data)")), is(Pair.create("-11U", "-- --")));
+ assertThat(DisplayFormat.detailedIob(rawIob("5.52U", "(0,5439|wrong)")), is(Pair.create("5.52U", ",54 --")));
+ assertThat(DisplayFormat.detailedIob(rawIob("-8.1U", "(|-8,1)")), is(Pair.create("-8.1U", "-- -8")));
+ assertThat(DisplayFormat.detailedIob(rawIob("-8.1U", "(|-8,1)")), is(Pair.create("-8.1U", "-- -8")));
+ assertThat(DisplayFormat.detailedIob(rawIob("7.6U", "(malformed)")), is(Pair.create("7.6U", "")));
+ assertThat(DisplayFormat.detailedIob(rawIob("-4.26U", "(6,97|1,3422|too much)")), is(Pair.create("-4.26U", "7 1,3")));
+ }
+
+ @Test
+ public void detailedCobTest() {
+ assertThat(DisplayFormat.detailedCob(rawCob("0g")), is(Pair.create("0g", "")));
+ assertThat(DisplayFormat.detailedCob(rawCob("50g")), is(Pair.create("50g", "")));
+ assertThat(DisplayFormat.detailedCob(rawCob("2(40)g")), is(Pair.create("2g", "40g")));
+ assertThat(DisplayFormat.detailedCob(rawCob("0(24)g")), is(Pair.create("0g", "24g")));
+ assertThat(DisplayFormat.detailedCob(rawCob("13(5)g")), is(Pair.create("13g", "5g")));
+ assertThat(DisplayFormat.detailedCob(rawCob("11(50)g")), is(Pair.create("11g", "50g")));
+ assertThat(DisplayFormat.detailedCob(rawCob("19(38)g")), is(Pair.create("19g", "38g")));
+ assertThat(DisplayFormat.detailedCob(rawCob("100(1)g")), is(Pair.create("100g", "1g")));
+ }
+
+}
diff --git a/wear/src/test/java/info/nightscout/androidaps/interaction/utils/PairTest.java b/wear/src/test/java/info/nightscout/androidaps/interaction/utils/PairTest.java
new file mode 100644
index 0000000000..ce63330e9c
--- /dev/null
+++ b/wear/src/test/java/info/nightscout/androidaps/interaction/utils/PairTest.java
@@ -0,0 +1,68 @@
+package info.nightscout.androidaps.interaction.utils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(PowerMockRunner.class)
+public class PairTest {
+
+ @Test
+ public void pairEqualsTest() {
+ // GIVEN
+ Pair left = Pair.create("aa", "bbb");
+ Pair right = Pair.create("ccc", "dd");
+ Pair another = Pair.create("aa", "bbb");
+ Pair samePos1 = Pair.create("aa", "d");
+ Pair samePos2 = Pair.create("zzzzz", "bbb");
+ Pair no1 = Pair.create(12, 345L);
+ Pair no2 = Pair.create(-943, 42);
+ Pair no3 = Pair.create(12L, 345);
+ Pair no4 = Pair.create(12, 345L);
+
+ // THEN
+ assertNotEquals(left, right);
+ assertEquals(left, another);
+ assertNotEquals(left, samePos1);
+ assertNotEquals(left, samePos2);
+ assertNotEquals(no1, no2);
+ assertNotEquals(no1, no3);
+ assertEquals(no1, no4);
+
+ assertFalse(left.equals("aa bbb"));
+ }
+
+ @Test
+ public void pairHashTest() {
+ // GIVEN
+ Pair inserted = Pair.create("aa", "bbb");
+ Set set = new HashSet<>();
+
+ // THEN
+ assertFalse(set.contains(inserted));
+ set.add(inserted);
+ assertTrue(set.contains(inserted));
+ }
+
+ @Test
+ public void toStringTest() {
+ // GIVEN
+ Pair pair = Pair.create("the-first", "2nd");
+
+ assertThat(pair.toString(), containsString("the-first"));
+ assertThat(pair+"", containsString("2nd"));
+ }
+
+
+
+}
diff --git a/wear/src/test/java/info/nightscout/androidaps/interaction/utils/PersistenceTest.java b/wear/src/test/java/info/nightscout/androidaps/interaction/utils/PersistenceTest.java
new file mode 100644
index 0000000000..1faa4ddfe7
--- /dev/null
+++ b/wear/src/test/java/info/nightscout/androidaps/interaction/utils/PersistenceTest.java
@@ -0,0 +1,189 @@
+package info.nightscout.androidaps.interaction.utils;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import com.google.android.gms.wearable.DataMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.util.Set;
+
+import info.nightscout.androidaps.aaps;
+import info.nightscout.androidaps.testing.mockers.AAPSMocker;
+import info.nightscout.androidaps.testing.mockers.AndroidMocker;
+import info.nightscout.androidaps.testing.mockers.LogMocker;
+import info.nightscout.androidaps.testing.mockers.WearUtilMocker;
+
+import static info.nightscout.androidaps.testing.mockers.WearUtilMocker.REF_NOW;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest( { WearUtil.class, Log.class, SharedPreferences.class, Context.class, aaps.class, android.util.Base64.class} )
+public class PersistenceTest {
+
+ @Before
+ public void mock() throws Exception {
+ WearUtilMocker.prepareMock();
+ LogMocker.prepareMock();
+ AAPSMocker.prepareMock();
+ AAPSMocker.resetMockedSharedPrefs();
+ AndroidMocker.mockBase64();
+ }
+
+ @Test
+ public void putStringTest() {
+ // GIVEN
+ Persistence persistence = new Persistence();
+
+ // WHEN
+ final String emptyGot = persistence.getString("test-key", "default-value");
+ persistence.putString("test-key", "newValue");
+ final String updatedGot = persistence.getString("test-key", "another-default-value");
+
+ // THEN
+ assertThat(emptyGot, is("default-value"));
+ assertThat(updatedGot, is("newValue"));
+ }
+
+ @Test
+ public void putBooleanTest() {
+ // GIVEN
+ Persistence persistence = new Persistence();
+
+ // WHEN
+ final boolean emptyGot = persistence.getBoolean("test-key", false);
+ persistence.putBoolean("test-key", true);
+ final boolean updatedGot = persistence.getBoolean("test-key", false);
+
+ // THEN
+ assertFalse(emptyGot);
+ assertTrue(updatedGot);
+ }
+
+ @Test
+ public void whenDataUpdatedTest() {
+ // GIVEN
+ Persistence persistence = new Persistence();
+ DataMap map = new DataMap();
+
+ // WHEN
+ final long whenNotUpdated = persistence.whenDataUpdated();
+
+ Persistence.storeDataMap("data-map", map);
+ final long whenUpdatedFirst = persistence.whenDataUpdated();
+
+ WearUtilMocker.progressClock(60000);
+ Persistence.storeDataMap("data-map", map);
+ final long whenUpdatedNext = persistence.whenDataUpdated();
+
+ // THEN
+ assertThat(whenNotUpdated, is(0L));
+ assertThat(whenUpdatedFirst, is(REF_NOW));
+ assertThat(whenUpdatedNext, is(REF_NOW + 60000));
+ }
+
+ @Test
+ public void getDataMapTest() {
+ // GIVEN
+ Persistence persistence = new Persistence();
+ DataMap map = new DataMap();
+ map.putByteArray("test-key", new byte[]{9, 42, 127, -5});
+
+ // WHEN
+ DataMap notExisting = persistence.getDataMap("not-there");
+ Persistence.storeDataMap("data-map", map);
+ DataMap restoredMap = persistence.getDataMap("data-map");
+ byte[] restoredMapContents = restoredMap.getByteArray("test-key");
+
+ // THEN
+ assertNull(notExisting);
+ assertNotNull(restoredMap);
+ assertTrue(restoredMap.containsKey("test-key"));
+
+ assertThat(restoredMapContents.length, is(4));
+ assertThat(restoredMapContents[0], is((byte)9));
+ assertThat(restoredMapContents[1], is((byte)42));
+ assertThat(restoredMapContents[2], is((byte)127));
+ assertThat(restoredMapContents[3], is((byte)-5));
+ }
+
+ @Test
+ public void brokenDataMapTest() {
+ // GIVEN
+ Persistence persistence = new Persistence();
+
+ // WHEN
+ persistence.putString("data-map", "ZmFrZSBkYXRh");
+ DataMap restoredMap = persistence.getDataMap("data-map");
+
+ // THEN
+ assertNull(restoredMap);
+ }
+
+ @Test
+ public void setsTest() {
+ // GIVEN
+ Persistence persistence = new Persistence();
+
+ // WHEN
+ Set emptySet = persistence.getSetOf("some fake id");
+
+ persistence.addToSet("test-set", "element1");
+ persistence.addToSet("test-set", "second-elem");
+ persistence.addToSet("test-set", "3rd");
+ persistence.addToSet("test-set", "czwarty");
+ persistence.addToSet("test-set", "V");
+ persistence.addToSet("test-set", "6");
+
+ Set initialSet = persistence.getSetOf("test-set");
+ Set sameInitialSet = Persistence.setOf("test-set");
+
+ persistence.addToSet("test-set", "second-elem");
+ persistence.addToSet("test-set", "new-one");
+
+ Set extendedSet = persistence.getSetOf("test-set");
+
+ persistence.removeFromSet("test-set", "czwarty");
+ persistence.removeFromSet("test-set", "6");
+ persistence.removeFromSet("test-set", "3rd");
+
+ Set reducedSet = persistence.getSetOf("test-set");
+
+ // THEN
+ assertThat(emptySet.size(), is(0));
+
+ assertThat(initialSet.size(), is(6));
+ assertTrue(initialSet.contains("element1"));
+ assertTrue(initialSet.contains("second-elem"));
+ assertTrue(initialSet.contains("3rd"));
+ assertTrue(initialSet.contains("czwarty"));
+ assertTrue(initialSet.contains("V"));
+ assertTrue(initialSet.contains("6"));
+
+ assertThat(initialSet, is(sameInitialSet));
+
+ assertThat(extendedSet.size(), is(7));
+ assertTrue(extendedSet.contains("new-one"));
+
+ assertThat(reducedSet.size(), is(4));
+ assertTrue(reducedSet.contains("element1"));
+ assertTrue(reducedSet.contains("second-elem"));
+ assertFalse(reducedSet.contains("3rd"));
+ assertFalse(reducedSet.contains("czwarty"));
+ assertTrue(reducedSet.contains("V"));
+ assertFalse(reducedSet.contains("6"));
+ assertTrue(reducedSet.contains("new-one"));
+ }
+
+}
diff --git a/wear/src/test/java/info/nightscout/androidaps/interaction/utils/WearUtilTest.java b/wear/src/test/java/info/nightscout/androidaps/interaction/utils/WearUtilTest.java
new file mode 100644
index 0000000000..9e60423c6a
--- /dev/null
+++ b/wear/src/test/java/info/nightscout/androidaps/interaction/utils/WearUtilTest.java
@@ -0,0 +1,186 @@
+package info.nightscout.androidaps.interaction.utils;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import com.google.android.gms.wearable.DataMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import info.nightscout.androidaps.testing.mockers.LogMocker;
+import info.nightscout.androidaps.testing.mockers.WearUtilMocker;
+import info.nightscout.androidaps.testing.mocks.BundleMock;
+
+import static info.nightscout.androidaps.testing.mockers.WearUtilMocker.REF_NOW;
+import static org.hamcrest.CoreMatchers.both;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Created by dlvoy on 22.11.2019.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest( { WearUtil.class, Log.class} )
+public class WearUtilTest {
+
+ @Before
+ public void mock() throws Exception {
+ WearUtilMocker.prepareMock();
+ LogMocker.prepareMock();
+ }
+
+ @Test
+ public void timestampAndTimeDiffsTest() {
+
+ // smoke for mocks - since we freeze "now" to get stable tests
+ assertThat(REF_NOW, is(WearUtil.timestamp()));
+
+ assertThat(0L, is(WearUtil.msTill(REF_NOW)));
+ assertThat(3456L, is(WearUtil.msTill(REF_NOW+3456L)));
+ assertThat(-6294L, is(WearUtil.msTill(REF_NOW-6294L)));
+
+ assertThat(0L, is(WearUtil.msTill(REF_NOW)));
+ assertThat(-3456L, is(WearUtil.msSince(REF_NOW+3456L)));
+ assertThat(6294L, is(WearUtil.msSince(REF_NOW-6294L)));
+ }
+
+ @Test
+ public void joinSetTest() {
+ // GIVEN
+ Set refSet = new HashSet<>();
+ refSet.add("element1");
+ refSet.add("second-elem");
+ refSet.add("3rd");
+
+ // WHEN
+ String joined = WearUtil.joinSet(refSet, "|");
+
+ // THEN
+ // we cannot guarantee order of items in joined string
+ // but all items have to be there
+ assertThat(joined.length(), is("element1".length() + "second-elem".length() + "3rd".length() + "|".length()*2 ));
+
+ assertThat("|"+joined+"|", containsString("|"+"element1"+"|"));
+ assertThat("|"+joined+"|", containsString("|"+"second-elem"+"|"));
+ assertThat("|"+joined+"|", containsString("|"+"3rd"+"|"));
+ }
+
+ @Test
+ public void explodeSetTest() {
+ // GIVEN
+ String serializedSet = "second-elem:element1:3rd";
+
+ // WHEN
+ Set set = WearUtil.explodeSet(serializedSet, ":");
+
+ // THEN
+ assertThat(set.size(), is(3));
+
+ assertTrue(set.contains("element1"));
+ assertTrue(set.contains("second-elem"));
+ assertTrue(set.contains("3rd"));
+ }
+
+ @Test
+ public void explodeSetEmptyElemsTest() {
+ // GIVEN
+ String serializedSet = ",,,,real,,,another,,,";
+
+ // WHEN
+ Set set = WearUtil.explodeSet(serializedSet, ",");
+
+ // THEN
+ assertThat(set.size(), is(2));
+
+ assertThat(true, is(set.contains("real")));
+ assertThat(true, is(set.contains("another")));
+ }
+
+ @Test
+ public void joinExplodeStabilityTest() {
+ // GIVEN
+ Set refSet = new HashSet<>();
+ refSet.add("element1");
+ refSet.add("second-elem");
+ refSet.add("3rd");
+ refSet.add("czwarty");
+ refSet.add("V");
+ refSet.add("6");
+
+ // WHEN
+ String joinedSet = WearUtil.joinSet(refSet, "#");
+ final Set explodedSet = WearUtil.explodeSet(joinedSet, "#");
+
+ // THEN
+ assertThat(explodedSet, is(refSet));
+ }
+
+ @Test
+ public void threadSleepTest() {
+ // GIVEN
+ final long testStart = System.currentTimeMillis();
+ final long requestedSleepDuration = 85L;
+ final long measuringMargin = 100L;
+
+ // WHEN
+ WearUtil.threadSleep(requestedSleepDuration);
+ final long measuredSleepDuration = System.currentTimeMillis() - testStart;
+
+ // THEN
+ // we cannot guarantee to be exact to the millisecond - we add some margin of error
+ assertThat(measuredSleepDuration, is(both(greaterThan(60L)).and(lessThan(requestedSleepDuration+measuringMargin))));
+ }
+
+ @Test
+ public void rateLimitTest() {
+ // WHEN
+ final boolean firstCall = WearUtil.isBelowRateLimit("test-limit", 3);
+ final boolean callAfterward = WearUtil.isBelowRateLimit("test-limit", 3);
+ WearUtilMocker.progressClock(500L);
+ final boolean callTooSoon = WearUtil.isBelowRateLimit("test-limit", 3);
+ WearUtilMocker.progressClock(3100L);
+ final boolean callAfterRateLimit = WearUtil.isBelowRateLimit("test-limit", 3);
+
+ // THEN
+ assertTrue(firstCall);
+ assertFalse(callAfterward);
+ assertFalse(callTooSoon);
+ assertTrue(callAfterRateLimit);
+ }
+
+ /**
+ * It tests if mock for bundleToDataMap is sane,
+ * because original impl. of bundleToDataMap
+ * uses DataMap.fromBundle which need Android SDK runtime
+ */
+ @Test
+ public void bundleToDataMapTest() throws Exception {
+ // GIVEN
+ DataMap refMap = new DataMap();
+ refMap.putString("ala", "ma kota");
+ refMap.putInt("why", 42);
+ refMap.putFloatArray("list", new float[]{0.45f, 3.2f, 6.8f});
+
+ // WHEN
+ WearUtilMocker.prepareMockNoReal();
+ Bundle bundle = BundleMock.mock(refMap);
+ DataMap gotMap = WearUtil.bundleToDataMap(bundle);
+
+ // THEN
+ assertThat(gotMap, is(refMap));
+ }
+
+
+}
\ No newline at end of file
diff --git a/wear/src/test/java/info/nightscout/androidaps/testing/mockers/AAPSMocker.java b/wear/src/test/java/info/nightscout/androidaps/testing/mockers/AAPSMocker.java
new file mode 100644
index 0000000000..7684d169f1
--- /dev/null
+++ b/wear/src/test/java/info/nightscout/androidaps/testing/mockers/AAPSMocker.java
@@ -0,0 +1,58 @@
+package info.nightscout.androidaps.testing.mockers;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import org.junit.Assert;
+import org.mockito.ArgumentMatchers;
+import org.mockito.invocation.InvocationOnMock;
+import org.powermock.api.mockito.PowerMockito;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import info.nightscout.androidaps.aaps;
+import info.nightscout.androidaps.testing.mocks.SharedPreferencesMock;
+
+import static org.mockito.Mockito.mock;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+
+public class AAPSMocker {
+
+ private static final Map mockedSharedPrefs = new HashMap<>();
+ private static boolean unicodeComplicationsOn = true;
+
+ public static void prepareMock() throws Exception {
+ Context mockedContext = mock(Context.class);
+ mockStatic(aaps.class, InvocationOnMock::callRealMethod);
+
+ PowerMockito.when(aaps.class, "getAppContext").thenReturn(mockedContext);
+ PowerMockito.when(mockedContext, "getSharedPreferences", ArgumentMatchers.anyString(), ArgumentMatchers.anyInt()).thenAnswer(invocation -> {
+
+ final String key = invocation.getArgument(0);
+ if (mockedSharedPrefs.containsKey(key)) {
+ return mockedSharedPrefs.get(key);
+ } else {
+ SharedPreferencesMock newPrefs = new SharedPreferencesMock();
+ mockedSharedPrefs.put(key, newPrefs);
+ return newPrefs;
+ }
+ });
+ PowerMockito.when(aaps.class, "areComplicationsUnicode").thenAnswer(invocation -> unicodeComplicationsOn);
+
+ setMockedUnicodeComplicationsOn(true);
+ resetMockedSharedPrefs();
+ }
+
+ public static void resetMockedSharedPrefs() {
+ mockedSharedPrefs.clear();
+ }
+
+ public static void resetMockedSharedPrefs(String forKey) {
+ mockedSharedPrefs.remove(forKey);
+ }
+
+ public static void setMockedUnicodeComplicationsOn(boolean setUnicodeOn) {
+ unicodeComplicationsOn = setUnicodeOn;
+ }
+}
diff --git a/wear/src/test/java/info/nightscout/androidaps/testing/mockers/AndroidMocker.java b/wear/src/test/java/info/nightscout/androidaps/testing/mockers/AndroidMocker.java
new file mode 100644
index 0000000000..a1addfdc1d
--- /dev/null
+++ b/wear/src/test/java/info/nightscout/androidaps/testing/mockers/AndroidMocker.java
@@ -0,0 +1,36 @@
+package info.nightscout.androidaps.testing.mockers;
+
+import org.junit.Assert;
+import org.powermock.api.mockito.PowerMockito;
+
+import java.util.Base64;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+
+public class AndroidMocker {
+
+ public static void mockBase64() throws Exception {
+ mockStatic(android.util.Base64.class);
+
+ PowerMockito.when(android.util.Base64.class, "decode", anyString(), anyInt()).thenAnswer(invocation -> {
+
+ final String payload = invocation.getArgument(0);
+ try {
+ return Base64.getDecoder().decode(payload);
+ } catch (java.lang.IllegalArgumentException ex) {
+ return null;
+ }
+ });
+
+ PowerMockito.when(android.util.Base64.class, "encodeToString", any(), anyInt()).thenAnswer(invocation -> {
+
+ final byte[] payload = invocation.getArgument(0);
+ return Base64.getEncoder().encodeToString(payload);
+
+ });
+ }
+
+}
diff --git a/wear/src/test/java/info/nightscout/androidaps/testing/mockers/LogMocker.java b/wear/src/test/java/info/nightscout/androidaps/testing/mockers/LogMocker.java
new file mode 100644
index 0000000000..5c374665bc
--- /dev/null
+++ b/wear/src/test/java/info/nightscout/androidaps/testing/mockers/LogMocker.java
@@ -0,0 +1,11 @@
+package info.nightscout.androidaps.testing.mockers;
+
+import android.util.Log;
+
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+
+public class LogMocker {
+ public static void prepareMock() {
+ mockStatic(Log.class);
+ }
+}
diff --git a/wear/src/test/java/info/nightscout/androidaps/testing/mockers/RawDataMocker.java b/wear/src/test/java/info/nightscout/androidaps/testing/mockers/RawDataMocker.java
new file mode 100644
index 0000000000..7d06f00c51
--- /dev/null
+++ b/wear/src/test/java/info/nightscout/androidaps/testing/mockers/RawDataMocker.java
@@ -0,0 +1,65 @@
+package info.nightscout.androidaps.testing.mockers;
+
+import info.nightscout.androidaps.data.RawDisplayData;
+import info.nightscout.androidaps.interaction.utils.SafeParse;
+
+import static info.nightscout.androidaps.testing.mockers.WearUtilMocker.backInTime;
+
+public class RawDataMocker {
+
+ public static RawDisplayData rawSgv(String sgv, int m, String deltaString) {
+ RawDisplayData raw = new RawDisplayData();
+ raw.datetime = backInTime(0, 0, m, 0);
+ raw.sDelta = deltaString;
+ raw.sSgv = sgv;
+
+ double delta = SafeParse.stringToDouble(deltaString);
+
+ if (delta <= (-3.5 * 5)) {
+ raw.sDirection = "\u21ca";
+ } else if (delta <= (-2 * 5)) {
+ raw.sDirection = "\u2193";
+ } else if (delta <= (-1 * 5)) {
+ raw.sDirection = "\u2198";
+ } else if (delta <= (1 * 5)) {
+ raw.sDirection = "\u2192";
+ } else if (delta <= (2 * 5)) {
+ raw.sDirection = "\u2197";
+ } else if (delta <= (3.5 * 5)) {
+ raw.sDirection = "\u2191";
+ } else {
+ raw.sDirection = "\u21c8";
+ }
+
+ return raw;
+ }
+
+ public static RawDisplayData rawDelta(int m, String delta) {
+ RawDisplayData raw = new RawDisplayData();
+ raw.datetime = backInTime(0, 0, m, 0);
+ raw.sDelta = delta;
+ return raw;
+ }
+
+ public static RawDisplayData rawCobIobBr(String cob, String iob, String br) {
+ RawDisplayData raw = new RawDisplayData();
+ raw.sCOB2 = cob;
+ raw.sIOB1 = iob;
+ raw.sBasalRate = br;
+ return raw;
+ }
+
+ public static RawDisplayData rawIob(String iob, String iob2) {
+ RawDisplayData raw = new RawDisplayData();
+ raw.sIOB1 = iob;
+ raw.sIOB2 = iob2;
+ return raw;
+ }
+
+ public static RawDisplayData rawCob(String cob) {
+ RawDisplayData raw = new RawDisplayData();
+ raw.sCOB2 = cob;
+ return raw;
+ }
+
+}
diff --git a/wear/src/test/java/info/nightscout/androidaps/testing/mockers/WearUtilMocker.java b/wear/src/test/java/info/nightscout/androidaps/testing/mockers/WearUtilMocker.java
new file mode 100644
index 0000000000..11a89f0699
--- /dev/null
+++ b/wear/src/test/java/info/nightscout/androidaps/testing/mockers/WearUtilMocker.java
@@ -0,0 +1,98 @@
+package info.nightscout.androidaps.testing.mockers;
+
+import android.os.Bundle;
+
+import com.google.android.gms.wearable.Asset;
+import com.google.android.gms.wearable.DataMap;
+
+import org.junit.Assert;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.powermock.api.mockito.PowerMockito;
+
+import java.util.ArrayList;
+
+import info.nightscout.androidaps.interaction.utils.Constants;
+import info.nightscout.androidaps.interaction.utils.WearUtil;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+
+public class WearUtilMocker {
+
+ public static final long REF_NOW = 1572610530000L;
+ private static long clockMsDiff = 0L;
+
+ public static void prepareMock() throws Exception {
+ resetClock();
+ mockStatic(WearUtil.class, InvocationOnMock::callRealMethod);
+
+ // because we cleverly used timestamp() by implementation, we can mock it
+ // and control the time in tests
+ PowerMockito.when(WearUtil.class, "timestamp").then(invocation -> (REF_NOW + clockMsDiff));
+ }
+
+ public static void prepareMockNoReal() throws Exception {
+ resetClock();
+ mockStatic(WearUtil.class);
+
+ PowerMockito.when(WearUtil.class, "timestamp").then(invocation -> REF_NOW + clockMsDiff);
+ PowerMockito.when(WearUtil.class, "getWakeLock", anyString(), anyInt()).then(invocation -> null);
+ PowerMockito.when(WearUtil.class, "bundleToDataMap", any(Bundle.class)).then(bundleToDataMapMock);
+ }
+
+ public static void resetClock() {
+ clockMsDiff = 0L;
+ }
+
+ public static void progressClock(long byMilliseconds) {
+ clockMsDiff = clockMsDiff + byMilliseconds;
+ }
+
+ public static void setClock(long atMillisecondsSinceEpoch) {
+ clockMsDiff = atMillisecondsSinceEpoch - REF_NOW;
+ }
+
+ public static long backInTime(int d, int h, int m, int s) {
+ return REF_NOW - (Constants.DAY_IN_MS * d + Constants.HOUR_IN_MS * h + Constants.MINUTE_IN_MS * m + Constants.SECOND_IN_MS * s);
+ }
+
+ private static Answer bundleToDataMapMock = invocation -> {
+ DataMap map = new DataMap();
+ Bundle bundle = invocation.getArgument(0);
+ for(String key: bundle.keySet()) {
+ Object v = bundle.get(key);
+ if (v instanceof Asset) map.putAsset(key, (Asset)v);
+ if (v instanceof Boolean) map.putBoolean(key, (Boolean)v);
+ if (v instanceof Byte) map.putByte(key, (Byte)v);
+ if (v instanceof byte[]) map.putByteArray(key, (byte[])v);
+ if (v instanceof DataMap) map.putDataMap(key, (DataMap)v);
+ if (v instanceof Double) map.putDouble(key, (Double)v);
+ if (v instanceof Float) map.putFloat(key, (Float)v);
+ if (v instanceof float[]) map.putFloatArray(key, (float[])v);
+ if (v instanceof Integer) map.putInt(key, (Integer)v);
+ if (v instanceof Long) map.putLong(key, (Long)v);
+ if (v instanceof long[]) map.putLongArray(key, (long[])v);
+ if (v instanceof String) map.putString(key, (String)v);
+ if (v instanceof String[]) map.putStringArray(key, (String[])v);
+
+ if (v instanceof ArrayList) {
+ if (!((ArrayList)v).isEmpty()) {
+ if (((ArrayList) v).get(0) instanceof Integer) {
+ map.putIntegerArrayList(key, (ArrayList)v);
+ }
+ if (((ArrayList) v).get(0) instanceof String) {
+ map.putStringArrayList(key, (ArrayList)v);
+ }
+ if (((ArrayList) v).get(0) instanceof DataMap) {
+ map.putDataMapArrayList(key, (ArrayList)v);
+ }
+ }
+ }
+ }
+
+ return map;
+ };
+}
diff --git a/wear/src/test/java/info/nightscout/androidaps/testing/mocks/BundleMock.java b/wear/src/test/java/info/nightscout/androidaps/testing/mocks/BundleMock.java
new file mode 100644
index 0000000000..24449dd352
--- /dev/null
+++ b/wear/src/test/java/info/nightscout/androidaps/testing/mocks/BundleMock.java
@@ -0,0 +1,233 @@
+package info.nightscout.androidaps.testing.mocks;
+
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import com.google.android.gms.wearable.DataMap;
+
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyByte;
+import static org.mockito.Matchers.anyChar;
+import static org.mockito.Matchers.anyDouble;
+import static org.mockito.Matchers.anyFloat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyShort;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+public final class BundleMock {
+
+ public static Bundle mock() {
+ return mock(new HashMap());
+ }
+
+ public static Bundle mock(DataMap dataMap) {
+ HashMap hm = new HashMap<>();
+ for (String key : dataMap.keySet()) {
+ hm.put(key, dataMap.get(key));
+ }
+ return mock(hm);
+ }
+
+ public static Bundle mock(final HashMap map) {
+
+ Answer unsupported = new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ throw new UnsupportedOperationException();
+ }
+ };
+ Answer put = new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ map.put((String)invocation.getArguments()[0], invocation.getArguments()[1]);
+ return null;
+ }
+ };
+ Answer