diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index 379afabddf..785aa84c38 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -129,7 +129,14 @@ public class MainApp extends Application { sConstraintsChecker = new ConstraintChecker(); sDatabaseHelper = OpenHelperManager.getHelper(sInstance, DatabaseHelper.class); - Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> log.error("Uncaught exception crashing app", ex)); + Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> { + if (ex instanceof InternalError) { + // usually the app trying to spawn a thread while being killed + return; + } + + log.error("Uncaught exception crashing app", ex); + }); try { if (FabricPrivacy.fabricEnabled()) { @@ -204,7 +211,7 @@ public class MainApp extends Application { pluginsList.add(SourcePoctechPlugin.getPlugin()); pluginsList.add(SourceTomatoPlugin.getPlugin()); pluginsList.add(SourceEversensePlugin.getPlugin()); - if (!Config.NSCLIENT) pluginsList.add(SmsCommunicatorPlugin.getPlugin()); + if (!Config.NSCLIENT) pluginsList.add(SmsCommunicatorPlugin.INSTANCE); pluginsList.add(FoodPlugin.getPlugin()); pluginsList.add(WearPlugin.initPlugin(this)); diff --git a/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.java b/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.java index 367429dbbf..0e93a47151 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.java @@ -9,27 +9,28 @@ import android.preference.PreferenceActivity; import android.preference.PreferenceFragment; import android.preference.PreferenceGroup; import android.preference.PreferenceManager; -import android.preference.PreferenceScreen; -import android.text.TextUtils; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.events.EventRebuildTabs; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.plugins.general.careportal.CareportalPlugin; -import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin; -import info.nightscout.androidaps.plugins.general.tidepool.TidepoolPlugin; -import info.nightscout.androidaps.plugins.general.tidepool.comm.TidepoolUploader; -import info.nightscout.androidaps.plugins.insulin.InsulinOrefFreePeakPlugin; import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; -import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin; import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin; import info.nightscout.androidaps.plugins.aps.openAPSMA.OpenAPSMAPlugin; import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin; +import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin; +import info.nightscout.androidaps.plugins.general.careportal.CareportalPlugin; +import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin; +import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin; +import info.nightscout.androidaps.plugins.general.tidepool.TidepoolPlugin; +import info.nightscout.androidaps.plugins.general.wear.WearPlugin; +import info.nightscout.androidaps.plugins.general.xdripStatusline.StatuslinePlugin; +import info.nightscout.androidaps.plugins.insulin.InsulinOrefFreePeakPlugin; import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin; import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin; import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin; @@ -42,14 +43,9 @@ import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin; -import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin; -import info.nightscout.androidaps.plugins.general.wear.WearPlugin; -import info.nightscout.androidaps.plugins.general.xdripStatusline.StatuslinePlugin; import info.nightscout.androidaps.plugins.source.SourceDexcomPlugin; -import info.nightscout.androidaps.utils.LocaleHelper; import info.nightscout.androidaps.utils.OKDialog; import info.nightscout.androidaps.utils.SP; -import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin; public class PreferencesActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener { MyPreferenceFragment myPreferenceFragment; @@ -80,7 +76,7 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre if (key.equals(MainApp.gs(R.string.key_openapsama_useautosens)) && SP.getBoolean(R.string.key_openapsama_useautosens, false)) { OKDialog.show(this, MainApp.gs(R.string.configbuilder_sensitivity), MainApp.gs(R.string.sensitivity_warning), null); } - updatePrefSummary(myPreferenceFragment.getPreference(key)); + updatePrefSummary(myPreferenceFragment.findPreference(key)); } private static void updatePrefSummary(Preference pref) { @@ -92,13 +88,13 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre EditTextPreference editTextPref = (EditTextPreference) pref; if (pref.getKey().contains("password") || pref.getKey().contains("secret")) { pref.setSummary("******"); - } else if (pref.getKey().equals(MainApp.gs(R.string.key_danars_name))) { - pref.setSummary(SP.getString(R.string.key_danars_name, "")); } else if (editTextPref.getText() != null) { ((EditTextPreference) pref).setDialogMessage(editTextPref.getDialogMessage()); pref.setSummary(editTextPref.getText()); - } else if (pref.getKey().contains("smscommunicator_allowednumbers") && (editTextPref.getText() == null || TextUtils.isEmpty(editTextPref.getText().trim()))) { - pref.setSummary(MainApp.gs(R.string.smscommunicator_allowednumbers_summary)); + } else { + for (PluginBase plugin : MainApp.getPluginsList()) { + plugin.updatePreferenceSummary(pref); + } } } } @@ -188,7 +184,7 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre addPreferencesFromResourceIfEnabled(NSClientPlugin.getPlugin(), PluginType.GENERAL); addPreferencesFromResourceIfEnabled(TidepoolPlugin.INSTANCE, PluginType.GENERAL); - addPreferencesFromResourceIfEnabled(SmsCommunicatorPlugin.getPlugin(), PluginType.GENERAL); + addPreferencesFromResourceIfEnabled(SmsCommunicatorPlugin.INSTANCE, PluginType.GENERAL); addPreferencesFromResourceIfEnabled(AutomationPlugin.INSTANCE, PluginType.GENERAL); addPreferencesFromResource(R.xml.pref_others); @@ -198,26 +194,11 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre addPreferencesFromResourceIfEnabled(StatuslinePlugin.getPlugin(), PluginType.GENERAL); } - if (Config.NSCLIENT) { - PreferenceScreen scrnAdvancedSettings = (PreferenceScreen) findPreference(getString(R.string.key_advancedsettings)); - if (scrnAdvancedSettings != null) { - scrnAdvancedSettings.removePreference(getPreference(getString(R.string.key_statuslights_res_warning))); - scrnAdvancedSettings.removePreference(getPreference(getString(R.string.key_statuslights_res_critical))); - scrnAdvancedSettings.removePreference(getPreference(getString(R.string.key_statuslights_bat_warning))); - scrnAdvancedSettings.removePreference(getPreference(getString(R.string.key_statuslights_bat_critical))); - scrnAdvancedSettings.removePreference(getPreference(getString(R.string.key_show_statuslights))); - scrnAdvancedSettings.removePreference(getPreference(getString(R.string.key_show_statuslights_extended))); - } - } - initSummary(getPreferenceScreen()); - final Preference tidepoolTestLogin = findPreference(MainApp.gs(R.string.key_tidepool_test_login)); - if (tidepoolTestLogin != null) - tidepoolTestLogin.setOnPreferenceClickListener(preference -> { - TidepoolUploader.INSTANCE.testLogin(getActivity()); - return false; - }); + for (PluginBase plugin : MainApp.getPluginsList()) { + plugin.preprocessPreferences(this); + } } @Override @@ -225,9 +206,5 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre super.onSaveInstanceState(outState); outState.putInt("id", id); } - - public Preference getPreference(String key) { - return findPreference(key); - } } } diff --git a/app/src/main/java/info/nightscout/androidaps/db/CareportalEvent.java b/app/src/main/java/info/nightscout/androidaps/db/CareportalEvent.java index 40fadff684..1c33e8f1be 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/CareportalEvent.java +++ b/app/src/main/java/info/nightscout/androidaps/db/CareportalEvent.java @@ -100,8 +100,8 @@ public class CareportalEvent implements DataPointWithLabelInterface, Interval { String hours = " " + MainApp.gs(R.string.hours) + " "; if (useShortText) { - days = "d"; - hours = "h"; + days = MainApp.gs(R.string.shortday); + hours = MainApp.gs(R.string.shorthour); } return diff.get(TimeUnit.DAYS) + days + diff.get(TimeUnit.HOURS) + hours; diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/PluginBase.java b/app/src/main/java/info/nightscout/androidaps/interfaces/PluginBase.java index f7c8e9ada4..8de88e1e0e 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/PluginBase.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/PluginBase.java @@ -1,10 +1,13 @@ package info.nightscout.androidaps.interfaces; import android.os.SystemClock; +import android.preference.Preference; +import android.preference.PreferenceFragment; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.FragmentActivity; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -215,4 +218,10 @@ public abstract class PluginBase { protected void onStateChange(PluginType type, State oldState, State newState) { } + + public void preprocessPreferences(@NotNull final PreferenceFragment preferenceFragment) { + } + + public void updatePreferenceSummary(@NotNull final Preference pref) { + } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/APSResult.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/APSResult.java index b3b0c091d5..7e568af2d9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/APSResult.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/APSResult.java @@ -247,7 +247,7 @@ public class APSResult { } } } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return array; } @@ -280,7 +280,7 @@ public class APSResult { } } } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return latest; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.java index ef31ad599e..65db6e440c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.java @@ -164,7 +164,7 @@ public class OpenAPSAMAPlugin extends PluginBase implements APSInterface { return; if (!HardLimits.checkOnlyHardLimits(Profile.toMgdl(profile.getIsf(), units), "sens", HardLimits.MINISF, HardLimits.MAXISF)) return; - if (!HardLimits.checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.05, HardLimits.maxBasal())) + if (!HardLimits.checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.02, HardLimits.maxBasal())) return; if (!HardLimits.checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, HardLimits.maxBasal())) return; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAPlugin.java index 899c0151d8..8cac8b87cc 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAPlugin.java @@ -162,7 +162,7 @@ public class OpenAPSMAPlugin extends PluginBase implements APSInterface { return; if (!checkOnlyHardLimits(Profile.toMgdl(profile.getIsf(), units), "sens", HardLimits.MINISF, HardLimits.MAXISF)) return; - if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.05, HardLimits.maxBasal())) + if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.02, HardLimits.maxBasal())) return; if (!checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, HardLimits.maxBasal())) return; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.java index d410eba291..c27a3f2059 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.java @@ -170,7 +170,7 @@ public class OpenAPSSMBPlugin extends PluginBase implements APSInterface, Constr return; if (!checkOnlyHardLimits(Profile.toMgdl(profile.getIsf(), units), "sens", HardLimits.MINISF, HardLimits.MAXISF)) return; - if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.05, HardLimits.maxBasal())) + if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.02, HardLimits.maxBasal())) return; if (!checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, HardLimits.maxBasal())) return; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctions.java b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctions.java index 025d8dac1e..91c20b095a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctions.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctions.java @@ -27,6 +27,7 @@ import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.overview.dialogs.ErrorHelperActivity; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.queue.Callback; +import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.SP; import io.reactivex.disposables.CompositeDisposable; @@ -75,35 +76,42 @@ public class ProfileFunctions { } public String getProfileName() { - return getProfileName(System.currentTimeMillis()); + return getProfileName(System.currentTimeMillis(), true, false); } public String getProfileName(boolean customized) { - return getProfileName(System.currentTimeMillis(), customized); + return getProfileName(System.currentTimeMillis(), customized, false); } - public String getProfileName(long time) { - return getProfileName(time, true); + public String getProfileNameWithDuration() { + return getProfileName(System.currentTimeMillis(), true, true); } - public String getProfileName(long time, boolean customized) { + public String getProfileName(long time, boolean customized, boolean showRemainingTime) { + String profileName = MainApp.gs(R.string.noprofileselected); + TreatmentsInterface activeTreatments = TreatmentsPlugin.getPlugin(); ProfileInterface activeProfile = ConfigBuilderPlugin.getPlugin().getActiveProfileInterface(); ProfileSwitch profileSwitch = activeTreatments.getProfileSwitchFromHistory(time); if (profileSwitch != null) { if (profileSwitch.profileJson != null) { - return customized ? profileSwitch.getCustomizedName() : profileSwitch.profileName; + profileName = customized ? profileSwitch.getCustomizedName() : profileSwitch.profileName; } else { ProfileStore profileStore = activeProfile.getProfile(); if (profileStore != null) { Profile profile = profileStore.getSpecificProfile(profileSwitch.profileName); if (profile != null) - return profileSwitch.profileName; + profileName = profileSwitch.profileName; } } + + if (showRemainingTime && profileSwitch.durationInMinutes != 0) { + profileName += DateUtil.untilString(profileSwitch.originalEnd()); + } + return profileName; } - return MainApp.gs(R.string.noprofileselected); + return profileName; } public boolean isProfileValid(String from) { @@ -176,7 +184,7 @@ public class ProfileFunctions { profileSwitch = new ProfileSwitch(); profileSwitch.date = System.currentTimeMillis(); profileSwitch.source = Source.USER; - profileSwitch.profileName = getInstance().getProfileName(System.currentTimeMillis(), false); + profileSwitch.profileName = getInstance().getProfileName(System.currentTimeMillis(), false, false); profileSwitch.profileJson = getInstance().getProfile().getData().toString(); profileSwitch.profilePlugin = ConfigBuilderPlugin.getPlugin().getActiveProfileInterface().getClass().getName(); profileSwitch.durationInMinutes = duration; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPlugin.java index e026d6589d..828fa1b86f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPlugin.java @@ -22,7 +22,7 @@ import info.nightscout.androidaps.plugins.general.overview.notifications.Notific public class DstHelperPlugin extends PluginBase implements ConstraintsInterface { public static final int DISABLE_TIMEFRAME_HOURS = -3; - public static final int WARN_PRIOR_TIMEFRAME_HOURS = 24; + public static final int WARN_PRIOR_TIMEFRAME_HOURS = 12; private static Logger log = LoggerFactory.getLogger(L.CONSTRAINTS); static DstHelperPlugin plugin = null; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt index aafd877672..5edb199c9a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt @@ -204,7 +204,7 @@ class ObjectivesFragment : Fragment() { holder.accomplished.setTextColor(-0x3e3e3f) holder.verify.setOnClickListener { holder.verify.visibility = View.INVISIBLE - NetworkChangeReceiver.fetch() + NetworkChangeReceiver.grabNetworkStatus(context) if (objectives_fake.isChecked) { objective.accomplishedOn = DateUtil.now() scrollToCurrentObjective() @@ -236,7 +236,7 @@ class ObjectivesFragment : Fragment() { } holder.start.setOnClickListener { holder.start.visibility = View.INVISIBLE - NetworkChangeReceiver.fetch() + NetworkChangeReceiver.grabNetworkStatus(context) if (objectives_fake.isChecked) { objective.startedOn = DateUtil.now() scrollToCurrentObjective() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt index ca3e1d3126..5882f84d61 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt @@ -105,7 +105,7 @@ object ObjectivesPlugin : PluginBase(PluginDescription() val requestCode = SP.getString(R.string.key_objectives_request_code, "") var url = SP.getString(R.string.key_nsclientinternal_url, "").toLowerCase() if (!url.endsWith("\"")) url = "$url/" - val hashNS = Hashing.sha1().hashString(url + BuildConfig.APPLICATION_ID + "/" + requestCode, Charsets.UTF_8).toString() + @Suppress("DEPRECATION") val hashNS = Hashing.sha1().hashString(url + BuildConfig.APPLICATION_ID + "/" + requestCode, Charsets.UTF_8).toString() if (request.equals(hashNS.substring(0, 10), ignoreCase = true)) { SP.putLong("Objectives_" + "openloop" + "_started", DateUtil.now()) SP.putLong("Objectives_" + "openloop" + "_accomplished", DateUtil.now()) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationEvent.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationEvent.java index 6eb3be382b..4f7bb62c74 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationEvent.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationEvent.java @@ -10,8 +10,11 @@ import java.util.List; import info.nightscout.androidaps.plugins.general.automation.actions.Action; import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger; import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class AutomationEvent { + private static final Logger log = LoggerFactory.getLogger(AutomationEvent.class); private Trigger trigger = new TriggerConnector(); private List actions = new ArrayList<>(); @@ -74,7 +77,7 @@ public class AutomationEvent { } o.put("actions", array); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -91,7 +94,7 @@ public class AutomationEvent { actions.add(Action.instantiate(new JSONObject(array.getString(i)))); } } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Action.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Action.java index 796f023f1d..ee4564fd6f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Action.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Action.java @@ -11,6 +11,8 @@ import javax.annotation.Nullable; import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger; import info.nightscout.androidaps.queue.Callback; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /* Action ideas: @@ -44,6 +46,7 @@ import info.nightscout.androidaps.queue.Callback; public abstract class Action { + private static final Logger log = LoggerFactory.getLogger(Action.class); public Trigger precondition = null; @@ -65,7 +68,7 @@ public abstract class Action { try { o.put("type", this.getClass().getName()); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -84,7 +87,7 @@ public abstract class Action { Class clazz = Class.forName(type); return ((Action) clazz.newInstance()).fromJSON(data != null ? data.toString() : ""); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return null; } @@ -98,7 +101,7 @@ public abstract class Action { fromJSON(data.toString()); } } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionLoopSuspend.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionLoopSuspend.java index bd221d8f81..39adbcac27 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionLoopSuspend.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionLoopSuspend.java @@ -18,8 +18,12 @@ import info.nightscout.androidaps.plugins.general.automation.elements.LabelWithE import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder; import info.nightscout.androidaps.queue.Callback; import info.nightscout.androidaps.utils.JsonHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ActionLoopSuspend extends Action { + private static final Logger log = LoggerFactory.getLogger(ActionLoopSuspend.class); + public InputDuration minutes = new InputDuration(0, InputDuration.TimeUnit.MINUTES); @Override @@ -59,7 +63,7 @@ public class ActionLoopSuspend extends Action { o.put("type", this.getClass().getName()); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -70,7 +74,7 @@ public class ActionLoopSuspend extends Action { JSONObject o = new JSONObject(data); minutes.setMinutes(JsonHelper.safeGetInt(o, "minutes")); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionNotification.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionNotification.java index 1f7ad543c2..7bd501b460 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionNotification.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionNotification.java @@ -20,8 +20,12 @@ import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotifi import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.queue.Callback; import info.nightscout.androidaps.utils.JsonHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ActionNotification extends Action { + private static final Logger log = LoggerFactory.getLogger(ActionNotification.class); + public InputString text = new InputString(); @Override @@ -59,7 +63,7 @@ public class ActionNotification extends Action { o.put("type", this.getClass().getName()); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -70,7 +74,7 @@ public class ActionNotification extends Action { JSONObject o = new JSONObject(data); text.setValue(JsonHelper.safeGetString(o, "text")); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionProfileSwitch.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionProfileSwitch.java index 50fbb786db..8a4762bd08 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionProfileSwitch.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionProfileSwitch.java @@ -92,7 +92,7 @@ public class ActionProfileSwitch extends Action { data.put("profileToSwitchTo", inputProfileName.getValue()); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -105,7 +105,7 @@ public class ActionProfileSwitch extends Action { profileName = JsonHelper.safeGetString(d, "profileToSwitchTo"); inputProfileName.setValue(profileName); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionProfileSwitchPercent.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionProfileSwitchPercent.java index 20b6abb081..23b54640a5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionProfileSwitchPercent.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionProfileSwitchPercent.java @@ -19,8 +19,12 @@ import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuil import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerProfilePercent; import info.nightscout.androidaps.queue.Callback; import info.nightscout.androidaps.utils.JsonHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ActionProfileSwitchPercent extends Action { + private static final Logger log = LoggerFactory.getLogger(ActionProfileSwitchPercent.class); + InputPercent pct = new InputPercent(); InputDuration duration = new InputDuration(0, InputDuration.TimeUnit.MINUTES); @@ -71,7 +75,7 @@ public class ActionProfileSwitchPercent extends Action { data.put("durationInMinutes", duration.getMinutes()); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -83,7 +87,7 @@ public class ActionProfileSwitchPercent extends Action { pct.setValue(JsonHelper.safeGetInt(d, "percentage")); duration.setMinutes(JsonHelper.safeGetInt(d, "durationInMinutes")); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionSendSMS.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionSendSMS.java index f8afafca75..fa9140e5f9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionSendSMS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionSendSMS.java @@ -21,6 +21,7 @@ import info.nightscout.androidaps.queue.Callback; import info.nightscout.androidaps.utils.JsonHelper; public class ActionSendSMS extends Action { + private static final Logger log = LoggerFactory.getLogger(ActionSendSMS.class); public InputString text = new InputString(); @@ -36,7 +37,7 @@ public class ActionSendSMS extends Action { @Override public void doAction(Callback callback) { - boolean result = SmsCommunicatorPlugin.getPlugin().sendNotificationToAllNumbers(text.getValue()); + boolean result = SmsCommunicatorPlugin.INSTANCE.sendNotificationToAllNumbers(text.getValue()); if (callback != null) callback.result(new PumpEnactResult().success(result).comment(result ? R.string.ok : R.string.danar_error)).run(); @@ -56,7 +57,7 @@ public class ActionSendSMS extends Action { o.put("type", this.getClass().getName()); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -67,7 +68,7 @@ public class ActionSendSMS extends Action { JSONObject o = new JSONObject(data); text.setValue(JsonHelper.safeGetString(o, "text")); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionStartTempTarget.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionStartTempTarget.java index 659bb508eb..b0ef645069 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionStartTempTarget.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionStartTempTarget.java @@ -24,8 +24,12 @@ import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.queue.Callback; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.JsonHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ActionStartTempTarget extends Action { + private static final Logger log = LoggerFactory.getLogger(ActionStartTempTarget.class); + String reason = ""; InputTempTarget value = new InputTempTarget(); InputDuration duration = new InputDuration(0, InputDuration.TimeUnit.MINUTES); @@ -93,7 +97,7 @@ public class ActionStartTempTarget extends Action { data.put("durationInMinutes", duration.getMinutes()); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -107,7 +111,7 @@ public class ActionStartTempTarget extends Action { value.setValue(JsonHelper.safeGetDouble(d, "value")); duration.setMinutes(JsonHelper.safeGetInt(d, "durationInMinutes")); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionStopTempTarget.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionStopTempTarget.java index de251c499e..abf354bc9c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionStopTempTarget.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionStopTempTarget.java @@ -14,8 +14,12 @@ import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.queue.Callback; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.JsonHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ActionStopTempTarget extends Action { + private static final Logger log = LoggerFactory.getLogger(ActionStopTempTarget.class); + String reason = ""; private TempTarget tempTarget; @@ -54,7 +58,7 @@ public class ActionStopTempTarget extends Action { data.put("reason", reason); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -65,7 +69,7 @@ public class ActionStopTempTarget extends Action { JSONObject d = new JSONObject(data); reason = JsonHelper.safeGetString(d, "reason"); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/Trigger.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/Trigger.java index f6ba6b6f67..69ac67bf07 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/Trigger.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/Trigger.java @@ -13,10 +13,13 @@ import com.google.common.base.Optional; import org.json.JSONException; import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nullable; public abstract class Trigger { + private static final Logger log = LoggerFactory.getLogger(Trigger.class); TriggerConnector connector = null; long lastRun; @@ -56,7 +59,7 @@ public abstract class Trigger { try { return instantiate(new JSONObject(json)); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return null; } @@ -69,7 +72,7 @@ public abstract class Trigger { Class clazz = Class.forName(type); return ((Trigger) clazz.newInstance()).fromJSON(data.toString()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return null; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerAutosensValue.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerAutosensValue.java index da850b187d..ce0a8f63bb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerAutosensValue.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerAutosensValue.java @@ -89,7 +89,7 @@ public class TriggerAutosensValue extends Trigger { data.put("comparator", comparator.getValue().toString()); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -102,7 +102,7 @@ public class TriggerAutosensValue extends Trigger { lastRun = JsonHelper.safeGetLong(d, "lastRun"); comparator.setValue(Comparator.Compare.valueOf(JsonHelper.safeGetString(d, "comparator"))); } catch (Exception e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBg.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBg.java index 1636377bf2..3733859b96 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBg.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBg.java @@ -104,7 +104,7 @@ public class TriggerBg extends Trigger { data.put("units", bg.getUnits()); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -118,7 +118,7 @@ public class TriggerBg extends Trigger { lastRun = JsonHelper.safeGetLong(d, "lastRun"); comparator.setValue(Comparator.Compare.valueOf(JsonHelper.safeGetString(d, "comparator"))); } catch (Exception e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBolusAgo.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBolusAgo.java index 0ad9f75ab2..0a038112e2 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBolusAgo.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBolusAgo.java @@ -86,7 +86,7 @@ public class TriggerBolusAgo extends Trigger { data.put("comparator", comparator.getValue().toString()); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -99,7 +99,7 @@ public class TriggerBolusAgo extends Trigger { lastRun = JsonHelper.safeGetLong(d, "lastRun"); comparator.setValue(Comparator.Compare.valueOf(JsonHelper.safeGetString(d, "comparator"))); } catch (Exception e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerCOB.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerCOB.java index 4ddeb4299a..bf17dc72d1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerCOB.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerCOB.java @@ -87,7 +87,7 @@ public class TriggerCOB extends Trigger { data.put("comparator", comparator.getValue().toString()); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -100,7 +100,7 @@ public class TriggerCOB extends Trigger { lastRun = JsonHelper.safeGetLong(d, "lastRun"); comparator.setValue(Comparator.Compare.valueOf(JsonHelper.safeGetString(d, "comparator"))); } catch (Exception e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerConnector.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerConnector.java index ae4bd35ad5..549925528a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerConnector.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerConnector.java @@ -162,7 +162,7 @@ public class TriggerConnector extends Trigger { data.put("triggerList", array); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -179,7 +179,7 @@ public class TriggerConnector extends Trigger { add(newItem); } } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerDelta.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerDelta.java index a3939a60b7..eee649307b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerDelta.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerDelta.java @@ -129,7 +129,7 @@ public class TriggerDelta extends Trigger { data.put("comparator", comparator.getValue().toString()); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -144,7 +144,7 @@ public class TriggerDelta extends Trigger { lastRun = JsonHelper.safeGetLong(d, "lastRun"); comparator.setValue(Comparator.Compare.valueOf(JsonHelper.safeGetString(d, "comparator"))); } catch (Exception e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerIob.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerIob.java index c4003af861..a3935d7cec 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerIob.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerIob.java @@ -82,7 +82,7 @@ public class TriggerIob extends Trigger { data.put("comparator", comparator.getValue().toString()); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -95,7 +95,7 @@ public class TriggerIob extends Trigger { lastRun = JsonHelper.safeGetLong(d, "lastRun"); comparator.setValue(Comparator.Compare.valueOf(JsonHelper.safeGetString(d, "comparator"))); } catch (Exception e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerLocation.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerLocation.java index 4772d29112..19965cfc9a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerLocation.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerLocation.java @@ -93,7 +93,7 @@ public class TriggerLocation extends Trigger { data.put("lastRun", lastRun); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -108,7 +108,7 @@ public class TriggerLocation extends Trigger { name.setValue(JsonHelper.safeGetString(d, "name")); lastRun = JsonHelper.safeGetLong(d, "lastRun"); } catch (Exception e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerProfilePercent.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerProfilePercent.java index d86bc35cb8..e4c0ae4346 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerProfilePercent.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerProfilePercent.java @@ -88,7 +88,7 @@ public class TriggerProfilePercent extends Trigger { data.put("comparator", comparator.getValue().toString()); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -101,7 +101,7 @@ public class TriggerProfilePercent extends Trigger { lastRun = JsonHelper.safeGetLong(d, "lastRun"); comparator.setValue(Comparator.Compare.valueOf(JsonHelper.safeGetString(d, "comparator"))); } catch (Exception e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerPumpLastConnection.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerPumpLastConnection.java index 8cec3bdf44..ff8bb335f9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerPumpLastConnection.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerPumpLastConnection.java @@ -82,7 +82,7 @@ public class TriggerPumpLastConnection extends Trigger { data.put("comparator", comparator.getValue().toString()); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -95,7 +95,7 @@ public class TriggerPumpLastConnection extends Trigger { lastRun = JsonHelper.safeGetLong(d, "lastRun"); comparator.setValue(Comparator.Compare.valueOf(JsonHelper.safeGetString(d, "comparator"))); } catch (Exception e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerRecurringTime.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerRecurringTime.java index a145253b36..89c8d17085 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerRecurringTime.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerRecurringTime.java @@ -163,7 +163,7 @@ public class TriggerRecurringTime extends Trigger { object.put("type", TriggerRecurringTime.class.getName()); object.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return object.toString(); } @@ -181,7 +181,7 @@ public class TriggerRecurringTime extends Trigger { minute = JsonHelper.safeGetInt(o, "minute"); validTo = JsonHelper.safeGetLong(o, "validTo"); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTempTarget.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTempTarget.java index 378652ed2a..f45efdf4a4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTempTarget.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTempTarget.java @@ -74,7 +74,7 @@ public class TriggerTempTarget extends Trigger { data.put("comparator", comparator.getValue().toString()); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -86,7 +86,7 @@ public class TriggerTempTarget extends Trigger { lastRun = JsonHelper.safeGetLong(d, "lastRun"); comparator.setValue(ComparatorExists.Compare.valueOf(JsonHelper.safeGetString(d, "comparator"))); } catch (Exception e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTime.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTime.java index 261783b99c..08f9a65aa7 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTime.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTime.java @@ -65,7 +65,7 @@ public class TriggerTime extends Trigger { object.put("type", TriggerTime.class.getName()); object.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return object.toString(); } @@ -78,7 +78,7 @@ public class TriggerTime extends Trigger { lastRun = JsonHelper.safeGetLong(o, "lastRun"); runAt = JsonHelper.safeGetLong(o, "runAt"); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTimeRange.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTimeRange.java index 8755c3559c..1d03871620 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTimeRange.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTimeRange.java @@ -93,7 +93,7 @@ public class TriggerTimeRange extends Trigger { object.put("type", TriggerTimeRange.class.getName()); object.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } log.debug(object.toString()); return object.toString(); @@ -108,7 +108,7 @@ public class TriggerTimeRange extends Trigger { start = JsonHelper.safeGetInt(o, "start"); end = JsonHelper.safeGetInt(o, "end"); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerWifiSsid.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerWifiSsid.java index ba10e81583..dd2c34bcb8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerWifiSsid.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerWifiSsid.java @@ -85,7 +85,7 @@ public class TriggerWifiSsid extends Trigger { data.put("comparator", comparator.getValue().toString()); o.put("data", data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return o.toString(); } @@ -98,7 +98,7 @@ public class TriggerWifiSsid extends Trigger { lastRun = JsonHelper.safeGetLong(d, "lastRun"); comparator.setValue(Comparator.Compare.valueOf(JsonHelper.safeGetString(d, "comparator"))); } catch (Exception e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.java index ae5d090211..037f91b3f0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.java @@ -7,9 +7,12 @@ import android.content.ServiceConnection; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; +import android.preference.PreferenceFragment; +import android.preference.PreferenceScreen; import android.text.Html; import android.text.Spanned; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -89,7 +92,7 @@ public class NSClientPlugin extends PluginBase { } nsClientReceiverDelegate = - new NsClientReceiverDelegate(MainApp.instance().getApplicationContext()); + new NsClientReceiverDelegate(); } public boolean isAllowed() { @@ -104,7 +107,7 @@ public class NSClientPlugin extends PluginBase { context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); super.onStart(); - nsClientReceiverDelegate.registerReceivers(); + nsClientReceiverDelegate.grabReceiversState(); disposable.add(RxBus.INSTANCE .toObservable(EventNSClientStatus.class) .observeOn(Schedulers.io()) @@ -129,7 +132,6 @@ public class NSClientPlugin extends PluginBase { .subscribe(event -> { if (nsClientService != null) { MainApp.instance().getApplicationContext().unbindService(mConnection); - nsClientReceiverDelegate.unregisterReceivers(); } }, FabricPrivacy::logException) ); @@ -152,11 +154,27 @@ public class NSClientPlugin extends PluginBase { @Override protected void onStop() { MainApp.instance().getApplicationContext().unbindService(mConnection); - nsClientReceiverDelegate.unregisterReceivers(); disposable.clear(); super.onStop(); } + @Override + public void preprocessPreferences(@NotNull PreferenceFragment preferenceFragment) { + super.preprocessPreferences(preferenceFragment); + + if (Config.NSCLIENT) { + PreferenceScreen scrnAdvancedSettings = (PreferenceScreen) preferenceFragment.findPreference(MainApp.gs(R.string.key_advancedsettings)); + if (scrnAdvancedSettings != null) { + scrnAdvancedSettings.removePreference(preferenceFragment.findPreference(MainApp.gs(R.string.key_statuslights_res_warning))); + scrnAdvancedSettings.removePreference(preferenceFragment.findPreference(MainApp.gs(R.string.key_statuslights_res_critical))); + scrnAdvancedSettings.removePreference(preferenceFragment.findPreference(MainApp.gs(R.string.key_statuslights_bat_warning))); + scrnAdvancedSettings.removePreference(preferenceFragment.findPreference(MainApp.gs(R.string.key_statuslights_bat_critical))); + scrnAdvancedSettings.removePreference(preferenceFragment.findPreference(MainApp.gs(R.string.key_show_statuslights))); + scrnAdvancedSettings.removePreference(preferenceFragment.findPreference(MainApp.gs(R.string.key_show_statuslights_extended))); + } + } + } + private ServiceConnection mConnection = new ServiceConnection() { public void onServiceDisconnected(ComponentName name) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSUpload.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSUpload.java index 881593b32a..f5582b71a2 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSUpload.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSUpload.java @@ -594,7 +594,7 @@ public class NSUpload { } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.java index c68750d828..fd05b133f5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.java @@ -18,44 +18,19 @@ import info.nightscout.androidaps.utils.SP; class NsClientReceiverDelegate { - private final Context context; - - private NetworkChangeReceiver networkChangeReceiver = new NetworkChangeReceiver(); - private ChargingStateReceiver chargingStateReceiver = new ChargingStateReceiver(); - private boolean allowedChargingState = true; private boolean allowedNetworkState = true; boolean allowed = true; - NsClientReceiverDelegate(Context context) { - this.context = context; - } - - void registerReceivers() { + void grabReceiversState() { Context context = MainApp.instance().getApplicationContext(); - // register NetworkChangeReceiver --> https://developer.android.com/training/monitoring-device-state/connectivity-monitoring.html - // Nougat is not providing Connectivity-Action anymore ;-( - context.registerReceiver(networkChangeReceiver, - new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - context.registerReceiver(networkChangeReceiver, - new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)); - EventNetworkChange event = networkChangeReceiver.grabNetworkStatus(context); - if (event != null) - RxBus.INSTANCE.send(event); + EventNetworkChange event = NetworkChangeReceiver.grabNetworkStatus(context); + if (event != null) RxBus.INSTANCE.send(event); - context.registerReceiver(chargingStateReceiver, - new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + EventChargingState eventChargingState = ChargingStateReceiver.grabChargingState(context); + if (eventChargingState != null) RxBus.INSTANCE.send(eventChargingState); - EventChargingState eventChargingState = chargingStateReceiver.grabChargingState(context); - if (eventChargingState != null) - RxBus.INSTANCE.send(eventChargingState); - - } - - void unregisterReceivers() { - context.unregisterReceiver(networkChangeReceiver); - context.unregisterReceiver(chargingStateReceiver); } void onStatusEvent(EventPreferenceChange ev) { @@ -63,11 +38,11 @@ class NsClientReceiverDelegate { ev.isChanged(R.string.key_ns_wifi_ssids) || ev.isChanged(R.string.key_ns_allowroaming) ) { - EventNetworkChange event = networkChangeReceiver.grabNetworkStatus(MainApp.instance().getApplicationContext()); + EventNetworkChange event = NetworkChangeReceiver.grabNetworkStatus(MainApp.instance().getApplicationContext()); if (event != null) RxBus.INSTANCE.send(event); } else if (ev.isChanged(R.string.key_ns_chargingonly)) { - EventChargingState event = chargingStateReceiver.grabChargingState(MainApp.instance().getApplicationContext()); + EventChargingState event = ChargingStateReceiver.grabChargingState(MainApp.instance().getApplicationContext()); if (event != null) RxBus.INSTANCE.send(event); } @@ -91,7 +66,7 @@ class NsClientReceiverDelegate { } } - void processStateChange() { + private void processStateChange() { boolean newAllowedState = allowedChargingState && allowedNetworkState; if (newAllowedState != allowed) { allowed = newAllowedState; @@ -101,7 +76,6 @@ class NsClientReceiverDelegate { boolean calculateStatus(final EventChargingState ev) { boolean chargingOnly = SP.getBoolean(R.string.key_ns_chargingonly, false); - boolean newAllowedState = true; if (!ev.isCharging() && chargingOnly) { @@ -129,8 +103,6 @@ class NsClientReceiverDelegate { } } - return newAllowedState; } - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java index 5c2a7cac84..046290500d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java @@ -1223,7 +1223,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, extendedBolusView.setVisibility(View.VISIBLE); } - activeProfileView.setText(ProfileFunctions.getInstance().getProfileName()); + activeProfileView.setText(ProfileFunctions.getInstance().getProfileNameWithDuration()); if (profile.getPercentage() != 100 || profile.getTimeshift() != 0) { activeProfileView.setBackgroundColor(MainApp.gc(R.color.ribbonWarning)); activeProfileView.setTextColor(MainApp.gc(R.color.ribbonTextWarning)); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/WizardDialog.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/WizardDialog.kt index 552ab502e7..efe800bef0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/WizardDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/WizardDialog.kt @@ -114,6 +114,7 @@ class WizardDialog : DialogFragment() { log.debug("guarding: ok already clicked") } else { okClicked = true + calculateInsulin() parentContext?.let { context -> wizard?.confirmAndExecute(context) } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/AuthRequest.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/AuthRequest.java deleted file mode 100644 index 4b05072eb1..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/AuthRequest.java +++ /dev/null @@ -1,59 +0,0 @@ -package info.nightscout.androidaps.plugins.general.smsCommunicator; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.utils.DateUtil; - -class AuthRequest { - private static Logger log = LoggerFactory.getLogger(L.SMS); - - Sms requester; - String confirmCode; - private Runnable action; - - private long date; - - private boolean processed; - private SmsCommunicatorPlugin plugin; - - AuthRequest(SmsCommunicatorPlugin plugin, Sms requester, String requestText, String confirmCode, SmsAction action) { - this.requester = requester; - this.confirmCode = confirmCode; - this.action = action; - this.plugin = plugin; - - this.date = DateUtil.now(); - - plugin.sendSMS(new Sms(requester.phoneNumber, requestText)); - } - - void action(String codeReceived) { - if (processed) { - if (L.isEnabled(L.SMS)) - log.debug("Already processed"); - return; - } - if (!confirmCode.equals(codeReceived)) { - processed = true; - if (L.isEnabled(L.SMS)) - log.debug("Wrong code"); - plugin.sendSMS(new Sms(requester.phoneNumber, R.string.sms_wrongcode)); - return; - } - if (DateUtil.now() - date < Constants.SMS_CONFIRM_TIMEOUT) { - processed = true; - if (L.isEnabled(L.SMS)) - log.debug("Processing confirmed SMS: " + requester.text); - if (action != null) - action.run(); - return; - } - if (L.isEnabled(L.SMS)) - log.debug("Timed out SMS: " + requester.text); - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/AuthRequest.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/AuthRequest.kt new file mode 100644 index 0000000000..48e816928f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/AuthRequest.kt @@ -0,0 +1,38 @@ +package info.nightscout.androidaps.plugins.general.smsCommunicator + +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.R +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.utils.DateUtil +import org.slf4j.LoggerFactory + +class AuthRequest internal constructor(val plugin: SmsCommunicatorPlugin, var requester: Sms, requestText: String, var confirmCode: String, val action: SmsAction) { + private val log = LoggerFactory.getLogger(L.SMS) + + private val date = DateUtil.now() + private var processed = false + + init { + plugin.sendSMS(Sms(requester.phoneNumber, requestText)) + } + + fun action(codeReceived: String) { + if (processed) { + if (L.isEnabled(L.SMS)) log.debug("Already processed") + return + } + if (confirmCode != codeReceived) { + processed = true + if (L.isEnabled(L.SMS)) log.debug("Wrong code") + plugin.sendSMS(Sms(requester.phoneNumber, R.string.sms_wrongcode)) + return + } + if (DateUtil.now() - date < Constants.SMS_CONFIRM_TIMEOUT) { + processed = true + if (L.isEnabled(L.SMS)) log.debug("Processing confirmed SMS: " + requester.text) + action.run() + return + } + if (L.isEnabled(L.SMS)) log.debug("Timed out SMS: " + requester.text) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/Sms.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/Sms.java deleted file mode 100644 index 2eedfa0a51..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/Sms.java +++ /dev/null @@ -1,42 +0,0 @@ -package info.nightscout.androidaps.plugins.general.smsCommunicator; - -import android.telephony.SmsMessage; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.utils.DateUtil; - -class Sms { - String phoneNumber; - String text; - long date; - boolean received = false; - boolean sent = false; - boolean processed = false; - boolean ignored = false; - - Sms(SmsMessage message) { - phoneNumber = message.getOriginatingAddress(); - text = message.getMessageBody(); - date = message.getTimestampMillis(); - received = true; - } - - Sms(String phoneNumber, String text) { - this.phoneNumber = phoneNumber; - this.text = text; - this.date = DateUtil.now(); - sent = true; - } - - Sms(String phoneNumber, int textId) { - this.phoneNumber = phoneNumber; - this.text = MainApp.gs(textId); - this.date = DateUtil.now(); - sent = true; - } - - public String toString() { - return "SMS from " + phoneNumber + ": " + text; - } -} - diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/Sms.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/Sms.kt new file mode 100644 index 0000000000..868620be2e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/Sms.kt @@ -0,0 +1,40 @@ +package info.nightscout.androidaps.plugins.general.smsCommunicator + +import android.telephony.SmsMessage +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.utils.DateUtil + +class Sms { + var phoneNumber: String + var text: String + var date: Long + var received = false + var sent = false + var processed = false + var ignored = false + + internal constructor(message: SmsMessage) { + phoneNumber = message.originatingAddress ?: "" + text = message.messageBody + date = message.timestampMillis + received = true + } + + internal constructor(phoneNumber: String, text: String) { + this.phoneNumber = phoneNumber + this.text = text + date = DateUtil.now() + sent = true + } + + internal constructor(phoneNumber: String, textId: Int) { + this.phoneNumber = phoneNumber + text = MainApp.gs(textId) + date = DateUtil.now() + sent = true + } + + override fun toString(): String { + return "SMS from $phoneNumber: $text" + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsAction.java deleted file mode 100644 index 6b5d5b8747..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsAction.java +++ /dev/null @@ -1,33 +0,0 @@ -package info.nightscout.androidaps.plugins.general.smsCommunicator; - -abstract class SmsAction implements Runnable { - Double aDouble; - Integer anInteger; - Integer secondInteger; - String aString; - - SmsAction() {} - - SmsAction(Double aDouble) { - this.aDouble = aDouble; - } - - SmsAction(Double aDouble, Integer secondInteger) { - this.aDouble = aDouble; - this.secondInteger = secondInteger; - } - - SmsAction(String aString, Integer secondInteger) { - this.aString = aString; - this.secondInteger = secondInteger; - } - - SmsAction(Integer anInteger) { - this.anInteger = anInteger; - } - - SmsAction(Integer anInteger, Integer secondInteger) { - this.anInteger = anInteger; - this.secondInteger = secondInteger; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsAction.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsAction.kt new file mode 100644 index 0000000000..98c892d918 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsAction.kt @@ -0,0 +1,68 @@ +package info.nightscout.androidaps.plugins.general.smsCommunicator + +abstract class SmsAction : Runnable { + var aDouble: Double? = null + var anInteger: Int? = null + var secondInteger: Int? = null + var secondLong: Long? = null + var aString: String? = null + + internal constructor() + internal constructor(aDouble: Double) { + this.aDouble = aDouble + } + + internal constructor(aDouble: Double, secondInteger: Int) { + this.aDouble = aDouble + this.secondInteger = secondInteger + } + + internal constructor(aString: String, secondInteger: Int) { + this.aString = aString + this.secondInteger = secondInteger + } + + internal constructor(anInteger: Int) { + this.anInteger = anInteger + } + + internal constructor(anInteger: Int, secondInteger: Int) { + this.anInteger = anInteger + this.secondInteger = secondInteger + } + + internal constructor(anInteger: Int, secondLong: Long) { + this.anInteger = anInteger + this.secondLong = secondLong + } + + fun aDouble(): Double { + return aDouble?.let { + aDouble + } ?: throw IllegalStateException() + } + + fun anInteger(): Int { + return anInteger?.let { + anInteger + } ?: throw IllegalStateException() + } + + fun secondInteger(): Int { + return secondInteger?.let { + secondInteger + } ?: throw IllegalStateException() + } + + fun secondLong(): Long { + return secondLong?.let { + secondLong + } ?: throw IllegalStateException() + } + + fun aString(): String { + return aString?.let { + aString + } ?: throw IllegalStateException() + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorFragment.java deleted file mode 100644 index 31f335a7cf..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorFragment.java +++ /dev/null @@ -1,83 +0,0 @@ -package info.nightscout.androidaps.plugins.general.smsCommunicator; - - -import android.os.Bundle; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import androidx.fragment.app.Fragment; - -import java.util.Collections; -import java.util.Comparator; - -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.plugins.bus.RxBus; -import info.nightscout.androidaps.plugins.general.smsCommunicator.events.EventSmsCommunicatorUpdateGui; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.FabricPrivacy; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; - -public class SmsCommunicatorFragment extends Fragment { - private CompositeDisposable disposable = new CompositeDisposable(); - TextView logView; - - public SmsCommunicatorFragment() { - super(); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.smscommunicator_fragment, container, false); - - logView = (TextView) view.findViewById(R.id.smscommunicator_log); - - return view; - } - - @Override - public synchronized void onResume() { - super.onResume(); - disposable.add(RxBus.INSTANCE - .toObservable(EventSmsCommunicatorUpdateGui.class) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(event -> updateGui(), FabricPrivacy::logException) - ); - updateGui(); - } - - @Override - public synchronized void onPause() { - super.onPause(); - disposable.clear(); - } - - protected void updateGui() { - class CustomComparator implements Comparator { - public int compare(Sms object1, Sms object2) { - return (int) (object1.date - object2.date); - } - } - Collections.sort(SmsCommunicatorPlugin.getPlugin().messages, new CustomComparator()); - int messagesToShow = 40; - - int start = Math.max(0, SmsCommunicatorPlugin.getPlugin().messages.size() - messagesToShow); - - String logText = ""; - for (int x = start; x < SmsCommunicatorPlugin.getPlugin().messages.size(); x++) { - Sms sms = SmsCommunicatorPlugin.getPlugin().messages.get(x); - if (sms.ignored) { - logText += DateUtil.timeString(sms.date) + " <<< " + "░ " + sms.phoneNumber + " " + sms.text + "
"; - } else if (sms.received) { - logText += DateUtil.timeString(sms.date) + " <<< " + (sms.processed ? "● " : "○ ") + sms.phoneNumber + " " + sms.text + "
"; - } else if (sms.sent) { - logText += DateUtil.timeString(sms.date) + " >>> " + (sms.processed ? "● " : "○ ") + sms.phoneNumber + " " + sms.text + "
"; - } - } - logView.setText(Html.fromHtml(logText)); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorFragment.kt new file mode 100644 index 0000000000..90ec078931 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorFragment.kt @@ -0,0 +1,70 @@ +package info.nightscout.androidaps.plugins.general.smsCommunicator + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import info.nightscout.androidaps.R +import info.nightscout.androidaps.plugins.bus.RxBus.toObservable +import info.nightscout.androidaps.plugins.general.smsCommunicator.events.EventSmsCommunicatorUpdateGui +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.HtmlHelper +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.smscommunicator_fragment.* +import java.util.* +import kotlin.math.max + +class SmsCommunicatorFragment : Fragment() { + private val disposable = CompositeDisposable() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.smscommunicator_fragment, container, false) + } + + @Synchronized + override fun onResume() { + super.onResume() + disposable.add(toObservable(EventSmsCommunicatorUpdateGui::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateGui() }) { FabricPrivacy.logException(it) } + ) + updateGui() + } + + @Synchronized + override fun onPause() { + super.onPause() + disposable.clear() + } + + fun updateGui() { + class CustomComparator : Comparator { + override fun compare(object1: Sms, object2: Sms): Int { + return (object1.date - object2.date).toInt() + } + } + Collections.sort(SmsCommunicatorPlugin.messages, CustomComparator()) + val messagesToShow = 40 + val start = max(0, SmsCommunicatorPlugin.messages.size - messagesToShow) + var logText = "" + for (x in start until SmsCommunicatorPlugin.messages.size) { + val sms = SmsCommunicatorPlugin.messages[x] + when { + sms.ignored -> { + logText += DateUtil.timeString(sms.date) + " <<< " + "░ " + sms.phoneNumber + " " + sms.text + "
" + } + sms.received -> { + logText += DateUtil.timeString(sms.date) + " <<< " + (if (sms.processed) "● " else "○ ") + sms.phoneNumber + " " + sms.text + "
" + } + sms.sent -> { + logText += DateUtil.timeString(sms.date) + " >>> " + (if (sms.processed) "● " else "○ ") + sms.phoneNumber + " " + sms.text + "
" + } + } + } + smscommunicator_log?.text = HtmlHelper.fromHtml(logText) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.java deleted file mode 100644 index 264d7c3e52..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.java +++ /dev/null @@ -1,812 +0,0 @@ -package info.nightscout.androidaps.plugins.general.smsCommunicator; - -import android.content.Intent; -import android.os.Bundle; -import android.telephony.SmsManager; -import android.telephony.SmsMessage; - -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.text.Normalizer; -import java.util.ArrayList; -import java.util.List; - -import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.DetailedBolusInfo; -import info.nightscout.androidaps.data.IobTotal; -import info.nightscout.androidaps.data.Profile; -import info.nightscout.androidaps.data.ProfileStore; -import info.nightscout.androidaps.db.BgReading; -import info.nightscout.androidaps.db.DatabaseHelper; -import info.nightscout.androidaps.db.Source; -import info.nightscout.androidaps.events.EventPreferenceChange; -import info.nightscout.androidaps.events.EventRefreshOverview; -import info.nightscout.androidaps.interfaces.Constraint; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.PluginDescription; -import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.interfaces.ProfileInterface; -import info.nightscout.androidaps.interfaces.PumpInterface; -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; -import info.nightscout.androidaps.plugins.bus.RxBus; -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; -import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; -import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart; -import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; -import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; -import info.nightscout.androidaps.plugins.general.smsCommunicator.events.EventSmsCommunicatorUpdateGui; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.CobInfo; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; -import info.nightscout.androidaps.queue.Callback; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.DecimalFormatter; -import info.nightscout.androidaps.utils.FabricPrivacy; -import info.nightscout.androidaps.utils.SP; -import info.nightscout.androidaps.utils.SafeParse; -import info.nightscout.androidaps.utils.XdripCalibrations; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.schedulers.Schedulers; - -/** - * Created by mike on 05.08.2016. - */ -public class SmsCommunicatorPlugin extends PluginBase { - private static Logger log = LoggerFactory.getLogger(L.SMS); - private CompositeDisposable disposable = new CompositeDisposable(); - - private static SmsCommunicatorPlugin smsCommunicatorPlugin; - - public static SmsCommunicatorPlugin getPlugin() { - - if (smsCommunicatorPlugin == null) { - smsCommunicatorPlugin = new SmsCommunicatorPlugin(); - } - return smsCommunicatorPlugin; - } - - List allowedNumbers = new ArrayList<>(); - - AuthRequest messageToConfirm = null; - - long lastRemoteBolusTime = 0; - - ArrayList messages = new ArrayList<>(); - - SmsCommunicatorPlugin() { - super(new PluginDescription() - .mainType(PluginType.GENERAL) - .fragmentClass(SmsCommunicatorFragment.class.getName()) - .pluginName(R.string.smscommunicator) - .shortName(R.string.smscommunicator_shortname) - .preferencesId(R.xml.pref_smscommunicator) - .description(R.string.description_sms_communicator) - ); - processSettings(null); - } - - @Override - protected void onStart() { - super.onStart(); - disposable.add(RxBus.INSTANCE - .toObservable(EventPreferenceChange.class) - .observeOn(Schedulers.io()) - .subscribe(event -> { - processSettings(event); - }, FabricPrivacy::logException) - ); - } - - @Override - protected void onStop() { - disposable.clear(); - super.onStop(); - } - - private void processSettings(final EventPreferenceChange ev) { - if (ev == null || ev.isChanged(R.string.key_smscommunicator_allowednumbers)) { - String settings = SP.getString(R.string.key_smscommunicator_allowednumbers, ""); - - allowedNumbers.clear(); - String[] substrings = settings.split(";"); - for (String number : substrings) { - String cleaned = number.replaceAll("\\s+", ""); - allowedNumbers.add(cleaned); - log.debug("Found allowed number: " + cleaned); - } - } - } - - boolean isCommand(String command, String number) { - switch (command.toUpperCase()) { - case "BG": - case "LOOP": - case "TREATMENTS": - case "NSCLIENT": - case "PUMP": - case "BASAL": - case "BOLUS": - case "EXTENDED": - case "CAL": - case "PROFILE": - return true; - } - if (messageToConfirm != null && messageToConfirm.requester.phoneNumber.equals(number)) - return true; - return false; - } - - boolean isAllowedNumber(String number) { - for (String num : allowedNumbers) { - if (num.equals(number)) return true; - } - return false; - } - - public void handleNewData(Intent intent) { - Bundle bundle = intent.getExtras(); - if (bundle == null) return; - - Object[] pdus = (Object[]) bundle.get("pdus"); - if (pdus != null) { - // For every SMS message received - for (Object pdu : pdus) { - SmsMessage message = SmsMessage.createFromPdu((byte[]) pdu); - processSms(new Sms(message)); - } - } - } - - void processSms(final Sms receivedSms) { - if (!isEnabled(PluginType.GENERAL)) { - log.debug("Ignoring SMS. Plugin disabled."); - return; - } - if (!isAllowedNumber(receivedSms.phoneNumber)) { - log.debug("Ignoring SMS from: " + receivedSms.phoneNumber + ". Sender not allowed"); - receivedSms.ignored = true; - messages.add(receivedSms); - RxBus.INSTANCE.send(new EventSmsCommunicatorUpdateGui()); - return; - } - - messages.add(receivedSms); - log.debug(receivedSms.toString()); - - String[] splitted = receivedSms.text.split("\\s+"); - boolean remoteCommandsAllowed = SP.getBoolean(R.string.key_smscommunicator_remotecommandsallowed, false); - - if (splitted.length > 0 && isCommand(splitted[0].toUpperCase(), receivedSms.phoneNumber)) { - switch (splitted[0].toUpperCase()) { - case "BG": - processBG(splitted, receivedSms); - break; - case "LOOP": - if (!remoteCommandsAllowed) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotecommandnotallowed)); - else if (splitted.length == 2 || splitted.length == 3) - processLOOP(splitted, receivedSms); - else - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - break; - case "TREATMENTS": - if (splitted.length == 2) - processTREATMENTS(splitted, receivedSms); - else - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - break; - case "NSCLIENT": - if (splitted.length == 2) - processNSCLIENT(splitted, receivedSms); - else - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - break; - case "PUMP": - processPUMP(splitted, receivedSms); - break; - case "PROFILE": - if (!remoteCommandsAllowed) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotecommandnotallowed)); - else if (splitted.length == 2 || splitted.length == 3) - processPROFILE(splitted, receivedSms); - else - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - break; - case "BASAL": - if (!remoteCommandsAllowed) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotecommandnotallowed)); - else if (splitted.length == 2 || splitted.length == 3) - processBASAL(splitted, receivedSms); - else - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - break; - case "EXTENDED": - if (!remoteCommandsAllowed) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotecommandnotallowed)); - else if (splitted.length == 2 || splitted.length == 3) - processEXTENDED(splitted, receivedSms); - else - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - break; - case "BOLUS": - if (!remoteCommandsAllowed) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotecommandnotallowed)); - else if (splitted.length == 2 && DateUtil.now() - lastRemoteBolusTime < Constants.remoteBolusMinDistance) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotebolusnotallowed)); - else if (splitted.length == 2 && ConfigBuilderPlugin.getPlugin().getActivePump().isSuspended()) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.pumpsuspended)); - else if (splitted.length == 2) - processBOLUS(splitted, receivedSms); - else - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - break; - case "CAL": - if (!remoteCommandsAllowed) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotecommandnotallowed)); - else if (splitted.length == 2) - processCAL(splitted, receivedSms); - else - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - break; - default: // expect passCode here - if (messageToConfirm != null && messageToConfirm.requester.phoneNumber.equals(receivedSms.phoneNumber)) { - messageToConfirm.action(splitted[0]); - messageToConfirm = null; - } else - sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_unknowncommand)); - break; - } - } - - RxBus.INSTANCE.send(new EventSmsCommunicatorUpdateGui()); - } - - @SuppressWarnings("unused") - private void processBG(String[] splitted, Sms receivedSms) { - BgReading actualBG = DatabaseHelper.actualBg(); - BgReading lastBG = DatabaseHelper.lastBg(); - - String reply = ""; - - String units = ProfileFunctions.getInstance().getProfileUnits(); - - if (actualBG != null) { - reply = MainApp.gs(R.string.sms_actualbg) + " " + actualBG.valueToUnitsToString(units) + ", "; - } else if (lastBG != null) { - Long agoMsec = System.currentTimeMillis() - lastBG.date; - int agoMin = (int) (agoMsec / 60d / 1000d); - reply = MainApp.gs(R.string.sms_lastbg) + " " + lastBG.valueToUnitsToString(units) + " " + String.format(MainApp.gs(R.string.sms_minago), agoMin) + ", "; - } - GlucoseStatus glucoseStatus = GlucoseStatus.getGlucoseStatusData(); - if (glucoseStatus != null) - reply += MainApp.gs(R.string.sms_delta) + " " + Profile.toUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units) + " " + units + ", "; - - TreatmentsPlugin.getPlugin().updateTotalIOBTreatments(); - IobTotal bolusIob = TreatmentsPlugin.getPlugin().getLastCalculationTreatments().round(); - TreatmentsPlugin.getPlugin().updateTotalIOBTempBasals(); - IobTotal basalIob = TreatmentsPlugin.getPlugin().getLastCalculationTempBasals().round(); - - String cobText = MainApp.gs(R.string.value_unavailable_short); - CobInfo cobInfo = IobCobCalculatorPlugin.getPlugin().getCobInfo(false, "SMS COB"); - - reply += MainApp.gs(R.string.sms_iob) + " " + DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob) + "U (" - + MainApp.gs(R.string.sms_bolus) + " " + DecimalFormatter.to2Decimal(bolusIob.iob) + "U " - + MainApp.gs(R.string.sms_basal) + " " + DecimalFormatter.to2Decimal(basalIob.basaliob) + "U), " - + MainApp.gs(R.string.cob) + ": " + cobInfo.generateCOBString(); - - sendSMS(new Sms(receivedSms.phoneNumber, reply)); - receivedSms.processed = true; - } - - private void processLOOP(String[] splitted, Sms receivedSms) { - String reply; - switch (splitted[1].toUpperCase()) { - case "DISABLE": - case "STOP": - LoopPlugin loopPlugin = LoopPlugin.getPlugin(); - if (loopPlugin.isEnabled(PluginType.LOOP)) { - loopPlugin.setPluginEnabled(PluginType.LOOP, false); - ConfigBuilderPlugin.getPlugin().getCommandQueue().cancelTempBasal(true, new Callback() { - @Override - public void run() { - RxBus.INSTANCE.send(new EventRefreshOverview("SMS_LOOP_STOP")); - String reply = MainApp.gs(R.string.smscommunicator_loophasbeendisabled) + " " + - MainApp.gs(result.success ? R.string.smscommunicator_tempbasalcanceled : R.string.smscommunicator_tempbasalcancelfailed); - sendSMS(new Sms(receivedSms.phoneNumber, reply)); - } - }); - } else { - sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_loopisdisabled)); - } - receivedSms.processed = true; - break; - case "ENABLE": - case "START": - loopPlugin = LoopPlugin.getPlugin(); - if (!loopPlugin.isEnabled(PluginType.LOOP)) { - loopPlugin.setPluginEnabled(PluginType.LOOP, true); - sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_loophasbeenenabled)); - RxBus.INSTANCE.send(new EventRefreshOverview("SMS_LOOP_START")); - } else { - sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_loopisenabled)); - } - receivedSms.processed = true; - break; - case "STATUS": - loopPlugin = LoopPlugin.getPlugin(); - if (loopPlugin.isEnabled(PluginType.LOOP)) { - if (loopPlugin.isSuspended()) - reply = String.format(MainApp.gs(R.string.loopsuspendedfor), loopPlugin.minutesToEndOfSuspend()); - else - reply = MainApp.gs(R.string.smscommunicator_loopisenabled); - } else { - reply = MainApp.gs(R.string.smscommunicator_loopisdisabled); - } - sendSMS(new Sms(receivedSms.phoneNumber, reply)); - receivedSms.processed = true; - break; - case "RESUME": - LoopPlugin.getPlugin().suspendTo(0); - RxBus.INSTANCE.send(new EventRefreshOverview("SMS_LOOP_RESUME")); - NSUpload.uploadOpenAPSOffline(0); - sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_loopresumed)); - break; - case "SUSPEND": - int duration = 0; - if (splitted.length == 3) - duration = SafeParse.stringToInt(splitted[2]); - duration = Math.max(0, duration); - duration = Math.min(180, duration); - if (duration == 0) { - receivedSms.processed = true; - sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_wrongduration)); - return; - } else { - String passCode = generatePasscode(); - reply = String.format(MainApp.gs(R.string.smscommunicator_suspendreplywithcode), duration, passCode); - receivedSms.processed = true; - messageToConfirm = new AuthRequest(this, receivedSms, reply, passCode, new SmsAction(duration) { - @Override - public void run() { - ConfigBuilderPlugin.getPlugin().getCommandQueue().cancelTempBasal(true, new Callback() { - @Override - public void run() { - if (result.success) { - LoopPlugin.getPlugin().suspendTo(System.currentTimeMillis() + anInteger * 60L * 1000); - NSUpload.uploadOpenAPSOffline(anInteger * 60); - RxBus.INSTANCE.send(new EventRefreshOverview("SMS_LOOP_SUSPENDED")); - String reply = MainApp.gs(R.string.smscommunicator_loopsuspended) + " " + - MainApp.gs(result.success ? R.string.smscommunicator_tempbasalcanceled : R.string.smscommunicator_tempbasalcancelfailed); - sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, reply)); - } else { - String reply = MainApp.gs(R.string.smscommunicator_tempbasalcancelfailed); - reply += "\n" + ConfigBuilderPlugin.getPlugin().getActivePump().shortStatus(true); - sendSMS(new Sms(receivedSms.phoneNumber, reply)); - } - } - }); - - } - }); - } - break; - default: - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - break; - } - } - - private void processTREATMENTS(String[] splitted, Sms receivedSms) { - if (splitted[1].toUpperCase().equals("REFRESH")) { - TreatmentsPlugin.getPlugin().getService().resetTreatments(); - RxBus.INSTANCE.send(new EventNSClientRestart()); - sendSMS(new Sms(receivedSms.phoneNumber, "TREATMENTS REFRESH SENT")); - receivedSms.processed = true; - } else - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - } - - private void processNSCLIENT(String[] splitted, Sms receivedSms) { - if (splitted[1].toUpperCase().equals("RESTART")) { - RxBus.INSTANCE.send(new EventNSClientRestart()); - sendSMS(new Sms(receivedSms.phoneNumber, "NSCLIENT RESTART SENT")); - receivedSms.processed = true; - } else - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - } - - @SuppressWarnings("unused") - private void processPUMP(String[] splitted, Sms receivedSms) { - ConfigBuilderPlugin.getPlugin().getCommandQueue().readStatus("SMS", new Callback() { - @Override - public void run() { - PumpInterface pump = ConfigBuilderPlugin.getPlugin().getActivePump(); - if (result.success) { - if (pump != null) { - String reply = pump.shortStatus(true); - sendSMS(new Sms(receivedSms.phoneNumber, reply)); - } - } else { - String reply = MainApp.gs(R.string.readstatusfailed); - sendSMS(new Sms(receivedSms.phoneNumber, reply)); - } - } - }); - receivedSms.processed = true; - } - - private void processPROFILE(String[] splitted, Sms receivedSms) { - // load profiles - ProfileInterface anInterface = ConfigBuilderPlugin.getPlugin().getActiveProfileInterface(); - if (anInterface == null) { - sendSMS(new Sms(receivedSms.phoneNumber, R.string.notconfigured)); - receivedSms.processed = true; - return; - } - ProfileStore store = anInterface.getProfile(); - if (store == null) { - sendSMS(new Sms(receivedSms.phoneNumber, R.string.notconfigured)); - receivedSms.processed = true; - return; - } - final ArrayList list = store.getProfileList(); - - if (splitted[1].toUpperCase().equals("STATUS")) { - sendSMS(new Sms(receivedSms.phoneNumber, ProfileFunctions.getInstance().getProfileName())); - } else if (splitted[1].toUpperCase().equals("LIST")) { - if (list.isEmpty()) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.invalidprofile)); - else { - String reply = ""; - for (int i = 0; i < list.size(); i++) { - if (i > 0) - reply += "\n"; - reply += (i + 1) + ". "; - reply += list.get(i); - } - sendSMS(new Sms(receivedSms.phoneNumber, reply)); - } - } else { - - int pindex = SafeParse.stringToInt(splitted[1]); - int percentage = 100; - if (splitted.length > 2) - percentage = SafeParse.stringToInt(splitted[2]); - - if (pindex > list.size()) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - else if (percentage == 0) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - else if (pindex == 0) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - else { - final Profile profile = store.getSpecificProfile((String) list.get(pindex - 1)); - if (profile == null) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.noprofile)); - else { - String passCode = generatePasscode(); - String reply = String.format(MainApp.gs(R.string.smscommunicator_profilereplywithcode), list.get(pindex - 1), percentage, passCode); - receivedSms.processed = true; - int finalPercentage = percentage; - messageToConfirm = new AuthRequest(this, receivedSms, reply, passCode, new SmsAction((String) list.get(pindex - 1), finalPercentage) { - @Override - public void run() { - ProfileFunctions.doProfileSwitch(store, (String) list.get(pindex - 1), 0, finalPercentage, 0); - sendSMS(new Sms(receivedSms.phoneNumber, R.string.profileswitchcreated)); - } - }); - } - } - } - receivedSms.processed = true; - } - - private void processBASAL(String[] splitted, Sms receivedSms) { - if (splitted[1].toUpperCase().equals("CANCEL") || splitted[1].toUpperCase().equals("STOP")) { - String passCode = generatePasscode(); - String reply = String.format(MainApp.gs(R.string.smscommunicator_basalstopreplywithcode), passCode); - receivedSms.processed = true; - messageToConfirm = new AuthRequest(this, receivedSms, reply, passCode, new SmsAction() { - @Override - public void run() { - ConfigBuilderPlugin.getPlugin().getCommandQueue().cancelTempBasal(true, new Callback() { - @Override - public void run() { - if (result.success) { - String reply = MainApp.gs(R.string.smscommunicator_tempbasalcanceled); - reply += "\n" + ConfigBuilderPlugin.getPlugin().getActivePump().shortStatus(true); - sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, reply)); - } else { - String reply = MainApp.gs(R.string.smscommunicator_tempbasalcancelfailed); - reply += "\n" + ConfigBuilderPlugin.getPlugin().getActivePump().shortStatus(true); - sendSMS(new Sms(receivedSms.phoneNumber, reply)); - } - } - }); - } - }); - } else if (splitted[1].endsWith("%")) { - int tempBasalPct = SafeParse.stringToInt(StringUtils.removeEnd(splitted[1], "%")); - int duration = 30; - if (splitted.length > 2) - duration = SafeParse.stringToInt(splitted[2]); - final Profile profile = ProfileFunctions.getInstance().getProfile(); - - if (profile == null) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.noprofile)); - else if (tempBasalPct == 0 && !splitted[1].equals("0%")) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - else if (duration == 0) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - else { - tempBasalPct = MainApp.getConstraintChecker().applyBasalPercentConstraints(new Constraint<>(tempBasalPct), profile).value(); - String passCode = generatePasscode(); - String reply = String.format(MainApp.gs(R.string.smscommunicator_basalpctreplywithcode), tempBasalPct, duration, passCode); - receivedSms.processed = true; - messageToConfirm = new AuthRequest(this, receivedSms, reply, passCode, new SmsAction(tempBasalPct, duration) { - @Override - public void run() { - ConfigBuilderPlugin.getPlugin().getCommandQueue().tempBasalPercent(anInteger, secondInteger, true, profile, new Callback() { - @Override - public void run() { - if (result.success) { - String reply; - if (result.isPercent) - reply = String.format(MainApp.gs(R.string.smscommunicator_tempbasalset_percent), result.percent, result.duration); - else - reply = String.format(MainApp.gs(R.string.smscommunicator_tempbasalset), result.absolute, result.duration); - reply += "\n" + ConfigBuilderPlugin.getPlugin().getActivePump().shortStatus(true); - sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, reply)); - } else { - String reply = MainApp.gs(R.string.smscommunicator_tempbasalfailed); - reply += "\n" + ConfigBuilderPlugin.getPlugin().getActivePump().shortStatus(true); - sendSMS(new Sms(receivedSms.phoneNumber, reply)); - } - } - }); - } - }); - } - } else { - Double tempBasal = SafeParse.stringToDouble(splitted[1]); - int duration = 30; - if (splitted.length > 2) - duration = SafeParse.stringToInt(splitted[2]); - final Profile profile = ProfileFunctions.getInstance().getProfile(); - if (profile == null) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.noprofile)); - else if (tempBasal == 0 && !splitted[1].equals("0")) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - else if (duration == 0) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - else { - tempBasal = MainApp.getConstraintChecker().applyBasalConstraints(new Constraint<>(tempBasal), profile).value(); - String passCode = generatePasscode(); - String reply = String.format(MainApp.gs(R.string.smscommunicator_basalreplywithcode), tempBasal, duration, passCode); - receivedSms.processed = true; - messageToConfirm = new AuthRequest(this, receivedSms, reply, passCode, new SmsAction(tempBasal, duration) { - @Override - public void run() { - ConfigBuilderPlugin.getPlugin().getCommandQueue().tempBasalAbsolute(aDouble, secondInteger, true, profile, new Callback() { - @Override - public void run() { - if (result.success) { - String reply; - if (result.isPercent) - reply = String.format(MainApp.gs(R.string.smscommunicator_tempbasalset_percent), result.percent, result.duration); - else - reply = String.format(MainApp.gs(R.string.smscommunicator_tempbasalset), result.absolute, result.duration); - reply += "\n" + ConfigBuilderPlugin.getPlugin().getActivePump().shortStatus(true); - sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, reply)); - } else { - String reply = MainApp.gs(R.string.smscommunicator_tempbasalfailed); - reply += "\n" + ConfigBuilderPlugin.getPlugin().getActivePump().shortStatus(true); - sendSMS(new Sms(receivedSms.phoneNumber, reply)); - } - } - }); - } - }); - } - } - } - - private void processEXTENDED(String[] splitted, Sms receivedSms) { - if (splitted[1].toUpperCase().equals("CANCEL") || splitted[1].toUpperCase().equals("STOP")) { - String passCode = generatePasscode(); - String reply = String.format(MainApp.gs(R.string.smscommunicator_extendedstopreplywithcode), passCode); - receivedSms.processed = true; - messageToConfirm = new AuthRequest(this, receivedSms, reply, passCode, new SmsAction() { - @Override - public void run() { - ConfigBuilderPlugin.getPlugin().getCommandQueue().cancelExtended(new Callback() { - @Override - public void run() { - if (result.success) { - String reply = MainApp.gs(R.string.smscommunicator_extendedcanceled); - reply += "\n" + ConfigBuilderPlugin.getPlugin().getActivePump().shortStatus(true); - sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, reply)); - } else { - String reply = MainApp.gs(R.string.smscommunicator_extendedcancelfailed); - reply += "\n" + ConfigBuilderPlugin.getPlugin().getActivePump().shortStatus(true); - sendSMS(new Sms(receivedSms.phoneNumber, reply)); - } - } - }); - } - }); - } else if (splitted.length != 3) { - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - } else { - Double extended = SafeParse.stringToDouble(splitted[1]); - int duration = SafeParse.stringToInt(splitted[2]); - extended = MainApp.getConstraintChecker().applyExtendedBolusConstraints(new Constraint<>(extended)).value(); - if (extended == 0 || duration == 0) - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - else { - String passCode = generatePasscode(); - String reply = String.format(MainApp.gs(R.string.smscommunicator_extendedreplywithcode), extended, duration, passCode); - receivedSms.processed = true; - messageToConfirm = new AuthRequest(this, receivedSms, reply, passCode, new SmsAction(extended, duration) { - @Override - public void run() { - ConfigBuilderPlugin.getPlugin().getCommandQueue().extendedBolus(aDouble, secondInteger, new Callback() { - @Override - public void run() { - if (result.success) { - String reply = String.format(MainApp.gs(R.string.smscommunicator_extendedset), aDouble, duration); - reply += "\n" + ConfigBuilderPlugin.getPlugin().getActivePump().shortStatus(true); - sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, reply)); - } else { - String reply = MainApp.gs(R.string.smscommunicator_extendedfailed); - reply += "\n" + ConfigBuilderPlugin.getPlugin().getActivePump().shortStatus(true); - sendSMS(new Sms(receivedSms.phoneNumber, reply)); - } - } - }); - } - }); - } - } - } - - - private void processBOLUS(String[] splitted, Sms receivedSms) { - Double bolus = SafeParse.stringToDouble(splitted[1]); - bolus = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(bolus)).value(); - if (bolus > 0d) { - String passCode = generatePasscode(); - String reply = String.format(MainApp.gs(R.string.smscommunicator_bolusreplywithcode), bolus, passCode); - receivedSms.processed = true; - messageToConfirm = new AuthRequest(this, receivedSms, reply, passCode, new SmsAction(bolus) { - @Override - public void run() { - DetailedBolusInfo detailedBolusInfo = new DetailedBolusInfo(); - detailedBolusInfo.insulin = aDouble; - detailedBolusInfo.source = Source.USER; - ConfigBuilderPlugin.getPlugin().getCommandQueue().bolus(detailedBolusInfo, new Callback() { - @Override - public void run() { - final boolean resultSuccess = result.success; - final double resultBolusDelivered = result.bolusDelivered; - ConfigBuilderPlugin.getPlugin().getCommandQueue().readStatus("SMS", new Callback() { - @Override - public void run() { - PumpInterface pump = ConfigBuilderPlugin.getPlugin().getActivePump(); - if (resultSuccess) { - String reply = String.format(MainApp.gs(R.string.smscommunicator_bolusdelivered), resultBolusDelivered); - if (pump != null) - reply += "\n" + pump.shortStatus(true); - lastRemoteBolusTime = DateUtil.now(); - sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, reply)); - } else { - String reply = MainApp.gs(R.string.smscommunicator_bolusfailed); - if (pump != null) - reply += "\n" + pump.shortStatus(true); - sendSMS(new Sms(receivedSms.phoneNumber, reply)); - } - } - }); - } - }); - } - }); - } else - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - } - - private void processCAL(String[] splitted, Sms receivedSms) { - Double cal = SafeParse.stringToDouble(splitted[1]); - if (cal > 0d) { - String passCode = generatePasscode(); - String reply = String.format(MainApp.gs(R.string.smscommunicator_calibrationreplywithcode), cal, passCode); - receivedSms.processed = true; - messageToConfirm = new AuthRequest(this, receivedSms, reply, passCode, new SmsAction(cal) { - @Override - public void run() { - boolean result = XdripCalibrations.sendIntent(aDouble); - if (result) - sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_calibrationsent)); - else - sendSMS(new Sms(receivedSms.phoneNumber, R.string.smscommunicator_calibrationfailed)); - } - }); - } else - sendSMS(new Sms(receivedSms.phoneNumber, R.string.wrongformat)); - } - - public boolean sendNotificationToAllNumbers(String text) { - boolean result = true; - for (int i = 0; i < allowedNumbers.size(); i++) { - Sms sms = new Sms(allowedNumbers.get(i), text); - result = result && sendSMS(sms); - } - return result; - } - - private void sendSMSToAllNumbers(Sms sms) { - for (String number : allowedNumbers) { - sms.phoneNumber = number; - sendSMS(sms); - } - } - - boolean sendSMS(Sms sms) { - SmsManager smsManager = SmsManager.getDefault(); - sms.text = stripAccents(sms.text); - - try { - if (L.isEnabled(L.SMS)) - log.debug("Sending SMS to " + sms.phoneNumber + ": " + sms.text); - if (sms.text.getBytes().length <= 140) - smsManager.sendTextMessage(sms.phoneNumber, null, sms.text, null, null); - else { - ArrayList parts = smsManager.divideMessage(sms.text); - smsManager.sendMultipartTextMessage(sms.phoneNumber, null, parts, - null, null); - } - - messages.add(sms); - } catch (IllegalArgumentException e) { - if (e.getMessage().equals("Invalid message body")) { - Notification notification = new Notification(Notification.INVALID_MESSAGE_BODY, MainApp.gs(R.string.smscommunicator_messagebody), Notification.NORMAL); - RxBus.INSTANCE.send(new EventNewNotification(notification)); - return false; - } else { - Notification notification = new Notification(Notification.INVALID_PHONE_NUMBER, MainApp.gs(R.string.smscommunicator_invalidphonennumber), Notification.NORMAL); - RxBus.INSTANCE.send(new EventNewNotification(notification)); - return false; - } - } catch (java.lang.SecurityException e) { - Notification notification = new Notification(Notification.MISSING_SMS_PERMISSION, MainApp.gs(R.string.smscommunicator_missingsmspermission), Notification.NORMAL); - RxBus.INSTANCE.send(new EventNewNotification(notification)); - return false; - } - RxBus.INSTANCE.send(new EventSmsCommunicatorUpdateGui()); - return true; - } - - private String generatePasscode() { - int startChar1 = 'A'; // on iphone 1st char is uppercase :) - String passCode = Character.toString((char) (startChar1 + Math.random() * ('z' - 'a' + 1))); - int startChar2 = Math.random() > 0.5 ? 'a' : 'A'; - passCode += Character.toString((char) (startChar2 + Math.random() * ('z' - 'a' + 1))); - int startChar3 = Math.random() > 0.5 ? 'a' : 'A'; - passCode += Character.toString((char) (startChar3 + Math.random() * ('z' - 'a' + 1))); - passCode = passCode.replace('l', 'k').replace('I', 'J'); - return passCode; - } - - private static String stripAccents(String s) { - s = Normalizer.normalize(s, Normalizer.Form.NFD); - s = s.replaceAll("[\\p{InCombiningDiacriticalMarks}]", ""); - return s; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.kt new file mode 100644 index 0000000000..e40ed8b1df --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.kt @@ -0,0 +1,922 @@ +package info.nightscout.androidaps.plugins.general.smsCommunicator + +import android.content.Intent +import android.preference.EditTextPreference +import android.preference.Preference +import android.preference.Preference.OnPreferenceChangeListener +import android.preference.PreferenceFragment +import android.telephony.SmsManager +import android.telephony.SmsMessage +import android.text.TextUtils +import com.andreabaccega.widget.ValidatingEditTextPreference +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.data.DetailedBolusInfo +import info.nightscout.androidaps.data.Profile +import info.nightscout.androidaps.db.DatabaseHelper +import info.nightscout.androidaps.db.Source +import info.nightscout.androidaps.db.TempTarget +import info.nightscout.androidaps.events.EventPreferenceChange +import info.nightscout.androidaps.events.EventRefreshOverview +import info.nightscout.androidaps.interfaces.Constraint +import info.nightscout.androidaps.interfaces.PluginBase +import info.nightscout.androidaps.interfaces.PluginDescription +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin +import info.nightscout.androidaps.plugins.bus.RxBus.send +import info.nightscout.androidaps.plugins.bus.RxBus.toObservable +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.plugins.general.nsclient.NSUpload +import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart +import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification +import info.nightscout.androidaps.plugins.general.smsCommunicator.events.EventSmsCommunicatorUpdateGui +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.queue.Callback +import info.nightscout.androidaps.utils.* +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.schedulers.Schedulers +import org.apache.commons.lang3.StringUtils +import org.slf4j.LoggerFactory +import java.text.Normalizer +import java.util.* + +object SmsCommunicatorPlugin : PluginBase(PluginDescription() + .mainType(PluginType.GENERAL) + .fragmentClass(SmsCommunicatorFragment::class.java.name) + .pluginName(R.string.smscommunicator) + .shortName(R.string.smscommunicator_shortname) + .preferencesId(R.xml.pref_smscommunicator) + .description(R.string.description_sms_communicator) +) { + private val log = LoggerFactory.getLogger(L.SMS) + private val disposable = CompositeDisposable() + var allowedNumbers: MutableList = ArrayList() + var messageToConfirm: AuthRequest? = null + var lastRemoteBolusTime: Long = 0 + var messages = ArrayList() + + val commands = mapOf( + "BG" to "BG", + "LOOP" to "LOOP STOP/DISABLE/START/ENABLE/RESUME/STATUS\nLOOP SUSPEND 20", + "TREATMENTS" to "TREATMENTS REFRESH", + "NSCLIENT" to "NSCLIENT RESTART", + "PUMP" to "PUMP", + "BASAL" to "BASAL STOP/CANCEL\nBASAL 0.3\nBASAL 0.3 20\nBASAL 30%\nBASAL 30% 20\n", + "BOLUS" to "BOLUS 1.2\nBOLUS 1.2 MEAL", + "EXTENDED" to "EXTENDED STOP/CANCEL\nEXTENDED 2 120", + "CAL" to "CAL 5.6", + "PROFILE" to "PROFILE STATUS/LIST\nPROFILE 1\nPROFILE 2 30", + "TARGET" to "TARGET MEAL/ACTIVITY/HYPO/STOP", + "SMS" to "SMS DISABLE/STOP", + "CARBS" to "CARBS 12\nCARBS 12 23:05\nCARBS 12 11:05PM", + "HELP" to "HELP\nHELP command" + ) + + init { + processSettings(null) + } + + override fun onStart() { + super.onStart() + disposable.add(toObservable(EventPreferenceChange::class.java) + .observeOn(Schedulers.io()) + .subscribe({ event: EventPreferenceChange? -> processSettings(event) }) { throwable: Throwable? -> FabricPrivacy.logException(throwable) } + ) + } + + override fun onStop() { + disposable.clear() + super.onStop() + } + + override fun preprocessPreferences(preferenceFragment: PreferenceFragment) { + super.preprocessPreferences(preferenceFragment) + val distance = preferenceFragment.findPreference(MainApp.gs(R.string.key_smscommunicator_remotebolusmindistance)) as ValidatingEditTextPreference? + ?: return + val allowedNumbers = preferenceFragment.findPreference(MainApp.gs(R.string.key_smscommunicator_allowednumbers)) as EditTextPreference? + ?: return + if (!areMoreNumbers(allowedNumbers.text)) { + distance.title = (MainApp.gs(R.string.smscommunicator_remotebolusmindistance) + + ".\n" + + MainApp.gs(R.string.smscommunicator_remotebolusmindistance_caveat)) + distance.isEnabled = false + } else { + distance.title = MainApp.gs(R.string.smscommunicator_remotebolusmindistance) + distance.isEnabled = true + } + allowedNumbers.onPreferenceChangeListener = OnPreferenceChangeListener { _: Preference?, newValue: Any -> + if (!areMoreNumbers(newValue as String)) { + distance.text = (Constants.remoteBolusMinDistance / (60 * 1000L)).toString() + distance.title = (MainApp.gs(R.string.smscommunicator_remotebolusmindistance) + + ".\n" + + MainApp.gs(R.string.smscommunicator_remotebolusmindistance_caveat)) + distance.isEnabled = false + } else { + distance.title = MainApp.gs(R.string.smscommunicator_remotebolusmindistance) + distance.isEnabled = true + } + true + } + } + + override fun updatePreferenceSummary(pref: Preference) { + super.updatePreferenceSummary(pref) + if (pref is EditTextPreference) { + val editTextPref = pref + if (pref.getKey().contains("smscommunicator_allowednumbers") && (editTextPref.text == null || TextUtils.isEmpty(editTextPref.text.trim { it <= ' ' }))) { + pref.setSummary(MainApp.gs(R.string.smscommunicator_allowednumbers_summary)) + } + } + } + + private fun processSettings(ev: EventPreferenceChange?) { + if (ev == null || ev.isChanged(R.string.key_smscommunicator_allowednumbers)) { + val settings = SP.getString(R.string.key_smscommunicator_allowednumbers, "") + allowedNumbers.clear() + val substrings = settings.split(";").toTypedArray() + for (number in substrings) { + val cleaned = number.replace("\\s+".toRegex(), "") + allowedNumbers.add(cleaned) + log.debug("Found allowed number: $cleaned") + } + } + } + + fun isCommand(command: String, number: String): Boolean { + var found = false + commands.forEach { (k, _) -> + if (k == command) found = true + } + return found || messageToConfirm?.requester?.phoneNumber == number + } + + fun isAllowedNumber(number: String): Boolean { + for (num in allowedNumbers) { + if (num == number) return true + } + return false + } + + fun handleNewData(intent: Intent) { + val bundle = intent.extras ?: return + val format = bundle.getString("format") ?: return + val pdus = bundle["pdus"] as Array<*> + for (pdu in pdus) { + val message = SmsMessage.createFromPdu(pdu as ByteArray, format) + processSms(Sms(message)) + } + } + + fun processSms(receivedSms: Sms) { + if (!isEnabled(PluginType.GENERAL)) { + log.debug("Ignoring SMS. Plugin disabled.") + return + } + if (!isAllowedNumber(receivedSms.phoneNumber)) { + log.debug("Ignoring SMS from: " + receivedSms.phoneNumber + ". Sender not allowed") + receivedSms.ignored = true + messages.add(receivedSms) + send(EventSmsCommunicatorUpdateGui()) + return + } + val pump = ConfigBuilderPlugin.getPlugin().activePump ?: return + messages.add(receivedSms) + log.debug(receivedSms.toString()) + val splitted = receivedSms.text.split(Regex("\\s+")).toTypedArray() + val remoteCommandsAllowed = SP.getBoolean(R.string.key_smscommunicator_remotecommandsallowed, false) + if (splitted.isNotEmpty() && isCommand(splitted[0].toUpperCase(Locale.getDefault()), receivedSms.phoneNumber)) { + when (splitted[0].toUpperCase(Locale.getDefault())) { + "BG" -> + if (splitted.size == 1) processBG(receivedSms) + else sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + "LOOP" -> + if (!remoteCommandsAllowed) sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotecommandnotallowed)) + else if (splitted.size == 2 || splitted.size == 3) processLOOP(splitted, receivedSms) + else sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + "TREATMENTS" -> + if (splitted.size == 2) processTREATMENTS(splitted, receivedSms) + else sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + "NSCLIENT" -> + if (splitted.size == 2) processNSCLIENT(splitted, receivedSms) + else sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + "PUMP" -> + if (splitted.size == 1) processPUMP(receivedSms) + else sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + "PROFILE" -> + if (!remoteCommandsAllowed) sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotecommandnotallowed)) + else if (splitted.size == 2 || splitted.size == 3) processPROFILE(splitted, receivedSms) + else sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + "BASAL" -> + if (!remoteCommandsAllowed) sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotecommandnotallowed)) + else if (splitted.size == 2 || splitted.size == 3) processBASAL(splitted, receivedSms) + else sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + "EXTENDED" -> + if (!remoteCommandsAllowed) sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotecommandnotallowed)) + else if (splitted.size == 2 || splitted.size == 3) processEXTENDED(splitted, receivedSms) + else sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + "BOLUS" -> + if (!remoteCommandsAllowed) sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotecommandnotallowed)) + else if (splitted.size == 2 && DateUtil.now() - lastRemoteBolusTime < Constants.remoteBolusMinDistance) sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotebolusnotallowed)) + else if (splitted.size == 2 && pump.isSuspended) sendSMS(Sms(receivedSms.phoneNumber, R.string.pumpsuspended)) + else if (splitted.size == 2 || splitted.size == 3) processBOLUS(splitted, receivedSms) + else sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + "CARBS" -> + if (!remoteCommandsAllowed) sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotecommandnotallowed)) + else if (splitted.size == 2 || splitted.size == 3) processCARBS(splitted, receivedSms) + else sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + "CAL" -> + if (!remoteCommandsAllowed) sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotecommandnotallowed)) + else if (splitted.size == 2) processCAL(splitted, receivedSms) + else sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + "TARGET" -> + if (!remoteCommandsAllowed) sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotecommandnotallowed)) + else if (splitted.size == 2) processTARGET(splitted, receivedSms) + else sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + "SMS" -> + if (!remoteCommandsAllowed) sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_remotecommandnotallowed)) + else if (splitted.size == 2) processSMS(splitted, receivedSms) + else sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + "HELP" -> + if (splitted.size == 1 || splitted.size == 2) processHELP(splitted, receivedSms) + else sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + else -> + if (messageToConfirm?.requester?.phoneNumber == receivedSms.phoneNumber) { + messageToConfirm?.action(splitted[0]) + messageToConfirm = null + } else sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_unknowncommand)) + } + } + send(EventSmsCommunicatorUpdateGui()) + } + + private fun processBG(receivedSms: Sms) { + val actualBG = DatabaseHelper.actualBg() + val lastBG = DatabaseHelper.lastBg() + var reply = "" + val units = ProfileFunctions.getInstance().profileUnits + if (actualBG != null) { + reply = MainApp.gs(R.string.sms_actualbg) + " " + actualBG.valueToUnitsToString(units) + ", " + } else if (lastBG != null) { + val agoMsec = System.currentTimeMillis() - lastBG.date + val agoMin = (agoMsec / 60.0 / 1000.0).toInt() + reply = MainApp.gs(R.string.sms_lastbg) + " " + lastBG.valueToUnitsToString(units) + " " + String.format(MainApp.gs(R.string.sms_minago), agoMin) + ", " + } + val glucoseStatus = GlucoseStatus.getGlucoseStatusData() + if (glucoseStatus != null) reply += MainApp.gs(R.string.sms_delta) + " " + Profile.toUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units) + " " + units + ", " + TreatmentsPlugin.getPlugin().updateTotalIOBTreatments() + val bolusIob = TreatmentsPlugin.getPlugin().lastCalculationTreatments.round() + TreatmentsPlugin.getPlugin().updateTotalIOBTempBasals() + val basalIob = TreatmentsPlugin.getPlugin().lastCalculationTempBasals.round() + val cobInfo = IobCobCalculatorPlugin.getPlugin().getCobInfo(false, "SMS COB") + reply += (MainApp.gs(R.string.sms_iob) + " " + DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob) + "U (" + + MainApp.gs(R.string.sms_bolus) + " " + DecimalFormatter.to2Decimal(bolusIob.iob) + "U " + + MainApp.gs(R.string.sms_basal) + " " + DecimalFormatter.to2Decimal(basalIob.basaliob) + "U), " + + MainApp.gs(R.string.cob) + ": " + cobInfo.generateCOBString()) + sendSMS(Sms(receivedSms.phoneNumber, reply)) + receivedSms.processed = true + } + + private fun processLOOP(splitted: Array, receivedSms: Sms) { + when (splitted[1].toUpperCase(Locale.getDefault())) { + "DISABLE", "STOP" -> { + val loopPlugin = LoopPlugin.getPlugin() + if (loopPlugin.isEnabled(PluginType.LOOP)) { + loopPlugin.setPluginEnabled(PluginType.LOOP, false) + ConfigBuilderPlugin.getPlugin().commandQueue.cancelTempBasal(true, object : Callback() { + override fun run() { + send(EventRefreshOverview("SMS_LOOP_STOP")) + val replyText = MainApp.gs(R.string.smscommunicator_loophasbeendisabled) + " " + + MainApp.gs(if (result.success) R.string.smscommunicator_tempbasalcanceled else R.string.smscommunicator_tempbasalcancelfailed) + sendSMS(Sms(receivedSms.phoneNumber, replyText)) + } + }) + } else + sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_loopisdisabled)) + receivedSms.processed = true + } + "ENABLE", "START" -> { + val loopPlugin = LoopPlugin.getPlugin() + if (!loopPlugin.isEnabled(PluginType.LOOP)) { + loopPlugin.setPluginEnabled(PluginType.LOOP, true) + sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_loophasbeenenabled)) + send(EventRefreshOverview("SMS_LOOP_START")) + } else + sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_loopisenabled)) + receivedSms.processed = true + } + "STATUS" -> { + val loopPlugin = LoopPlugin.getPlugin() + val reply = if (loopPlugin.isEnabled(PluginType.LOOP)) { + if (loopPlugin.isSuspended()) String.format(MainApp.gs(R.string.loopsuspendedfor), loopPlugin.minutesToEndOfSuspend()) + else MainApp.gs(R.string.smscommunicator_loopisenabled) + } else + MainApp.gs(R.string.smscommunicator_loopisdisabled) + sendSMS(Sms(receivedSms.phoneNumber, reply)) + receivedSms.processed = true + } + "RESUME" -> { + LoopPlugin.getPlugin().suspendTo(0) + send(EventRefreshOverview("SMS_LOOP_RESUME")) + NSUpload.uploadOpenAPSOffline(0.0) + sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, R.string.smscommunicator_loopresumed)) + } + "SUSPEND" -> { + var duration = 0 + if (splitted.size == 3) duration = SafeParse.stringToInt(splitted[2]) + duration = Math.max(0, duration) + duration = Math.min(180, duration) + if (duration == 0) { + receivedSms.processed = true + sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_wrongduration)) + return + } else { + val passCode = generatePasscode() + val reply = String.format(MainApp.gs(R.string.smscommunicator_suspendreplywithcode), duration, passCode) + receivedSms.processed = true + messageToConfirm = AuthRequest(this, receivedSms, reply, passCode, object : SmsAction(duration) { + override fun run() { + ConfigBuilderPlugin.getPlugin().commandQueue.cancelTempBasal(true, object : Callback() { + override fun run() { + if (result.success) { + LoopPlugin.getPlugin().suspendTo(System.currentTimeMillis() + anInteger() * 60L * 1000) + NSUpload.uploadOpenAPSOffline(anInteger() * 60.toDouble()) + send(EventRefreshOverview("SMS_LOOP_SUSPENDED")) + val replyText = MainApp.gs(R.string.smscommunicator_loopsuspended) + " " + + MainApp.gs(if (result.success) R.string.smscommunicator_tempbasalcanceled else R.string.smscommunicator_tempbasalcancelfailed) + sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) + } else { + var replyText = MainApp.gs(R.string.smscommunicator_tempbasalcancelfailed) + replyText += "\n" + ConfigBuilderPlugin.getPlugin().activePump?.shortStatus(true) + sendSMS(Sms(receivedSms.phoneNumber, replyText)) + } + } + }) + } + }) + } + } + else -> sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + } + } + + private fun processTREATMENTS(splitted: Array, receivedSms: Sms) { + if (splitted[1].toUpperCase(Locale.getDefault()) == "REFRESH") { + TreatmentsPlugin.getPlugin().service.resetTreatments() + send(EventNSClientRestart()) + sendSMS(Sms(receivedSms.phoneNumber, "TREATMENTS REFRESH SENT")) + receivedSms.processed = true + } else + sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + } + + private fun processNSCLIENT(splitted: Array, receivedSms: Sms) { + if (splitted[1].toUpperCase(Locale.getDefault()) == "RESTART") { + send(EventNSClientRestart()) + sendSMS(Sms(receivedSms.phoneNumber, "NSCLIENT RESTART SENT")) + receivedSms.processed = true + } else + sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + } + + private fun processHELP(splitted: Array, receivedSms: Sms) { + if (splitted.size == 1) { + sendSMS(Sms(receivedSms.phoneNumber, commands.keys.toString().replace("[", "").replace("]", ""))) + receivedSms.processed = true + } else if (isCommand(splitted[1].toUpperCase(Locale.getDefault()), receivedSms.phoneNumber)) { + commands[splitted[1].toUpperCase(Locale.getDefault())]?.let { + sendSMS(Sms(receivedSms.phoneNumber, it)) + receivedSms.processed = true + } + } else + sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + } + + private fun processPUMP(receivedSms: Sms) { + ConfigBuilderPlugin.getPlugin().commandQueue.readStatus("SMS", object : Callback() { + override fun run() { + val pump = ConfigBuilderPlugin.getPlugin().activePump + if (result.success) { + if (pump != null) { + val reply = pump.shortStatus(true) + sendSMS(Sms(receivedSms.phoneNumber, reply)) + } + } else { + val reply = MainApp.gs(R.string.readstatusfailed) + sendSMS(Sms(receivedSms.phoneNumber, reply)) + } + } + }) + receivedSms.processed = true + } + + private fun processPROFILE(splitted: Array, receivedSms: Sms) { // load profiles + val anInterface = ConfigBuilderPlugin.getPlugin().activeProfileInterface + if (anInterface == null) { + sendSMS(Sms(receivedSms.phoneNumber, R.string.notconfigured)) + receivedSms.processed = true + return + } + val store = anInterface.profile + if (store == null) { + sendSMS(Sms(receivedSms.phoneNumber, R.string.notconfigured)) + receivedSms.processed = true + return + } + val list = store.profileList + if (splitted[1].toUpperCase(Locale.getDefault()) == "STATUS") { + sendSMS(Sms(receivedSms.phoneNumber, ProfileFunctions.getInstance().profileName)) + } else if (splitted[1].toUpperCase(Locale.getDefault()) == "LIST") { + if (list.isEmpty()) sendSMS(Sms(receivedSms.phoneNumber, R.string.invalidprofile)) + else { + var reply = "" + for (i in list.indices) { + if (i > 0) reply += "\n" + reply += (i + 1).toString() + ". " + reply += list[i] + } + sendSMS(Sms(receivedSms.phoneNumber, reply)) + } + } else { + val pindex = SafeParse.stringToInt(splitted[1]) + var percentage = 100 + if (splitted.size > 2) percentage = SafeParse.stringToInt(splitted[2]) + if (pindex > list.size) sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + else if (percentage == 0) sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + else if (pindex == 0) sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + else { + val profile = store.getSpecificProfile(list[pindex - 1] as String) + if (profile == null) sendSMS(Sms(receivedSms.phoneNumber, R.string.noprofile)) + else { + val passCode = generatePasscode() + val reply = String.format(MainApp.gs(R.string.smscommunicator_profilereplywithcode), list[pindex - 1], percentage, passCode) + receivedSms.processed = true + val finalPercentage = percentage + messageToConfirm = AuthRequest(this, receivedSms, reply, passCode, object : SmsAction(list[pindex - 1] as String, finalPercentage) { + override fun run() { + ProfileFunctions.doProfileSwitch(store, list[pindex - 1] as String, 0, finalPercentage, 0) + sendSMS(Sms(receivedSms.phoneNumber, R.string.profileswitchcreated)) + } + }) + } + } + } + receivedSms.processed = true + } + + private fun processBASAL(splitted: Array, receivedSms: Sms) { + if (splitted[1].toUpperCase(Locale.getDefault()) == "CANCEL" || splitted[1].toUpperCase(Locale.getDefault()) == "STOP") { + val passCode = generatePasscode() + val reply = String.format(MainApp.gs(R.string.smscommunicator_basalstopreplywithcode), passCode) + receivedSms.processed = true + messageToConfirm = AuthRequest(this, receivedSms, reply, passCode, object : SmsAction() { + override fun run() { + ConfigBuilderPlugin.getPlugin().commandQueue.cancelTempBasal(true, object : Callback() { + override fun run() { + if (result.success) { + var replyText = MainApp.gs(R.string.smscommunicator_tempbasalcanceled) + replyText += "\n" + ConfigBuilderPlugin.getPlugin().activePump?.shortStatus(true) + sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) + } else { + var replyText = MainApp.gs(R.string.smscommunicator_tempbasalcancelfailed) + replyText += "\n" + ConfigBuilderPlugin.getPlugin().activePump?.shortStatus(true) + sendSMS(Sms(receivedSms.phoneNumber, replyText)) + } + } + }) + } + }) + } else if (splitted[1].endsWith("%")) { + var tempBasalPct = SafeParse.stringToInt(StringUtils.removeEnd(splitted[1], "%")) + var duration = 30 + if (splitted.size > 2) duration = SafeParse.stringToInt(splitted[2]) + val profile = ProfileFunctions.getInstance().profile + if (profile == null) sendSMS(Sms(receivedSms.phoneNumber, R.string.noprofile)) + else if (tempBasalPct == 0 && splitted[1] != "0%") sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + else if (duration == 0) sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + else { + tempBasalPct = MainApp.getConstraintChecker().applyBasalPercentConstraints(Constraint(tempBasalPct), profile).value() + val passCode = generatePasscode() + val reply = String.format(MainApp.gs(R.string.smscommunicator_basalpctreplywithcode), tempBasalPct, duration, passCode) + receivedSms.processed = true + messageToConfirm = AuthRequest(this, receivedSms, reply, passCode, object : SmsAction(tempBasalPct, duration) { + override fun run() { + ConfigBuilderPlugin.getPlugin().commandQueue.tempBasalPercent(anInteger(), secondInteger(), true, profile, object : Callback() { + override fun run() { + if (result.success) { + var replyText: String + replyText = if (result.isPercent) String.format(MainApp.gs(R.string.smscommunicator_tempbasalset_percent), result.percent, result.duration) else String.format(MainApp.gs(R.string.smscommunicator_tempbasalset), result.absolute, result.duration) + replyText += "\n" + ConfigBuilderPlugin.getPlugin().activePump?.shortStatus(true) + sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) + } else { + var replyText = MainApp.gs(R.string.smscommunicator_tempbasalfailed) + replyText += "\n" + ConfigBuilderPlugin.getPlugin().activePump?.shortStatus(true) + sendSMS(Sms(receivedSms.phoneNumber, replyText)) + } + } + }) + } + }) + } + } else { + var tempBasal = SafeParse.stringToDouble(splitted[1]) + var duration = 30 + if (splitted.size > 2) duration = SafeParse.stringToInt(splitted[2]) + val profile = ProfileFunctions.getInstance().profile + if (profile == null) sendSMS(Sms(receivedSms.phoneNumber, R.string.noprofile)) + else if (tempBasal == 0.0 && splitted[1] != "0") sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + else if (duration == 0) sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + else { + tempBasal = MainApp.getConstraintChecker().applyBasalConstraints(Constraint(tempBasal), profile).value() + val passCode = generatePasscode() + val reply = String.format(MainApp.gs(R.string.smscommunicator_basalreplywithcode), tempBasal, duration, passCode) + receivedSms.processed = true + messageToConfirm = AuthRequest(this, receivedSms, reply, passCode, object : SmsAction(tempBasal, duration) { + override fun run() { + ConfigBuilderPlugin.getPlugin().commandQueue.tempBasalAbsolute(aDouble(), secondInteger(), true, profile, object : Callback() { + override fun run() { + if (result.success) { + var replyText = if (result.isPercent) String.format(MainApp.gs(R.string.smscommunicator_tempbasalset_percent), result.percent, result.duration) + else String.format(MainApp.gs(R.string.smscommunicator_tempbasalset), result.absolute, result.duration) + replyText += "\n" + ConfigBuilderPlugin.getPlugin().activePump?.shortStatus(true) + sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) + } else { + var replyText = MainApp.gs(R.string.smscommunicator_tempbasalfailed) + replyText += "\n" + ConfigBuilderPlugin.getPlugin().activePump?.shortStatus(true) + sendSMS(Sms(receivedSms.phoneNumber, replyText)) + } + } + }) + } + }) + } + } + } + + private fun processEXTENDED(splitted: Array, receivedSms: Sms) { + if (splitted[1].toUpperCase(Locale.getDefault()) == "CANCEL" || splitted[1].toUpperCase(Locale.getDefault()) == "STOP") { + val passCode = generatePasscode() + val reply = String.format(MainApp.gs(R.string.smscommunicator_extendedstopreplywithcode), passCode) + receivedSms.processed = true + messageToConfirm = AuthRequest(this, receivedSms, reply, passCode, object : SmsAction() { + override fun run() { + ConfigBuilderPlugin.getPlugin().commandQueue.cancelExtended(object : Callback() { + override fun run() { + if (result.success) { + var replyText = MainApp.gs(R.string.smscommunicator_extendedcanceled) + replyText += "\n" + ConfigBuilderPlugin.getPlugin().activePump?.shortStatus(true) + sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) + } else { + var replyText = MainApp.gs(R.string.smscommunicator_extendedcancelfailed) + replyText += "\n" + ConfigBuilderPlugin.getPlugin().activePump?.shortStatus(true) + sendSMS(Sms(receivedSms.phoneNumber, replyText)) + } + } + }) + } + }) + } else if (splitted.size != 3) { + sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + } else { + var extended = SafeParse.stringToDouble(splitted[1]) + val duration = SafeParse.stringToInt(splitted[2]) + extended = MainApp.getConstraintChecker().applyExtendedBolusConstraints(Constraint(extended)).value() + if (extended == 0.0 || duration == 0) sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + else { + val passCode = generatePasscode() + val reply = String.format(MainApp.gs(R.string.smscommunicator_extendedreplywithcode), extended, duration, passCode) + receivedSms.processed = true + messageToConfirm = AuthRequest(this, receivedSms, reply, passCode, object : SmsAction(extended, duration) { + override fun run() { + ConfigBuilderPlugin.getPlugin().commandQueue.extendedBolus(aDouble(), secondInteger(), object : Callback() { + override fun run() { + if (result.success) { + var replyText = String.format(MainApp.gs(R.string.smscommunicator_extendedset), aDouble, duration) + replyText += "\n" + ConfigBuilderPlugin.getPlugin().activePump?.shortStatus(true) + sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) + } else { + var replyText = MainApp.gs(R.string.smscommunicator_extendedfailed) + replyText += "\n" + ConfigBuilderPlugin.getPlugin().activePump?.shortStatus(true) + sendSMS(Sms(receivedSms.phoneNumber, replyText)) + } + } + }) + } + }) + } + } + } + + private fun processBOLUS(splitted: Array, receivedSms: Sms) { + var bolus = SafeParse.stringToDouble(splitted[1]) + val isMeal = splitted.size > 2 && splitted[2].equals("MEAL", ignoreCase = true) + bolus = MainApp.getConstraintChecker().applyBolusConstraints(Constraint(bolus)).value() + if (splitted.size == 3 && !isMeal) { + sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + } else if (bolus > 0.0) { + val passCode = generatePasscode() + val reply = if (isMeal) + String.format(MainApp.gs(R.string.smscommunicator_mealbolusreplywithcode), bolus, passCode) + else + String.format(MainApp.gs(R.string.smscommunicator_bolusreplywithcode), bolus, passCode) + receivedSms.processed = true + messageToConfirm = AuthRequest(this, receivedSms, reply, passCode, object : SmsAction(bolus) { + override fun run() { + val detailedBolusInfo = DetailedBolusInfo() + detailedBolusInfo.insulin = aDouble() + detailedBolusInfo.source = Source.USER + ConfigBuilderPlugin.getPlugin().commandQueue.bolus(detailedBolusInfo, object : Callback() { + override fun run() { + val resultSuccess = result.success + val resultBolusDelivered = result.bolusDelivered + ConfigBuilderPlugin.getPlugin().commandQueue.readStatus("SMS", object : Callback() { + override fun run() { + if (resultSuccess) { + var replyText = if (isMeal) + String.format(MainApp.gs(R.string.smscommunicator_mealbolusdelivered), resultBolusDelivered) + else + String.format(MainApp.gs(R.string.smscommunicator_bolusdelivered), resultBolusDelivered) + replyText += "\n" + ConfigBuilderPlugin.getPlugin().activePump?.shortStatus(true) + lastRemoteBolusTime = DateUtil.now() + if (isMeal) { + ProfileFunctions.getInstance().profile?.let { currentProfile -> + var eatingSoonTTDuration = SP.getInt(R.string.key_eatingsoon_duration, Constants.defaultEatingSoonTTDuration) + eatingSoonTTDuration = + if (eatingSoonTTDuration > 0) eatingSoonTTDuration + else Constants.defaultEatingSoonTTDuration + var eatingSoonTT = SP.getDouble(R.string.key_eatingsoon_target, if (currentProfile.units == Constants.MMOL) Constants.defaultEatingSoonTTmmol else Constants.defaultEatingSoonTTmgdl) + eatingSoonTT = + if (eatingSoonTT > 0) eatingSoonTT + else if (currentProfile.units == Constants.MMOL) Constants.defaultEatingSoonTTmmol + else Constants.defaultEatingSoonTTmgdl + val tempTarget = TempTarget() + .date(System.currentTimeMillis()) + .duration(eatingSoonTTDuration) + .reason(MainApp.gs(R.string.eatingsoon)) + .source(Source.USER) + .low(Profile.toMgdl(eatingSoonTT, currentProfile.units)) + .high(Profile.toMgdl(eatingSoonTT, currentProfile.units)) + TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget) + val tt = if (currentProfile.units == Constants.MMOL) { + DecimalFormatter.to1Decimal(eatingSoonTT) + } else DecimalFormatter.to0Decimal(eatingSoonTT) + replyText += "\n" + String.format(MainApp.gs(R.string.smscommunicator_mealbolusdelivered_tt), tt, eatingSoonTTDuration) + } + } + sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) + } else { + var replyText = MainApp.gs(R.string.smscommunicator_bolusfailed) + replyText += "\n" + ConfigBuilderPlugin.getPlugin().activePump?.shortStatus(true) + sendSMS(Sms(receivedSms.phoneNumber, replyText)) + } + } + }) + } + }) + } + }) + } else sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + } + + private fun processCARBS(splitted: Array, receivedSms: Sms) { + var grams = SafeParse.stringToInt(splitted[1]) + var time = DateUtil.now() + if (splitted.size > 2) { + val seconds = DateUtil.toSeconds(splitted[2].toUpperCase(Locale.getDefault())) + val midnight = MidnightTime.calc() + if (seconds == 0 && (!splitted[2].startsWith("00:00") || !splitted[2].startsWith("12:00"))) { + sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + return + } + time = midnight + T.secs(seconds.toLong()).msecs() + } + grams = MainApp.getConstraintChecker().applyCarbsConstraints(Constraint(grams)).value() + if (grams == 0) sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + else { + val passCode = generatePasscode() + val reply = String.format(MainApp.gs(R.string.smscommunicator_carbsreplywithcode), grams, DateUtil.timeString(time), passCode) + receivedSms.processed = true + messageToConfirm = AuthRequest(this, receivedSms, reply, passCode, object : SmsAction(grams, time) { + override fun run() { + val detailedBolusInfo = DetailedBolusInfo() + detailedBolusInfo.carbs = anInteger().toDouble() + detailedBolusInfo.date = secondLong() + ConfigBuilderPlugin.getPlugin().commandQueue.bolus(detailedBolusInfo, object : Callback() { + override fun run() { + if (result.success) { + var replyText = String.format(MainApp.gs(R.string.smscommunicator_carbsset), anInteger) + replyText += "\n" + ConfigBuilderPlugin.getPlugin().activePump?.shortStatus(true) + sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) + } else { + var replyText = MainApp.gs(R.string.smscommunicator_carbsfailed) + replyText += "\n" + ConfigBuilderPlugin.getPlugin().activePump?.shortStatus(true) + sendSMS(Sms(receivedSms.phoneNumber, replyText)) + } + } + }) + } + }) + } + } + + private fun processTARGET(splitted: Array, receivedSms: Sms) { + val isMeal = splitted[1].equals("MEAL", ignoreCase = true) + val isActivity = splitted[1].equals("ACTIVITY", ignoreCase = true) + val isHypo = splitted[1].equals("HYPO", ignoreCase = true) + val isStop = splitted[1].equals("STOP", ignoreCase = true) || splitted[1].equals("CANCEL", ignoreCase = true) + if (isMeal || isActivity || isHypo) { + val passCode = generatePasscode() + val reply = String.format(MainApp.gs(R.string.smscommunicator_temptargetwithcode), splitted[1].toUpperCase(Locale.getDefault()), passCode) + receivedSms.processed = true + messageToConfirm = AuthRequest(this, receivedSms, reply, passCode, object : SmsAction() { + override fun run() { + val currentProfile = ProfileFunctions.getInstance().profile + if (currentProfile != null) { + var keyDuration = 0 + var defaultTargetDuration = 0 + var keyTarget = 0 + var defaultTargetMMOL = 0.0 + var defaultTargetMGDL = 0.0 + if (isMeal) { + keyDuration = R.string.key_eatingsoon_duration + defaultTargetDuration = Constants.defaultEatingSoonTTDuration + keyTarget = R.string.key_eatingsoon_target + defaultTargetMMOL = Constants.defaultEatingSoonTTmmol + defaultTargetMGDL = Constants.defaultEatingSoonTTmgdl + } else if (isActivity) { + keyDuration = R.string.key_activity_duration + defaultTargetDuration = Constants.defaultActivityTTDuration + keyTarget = R.string.key_activity_target + defaultTargetMMOL = Constants.defaultActivityTTmmol + defaultTargetMGDL = Constants.defaultActivityTTmgdl + } else if (isHypo) { + keyDuration = R.string.key_hypo_duration + defaultTargetDuration = Constants.defaultHypoTTDuration + keyTarget = R.string.key_hypo_target + defaultTargetMMOL = Constants.defaultHypoTTmmol + defaultTargetMGDL = Constants.defaultHypoTTmgdl + } + var ttDuration = SP.getInt(keyDuration, defaultTargetDuration) + ttDuration = if (ttDuration > 0) ttDuration else defaultTargetDuration + var tt = SP.getDouble(keyTarget, if (currentProfile.units == Constants.MMOL) defaultTargetMMOL else defaultTargetMGDL) + tt = if (tt > 0) tt else if (currentProfile.units == Constants.MMOL) defaultTargetMMOL else defaultTargetMGDL + val tempTarget = TempTarget() + .date(System.currentTimeMillis()) + .duration(ttDuration) + .reason(MainApp.gs(R.string.eatingsoon)) + .source(Source.USER) + .low(Profile.toMgdl(tt, currentProfile.units)) + .high(Profile.toMgdl(tt, currentProfile.units)) + TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget) + val ttString = if (currentProfile.units == Constants.MMOL) DecimalFormatter.to1Decimal(tt) else DecimalFormatter.to0Decimal(tt) + val replyText = String.format(MainApp.gs(R.string.smscommunicator_tt_set), ttString, ttDuration) + sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) + } else { + sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_unknowncommand)) + } + } + }) + } else if (isStop) { + val passCode = generatePasscode() + val reply = String.format(MainApp.gs(R.string.smscommunicator_temptargetcancel), passCode) + receivedSms.processed = true + messageToConfirm = AuthRequest(this, receivedSms, reply, passCode, object : SmsAction() { + override fun run() { + val currentProfile = ProfileFunctions.getInstance().profile + if (currentProfile != null) { + val tempTarget = TempTarget() + .source(Source.USER) + .date(DateUtil.now()) + .duration(0) + .low(0.0) + .high(0.0) + TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget) + val replyText = String.format(MainApp.gs(R.string.smscommunicator_tt_canceled)) + sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) + } else { + sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_unknowncommand)) + } + } + }) + } else + sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + } + + private fun processSMS(splitted: Array, receivedSms: Sms) { + val isStop = (splitted[1].equals("STOP", ignoreCase = true) + || splitted[1].equals("DISABLE", ignoreCase = true)) + if (isStop) { + val passCode = generatePasscode() + val reply = String.format(MainApp.gs(R.string.smscommunicator_stopsmswithcode), passCode) + receivedSms.processed = true + messageToConfirm = AuthRequest(this, receivedSms, reply, passCode, object : SmsAction() { + override fun run() { + SP.putBoolean(R.string.key_smscommunicator_remotecommandsallowed, false) + val replyText = String.format(MainApp.gs(R.string.smscommunicator_stoppedsms)) + sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) + } + }) + } else sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + } + + private fun processCAL(splitted: Array, receivedSms: Sms) { + val cal = SafeParse.stringToDouble(splitted[1]) + if (cal > 0.0) { + val passCode = generatePasscode() + val reply = String.format(MainApp.gs(R.string.smscommunicator_calibrationreplywithcode), cal, passCode) + receivedSms.processed = true + messageToConfirm = AuthRequest(this, receivedSms, reply, passCode, object : SmsAction(cal) { + override fun run() { + val result = XdripCalibrations.sendIntent(aDouble) + if (result) sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, R.string.smscommunicator_calibrationsent)) else sendSMS(Sms(receivedSms.phoneNumber, R.string.smscommunicator_calibrationfailed)) + } + }) + } else sendSMS(Sms(receivedSms.phoneNumber, R.string.wrongformat)) + } + + fun sendNotificationToAllNumbers(text: String): Boolean { + var result = true + for (i in allowedNumbers.indices) { + val sms = Sms(allowedNumbers[i], text) + result = result && sendSMS(sms) + } + return result + } + + private fun sendSMSToAllNumbers(sms: Sms) { + for (number in allowedNumbers) { + sms.phoneNumber = number + sendSMS(sms) + } + } + + fun sendSMS(sms: Sms): Boolean { + val smsManager = SmsManager.getDefault() + sms.text = stripAccents(sms.text) + try { + if (L.isEnabled(L.SMS)) log.debug("Sending SMS to " + sms.phoneNumber + ": " + sms.text) + if (sms.text.toByteArray().size <= 140) smsManager.sendTextMessage(sms.phoneNumber, null, sms.text, null, null) + else { + val parts = smsManager.divideMessage(sms.text) + smsManager.sendMultipartTextMessage(sms.phoneNumber, null, parts, + null, null) + } + messages.add(sms) + } catch (e: IllegalArgumentException) { + return if (e.message == "Invalid message body") { + val notification = Notification(Notification.INVALID_MESSAGE_BODY, MainApp.gs(R.string.smscommunicator_messagebody), Notification.NORMAL) + send(EventNewNotification(notification)) + false + } else { + val notification = Notification(Notification.INVALID_PHONE_NUMBER, MainApp.gs(R.string.smscommunicator_invalidphonennumber), Notification.NORMAL) + send(EventNewNotification(notification)) + false + } + } catch (e: SecurityException) { + val notification = Notification(Notification.MISSING_SMS_PERMISSION, MainApp.gs(R.string.smscommunicator_missingsmspermission), Notification.NORMAL) + send(EventNewNotification(notification)) + return false + } + send(EventSmsCommunicatorUpdateGui()) + return true + } + + private fun generatePasscode(): String { + val startChar1 = 'A'.toInt() // on iphone 1st char is uppercase :) + var passCode = Character.toString((startChar1 + Math.random() * ('z' - 'a' + 1)).toChar()) + val startChar2: Int = if (Math.random() > 0.5) 'a'.toInt() else 'A'.toInt() + passCode += Character.toString((startChar2 + Math.random() * ('z' - 'a' + 1)).toChar()) + val startChar3: Int = if (Math.random() > 0.5) 'a'.toInt() else 'A'.toInt() + passCode += Character.toString((startChar3 + Math.random() * ('z' - 'a' + 1)).toChar()) + passCode = passCode.replace('l', 'k').replace('I', 'J') + return passCode + } + + private fun stripAccents(str: String): String { + var s = str + s = Normalizer.normalize(s, Normalizer.Form.NFD) + s = s.replace("[\\p{InCombiningDiacriticalMarks}]".toRegex(), "") + return s + } + + fun areMoreNumbers(allowednumbers: String?): Boolean { + return allowednumbers?.let { + var countNumbers = 0 + val substrings = it.split(";").toTypedArray() + for (number in substrings) { + var cleaned = number.replace(Regex("\\s+"), "") + if (cleaned.length < 4) continue + cleaned = cleaned.replace("+", "") + cleaned = cleaned.replace("-", "") + if (!cleaned.matches(Regex("[0-9]+"))) continue + countNumbers++ + } + countNumbers > 1 + } ?: false + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt index 78ad0ab37d..e8971ab9d5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt @@ -1,5 +1,6 @@ package info.nightscout.androidaps.plugins.general.tidepool +import android.preference.PreferenceFragment import android.text.Spanned import info.nightscout.androidaps.Constants import info.nightscout.androidaps.MainApp @@ -34,7 +35,6 @@ object TidepoolPlugin : PluginBase(PluginDescription() .preferencesId(R.xml.pref_tidepool) .description(R.string.description_tidepool) ) { - private val log = LoggerFactory.getLogger(L.TIDEPOOL) private var disposable: CompositeDisposable = CompositeDisposable() @@ -111,6 +111,16 @@ object TidepoolPlugin : PluginBase(PluginDescription() super.onStop() } + override fun preprocessPreferences(preferenceFragment: PreferenceFragment) { + super.preprocessPreferences(preferenceFragment) + + val tidepoolTestLogin = preferenceFragment.findPreference(MainApp.gs(R.string.key_tidepool_test_login)) + tidepoolTestLogin?.setOnPreferenceClickListener { + TidepoolUploader.testLogin(preferenceFragment.getActivity()) + false + } + } + private fun doUpload() = when (TidepoolUploader.connectionStatus) { TidepoolUploader.ConnectionStatus.FAILED -> {} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6bLoop.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6bLoop.java index 3e551b6ccf..a7b005b9b8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6bLoop.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6bLoop.java @@ -16,6 +16,7 @@ import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; */ public class Encoding4b6bLoop extends Encoding4b6bAbstract { + private static final Logger log = LoggerFactory.getLogger(Encoding4b6bLoop.class); public static final Logger LOG = LoggerFactory.getLogger(Encoding4b6bLoop.class); public Map codesRev = null; @@ -108,9 +109,8 @@ public class Encoding4b6bLoop extends Encoding4b6bAbstract { int index2 = ((bitAccumulator >> (availBits - 12)) & 0b111111); hiNibble = codesRev.get((bitAccumulator >> (availBits - 6))); loNibble = codesRev.get(((bitAccumulator >> (availBits - 12)) & 0b111111)); - } catch (Exception ex) { - System.out.println("Exception: " + ex.getMessage()); - ex.printStackTrace(); + } catch (Exception e) { + log.error("Unhandled exception", e); return null; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/DanaRExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/DanaRExecutionService.java index 0801d8a173..dba3b4ca77 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/DanaRExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/DanaRExecutionService.java @@ -383,7 +383,7 @@ public class DanaRExecutionService extends AbstractDanaRExecutionService { try { o.wait(); } catch (InterruptedException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } } } else { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/DanaRSPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/DanaRSPlugin.java index 7b2668d45c..55be88996b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/DanaRSPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/DanaRSPlugin.java @@ -5,10 +5,12 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; +import android.preference.Preference; import androidx.annotation.Nullable; import androidx.fragment.app.FragmentActivity; +import org.jetbrains.annotations.NotNull; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; @@ -110,6 +112,14 @@ public class DanaRSPlugin extends PluginBase implements PumpInterface, DanaRInte } } + @Override + public void updatePreferenceSummary(@NotNull Preference pref) { + super.updatePreferenceSummary(pref); + + if (pref.getKey().equals(MainApp.gs(R.string.key_danars_name))) + pref.setSummary(SP.getString(R.string.key_danars_name, "")); + } + @Override protected void onStart() { Context context = MainApp.instance().getApplicationContext(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet.java index 3d59b34e16..55a2e0c51e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet.java @@ -4,11 +4,15 @@ import android.annotation.TargetApi; import android.os.Build; import com.cozmo.danar.util.BleCommandUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.nio.charset.StandardCharsets; import java.util.Date; public class DanaRS_Packet { + private static final Logger log = LoggerFactory.getLogger(DanaRS_Packet.class); + protected static final int TYPE_START = 0; protected static final int OPCODE_START = 1; protected static final int DATA_START = 2; @@ -73,7 +77,7 @@ public class DanaRS_Packet { return ret; } catch (Exception e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return null; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/services/BLEComm.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/services/BLEComm.java index 8a43d6e35a..253b861811 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/services/BLEComm.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/services/BLEComm.java @@ -551,7 +551,7 @@ public class BLEComm { break; } } catch (Exception e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } startSignatureFound = false; packetIsValid = false; @@ -635,7 +635,7 @@ public class BLEComm { message.wait(5000); } catch (InterruptedException e) { log.error("sendMessage InterruptedException", e); - e.printStackTrace(); + log.error("Unhandled exception", e); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightPlugin.java index 7baf9458c9..5104e70fe8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightPlugin.java @@ -1526,7 +1526,7 @@ public class LocalInsightPlugin extends PluginBase implements PumpInterface, Con data.put("notes", note); NSUpload.uploadCareportalEntryToNS(data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } } @@ -1554,7 +1554,7 @@ public class LocalInsightPlugin extends PluginBase implements PumpInterface, Con data.put("eventType", event); NSUpload.uploadCareportalEntryToNS(data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/app_layer/history/history_events/HistoryEvent.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/app_layer/history/history_events/HistoryEvent.java index 874c908ca5..f6b3dc588c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/app_layer/history/history_events/HistoryEvent.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/app_layer/history/history_events/HistoryEvent.java @@ -3,8 +3,11 @@ package info.nightscout.androidaps.plugins.pump.insight.app_layer.history.histor import info.nightscout.androidaps.plugins.pump.insight.ids.HistoryEventIDs; import info.nightscout.androidaps.plugins.pump.insight.utils.BOCUtil; import info.nightscout.androidaps.plugins.pump.insight.utils.ByteBuf; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class HistoryEvent implements Comparable { + private static final Logger log = LoggerFactory.getLogger(HistoryEvent.class); private int eventYear; private int eventMonth; @@ -22,10 +25,8 @@ public class HistoryEvent implements Comparable { else { try { event = eventClass.newInstance(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InstantiationException e) { - e.printStackTrace(); + } catch (IllegalAccessException | InstantiationException e) { + log.error("Unhandled exception", e); } } event.parseHeader(byteBuf); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java index ae6d754493..f0d10e0370 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java @@ -48,6 +48,7 @@ import info.nightscout.androidaps.plugins.general.overview.dialogs.ErrorHelperAc import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.pump.common.PumpPluginAbstract; +import info.nightscout.androidaps.plugins.pump.common.bolusInfo.DetailedBolusInfoStorage; import info.nightscout.androidaps.plugins.pump.common.defs.PumpDriverState; import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; @@ -372,7 +373,7 @@ public class MedtronicPumpPlugin extends PumpPluginAbstract implements PumpInter refreshAnyStatusThatNeedsToBeRefreshed(); } - RxBus.INSTANCE.send(new EventMedtronicPumpValuesChanged()); + RxBus.INSTANCE.send(new EventMedtronicPumpValuesChanged()); } @@ -386,7 +387,7 @@ public class MedtronicPumpPlugin extends PumpPluginAbstract implements PumpInter RileyLinkServiceState rileyLinkServiceState = MedtronicUtil.getServiceState(); - if (rileyLinkServiceState==null) { + if (rileyLinkServiceState == null) { LOG.error("RileyLink unreachable. RileyLinkServiceState is null."); return false; } @@ -744,13 +745,13 @@ public class MedtronicPumpPlugin extends PumpPluginAbstract implements PumpInter ClockDTO clock = MedtronicUtil.getPumpTime(); - if (clock==null) { // retry + if (clock == null) { // retry medtronicUIComm.executeCommand(MedtronicCommandType.GetRealTimeClock); clock = MedtronicUtil.getPumpTime(); } - if (clock==null) + if (clock == null) return; int timeDiff = Math.abs(clock.timeDifference); @@ -866,6 +867,11 @@ public class MedtronicPumpPlugin extends PumpPluginAbstract implements PumpInter }).start(); } + long now = System.currentTimeMillis(); + + detailedBolusInfo.date = now; + detailedBolusInfo.deliverAt = now; // not sure about that one + TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo, true); // we subtract insulin, exact amount will be visible with next remainingInsulin update. @@ -877,7 +883,7 @@ public class MedtronicPumpPlugin extends PumpPluginAbstract implements PumpInter // calculate time for bolus and set driver to busy for that time int bolusTime = (int) (detailedBolusInfo.insulin * 42.0d); - long time = System.currentTimeMillis() + (bolusTime * 1000); + long time = now + (bolusTime * 1000); this.busyTimestamps.add(time); setEnableCustomAction(MedtronicCustomActionType.ClearBolusBlock, true); @@ -1065,10 +1071,10 @@ public class MedtronicPumpPlugin extends PumpPluginAbstract implements PumpInter @Override public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, Profile profile, boolean enforceNew) { - if (percent==0) { + if (percent == 0) { return setTempBasalAbsolute(0.0d, durationInMinutes, profile, enforceNew); } else { - double absoluteValue = profile.getBasal() * (percent /100.0d); + double absoluteValue = profile.getBasal() * (percent / 100.0d); getMDTPumpStatus(); absoluteValue = pumpStatusLocal.pumpType.determineCorrectBasalSize(absoluteValue); LOG.warn("setTempBasalPercent [MedtronicPumpPlugin] - You are trying to use setTempBasalPercent with percent other then 0% (%d). This will start setTempBasalAbsolute, with calculated value (%.3f). Result might not be 100% correct.", percent, absoluteValue); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/MedtronicPumpHistoryDecoder.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/MedtronicPumpHistoryDecoder.java index 32ca320de2..71f387d176 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/MedtronicPumpHistoryDecoder.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/MedtronicPumpHistoryDecoder.java @@ -412,25 +412,18 @@ public class MedtronicPumpHistoryDecoder extends MedtronicHistoryDecoder validEntries; - // private Object validValues; - public PumpHistoryResult(PumpHistoryEntry searchEntry, Long targetDate) { if (searchEntry != null) { /* @@ -109,9 +105,8 @@ public class PumpHistoryResult { if (unprocessedEntry.isAfter(this.searchDate)) { this.validEntries.add(unprocessedEntry); } else { - LOG.debug("PE. PumpHistoryResult. Not after.. Unprocessed Entry [year={},entry={}]", - DateTimeUtil.getYear(unprocessedEntry.atechDateTime), unprocessedEntry); - +// LOG.debug("PE. PumpHistoryResult. Not after.. Unprocessed Entry [year={},entry={}]", +// DateTimeUtil.getYear(unprocessedEntry.atechDateTime), unprocessedEntry); if (DateTimeUtil.getYear(unprocessedEntry.atechDateTime) > 2015) olderEntries++; } @@ -131,14 +126,6 @@ public class PumpHistoryResult { } - private void clearOrPrepareList() { - if (this.validEntries == null) - this.validEntries = new ArrayList<>(); - else - this.validEntries.clear(); - } - - public String toString() { return "PumpHistoryResult [unprocessed=" + (unprocessedEntries != null ? "" + unprocessedEntries.size() : "0") + // ", valid=" + (validEntries != null ? "" + validEntries.size() : "0") + // diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/MedtronicHistoryData.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/MedtronicHistoryData.java index 1eed440cdc..2e23d2a955 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/MedtronicHistoryData.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/MedtronicHistoryData.java @@ -49,6 +49,7 @@ import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpSta import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicConst; import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; import info.nightscout.androidaps.plugins.treatments.Treatment; +import info.nightscout.androidaps.plugins.treatments.TreatmentService; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.SP; @@ -67,7 +68,6 @@ import info.nightscout.androidaps.utils.SP; // All things marked with "TODO: Fix db code" needs to be updated in new 2.5 database code public class MedtronicHistoryData { - private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); private List allHistory = null; @@ -77,6 +77,7 @@ public class MedtronicHistoryData { private boolean isInit = false; private Gson gson; + private Gson gsonCore; private DatabaseHelper databaseHelper = MainApp.getDbHelper(); private ClockDTO pumpTime; @@ -94,10 +95,15 @@ public class MedtronicHistoryData { public MedtronicHistoryData() { this.allHistory = new ArrayList<>(); this.gson = MedtronicUtil.gsonInstance; + this.gsonCore = MedtronicUtil.getGsonInstanceCore(); if (this.gson == null) { this.gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); } + + if (this.gsonCore == null) { + this.gsonCore = new GsonBuilder().create(); + } } @@ -523,7 +529,7 @@ public class MedtronicHistoryData { data.put("eventType", event); NSUpload.uploadCareportalEntryToNS(data); } catch (JSONException e) { - e.printStackTrace(); + LOG.error("Unhandled exception", e); } } @@ -597,7 +603,7 @@ public class MedtronicHistoryData { if (doubleBolusDebug) LOG.debug("DoubleBolusDebug: List (before filter): {}, FromDb={}", gson.toJson(entryList), - gson.toJson(entriesFromHistory)); + gsonCore.toJson(entriesFromHistory)); filterOutAlreadyAddedEntries(entryList, entriesFromHistory); @@ -609,7 +615,7 @@ public class MedtronicHistoryData { if (doubleBolusDebug) LOG.debug("DoubleBolusDebug: List (after filter): {}, FromDb={}", gson.toJson(entryList), - gson.toJson(entriesFromHistory)); + gsonCore.toJson(entriesFromHistory)); if (isCollectionEmpty(entriesFromHistory)) { for (PumpHistoryEntry treatment : entryList) { @@ -862,6 +868,7 @@ public class MedtronicHistoryData { return; List removeTreatmentsFromHistory = new ArrayList<>(); + List removeTreatmentsFromPH = new ArrayList<>(); for (DbObjectBase treatment : treatmentsFromHistory) { @@ -879,11 +886,17 @@ public class MedtronicHistoryData { if (selectedBolus != null) { entryList.remove(selectedBolus); + removeTreatmentsFromPH.add(selectedBolus); removeTreatmentsFromHistory.add(treatment); } } } + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: filterOutAlreadyAddedEntries: PumpHistory={}, Treatments={}", + gson.toJson(removeTreatmentsFromPH), + gsonCore.toJson(removeTreatmentsFromHistory)); + treatmentsFromHistory.removeAll(removeTreatmentsFromHistory); } @@ -947,36 +960,23 @@ public class MedtronicHistoryData { } else { - DetailedBolusInfo detailedBolusInfo = DetailedBolusInfoStorage.INSTANCE.findDetailedBolusInfo(treatment.date, bolusDTO.getDeliveredAmount()); + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: addBolus(OldTreatment={}): Bolus={}", treatment, bolusDTO); + + treatment.source = Source.PUMP; + treatment.pumpId = bolus.getPumpId(); + treatment.insulin = bolusDTO.getDeliveredAmount(); + + TreatmentService.UpdateReturn updateReturn = TreatmentsPlugin.getPlugin().getService().createOrUpdateMedtronic(treatment, false); if (doubleBolusDebug) - LOG.debug("DoubleBolusDebug: addBolus(tretament={}): Bolus={}, DetailedBolusInfo={}", treatment, bolusDTO, detailedBolusInfo); - - if (detailedBolusInfo == null) { - detailedBolusInfo = new DetailedBolusInfo(); - - if (doubleBolusDebug) - LOG.debug("DoubleBolusDebug: detailedBolusInfoCouldNotBeRetrived !"); - } - - detailedBolusInfo.date = treatment.date; - detailedBolusInfo.source = Source.PUMP; - detailedBolusInfo.pumpId = bolus.getPumpId(); - detailedBolusInfo.insulin = bolusDTO.getDeliveredAmount(); - detailedBolusInfo.carbs = treatment.carbs; - - addCarbsFromEstimate(detailedBolusInfo, bolus); - - if (doubleBolusDebug) - LOG.debug("DoubleBolusDebug: addBolus(tretament!=null): DetailedBolusInfo(New)={}", detailedBolusInfo); - - boolean newRecord = TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo, false); - - bolus.setLinkedObject(detailedBolusInfo); + LOG.debug("DoubleBolusDebug: addBolus(tretament!=null): NewTreatment={}, UpdateReturn={}", treatment, updateReturn); if (isLogEnabled()) - LOG.debug("editBolus - [date={},pumpId={}, insulin={}, newRecord={}]", detailedBolusInfo.date, - detailedBolusInfo.pumpId, detailedBolusInfo.insulin, newRecord); + LOG.debug("editBolus - [date={},pumpId={}, insulin={}, newRecord={}]", treatment.date, + treatment.pumpId, treatment.insulin, updateReturn.toString()); + + bolus.setLinkedObject(treatment); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicUtil.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicUtil.java index 96bf0a5d44..2c7ee69c2f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicUtil.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicUtil.java @@ -61,8 +61,7 @@ public class MedtronicUtil extends RileyLinkUtil { private static int doneBit = 1 << 7; private static ClockDTO pumpTime; public static Gson gsonInstance = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); - public static Gson gsonInstancePretty = new GsonBuilder().excludeFieldsWithoutExposeAnnotation() - .setPrettyPrinting().create(); + public static Gson gsonInstanceCore = new GsonBuilder().create(); private static BatteryType batteryType = BatteryType.None; @@ -70,8 +69,9 @@ public class MedtronicUtil extends RileyLinkUtil { return gsonInstance; } - public static Gson getGsonInstancePretty() { - return gsonInstancePretty; + + public static Gson getGsonInstanceCore() { + return gsonInstanceCore; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/SourceDexcomPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/SourceDexcomPlugin.kt index dfbe2fff39..633f16cf10 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/SourceDexcomPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/SourceDexcomPlugin.kt @@ -17,6 +17,7 @@ import info.nightscout.androidaps.logging.L import info.nightscout.androidaps.plugins.general.nsclient.NSUpload import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.SP +import info.nightscout.androidaps.utils.T import org.json.JSONObject import org.slf4j.LoggerFactory @@ -51,7 +52,7 @@ object SourceDexcomPlugin : PluginBase(PluginDescription() } fun findDexcomPackageName(): String? { - val packageManager = MainApp.instance().packageManager; + val packageManager = MainApp.instance().packageManager for (packageInfo in packageManager.getInstalledPackages(0)) { if (PACKAGE_NAMES.contains(packageInfo.packageName)) return packageInfo.packageName } @@ -64,43 +65,53 @@ object SourceDexcomPlugin : PluginBase(PluginDescription() val sensorType = intent.getStringExtra("sensorType") ?: "" val glucoseValues = intent.getBundleExtra("glucoseValues") for (i in 0 until glucoseValues.size()) { - val glucoseValue = glucoseValues.getBundle(i.toString()) - val bgReading = BgReading() - bgReading.value = glucoseValue!!.getInt("glucoseValue").toDouble() - bgReading.direction = glucoseValue.getString("trendArrow") - bgReading.date = glucoseValue.getLong("timestamp") * 1000 - bgReading.raw = 0.0 - if (MainApp.getDbHelper().createIfNotExists(bgReading, "Dexcom$sensorType")) { - if (SP.getBoolean(R.string.key_dexcomg5_nsupload, false)) { - NSUpload.uploadBg(bgReading, "AndroidAPS-Dexcom$sensorType") - } - if (SP.getBoolean(R.string.key_dexcomg5_xdripupload, false)) { - NSUpload.sendToXdrip(bgReading) + glucoseValues.getBundle(i.toString())?.let { glucoseValue -> + val bgReading = BgReading() + bgReading.value = glucoseValue.getInt("glucoseValue").toDouble() + bgReading.direction = glucoseValue.getString("trendArrow") + bgReading.date = glucoseValue.getLong("timestamp") * 1000 + bgReading.raw = 0.0 + if (MainApp.getDbHelper().createIfNotExists(bgReading, "Dexcom$sensorType")) { + if (SP.getBoolean(R.string.key_dexcomg5_nsupload, false)) { + NSUpload.uploadBg(bgReading, "AndroidAPS-Dexcom$sensorType") + } + if (SP.getBoolean(R.string.key_dexcomg5_xdripupload, false)) { + NSUpload.sendToXdrip(bgReading) + } } } } val meters = intent.getBundleExtra("meters") for (i in 0 until meters.size()) { val meter = meters.getBundle(i.toString()) - val timestamp = meter!!.getLong("timestamp") * 1000 - if (MainApp.getDbHelper().getCareportalEventFromTimestamp(timestamp) != null) continue - val jsonObject = JSONObject() - jsonObject.put("enteredBy", "AndroidAPS-Dexcom$sensorType") - jsonObject.put("created_at", DateUtil.toISOString(timestamp)) - jsonObject.put("eventType", CareportalEvent.BGCHECK) - jsonObject.put("glucoseType", "Finger") - jsonObject.put("glucose", meter.getInt("meterValue")) - jsonObject.put("units", Constants.MGDL) - NSUpload.uploadCareportalEntryToNS(jsonObject) + meter?.let { + val timestamp = it.getLong("timestamp") * 1000 + val now = DateUtil.now() + if (timestamp > now - T.months(1).msecs() && timestamp < now) + if (MainApp.getDbHelper().getCareportalEventFromTimestamp(timestamp) == null) { + val jsonObject = JSONObject() + jsonObject.put("enteredBy", "AndroidAPS-Dexcom$sensorType") + jsonObject.put("created_at", DateUtil.toISOString(timestamp)) + jsonObject.put("eventType", CareportalEvent.BGCHECK) + jsonObject.put("glucoseType", "Finger") + jsonObject.put("glucose", meter.getInt("meterValue")) + jsonObject.put("units", Constants.MGDL) + NSUpload.uploadCareportalEntryToNS(jsonObject) + } + } } if (SP.getBoolean(R.string.key_dexcom_lognssensorchange, false) && intent.hasExtra("sensorInsertionTime")) { - val sensorInsertionTime = intent.extras!!.getLong("sensorInsertionTime") * 1000 - if (MainApp.getDbHelper().getCareportalEventFromTimestamp(sensorInsertionTime) == null) { - val jsonObject = JSONObject() - jsonObject.put("enteredBy", "AndroidAPS-Dexcom$sensorType") - jsonObject.put("created_at", DateUtil.toISOString(sensorInsertionTime)) - jsonObject.put("eventType", CareportalEvent.SENSORCHANGE) - NSUpload.uploadCareportalEntryToNS(jsonObject) + intent.extras?.let { + val sensorInsertionTime = it.getLong("sensorInsertionTime") * 1000 + val now = DateUtil.now() + if (sensorInsertionTime > now - T.months(1).msecs() && sensorInsertionTime < now) + if (MainApp.getDbHelper().getCareportalEventFromTimestamp(sensorInsertionTime) == null) { + val jsonObject = JSONObject() + jsonObject.put("enteredBy", "AndroidAPS-Dexcom$sensorType") + jsonObject.put("created_at", DateUtil.toISOString(sensorInsertionTime)) + jsonObject.put("eventType", CareportalEvent.SENSORCHANGE) + NSUpload.uploadCareportalEntryToNS(jsonObject) + } } } } catch (e: Exception) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java index 7123b6c96b..1331c8eedf 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java @@ -133,7 +133,7 @@ public class TreatmentService extends OrmLiteBaseService { try { getDao().executeRaw("ALTER TABLE `" + Treatment.TABLE_TREATMENTS + "` ADD COLUMN boluscalc STRING;"); } catch (SQLException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } } else { if (L.isEnabled(L.DATATREATMENTS)) @@ -147,7 +147,7 @@ public class TreatmentService extends OrmLiteBaseService { try { getDao().executeRaw("ALTER TABLE `" + Treatment.TABLE_TREATMENTS + "` DROP COLUMN boluscalc STRING;"); } catch (SQLException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } } } @@ -736,6 +736,14 @@ public class TreatmentService extends OrmLiteBaseService { boolean newRecord; boolean success; + + @Override + public String toString() { + return "UpdateReturn [" + + "newRecord=" + newRecord + + ", success=" + success + + ']'; + } } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java index 28181da190..004f44858d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java @@ -349,7 +349,7 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface long time = System.currentTimeMillis(); synchronized (treatments) { if (MedtronicHistoryData.doubleBolusDebug) - log.debug("DoubleBolusDebug: AllTreatmentsInDb: {}", MedtronicUtil.getGsonInstance().toJson(treatments)); + log.debug("DoubleBolusDebug: AllTreatmentsInDb: {}", MedtronicUtil.getGsonInstanceCore().toJson(treatments)); for (Treatment t : treatments) { if (t.date <= time && t.date >= fromTimestamp) @@ -357,7 +357,7 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface } if (MedtronicHistoryData.doubleBolusDebug) - log.debug("DoubleBolusDebug: FilteredTreatments: AfterTime={}, Items={}", fromTimestamp, MedtronicUtil.getGsonInstance().toJson(in5minback)); + log.debug("DoubleBolusDebug: FilteredTreatments: AfterTime={}, Items={}", fromTimestamp, MedtronicUtil.getGsonInstanceCore().toJson(in5minback)); return in5minback; } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSetProfile.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSetProfile.java index 62e14285eb..b69f3f4dc7 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSetProfile.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSetProfile.java @@ -50,7 +50,7 @@ public class CommandSetProfile extends Command { // Send SMS notification if ProfileSwitch is comming from NS ProfileSwitch profileSwitch = TreatmentsPlugin.getPlugin().getProfileSwitchFromHistory(System.currentTimeMillis()); if (profileSwitch != null && r.enacted && profileSwitch.source == Source.NIGHTSCOUT) { - SmsCommunicatorPlugin smsCommunicatorPlugin = SmsCommunicatorPlugin.getPlugin(); + SmsCommunicatorPlugin smsCommunicatorPlugin = SmsCommunicatorPlugin.INSTANCE; if (smsCommunicatorPlugin.isEnabled(PluginType.GENERAL)) { smsCommunicatorPlugin.sendNotificationToAllNumbers(MainApp.gs(R.string.profile_set_ok)); } diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java index 9af15e9ddc..cb49d8cf4e 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java +++ b/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java @@ -21,7 +21,7 @@ public class ChargingStateReceiver extends BroadcastReceiver { lastEvent = event; } - public EventChargingState grabChargingState(Context context) { + public static EventChargingState grabChargingState(Context context) { BatteryManager bm = (BatteryManager) context.getSystemService(Context.BATTERY_SERVICE); if (bm == null) diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java index 3f12d75fce..19cf12bc8a 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java +++ b/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java @@ -26,11 +26,6 @@ public class NetworkChangeReceiver extends BroadcastReceiver { public static final NetworkChangeReceiver instance = new NetworkChangeReceiver(); - // TODO: Split NSClient into network state component that can be used by several plugins and logic for plugin - public static void fetch() { - new NetworkChangeReceiver().grabNetworkStatus(MainApp.instance().getApplicationContext()); - } - @Override public void onReceive(final Context context, final Intent intent) { EventNetworkChange event = grabNetworkStatus(context); @@ -39,7 +34,7 @@ public class NetworkChangeReceiver extends BroadcastReceiver { } @Nullable - public EventNetworkChange grabNetworkStatus(final Context context) { + public static EventNetworkChange grabNetworkStatus(final Context context) { EventNetworkChange event = new EventNetworkChange(); ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); diff --git a/app/src/main/java/info/nightscout/androidaps/services/DataService.java b/app/src/main/java/info/nightscout/androidaps/services/DataService.java index 80a42e145a..44050ece35 100644 --- a/app/src/main/java/info/nightscout/androidaps/services/DataService.java +++ b/app/src/main/java/info/nightscout/androidaps/services/DataService.java @@ -103,7 +103,7 @@ public class DataService extends IntentService { ) { handleNewDataFromNSClient(intent); } else if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(action)) { - SmsCommunicatorPlugin.getPlugin().handleNewData(intent); + SmsCommunicatorPlugin.INSTANCE.handleNewData(intent); } if (L.isEnabled(L.DATASERVICE)) diff --git a/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java b/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java index d592347d1b..98c4d3a071 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java @@ -93,15 +93,15 @@ public class DateUtil { } public static int toSeconds(String hh_colon_mm) { - Pattern p = Pattern.compile("(\\d+):(\\d+)( a.m.| p.m.| AM | PM|)"); + Pattern p = Pattern.compile("(\\d+):(\\d+)( a.m.| p.m.| AM| PM|AM|PM|)"); Matcher m = p.matcher(hh_colon_mm); int retval = 0; if (m.find()) { retval = SafeParse.stringToInt(m.group(1)) * 60 * 60 + SafeParse.stringToInt(m.group(2)) * 60; - if ((m.group(3).equals(" a.m.") || m.group(3).equals(" AM")) && m.group(1).equals("12")) + if ((m.group(3).equals(" a.m.") || m.group(3).equals(" AM") || m.group(3).equals("AM")) && m.group(1).equals("12")) retval -= 12 * 60 * 60; - if ((m.group(3).equals(" p.m.") || m.group(3).equals(" PM")) && !(m.group(1).equals("12"))) + if ((m.group(3).equals(" p.m.") || m.group(3).equals(" PM") || m.group(3).equals("PM")) && !(m.group(1).equals("12"))) retval += 12 * 60 * 60; } return retval; @@ -185,7 +185,7 @@ public class DateUtil { long remainingTimeMinutes = timeInMillis / (1000 * 60); long remainingTimeHours = remainingTimeMinutes / 60; remainingTimeMinutes = remainingTimeMinutes % 60; - return "(" + ((remainingTimeHours > 0) ? (remainingTimeHours + "h ") : "") + remainingTimeMinutes + "')"; + return "(" + ((remainingTimeHours > 0) ? (remainingTimeHours + MainApp.gs(R.string.shorthour) + " ") : "") + remainingTimeMinutes + "')"; } public static String sinceString(long timestamp) { diff --git a/app/src/main/java/info/nightscout/androidaps/utils/Round.java b/app/src/main/java/info/nightscout/androidaps/utils/Round.java index ba7f7e3f86..3e312a20a9 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/Round.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/Round.java @@ -1,14 +1,23 @@ package info.nightscout.androidaps.utils; +import java.math.BigDecimal; + /** * Created by mike on 20.06.2016. */ public class Round { public static Double roundTo(double x, Double step) { - if (x != 0d) { - return Math.round(x / step) * step; + if (x == 0d) { + return 0d; } - return 0d; + + //Double oldCalc = Math.round(x / step) * step; + Double newCalc = BigDecimal.valueOf(Math.round(x / step)).multiply(BigDecimal.valueOf(step)).doubleValue(); + + // just for the tests, forcing failures + //newCalc = oldCalc; + + return newCalc; } public static Double floorTo(Double x, Double step) { if (x != 0d) { diff --git a/app/src/main/res/values/objectives.xml b/app/src/main/res/values/objectives.xml index 469db88006..d5ffbc2e3a 100644 --- a/app/src/main/res/values/objectives.xml +++ b/app/src/main/res/values/objectives.xml @@ -47,7 +47,7 @@ Display content of Loop plugin Use scale function by long-pressing BG chart Enter - Enter code obtained from developers to bypass the rest of objectives + If you were OpenAPS user before and your NS has at least 3 months of looping data, you can send an email to objectives@androidaps.org with your NS address and request code to bypass the rest of objectives. Enter code obtained from developers Code accepted Code invalid Prove your knowledge diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4c43ba1193..e357e6fc5f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -292,11 +292,24 @@ Allowed phone numbers +XXXXXXXXXX;+YYYYYYYYYY To deliver bolus %1$.2fU reply with code %2$s + To deliver meal bolus %1$.2fU reply with code %2$s + To set the Temp Target %1$s reply with code %2$s + To cancel Temp Target reply with code %1$s + To disable the SMS Remote Service reply with code %1$s.\n\nKeep in mind that you\'ll able to reactivate it directly from the AAPS master smartphone only. + SMS Remote Service stopped. To reactivate it, use AAPS on master smartphone. To send calibration %1$.2f reply with code %2$s Bolus failed + smscommunicator_remotebolusmindistance + Minimum number of minutes that must elapse between one remote bolus and the next + How many minutes must elapse, at least, between one bolus and the next + For your safety, to edit this preference you need to add at least 2 phone numbers. Bolus %1$.2fU delivered successfully Going to deliver %1$.2fU Bolus %1$.2fU delivered successfully + Meal Bolus %1$.2fU delivered successfully + Target %1$s for %2$d minutes + Target %1$s for %2$d minutes set successfully + Temp Target canceled successfully Delivering %1$.2fU Allow remote commands via SMS Finger @@ -356,10 +369,13 @@ To start basal %1$.2fU/h for %2$d min reply with code %3$s To switch profile to %1$s %2$d%% reply with code %3$s To start extended bolus %1$.2fU for %2$d min reply with code %3$s + To enter %1$dg at %2$s reply with code %3$s To start basal %1$d%% for %2$d min reply with code %3$s To suspend loop for %1$d minutes reply with code %2$s Temp basal %1$.2fU/h for %2$d min started successfully Extended bolus %1$.2fU for %2$d min started successfully + Carbs %1$dg entered successfully + Entering %1$dg of carbs failed Temp basal %1$d%% for %2$d min started successfully Temp basal start failed Extended bolus start failed @@ -778,6 +794,7 @@ g m h + d ]]> kJ En @@ -814,7 +831,7 @@ BG upload settings Show detailed delta Show delta with one more decimal place - 45 60 75 90 105 120 + SMB max minutes Max minutes of basal to limit SMB to Unsupported pump firmware Send BG data to xDrip+ diff --git a/app/src/main/res/xml/pref_smscommunicator.xml b/app/src/main/res/xml/pref_smscommunicator.xml index 202e7348c5..e76b1b15b2 100644 --- a/app/src/main/res/xml/pref_smscommunicator.xml +++ b/app/src/main/res/xml/pref_smscommunicator.xml @@ -1,5 +1,6 @@ - + @@ -8,6 +9,16 @@ android:key="@string/key_smscommunicator_allowednumbers" android:summary="@string/smscommunicator_allowednumbers_summary" android:title="@string/smscommunicator_allowednumbers" /> + { - sentSms = invocation.getArgument(0); - return null; - }).when(smsCommunicatorPlugin).sendSMS(any(Sms.class)); - AAPSMocker.mockMainApp(); AAPSMocker.mockApplicationContext(); AAPSMocker.mockSP(); @@ -90,5 +82,12 @@ public class AuthRequestTest { AAPSMocker.mockStrings(); mockStatic(DateUtil.class); + + smsCommunicatorPlugin = mock(SmsCommunicatorPlugin.class); + doAnswer((Answer) invocation -> { + sentSms = invocation.getArgument(0); + return null; + }).when(smsCommunicatorPlugin).sendSMS(any(Sms.class)); + } } diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsActionTest.java b/app/src/test/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsActionTest.java index f4d61e56eb..f6a4712b72 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsActionTest.java +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsActionTest.java @@ -41,7 +41,7 @@ public class SmsActionTest { }; smsAction.run(); Assert.assertEquals(result, "B"); - Assert.assertEquals(smsAction.aDouble, 1d, 0.000001d); + Assert.assertEquals(smsAction.aDouble(), 1d, 0.000001d); smsAction = new SmsAction(1d, 2) { @Override @@ -51,8 +51,8 @@ public class SmsActionTest { }; smsAction.run(); Assert.assertEquals(result, "C"); - Assert.assertEquals(smsAction.aDouble, 1d, 0.000001d); - Assert.assertEquals(smsAction.secondInteger.intValue(), 2); + Assert.assertEquals(smsAction.aDouble(), 1d, 0.000001d); + Assert.assertEquals(smsAction.secondInteger(), 2); smsAction = new SmsAction("aString", 3) { @Override @@ -62,8 +62,8 @@ public class SmsActionTest { }; smsAction.run(); Assert.assertEquals(result, "D"); - Assert.assertEquals(smsAction.aString, "aString"); - Assert.assertEquals(smsAction.secondInteger.intValue(), 3); + Assert.assertEquals(smsAction.aString(), "aString"); + Assert.assertEquals(smsAction.secondInteger(), 3); smsAction = new SmsAction(4) { @Override @@ -73,7 +73,7 @@ public class SmsActionTest { }; smsAction.run(); Assert.assertEquals(result, "E"); - Assert.assertEquals(smsAction.anInteger.intValue(), 4); + Assert.assertEquals(smsAction.anInteger(), 4); smsAction = new SmsAction(5, 6) { @Override @@ -83,8 +83,8 @@ public class SmsActionTest { }; smsAction.run(); Assert.assertEquals(result, "F"); - Assert.assertEquals(smsAction.anInteger.intValue(), 5); - Assert.assertEquals(smsAction.secondInteger.intValue(), 6); + Assert.assertEquals(smsAction.anInteger(), 5); + Assert.assertEquals(smsAction.secondInteger(), 6); } } diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPluginTest.java b/app/src/test/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPluginTest.java index bc928ded85..c757840c1f 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPluginTest.java +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPluginTest.java @@ -25,7 +25,6 @@ import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.ProfileInterface; -import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; @@ -50,6 +49,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.powermock.api.mockito.PowerMockito.doAnswer; import static org.powermock.api.mockito.PowerMockito.mock; import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.spy; import static org.powermock.api.mockito.PowerMockito.when; @RunWith(PowerMockRunner.class) @@ -67,27 +67,29 @@ public class SmsCommunicatorPluginTest { private boolean hasBeenRun = false; + private VirtualPumpPlugin virtualPumpPlugin; + @Test public void processSettingsTest() { // called from constructor - Assert.assertEquals("1234", smsCommunicatorPlugin.allowedNumbers.get(0)); - Assert.assertEquals("5678", smsCommunicatorPlugin.allowedNumbers.get(1)); - Assert.assertEquals(2, smsCommunicatorPlugin.allowedNumbers.size()); + Assert.assertEquals("1234", smsCommunicatorPlugin.getAllowedNumbers().get(0)); + Assert.assertEquals("5678", smsCommunicatorPlugin.getAllowedNumbers().get(1)); + Assert.assertEquals(2, smsCommunicatorPlugin.getAllowedNumbers().size()); } @Test public void isCommandTest() { Assert.assertTrue(smsCommunicatorPlugin.isCommand("BOLUS", "")); - smsCommunicatorPlugin.messageToConfirm = null; + smsCommunicatorPlugin.setMessageToConfirm(null); Assert.assertFalse(smsCommunicatorPlugin.isCommand("BLB", "")); - smsCommunicatorPlugin.messageToConfirm = new AuthRequest(smsCommunicatorPlugin, new Sms("1234", "ddd"), "RequestText", "ccode", new SmsAction() { + smsCommunicatorPlugin.setMessageToConfirm(new AuthRequest(smsCommunicatorPlugin, new Sms("1234", "ddd"), "RequestText", "ccode", new SmsAction() { @Override public void run() { } - }); + })); Assert.assertTrue(smsCommunicatorPlugin.isCommand("BLB", "1234")); Assert.assertFalse(smsCommunicatorPlugin.isCommand("BLB", "2345")); - smsCommunicatorPlugin.messageToConfirm = null; + smsCommunicatorPlugin.setMessageToConfirm(null); } @Test @@ -101,82 +103,82 @@ public class SmsCommunicatorPluginTest { Sms sms; // SMS from not allowed number should be ignored - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("12", "aText"); smsCommunicatorPlugin.processSms(sms); - Assert.assertTrue(sms.ignored); - Assert.assertEquals("aText", smsCommunicatorPlugin.messages.get(0).text); + Assert.assertTrue(sms.getIgnored()); + Assert.assertEquals("aText", smsCommunicatorPlugin.getMessages().get(0).getText()); //UNKNOWN - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "UNKNOWN"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("UNKNOWN", smsCommunicatorPlugin.messages.get(0).text); + Assert.assertEquals("UNKNOWN", smsCommunicatorPlugin.getMessages().get(0).getText()); //BG - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "BG"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("BG", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertTrue(smsCommunicatorPlugin.messages.get(1).text.contains("IOB:")); - Assert.assertTrue(smsCommunicatorPlugin.messages.get(1).text.contains("Last BG: 100")); - Assert.assertTrue(smsCommunicatorPlugin.messages.get(1).text.contains("COB: 10(2)g")); + Assert.assertEquals("BG", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("IOB:")); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("Last BG: 100")); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("COB: 10(2)g")); // LOOP : test remote control disabled when(SP.getBoolean(R.string.key_smscommunicator_remotecommandsallowed, false)).thenReturn(false); - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "LOOP STATUS"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("LOOP STATUS", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertTrue(smsCommunicatorPlugin.messages.get(1).text.contains("Remote command is not allowed")); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("LOOP STATUS", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("Remote command is not allowed")); when(SP.getBoolean(R.string.key_smscommunicator_remotecommandsallowed, false)).thenReturn(true); //LOOP STATUS : disabled when(loopPlugin.isEnabled(PluginType.LOOP)).thenReturn(false); - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "LOOP STATUS"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("LOOP STATUS", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Loop is disabled", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("LOOP STATUS", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Loop is disabled", smsCommunicatorPlugin.getMessages().get(1).getText()); //LOOP STATUS : suspended when(loopPlugin.minutesToEndOfSuspend()).thenReturn(10); when(loopPlugin.isEnabled(PluginType.LOOP)).thenReturn(true); when(loopPlugin.isSuspended()).thenReturn(true); - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "LOOP STATUS"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("LOOP STATUS", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Suspended (10 m)", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("LOOP STATUS", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Suspended (10 m)", smsCommunicatorPlugin.getMessages().get(1).getText()); //LOOP STATUS : enabled when(loopPlugin.isEnabled(PluginType.LOOP)).thenReturn(true); when(loopPlugin.isSuspended()).thenReturn(false); - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "LOOP STATUS"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("LOOP STATUS", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Loop is enabled", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("LOOP STATUS", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Loop is enabled", smsCommunicatorPlugin.getMessages().get(1).getText()); //LOOP : wrong format when(loopPlugin.isEnabled(PluginType.LOOP)).thenReturn(true); - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "LOOP"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("LOOP", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("LOOP", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //LOOP DISABLE : already disabled when(loopPlugin.isEnabled(PluginType.LOOP)).thenReturn(false); - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "LOOP DISABLE"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("LOOP DISABLE", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Loop is disabled", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("LOOP DISABLE", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Loop is disabled", smsCommunicatorPlugin.getMessages().get(1).getText()); //LOOP DISABLE : from enabled hasBeenRun = false; @@ -185,22 +187,22 @@ public class SmsCommunicatorPluginTest { hasBeenRun = true; return null; }).when(loopPlugin).setPluginEnabled(PluginType.LOOP, false); - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "LOOP DISABLE"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("LOOP DISABLE", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Loop has been disabled Temp basal canceled", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("LOOP DISABLE", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Loop has been disabled Temp basal canceled", smsCommunicatorPlugin.getMessages().get(1).getText()); Assert.assertTrue(hasBeenRun); //LOOP ENABLE : already enabled when(loopPlugin.isEnabled(PluginType.LOOP)).thenReturn(true); - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "LOOP ENABLE"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("LOOP ENABLE", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Loop is enabled", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("LOOP ENABLE", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Loop is enabled", smsCommunicatorPlugin.getMessages().get(1).getText()); //LOOP ENABLE : from disabled hasBeenRun = false; @@ -209,143 +211,205 @@ public class SmsCommunicatorPluginTest { hasBeenRun = true; return null; }).when(loopPlugin).setPluginEnabled(PluginType.LOOP, true); - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "LOOP ENABLE"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("LOOP ENABLE", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Loop has been enabled", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("LOOP ENABLE", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Loop has been enabled", smsCommunicatorPlugin.getMessages().get(1).getText()); Assert.assertTrue(hasBeenRun); //LOOP RESUME : already enabled - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "LOOP RESUME"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("LOOP RESUME", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Loop resumed", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("LOOP RESUME", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Loop resumed", smsCommunicatorPlugin.getMessages().get(1).getText()); //LOOP SUSPEND 1 2: wrong format - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "LOOP SUSPEND 1 2"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("LOOP SUSPEND 1 2", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("LOOP SUSPEND 1 2", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //LOOP SUSPEND 0 : wrong duration - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "LOOP SUSPEND 0"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("LOOP SUSPEND 0", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong duration", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("LOOP SUSPEND 0", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong duration", smsCommunicatorPlugin.getMessages().get(1).getText()); //LOOP SUSPEND 100 : suspend for 100 min + correct answer - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "LOOP SUSPEND 100"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("LOOP SUSPEND 100", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertTrue(smsCommunicatorPlugin.messages.get(1).text.contains("To suspend loop for 100 minutes reply with code ")); - String passCode = smsCommunicatorPlugin.messageToConfirm.confirmCode; + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("LOOP SUSPEND 100", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To suspend loop for 100 minutes reply with code ")); + String passCode = smsCommunicatorPlugin.getMessageToConfirm().getConfirmCode(); smsCommunicatorPlugin.processSms(new Sms("1234", passCode)); - Assert.assertEquals(passCode, smsCommunicatorPlugin.messages.get(2).text); - Assert.assertEquals("Loop suspended Temp basal canceled", smsCommunicatorPlugin.messages.get(3).text); + Assert.assertEquals(passCode, smsCommunicatorPlugin.getMessages().get(2).getText()); + Assert.assertEquals("Loop suspended Temp basal canceled", smsCommunicatorPlugin.getMessages().get(3).getText()); //LOOP SUSPEND 200 : limit to 180 min + wrong answer - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "LOOP SUSPEND 200"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("LOOP SUSPEND 200", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertTrue(smsCommunicatorPlugin.messages.get(1).text.contains("To suspend loop for 180 minutes reply with code ")); - passCode = smsCommunicatorPlugin.messageToConfirm.confirmCode; + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("LOOP SUSPEND 200", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To suspend loop for 180 minutes reply with code ")); + passCode = smsCommunicatorPlugin.getMessageToConfirm().getConfirmCode(); // ignore from other number smsCommunicatorPlugin.processSms(new Sms("5678", passCode)); smsCommunicatorPlugin.processSms(new Sms("1234", "XXXX")); - Assert.assertEquals("XXXX", smsCommunicatorPlugin.messages.get(3).text); - Assert.assertEquals("Wrong code. Command cancelled.", smsCommunicatorPlugin.messages.get(4).text); + Assert.assertEquals("XXXX", smsCommunicatorPlugin.getMessages().get(3).getText()); + Assert.assertEquals("Wrong code. Command cancelled.", smsCommunicatorPlugin.getMessages().get(4).getText()); //then correct code should not work smsCommunicatorPlugin.processSms(new Sms("1234", passCode)); - Assert.assertEquals(passCode, smsCommunicatorPlugin.messages.get(5).text); - Assert.assertEquals(6, smsCommunicatorPlugin.messages.size()); // processed as common message + Assert.assertEquals(passCode, smsCommunicatorPlugin.getMessages().get(5).getText()); + Assert.assertEquals(6, smsCommunicatorPlugin.getMessages().size()); // processed as common message //LOOP BLABLA - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "LOOP BLABLA"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("LOOP BLABLA", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("LOOP BLABLA", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //TREATMENTS REFRESH when(loopPlugin.isEnabled(PluginType.LOOP)).thenReturn(true); when(loopPlugin.isSuspended()).thenReturn(false); - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "TREATMENTS REFRESH"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("TREATMENTS REFRESH", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertTrue(smsCommunicatorPlugin.messages.get(1).text.contains("TREATMENTS REFRESH")); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("TREATMENTS REFRESH", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("TREATMENTS REFRESH")); //TREATMENTS BLA BLA when(loopPlugin.isEnabled(PluginType.LOOP)).thenReturn(true); when(loopPlugin.isSuspended()).thenReturn(false); - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "TREATMENTS BLA BLA"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("TREATMENTS BLA BLA", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("TREATMENTS BLA BLA", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //TREATMENTS BLABLA when(loopPlugin.isEnabled(PluginType.LOOP)).thenReturn(true); when(loopPlugin.isSuspended()).thenReturn(false); - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "TREATMENTS BLABLA"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("TREATMENTS BLABLA", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("TREATMENTS BLABLA", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //NSCLIENT RESTART when(loopPlugin.isEnabled(PluginType.LOOP)).thenReturn(true); when(loopPlugin.isSuspended()).thenReturn(false); - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "NSCLIENT RESTART"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("NSCLIENT RESTART", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertTrue(smsCommunicatorPlugin.messages.get(1).text.contains("NSCLIENT RESTART")); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("NSCLIENT RESTART", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("NSCLIENT RESTART")); //NSCLIENT BLA BLA when(loopPlugin.isEnabled(PluginType.LOOP)).thenReturn(true); when(loopPlugin.isSuspended()).thenReturn(false); - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "NSCLIENT BLA BLA"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("NSCLIENT BLA BLA", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("NSCLIENT BLA BLA", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //NSCLIENT BLABLA when(loopPlugin.isEnabled(PluginType.LOOP)).thenReturn(true); when(loopPlugin.isSuspended()).thenReturn(false); - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "NSCLIENT BLABLA"); smsCommunicatorPlugin.processSms(sms); - Assert.assertFalse(sms.ignored); - Assert.assertEquals("NSCLIENT BLABLA", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("NSCLIENT BLABLA", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //PUMP - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "PUMP"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("PUMP", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Virtual Pump", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("PUMP", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Virtual Pump", smsCommunicatorPlugin.getMessages().get(1).getText()); + //HELP + smsCommunicatorPlugin.setMessages(new ArrayList<>()); + sms = new Sms("1234", "HELP"); + smsCommunicatorPlugin.processSms(sms); + Assert.assertEquals("HELP", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("PUMP")); + + //HELP PUMP + smsCommunicatorPlugin.setMessages(new ArrayList<>()); + sms = new Sms("1234", "HELP PUMP"); + smsCommunicatorPlugin.processSms(sms); + Assert.assertEquals("HELP PUMP", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("PUMP")); + + //SMS : wrong format + smsCommunicatorPlugin.setMessages(new ArrayList<>()); + sms = new Sms("1234", "SMS"); + smsCommunicatorPlugin.processSms(sms); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("SMS", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); + + //SMS STOP + smsCommunicatorPlugin.setMessages(new ArrayList<>()); + sms = new Sms("1234", "SMS DISABLE"); + smsCommunicatorPlugin.processSms(sms); + Assert.assertEquals("SMS DISABLE", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To disable the SMS Remote Service reply with code")); + passCode = smsCommunicatorPlugin.getMessageToConfirm().getConfirmCode(); + smsCommunicatorPlugin.processSms(new Sms("1234", passCode)); + Assert.assertEquals(passCode, smsCommunicatorPlugin.getMessages().get(2).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(3).getText().contains("SMS Remote Service stopped. To reactivate it, use AAPS on master smartphone.")); + + //TARGET : wrong format + smsCommunicatorPlugin.setMessages(new ArrayList<>()); + sms = new Sms("1234", "TARGET"); + smsCommunicatorPlugin.processSms(sms); + Assert.assertFalse(sms.getIgnored()); + Assert.assertEquals("TARGET", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); + + //TARGET MEAL + smsCommunicatorPlugin.setMessages(new ArrayList<>()); + sms = new Sms("1234", "TARGET MEAL"); + smsCommunicatorPlugin.processSms(sms); + Assert.assertEquals("TARGET MEAL", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To set the Temp Target")); + passCode = smsCommunicatorPlugin.getMessageToConfirm().getConfirmCode(); + smsCommunicatorPlugin.processSms(new Sms("1234", passCode)); + Assert.assertEquals(passCode, smsCommunicatorPlugin.getMessages().get(2).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(3).getText().contains("set successfully")); + + //TARGET STOP/CANCEL + smsCommunicatorPlugin.setMessages(new ArrayList<>()); + sms = new Sms("1234", "TARGET STOP"); + smsCommunicatorPlugin.processSms(sms); + Assert.assertEquals("TARGET STOP", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To cancel Temp Target reply with code")); + passCode = smsCommunicatorPlugin.getMessageToConfirm().getConfirmCode(); + smsCommunicatorPlugin.processSms(new Sms("1234", passCode)); + Assert.assertEquals(passCode, smsCommunicatorPlugin.getMessages().get(2).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(3).getText().contains("Temp Target canceled successfully")); } @Test @@ -353,92 +417,92 @@ public class SmsCommunicatorPluginTest { Sms sms; //PROFILE - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "PROFILE"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("PROFILE", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Remote command is not allowed", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("PROFILE", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Remote command is not allowed", smsCommunicatorPlugin.getMessages().get(1).getText()); when(SP.getBoolean(R.string.key_smscommunicator_remotecommandsallowed, false)).thenReturn(true); //PROFILE - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "PROFILE"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("PROFILE", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("PROFILE", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //PROFILE LIST (no profile interface) - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "PROFILE LIST"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("PROFILE LIST", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Not configured", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("PROFILE LIST", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Not configured", smsCommunicatorPlugin.getMessages().get(1).getText()); ProfileInterface profileInterface = mock(SimpleProfilePlugin.class); when(ConfigBuilderPlugin.getPlugin().getActiveProfileInterface()).thenReturn(profileInterface); //PROFILE LIST (no profile defined) - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "PROFILE LIST"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("PROFILE LIST", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Not configured", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("PROFILE LIST", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Not configured", smsCommunicatorPlugin.getMessages().get(1).getText()); when(profileInterface.getProfile()).thenReturn(AAPSMocker.getValidProfileStore()); //PROFILE STATUS - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "PROFILE STATUS"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("PROFILE STATUS", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals(AAPSMocker.TESTPROFILENAME, smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("PROFILE STATUS", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals(AAPSMocker.TESTPROFILENAME, smsCommunicatorPlugin.getMessages().get(1).getText()); //PROFILE LIST - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "PROFILE LIST"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("PROFILE LIST", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("1. " + AAPSMocker.TESTPROFILENAME, smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("PROFILE LIST", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("1. " + AAPSMocker.TESTPROFILENAME, smsCommunicatorPlugin.getMessages().get(1).getText()); //PROFILE 2 (non existing) - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "PROFILE 2"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("PROFILE 2", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("PROFILE 2", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //PROFILE 1 0(wrong percentage) - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "PROFILE 1 0"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("PROFILE 1 0", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("PROFILE 1 0", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //PROFILE 0(wrong index) - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "PROFILE 0"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("PROFILE 0", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("PROFILE 0", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //PROFILE 1(OK) - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "PROFILE 1"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("PROFILE 1", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertTrue(smsCommunicatorPlugin.messages.get(1).text.contains("To switch profile to someProfile 100% reply with code")); + Assert.assertEquals("PROFILE 1", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To switch profile to someProfile 100% reply with code")); //PROFILE 1 90(OK) - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "PROFILE 1 90"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("PROFILE 1 90", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertTrue(smsCommunicatorPlugin.messages.get(1).text.contains("To switch profile to someProfile 90% reply with code")); - String passCode = smsCommunicatorPlugin.messageToConfirm.confirmCode; + Assert.assertEquals("PROFILE 1 90", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To switch profile to someProfile 90% reply with code")); + String passCode = smsCommunicatorPlugin.getMessageToConfirm().getConfirmCode(); smsCommunicatorPlugin.processSms(new Sms("1234", passCode)); - Assert.assertEquals(passCode, smsCommunicatorPlugin.messages.get(2).text); - Assert.assertEquals("Profile switch created", smsCommunicatorPlugin.messages.get(3).text); + Assert.assertEquals(passCode, smsCommunicatorPlugin.getMessages().get(2).getText()); + Assert.assertEquals("Profile switch created", smsCommunicatorPlugin.getMessages().get(3).getText()); } @Test @@ -446,85 +510,85 @@ public class SmsCommunicatorPluginTest { Sms sms; //BASAL - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "BASAL"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("BASAL", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Remote command is not allowed", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("BASAL", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Remote command is not allowed", smsCommunicatorPlugin.getMessages().get(1).getText()); when(SP.getBoolean(R.string.key_smscommunicator_remotecommandsallowed, false)).thenReturn(true); //BASAL - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "BASAL"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("BASAL", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("BASAL", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //BASAL CANCEL - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "BASAL CANCEL"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("BASAL CANCEL", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertTrue(smsCommunicatorPlugin.messages.get(1).text.contains("To stop temp basal reply with code")); - String passCode = smsCommunicatorPlugin.messageToConfirm.confirmCode; + Assert.assertEquals("BASAL CANCEL", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To stop temp basal reply with code")); + String passCode = smsCommunicatorPlugin.getMessageToConfirm().getConfirmCode(); smsCommunicatorPlugin.processSms(new Sms("1234", passCode)); - Assert.assertEquals(passCode, smsCommunicatorPlugin.messages.get(2).text); - Assert.assertEquals("Temp basal canceled\nVirtual Pump", smsCommunicatorPlugin.messages.get(3).text); + Assert.assertEquals(passCode, smsCommunicatorPlugin.getMessages().get(2).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(3).getText().contains("Temp basal canceled")); //BASAL a% - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "BASAL a%"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("BASAL a%", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("BASAL a%", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //BASAL 10% 0 - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "BASAL 10% 0"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("BASAL 10% 0", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("BASAL 10% 0", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); when(MainApp.getConstraintChecker().applyBasalPercentConstraints(any(), any())).thenReturn(new Constraint<>(20)); //BASAL 20% 20 - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "BASAL 20% 20"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("BASAL 20% 20", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertTrue(smsCommunicatorPlugin.messages.get(1).text.contains("To start basal 20% for 20 min reply with code")); - passCode = smsCommunicatorPlugin.messageToConfirm.confirmCode; + Assert.assertEquals("BASAL 20% 20", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To start basal 20% for 20 min reply with code")); + passCode = smsCommunicatorPlugin.getMessageToConfirm().getConfirmCode(); smsCommunicatorPlugin.processSms(new Sms("1234", passCode)); - Assert.assertEquals(passCode, smsCommunicatorPlugin.messages.get(2).text); - Assert.assertEquals("Temp basal 20% for 20 min started successfully\nVirtual Pump", smsCommunicatorPlugin.messages.get(3).text); + Assert.assertEquals(passCode, smsCommunicatorPlugin.getMessages().get(2).getText()); + Assert.assertEquals("Temp basal 20% for 20 min started successfully\nVirtual Pump", smsCommunicatorPlugin.getMessages().get(3).getText()); //BASAL a - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "BASAL a"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("BASAL a", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("BASAL a", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //BASAL 1 0 - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "BASAL 1 0"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("BASAL 1 0", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("BASAL 1 0", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); when(MainApp.getConstraintChecker().applyBasalConstraints(any(), any())).thenReturn(new Constraint<>(1d)); //BASAL 1 20 - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "BASAL 1 20"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("BASAL 1 20", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertTrue(smsCommunicatorPlugin.messages.get(1).text.contains("To start basal 1.00U/h for 20 min reply with code")); - passCode = smsCommunicatorPlugin.messageToConfirm.confirmCode; + Assert.assertEquals("BASAL 1 20", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To start basal 1.00U/h for 20 min reply with code")); + passCode = smsCommunicatorPlugin.getMessageToConfirm().getConfirmCode(); smsCommunicatorPlugin.processSms(new Sms("1234", passCode)); - Assert.assertEquals(passCode, smsCommunicatorPlugin.messages.get(2).text); - Assert.assertEquals("Temp basal 1.00U/h for 20 min started successfully\nVirtual Pump", smsCommunicatorPlugin.messages.get(3).text); + Assert.assertEquals(passCode, smsCommunicatorPlugin.getMessages().get(2).getText()); + Assert.assertEquals("Temp basal 1.00U/h for 20 min started successfully\nVirtual Pump", smsCommunicatorPlugin.getMessages().get(3).getText()); } @@ -533,58 +597,58 @@ public class SmsCommunicatorPluginTest { Sms sms; //EXTENDED - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "EXTENDED"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("EXTENDED", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Remote command is not allowed", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("EXTENDED", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Remote command is not allowed", smsCommunicatorPlugin.getMessages().get(1).getText()); when(SP.getBoolean(R.string.key_smscommunicator_remotecommandsallowed, false)).thenReturn(true); //EXTENDED - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "EXTENDED"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("EXTENDED", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("EXTENDED", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //EXTENDED CANCEL - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "EXTENDED CANCEL"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("EXTENDED CANCEL", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertTrue(smsCommunicatorPlugin.messages.get(1).text.contains("To stop extended bolus reply with code")); - String passCode = smsCommunicatorPlugin.messageToConfirm.confirmCode; + Assert.assertEquals("EXTENDED CANCEL", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To stop extended bolus reply with code")); + String passCode = smsCommunicatorPlugin.getMessageToConfirm().getConfirmCode(); smsCommunicatorPlugin.processSms(new Sms("1234", passCode)); - Assert.assertEquals(passCode, smsCommunicatorPlugin.messages.get(2).text); - Assert.assertEquals("Extended bolus canceled\nVirtual Pump", smsCommunicatorPlugin.messages.get(3).text); + Assert.assertEquals(passCode, smsCommunicatorPlugin.getMessages().get(2).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(3).getText().contains("Extended bolus canceled")); //EXTENDED a% - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "EXTENDED a%"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("EXTENDED a%", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("EXTENDED a%", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); when(MainApp.getConstraintChecker().applyExtendedBolusConstraints(any())).thenReturn(new Constraint<>(1d)); //EXTENDED 1 0 - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "EXTENDED 1 0"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("EXTENDED 1 0", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("EXTENDED 1 0", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //EXTENDED 1 20 - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "EXTENDED 1 20"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("EXTENDED 1 20", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertTrue(smsCommunicatorPlugin.messages.get(1).text.contains("To start extended bolus 1.00U for 20 min reply with code")); - passCode = smsCommunicatorPlugin.messageToConfirm.confirmCode; + Assert.assertEquals("EXTENDED 1 20", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To start extended bolus 1.00U for 20 min reply with code")); + passCode = smsCommunicatorPlugin.getMessageToConfirm().getConfirmCode(); smsCommunicatorPlugin.processSms(new Sms("1234", passCode)); - Assert.assertEquals(passCode, smsCommunicatorPlugin.messages.get(2).text); - Assert.assertEquals("Extended bolus 1.00U for 20 min started successfully\nVirtual Pump", smsCommunicatorPlugin.messages.get(3).text); + Assert.assertEquals(passCode, smsCommunicatorPlugin.getMessages().get(2).getText()); + Assert.assertEquals("Extended bolus 1.00U for 20 min started successfully\nVirtual Pump", smsCommunicatorPlugin.getMessages().get(3).getText()); } @Test @@ -592,75 +656,91 @@ public class SmsCommunicatorPluginTest { Sms sms; //BOLUS - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "BOLUS"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("BOLUS", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Remote command is not allowed", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("BOLUS", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Remote command is not allowed", smsCommunicatorPlugin.getMessages().get(1).getText()); when(SP.getBoolean(R.string.key_smscommunicator_remotecommandsallowed, false)).thenReturn(true); //BOLUS - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "BOLUS"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("BOLUS", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("BOLUS", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); when(MainApp.getConstraintChecker().applyBolusConstraints(any())).thenReturn(new Constraint<>(1d)); when(DateUtil.now()).thenReturn(1000L); //BOLUS 1 - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "BOLUS 1"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("BOLUS 1", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Remote bolus not available. Try again later.", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("BOLUS 1", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Remote bolus not available. Try again later.", smsCommunicatorPlugin.getMessages().get(1).getText()); when(MainApp.getConstraintChecker().applyBolusConstraints(any())).thenReturn(new Constraint<>(0d)); when(DateUtil.now()).thenReturn(Constants.remoteBolusMinDistance + 1002L); //BOLUS 0 - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "BOLUS 0"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("BOLUS 0", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("BOLUS 0", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //BOLUS a - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "BOLUS a"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("BOLUS a", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("BOLUS a", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); when(MainApp.getConstraintChecker().applyExtendedBolusConstraints(any())).thenReturn(new Constraint<>(1d)); when(MainApp.getConstraintChecker().applyBolusConstraints(any())).thenReturn(new Constraint<>(1d)); //BOLUS 1 - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "BOLUS 1"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("BOLUS 1", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertTrue(smsCommunicatorPlugin.messages.get(1).text.contains("To deliver bolus 1.00U reply with code")); - String passCode = smsCommunicatorPlugin.messageToConfirm.confirmCode; + Assert.assertEquals("BOLUS 1", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To deliver bolus 1.00U reply with code")); + String passCode = smsCommunicatorPlugin.getMessageToConfirm().getConfirmCode(); smsCommunicatorPlugin.processSms(new Sms("1234", passCode)); - Assert.assertEquals(passCode, smsCommunicatorPlugin.messages.get(2).text); - Assert.assertEquals("Bolus 1.00U delivered successfully\nVirtual Pump", smsCommunicatorPlugin.messages.get(3).text); + Assert.assertEquals(passCode, smsCommunicatorPlugin.getMessages().get(2).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(3).getText().contains("Bolus 1.00U delivered successfully")); //BOLUS 1 (Suspended pump) - smsCommunicatorPlugin.lastRemoteBolusTime = 0; - PumpInterface pump = mock(VirtualPumpPlugin.class); - when(ConfigBuilderPlugin.getPlugin().getActivePump()).thenReturn(pump); - when(pump.isSuspended()).thenReturn(true); - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setLastRemoteBolusTime(0); + when(virtualPumpPlugin.isSuspended()).thenReturn(true); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "BOLUS 1"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("BOLUS 1", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Pump suspended", smsCommunicatorPlugin.messages.get(1).text); - when(pump.isSuspended()).thenReturn(false); + Assert.assertEquals("BOLUS 1", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Pump suspended", smsCommunicatorPlugin.getMessages().get(1).getText()); + when(virtualPumpPlugin.isSuspended()).thenReturn(false); + + //BOLUS 1 a + smsCommunicatorPlugin.setMessages(new ArrayList<>()); + sms = new Sms("1234", "BOLUS 1 a"); + smsCommunicatorPlugin.processSms(sms); + Assert.assertEquals("BOLUS 1 a", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); + + //BOLUS 1 MEAL + smsCommunicatorPlugin.setMessages(new ArrayList<>()); + sms = new Sms("1234", "BOLUS 1 MEAL"); + smsCommunicatorPlugin.processSms(sms); + Assert.assertEquals("BOLUS 1 MEAL", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To deliver meal bolus 1.00U reply with code")); + passCode = smsCommunicatorPlugin.getMessageToConfirm().getConfirmCode(); + smsCommunicatorPlugin.processSms(new Sms("1234", passCode)); + Assert.assertEquals(passCode, smsCommunicatorPlugin.getMessages().get(2).getText()); + Assert.assertEquals("Meal Bolus 1.00U delivered successfully\nVirtual Pump\nTarget 5.0 for 45 minutes", smsCommunicatorPlugin.getMessages().get(3).getText()); } @Test @@ -668,47 +748,128 @@ public class SmsCommunicatorPluginTest { Sms sms; //CAL - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "CAL"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("CAL", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Remote command is not allowed", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("CAL", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Remote command is not allowed", smsCommunicatorPlugin.getMessages().get(1).getText()); when(SP.getBoolean(R.string.key_smscommunicator_remotecommandsallowed, false)).thenReturn(true); //CAL - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "CAL"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("CAL", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("CAL", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); //CAL 0 - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "CAL 0"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("CAL 0", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("Wrong format", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("CAL 0", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); when(XdripCalibrations.sendIntent(any())).thenReturn(true); //CAL 1 - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); sms = new Sms("1234", "CAL 1"); smsCommunicatorPlugin.processSms(sms); - Assert.assertEquals("CAL 1", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertTrue(smsCommunicatorPlugin.messages.get(1).text.contains("To send calibration 1.00 reply with code")); - String passCode = smsCommunicatorPlugin.messageToConfirm.confirmCode; + Assert.assertEquals("CAL 1", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To send calibration 1.00 reply with code")); + String passCode = smsCommunicatorPlugin.getMessageToConfirm().getConfirmCode(); smsCommunicatorPlugin.processSms(new Sms("1234", passCode)); - Assert.assertEquals(passCode, smsCommunicatorPlugin.messages.get(2).text); - Assert.assertEquals("Calibration sent. Receiving must be enabled in xDrip.", smsCommunicatorPlugin.messages.get(3).text); + Assert.assertEquals(passCode, smsCommunicatorPlugin.getMessages().get(2).getText()); + Assert.assertEquals("Calibration sent. Receiving must be enabled in xDrip.", smsCommunicatorPlugin.getMessages().get(3).getText()); + } + + @Test + public void processCarbsTest() { + Sms sms; + + when(DateUtil.now()).thenReturn(1000000L); + when(SP.getBoolean(R.string.key_smscommunicator_remotecommandsallowed, false)).thenReturn(false); + //CAL + smsCommunicatorPlugin.setMessages(new ArrayList<>()); + sms = new Sms("1234", "CARBS"); + smsCommunicatorPlugin.processSms(sms); + Assert.assertEquals("CARBS", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Remote command is not allowed", smsCommunicatorPlugin.getMessages().get(1).getText()); + + when(SP.getBoolean(R.string.key_smscommunicator_remotecommandsallowed, false)).thenReturn(true); + + //CARBS + smsCommunicatorPlugin.setMessages(new ArrayList<>()); + sms = new Sms("1234", "CARBS"); + smsCommunicatorPlugin.processSms(sms); + Assert.assertEquals("CARBS", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); + + when(MainApp.getConstraintChecker().applyCarbsConstraints(any())).thenReturn(new Constraint<>(0)); + + //CARBS 0 + smsCommunicatorPlugin.setMessages(new ArrayList<>()); + sms = new Sms("1234", "CARBS 0"); + smsCommunicatorPlugin.processSms(sms); + Assert.assertEquals("CARBS 0", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("Wrong format", smsCommunicatorPlugin.getMessages().get(1).getText()); + + when(MainApp.getConstraintChecker().applyCarbsConstraints(any())).thenReturn(new Constraint<>(1)); + + //CARBS 1 + smsCommunicatorPlugin.setMessages(new ArrayList<>()); + sms = new Sms("1234", "CARBS 1"); + smsCommunicatorPlugin.processSms(sms); + Assert.assertEquals("CARBS 1", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To enter 1g at")); + String passCode = smsCommunicatorPlugin.getMessageToConfirm().getConfirmCode(); + smsCommunicatorPlugin.processSms(new Sms("1234", passCode)); + Assert.assertEquals(passCode, smsCommunicatorPlugin.getMessages().get(2).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(3).getText().startsWith("Carbs 1g entered successfully")); + + //CARBS 1 a + smsCommunicatorPlugin.setMessages(new ArrayList<>()); + sms = new Sms("1234", "CARBS 1 a"); + smsCommunicatorPlugin.processSms(sms); + Assert.assertEquals("CARBS 1 a", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("Wrong format")); + + //CARBS 1 00 + smsCommunicatorPlugin.setMessages(new ArrayList<>()); + sms = new Sms("1234", "CARBS 1 00"); + smsCommunicatorPlugin.processSms(sms); + Assert.assertEquals("CARBS 1 00", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("Wrong format")); + + //CARBS 1 12:01 + smsCommunicatorPlugin.setMessages(new ArrayList<>()); + sms = new Sms("1234", "CARBS 1 12:01"); + smsCommunicatorPlugin.processSms(sms); + Assert.assertEquals("CARBS 1 12:01", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To enter 1g at 12:01PM reply with code")); + passCode = smsCommunicatorPlugin.getMessageToConfirm().getConfirmCode(); + smsCommunicatorPlugin.processSms(new Sms("1234", passCode)); + Assert.assertEquals(passCode, smsCommunicatorPlugin.getMessages().get(2).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(3).getText().startsWith("Carbs 1g entered successfully")); + + //CARBS 1 3:01AM + smsCommunicatorPlugin.setMessages(new ArrayList<>()); + sms = new Sms("1234", "CARBS 1 3:01AM"); + smsCommunicatorPlugin.processSms(sms); + Assert.assertEquals("CARBS 1 3:01AM", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(1).getText().contains("To enter 1g at 03:01AM reply with code")); + passCode = smsCommunicatorPlugin.getMessageToConfirm().getConfirmCode(); + smsCommunicatorPlugin.processSms(new Sms("1234", passCode)); + Assert.assertEquals(passCode, smsCommunicatorPlugin.getMessages().get(2).getText()); + Assert.assertTrue(smsCommunicatorPlugin.getMessages().get(3).getText().startsWith("Carbs 1g entered successfully")); } @Test public void sendNotificationToAllNumbers() { - smsCommunicatorPlugin.messages = new ArrayList<>(); + smsCommunicatorPlugin.setMessages(new ArrayList<>()); smsCommunicatorPlugin.sendNotificationToAllNumbers("abc"); - Assert.assertEquals("abc", smsCommunicatorPlugin.messages.get(0).text); - Assert.assertEquals("abc", smsCommunicatorPlugin.messages.get(1).text); + Assert.assertEquals("abc", smsCommunicatorPlugin.getMessages().get(0).getText()); + Assert.assertEquals("abc", smsCommunicatorPlugin.getMessages().get(1).getText()); } @Before @@ -735,13 +896,13 @@ public class SmsCommunicatorPluginTest { PowerMockito.when(IobCobCalculatorPlugin.getPlugin().getCobInfo(false, "SMS COB")).thenReturn(new CobInfo(10d, 2d)); mockStatic(XdripCalibrations.class); - mockStatic(DateUtil.class); + spy(DateUtil.class); mockStatic(SmsManager.class); SmsManager smsManager = mock(SmsManager.class); when(SmsManager.getDefault()).thenReturn(smsManager); when(SP.getString(R.string.key_smscommunicator_allowednumbers, "")).thenReturn("1234;5678"); - smsCommunicatorPlugin = SmsCommunicatorPlugin.getPlugin(); + smsCommunicatorPlugin = SmsCommunicatorPlugin.INSTANCE; smsCommunicatorPlugin.setPluginEnabled(PluginType.GENERAL, true); mockStatic(LoopPlugin.class); @@ -797,8 +958,10 @@ public class SmsCommunicatorPluginTest { return null; }).when(AAPSMocker.queue).extendedBolus(anyDouble(), anyInt(), any(Callback.class)); - VirtualPumpPlugin virtualPumpPlugin = VirtualPumpPlugin.getPlugin(); + virtualPumpPlugin = mock(VirtualPumpPlugin.class); when(ConfigBuilderPlugin.getPlugin().getActivePump()).thenReturn(virtualPumpPlugin); + when(virtualPumpPlugin.shortStatus(anyBoolean())).thenReturn("Virtual Pump"); + when(virtualPumpPlugin.isSuspended()).thenReturn(false); } } diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsTest.java b/app/src/test/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsTest.java index 5f7af11a67..a406a35f9d 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsTest.java +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsTest.java @@ -28,19 +28,19 @@ public class SmsTest { when(smsMessage.getMessageBody()).thenReturn("aBody"); Sms sms = new Sms(smsMessage); - Assert.assertEquals(sms.phoneNumber, "aNumber"); - Assert.assertEquals(sms.text, "aBody"); - Assert.assertTrue(sms.received); + Assert.assertEquals(sms.getPhoneNumber(), "aNumber"); + Assert.assertEquals(sms.getText(), "aBody"); + Assert.assertTrue(sms.getReceived()); sms = new Sms("aNumber", "aBody"); - Assert.assertEquals(sms.phoneNumber, "aNumber"); - Assert.assertEquals(sms.text, "aBody"); - Assert.assertTrue(sms.sent); + Assert.assertEquals(sms.getPhoneNumber(), "aNumber"); + Assert.assertEquals(sms.getText(), "aBody"); + Assert.assertTrue(sms.getSent()); sms = new Sms("aNumber", R.string.insulin_unit_shortname); - Assert.assertEquals(sms.phoneNumber, "aNumber"); - Assert.assertEquals(sms.text, MainApp.gs(R.string.insulin_unit_shortname)); - Assert.assertTrue(sms.sent); + Assert.assertEquals(sms.getPhoneNumber(), "aNumber"); + Assert.assertEquals(sms.getText(), MainApp.gs(R.string.insulin_unit_shortname)); + Assert.assertTrue(sms.getSent()); Assert.assertEquals(sms.toString(), "SMS from aNumber: U"); } diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculatorPlugin/GlucoseStatusTest.java b/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculatorPlugin/GlucoseStatusTest.java index 19e497b834..6d91bea911 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculatorPlugin/GlucoseStatusTest.java +++ b/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculatorPlugin/GlucoseStatusTest.java @@ -153,7 +153,7 @@ public class GlucoseStatusTest { list.add(new BgReading(new NSSgv(new JSONObject("{\"mgdl\":226,\"mills\":1514765100000,\"direction\":\"Flat\"}")))); list.add(new BgReading(new NSSgv(new JSONObject("{\"mgdl\":228,\"mills\":1514764800000,\"direction\":\"Flat\"}")))); } catch (JSONException e) { - e.printStackTrace(); + throw new RuntimeException(e); } return list; } @@ -165,7 +165,7 @@ public class GlucoseStatusTest { list.add(new BgReading(new NSSgv(new JSONObject("{\"mgdl\":216,\"mills\":1514766800000,\"direction\":\"Flat\"}")))); // +2 list.add(new BgReading(new NSSgv(new JSONObject("{\"mgdl\":216,\"mills\":1514766600000,\"direction\":\"Flat\"}")))); } catch (JSONException e) { - e.printStackTrace(); + throw new RuntimeException(e); } return list; } @@ -180,7 +180,7 @@ public class GlucoseStatusTest { try { list.add(new BgReading(new NSSgv(new JSONObject("{\"mgdl\":228,\"mills\":1514764800000,\"direction\":\"Flat\"}")))); } catch (JSONException e) { - e.printStackTrace(); + throw new RuntimeException(e); } return list; } @@ -190,7 +190,7 @@ public class GlucoseStatusTest { try { list.add(new BgReading(new NSSgv(new JSONObject("{\"mgdl\":214,\"mills\":1514766900000,\"direction\":\"Flat\"}")))); } catch (JSONException e) { - e.printStackTrace(); + throw new RuntimeException(e); } return list; } @@ -211,7 +211,7 @@ public class GlucoseStatusTest { list.add(new BgReading(new NSSgv(new JSONObject("{\"mgdl\":" + (latest_reading + (i*2)) + ",\"mills\":" + (end_time - (1000 * 60 * i)) + ",\"direction\":\"Flat\"}")))); } } catch (JSONException e) { - e.printStackTrace(); + throw new RuntimeException(e); } return list; } diff --git a/app/src/test/java/info/nightscout/androidaps/utils/RoundTest.java b/app/src/test/java/info/nightscout/androidaps/utils/RoundTest.java index fb444804bc..568b67057e 100644 --- a/app/src/test/java/info/nightscout/androidaps/utils/RoundTest.java +++ b/app/src/test/java/info/nightscout/androidaps/utils/RoundTest.java @@ -12,9 +12,16 @@ public class RoundTest { @Test public void roundToTest() throws Exception { - assertEquals( 0.55d, Round.roundTo(0.54d, 0.05d), 0.00000001d ); - assertEquals( 1d, Round.roundTo(1.49d, 1d), 0.00000001d ); - assertEquals( 0d, Round.roundTo(0d, 1d), 0.00000001d ); + assertEquals( 0.55d, Round.roundTo(0.54d, 0.05d), 0.00000000000000000001d ); + assertEquals( -3.26d, Round.roundTo(-3.2553715764602713d, 0.01d), 0.00000000000000000001d ); + assertEquals( 0.816d, Round.roundTo(0.8156666666666667d, 0.001d), 0.00000000000000000001d ); + assertEquals( 0.235d, Round.roundTo(0.235d, 0.001d), 0.00000000000000000001d ); + assertEquals( 0.3d, Round.roundTo(0.3d, 0.1d), 0.00000000000000001d ); + assertEquals( 0.0017d, Round.roundTo(0.0016960652144170627d, 0.0001d), 0.00000000000000000001d ); + assertEquals( 0.0078d, Round.roundTo(0.007804436682291013d, 0.0001d), 0.00000000000000000001d ); + assertEquals( 0.6d, Round.roundTo(0.6d, 0.05d), 0.00000000000000000001d ); + assertEquals( 1d, Round.roundTo(1.49d, 1d), 0.00000000000000000001d ); + assertEquals( 0d, Round.roundTo(0d, 1d), 0.00000000000000000001d ); } @Test diff --git a/build.gradle b/build.gradle index 31658346b6..f8456a8971 100644 --- a/build.gradle +++ b/build.gradle @@ -8,9 +8,9 @@ buildscript { maven { url 'https://maven.fabric.io/public' } } dependencies { - classpath 'com.android.tools.build:gradle:3.5.1' - classpath 'com.google.gms:google-services:4.3.2' - classpath 'io.fabric.tools:gradle:1.31.0' + classpath 'com.android.tools.build:gradle:3.5.2' + classpath 'com.google.gms:google-services:4.3.3' + classpath 'io.fabric.tools:gradle:1.31.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files 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 @@ + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + + image/svg+xml + + + + + + + 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 @@ + + + + + + + + image/svg+xml + + + + + + + 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 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + + image/svg+xml + + + + + + + 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 @@ + + + + + + + image/svg+xml + + + + + + + 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 @@ + + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/wear/build.gradle b/wear/build.gradle index 7e6500fe56..3a2277e2fe 100644 --- a/wear/build.gradle +++ b/wear/build.gradle @@ -1,8 +1,23 @@ +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" playServicesWearable = "17.0.0" + powermockVersion = "1.7.3" } def generateGitBuild = { -> @@ -92,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}" @@ -101,4 +120,27 @@ dependencies { implementation 'androidx.wear:wear:1.0.0' implementation('me.denley.wearpreferenceactivity:wearpreferenceactivity:0.5.0') implementation('com.github.lecho:hellocharts-library:1.5.8@aar') + + testImplementation "junit:junit:4.12" + testImplementation "org.json:json:20140107" + 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}" + testImplementation "org.powermock:powermock-module-junit4:${powermockVersion}" + testImplementation "joda-time:joda-time:2.9.9" + testImplementation("com.google.truth:truth:0.39") { + exclude group: "com.google.guava", module: "guava" + } + testImplementation "org.skyscreamer:jsonassert:1.5.0" + testImplementation "org.hamcrest:hamcrest-all:1.3" } 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 24bb735067..78a6e401a2 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; @@ -75,11 +69,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 @@ -91,26 +83,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() { @@ -127,6 +102,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen } sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); sharedPrefs.registerOnSharedPreferenceChangeListener(this); + + BaseComplicationProviderService.turnOff(); } @Override @@ -198,11 +175,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"); @@ -267,50 +244,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); @@ -329,7 +276,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 @@ -342,7 +289,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); @@ -351,7 +298,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); @@ -360,7 +307,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); @@ -368,7 +315,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); @@ -378,7 +325,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 { @@ -390,12 +337,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); @@ -405,10 +352,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(""); @@ -435,13 +382,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 { @@ -451,7 +398,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); @@ -460,7 +407,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); @@ -468,8 +415,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); @@ -478,7 +425,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); @@ -488,8 +435,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; @@ -596,50 +543,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()); @@ -648,54 +558,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 786dbd8179..f1929434f3 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home.java @@ -69,15 +69,15 @@ public class Home extends BaseWatchFace { R.color.dark_background : 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)); @@ -90,7 +90,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(), dividerMatchesBg ? R.color.dark_midColor : R.color.dark_uploaderBattery)); } else { @@ -138,15 +138,15 @@ public class Home extends BaseWatchFace { mLinearLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), dividerMatchesBg ? R.color.light_background : 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)); @@ -158,7 +158,7 @@ public class Home extends BaseWatchFace { mTimestamp.setTextColor(Color.RED); } - if (batteryLevel == 1) { + if (rawData.batteryLevel == 1) { mUploaderBattery.setTextColor(dividerMatchesBg ? Color.BLACK : 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 64ec7e17c8..582bcd761f 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home2.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home2.java @@ -86,13 +86,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)); } @@ -103,7 +103,7 @@ public class Home2 extends BaseWatchFace { mTimestamp.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_TimestampOld)); } - if (batteryLevel == 1) { + if (rawData.batteryLevel == 1) { mUploaderBattery.setTextColor(dividerBatteryOkColor); } else { mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_uploaderBatteryEmpty)); @@ -195,13 +195,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)); } @@ -212,7 +212,7 @@ public class Home2 extends BaseWatchFace { mTimestamp.setTextColor(Color.RED); } - if (batteryLevel == 1) { + if (rawData.batteryLevel == 1) { mUploaderBattery.setTextColor(dividerTxtColor); } else { mUploaderBattery.setTextColor(Color.RED); @@ -248,7 +248,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 672b95dee0..efce6baab7 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/LargeHome.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/LargeHome.java @@ -53,15 +53,15 @@ public class LargeHome extends BaseWatchFace { R.color.dark_background : 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)); @@ -74,7 +74,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(), dividerMatchesBg ? R.color.dark_midColor : R.color.dark_uploaderBattery)); } else { @@ -90,15 +90,15 @@ public class LargeHome extends BaseWatchFace { mLinearLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), dividerMatchesBg ? R.color.light_background : 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)); @@ -110,7 +110,7 @@ public class LargeHome extends BaseWatchFace { mTimestamp.setTextColor(Color.RED); } - if (batteryLevel == 1) { + if (rawData.batteryLevel == 1) { mUploaderBattery.setTextColor(dividerMatchesBg ? Color.BLACK : Color.WHITE); } else { mUploaderBattery.setTextColor(Color.RED); @@ -120,15 +120,15 @@ public class LargeHome extends BaseWatchFace { } else { mRelativeLayout.setBackgroundColor(Color.BLACK); mLinearLayout.setBackgroundColor(dividerMatchesBg ? Color.BLACK : 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 f0fc4b5789..d74b6e1301 100644 --- a/wear/src/main/res/xml/preferences.xml +++ b/wear/src/main/res/xml/preferences.xml @@ -217,6 +217,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/SafeParseTest.java b/wear/src/test/java/info/nightscout/androidaps/interaction/utils/SafeParseTest.java new file mode 100644 index 0000000000..62a71e6420 --- /dev/null +++ b/wear/src/test/java/info/nightscout/androidaps/interaction/utils/SafeParseTest.java @@ -0,0 +1,128 @@ +package info.nightscout.androidaps.interaction.utils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +/** + * Created by dlvoy on 21.11.2019. + */ +@RunWith(PowerMockRunner.class) +public class SafeParseTest { + + @Test + public void stringToDoubleTest() { + // correct values + assertThat(0.1234, is(SafeParse.stringToDouble("0.1234"))); + assertThat(0.1234, is(SafeParse.stringToDouble("0,1234"))); + assertThat(0.5436564812, is(SafeParse.stringToDouble(".5436564812"))); + assertThat(0.5436564812, is(SafeParse.stringToDouble(",5436564812"))); + assertThat(1000500100900.0, is(SafeParse.stringToDouble("1000500100900"))); + assertThat(42.0, is(SafeParse.stringToDouble("42"))); + + // units or other extra values are not permitted + assertThat(0.0, is(SafeParse.stringToDouble("12 U/h"))); + + // strings are not parsable + assertThat(0.0, is(SafeParse.stringToDouble("ala ma kota"))); + + // separator errors + assertThat(0.0, is(SafeParse.stringToDouble("0.1234.5678"))); + assertThat(0.0, is(SafeParse.stringToDouble("0,1234,5678"))); + + // various emptiness + assertThat(0.0, is(SafeParse.stringToDouble(""))); + assertThat(0.0, is(SafeParse.stringToDouble(" "))); + assertThat(0.0, is(SafeParse.stringToDouble("\n\r"))); + } + + @Test + public void stringToIntTest() { + // correct values + assertThat(1052934, is(SafeParse.stringToInt("1052934"))); + assertThat(-42, is(SafeParse.stringToInt("-42"))); + assertThat(2147483647, is(SafeParse.stringToInt("2147483647"))); + assertThat(-2147483648, is(SafeParse.stringToInt("-2147483648"))); + + // outside Integer range + assertThat(0, is(SafeParse.stringToInt("2147483648"))); + assertThat(0, is(SafeParse.stringToInt("-2147483649"))); + + // units or other extra values are not permitted + assertThat(0, is(SafeParse.stringToInt("12 U/h"))); + assertThat(0, is(SafeParse.stringToInt("0.1234"))); + assertThat(0, is(SafeParse.stringToInt("0,1234"))); + assertThat(0, is(SafeParse.stringToInt(".5436564812"))); + assertThat(0, is(SafeParse.stringToInt(",5436564812"))); + assertThat(0, is(SafeParse.stringToInt("42.1234"))); + assertThat(0, is(SafeParse.stringToInt("42,1234"))); + assertThat(0, is(SafeParse.stringToInt("3212.5436564812"))); + assertThat(0, is(SafeParse.stringToInt("3212,5436564812"))); + assertThat(0, is(SafeParse.stringToInt("1000500100900"))); + + // strings are not parsable + assertThat(0, is(SafeParse.stringToInt("ala ma kota"))); + + // various emptiness + assertThat(0, is(SafeParse.stringToInt(""))); + assertThat(0, is(SafeParse.stringToInt(" "))); + assertThat(0, is(SafeParse.stringToInt("\n\r"))); + } + + @Test + public void stringToLongTest() { + // correct values + assertThat(1052934L, is(SafeParse.stringToLong("1052934"))); + assertThat(-42L, is(SafeParse.stringToLong("-42"))); + assertThat(2147483647L, is(SafeParse.stringToLong("2147483647"))); + assertThat(-2147483648L, is(SafeParse.stringToLong("-2147483648"))); + assertThat(1000500100900L, is(SafeParse.stringToLong("1000500100900"))); + + // outside Integer range + assertThat(2147483648L, is(SafeParse.stringToLong("2147483648"))); + assertThat(-2147483649L, is(SafeParse.stringToLong("-2147483649"))); + + // units or other extra values are not permitted + assertThat(0L, is(SafeParse.stringToLong("12 U/h"))); + assertThat(0L, is(SafeParse.stringToLong("0.1234"))); + assertThat(0L, is(SafeParse.stringToLong("0,1234"))); + assertThat(0L, is(SafeParse.stringToLong(".5436564812"))); + assertThat(0L, is(SafeParse.stringToLong(",5436564812"))); + assertThat(0L, is(SafeParse.stringToLong("42.1234"))); + assertThat(0L, is(SafeParse.stringToLong("42,1234"))); + assertThat(0L, is(SafeParse.stringToLong("3212.5436564812"))); + assertThat(0L, is(SafeParse.stringToLong("3212,5436564812"))); + + // strings are not parsable + assertThat(0L, is(SafeParse.stringToLong("ala ma kota"))); + + // various emptiness + assertThat(0L, is(SafeParse.stringToLong(""))); + assertThat(0L, is(SafeParse.stringToLong(" "))); + assertThat(0L, is(SafeParse.stringToLong("\n\r"))); + } + + @Test(expected=NullPointerException.class) + public void stringToDoubleNullTest() { + SafeParse.stringToDouble(null); + } + + @Test(expected=NullPointerException.class) + public void stringToIntNullTest() { + SafeParse.stringToInt(null); + } + + @Test(expected=NullPointerException.class) + public void stringToLongNullTest() { + SafeParse.stringToLong(null); + } + + @Before + public void prepareMock() { + + } +} 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 get = new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return map.get(invocation.getArguments()[0]); + } + }; + Answer getOrDefault = new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Object key = invocation.getArguments()[0]; + return map.containsKey(key) ? map.get(key) : invocation.getArguments()[1]; + } + }; + + Bundle bundle = Mockito.mock(Bundle.class); + + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return map.size(); + } + }).when(bundle).size(); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return map.isEmpty(); + } + }).when(bundle).isEmpty(); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + map.clear(); + return null; + } + }).when(bundle).clear(); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return map.containsKey(invocation.getArguments()[0]); + } + }).when(bundle).containsKey(anyString()); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return map.get(invocation.getArguments()[0]); + } + }).when(bundle).get(anyString()); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + map.remove(invocation.getArguments()[0]); + return null; + } + }).when(bundle).remove(anyString()); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return map.keySet(); + } + }).when(bundle).keySet(); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return BundleMock.class.getSimpleName() + "{map=" + map.toString() + "}"; + } + }).when(bundle).toString(); + + doAnswer(put).when(bundle).putBoolean(anyString(), anyBoolean()); + when(bundle.getBoolean(anyString())).thenAnswer(get); + when(bundle.getBoolean(anyString(), anyBoolean())).thenAnswer(getOrDefault); + + doAnswer(put).when(bundle).putByte(anyString(), anyByte()); + when(bundle.getByte(anyString())).thenAnswer(get); + when(bundle.getByte(anyString(), anyByte())).thenAnswer(getOrDefault); + + doAnswer(put).when(bundle).putChar(anyString(), anyChar()); + when(bundle.getChar(anyString())).thenAnswer(get); + when(bundle.getChar(anyString(), anyChar())).thenAnswer(getOrDefault); + + doAnswer(put).when(bundle).putInt(anyString(), anyShort()); + when(bundle.getShort(anyString())).thenAnswer(get); + when(bundle.getShort(anyString(), anyShort())).thenAnswer(getOrDefault); + + doAnswer(put).when(bundle).putLong(anyString(), anyLong()); + when(bundle.getLong(anyString())).thenAnswer(get); + when(bundle.getLong(anyString(), anyLong())).thenAnswer(getOrDefault); + + doAnswer(put).when(bundle).putFloat(anyString(), anyFloat()); + when(bundle.getFloat(anyString())).thenAnswer(get); + when(bundle.getFloat(anyString(), anyFloat())).thenAnswer(getOrDefault); + + doAnswer(put).when(bundle).putDouble(anyString(), anyDouble()); + when(bundle.getDouble(anyString())).thenAnswer(get); + when(bundle.getDouble(anyString(), anyDouble())).thenAnswer(getOrDefault); + + doAnswer(put).when(bundle).putString(anyString(), anyString()); + when(bundle.getString(anyString())).thenAnswer(get); + when(bundle.getString(anyString(), anyString())).thenAnswer(getOrDefault); + + doAnswer(put).when(bundle).putBooleanArray(anyString(), any(boolean[].class)); + when(bundle.getBooleanArray(anyString())).thenAnswer(get); + + doAnswer(put).when(bundle).putLongArray(anyString(), any(long[].class)); + when(bundle.getLongArray(anyString())).thenAnswer(get); + + doAnswer(put).when(bundle).putDoubleArray(anyString(), any(double[].class)); + when(bundle.getDoubleArray(anyString())).thenAnswer(get); + + doAnswer(put).when(bundle).putIntArray(anyString(), any(int[].class)); + when(bundle.getIntArray(anyString())).thenAnswer(get); + + doAnswer(put).when(bundle).putInt(anyString(), anyInt()); + when(bundle.getInt(anyString())).thenAnswer(get); + when(bundle.getInt(anyString(), anyInt())).thenAnswer(getOrDefault); + + doAnswer(unsupported).when(bundle).putAll(any(Bundle.class)); + when(bundle.hasFileDescriptors()).thenAnswer(unsupported); + + doAnswer(put).when(bundle).putShort(anyString(), anyShort()); + when(bundle.getShort(anyString())).thenAnswer(get); + when(bundle.getShort(anyString(), anyShort())).thenAnswer(getOrDefault); + + doAnswer(put).when(bundle).putFloat(anyString(), anyFloat()); + when(bundle.getFloat(anyString())).thenAnswer(get); + when(bundle.getFloat(anyString(), anyFloat())).thenAnswer(getOrDefault); + + doAnswer(put).when(bundle).putCharSequence(anyString(), any(CharSequence.class)); + when(bundle.getCharSequence(anyString())).thenAnswer(get); + when(bundle.getCharSequence(anyString(), any(CharSequence.class))).thenAnswer(getOrDefault); + + doAnswer(put).when(bundle).putBundle(anyString(), any(Bundle.class)); + when(bundle.getBundle(anyString())).thenAnswer(get); + + doAnswer(put).when(bundle).putParcelable(anyString(), any(Parcelable.class)); + when(bundle.getParcelable(anyString())).thenAnswer(get); + + doAnswer(put).when(bundle).putParcelableArray(anyString(), any(Parcelable[].class)); + when(bundle.getParcelableArray(anyString())).thenAnswer(get); + + doAnswer(put).when(bundle).putParcelableArrayList(anyString(), any(ArrayList.class)); + when(bundle.getParcelableArrayList(anyString())).thenAnswer(get); + + doAnswer(put).when(bundle).putSparseParcelableArray(anyString(), any(SparseArray.class)); + when(bundle.getSparseParcelableArray(anyString())).thenAnswer(get); + + doAnswer(put).when(bundle).putSerializable(anyString(), any(Serializable.class)); + when(bundle.getSerializable(anyString())).thenAnswer(get); + + doAnswer(put).when(bundle).putIntegerArrayList(anyString(), any(ArrayList.class)); + when(bundle.getIntegerArrayList(anyString())).thenAnswer(get); + + doAnswer(put).when(bundle).putStringArrayList(anyString(), any(ArrayList.class)); + when(bundle.getStringArrayList(anyString())).thenAnswer(get); + + doAnswer(put).when(bundle).putCharSequenceArrayList(anyString(), any(ArrayList.class)); + when(bundle.getCharSequenceArrayList(anyString())).thenAnswer(get); + + doAnswer(put).when(bundle).putCharArray(anyString(), any(char[].class)); + when(bundle.getCharArray(anyString())).thenAnswer(get); + + doAnswer(put).when(bundle).putByteArray(anyString(), any(byte[].class)); + when(bundle.getByteArray(anyString())).thenAnswer(get); + + doAnswer(put).when(bundle).putShortArray(anyString(), any(short[].class)); + when(bundle.getShortArray(anyString())).thenAnswer(get); + + doAnswer(put).when(bundle).putFloatArray(anyString(), any(float[].class)); + when(bundle.getFloatArray(anyString())).thenAnswer(get); + + doAnswer(put).when(bundle).putCharSequenceArray(anyString(), any(CharSequence[].class)); + when(bundle.getCharSequenceArray(anyString())).thenAnswer(get); + + return bundle; + } +} \ No newline at end of file diff --git a/wear/src/test/java/info/nightscout/androidaps/testing/mocks/IntentMock.java b/wear/src/test/java/info/nightscout/androidaps/testing/mocks/IntentMock.java new file mode 100644 index 0000000000..c82a43fed9 --- /dev/null +++ b/wear/src/test/java/info/nightscout/androidaps/testing/mocks/IntentMock.java @@ -0,0 +1,39 @@ +package info.nightscout.androidaps.testing.mocks; + +import android.content.Intent; +import android.os.Bundle; + +import org.mockito.Mockito; +import org.mockito.stubbing.Answer; + +import java.util.HashMap; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; + +public final class IntentMock { + + public static Intent mock() { + return mock(new HashMap()); + } + + public static Intent mock(final HashMap map) { + + Answer put = invocation -> { + map.put((String)invocation.getArguments()[0], invocation.getArguments()[1]); + return null; + }; + Answer get = invocation -> map.get(invocation.getArguments()[0]); + + Intent intent = Mockito.mock(Intent.class); + + when(intent.putExtra(anyString(), any(Bundle.class))).thenAnswer(put); + when(intent.getBundleExtra(anyString())).thenAnswer(get); + + doAnswer(invocation -> map.containsKey(invocation.getArguments()[0])).when(intent).hasExtra(anyString()); + + return intent; + } +} \ No newline at end of file diff --git a/wear/src/test/java/info/nightscout/androidaps/testing/mocks/SharedPreferencesMock.java b/wear/src/test/java/info/nightscout/androidaps/testing/mocks/SharedPreferencesMock.java new file mode 100644 index 0000000000..5b0736a450 --- /dev/null +++ b/wear/src/test/java/info/nightscout/androidaps/testing/mocks/SharedPreferencesMock.java @@ -0,0 +1,158 @@ +package info.nightscout.androidaps.testing.mocks; + +import android.content.SharedPreferences; + +import androidx.annotation.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class SharedPreferencesMock implements SharedPreferences { + + private final EditorInternals editor = new EditorInternals(); + + class EditorInternals implements Editor { + + Map innerMap = new HashMap<>(); + + @Override + public Editor putString(String k, @Nullable String v) { + innerMap.put(k, v); + return this; + } + + @Override + public Editor putStringSet(String k, @Nullable Set set) { + innerMap.put(k, set); + return this; + } + + @Override + public Editor putInt(String k, int i) { + innerMap.put(k, i); + return this; + } + + @Override + public Editor putLong(String k, long l) { + innerMap.put(k, l); + return this; + } + + @Override + public Editor putFloat(String k, float v) { + innerMap.put(k, v); + return this; + } + + @Override + public Editor putBoolean(String k, boolean b) { + innerMap.put(k, b); + return this; + } + + @Override + public Editor remove(String k) { + innerMap.remove(k); + return this; + } + + @Override + public Editor clear() { + innerMap.clear(); + return this; + } + + @Override + public boolean commit() { + return true; + } + + @Override + public void apply() { + + } + } + + @Override + public Map getAll() { + return editor.innerMap; + } + + @Nullable + @Override + public String getString(String k, @Nullable String s) { + if (editor.innerMap.containsKey(k)) { + return (String) editor.innerMap.get(k); + } else { + return s; + } + } + + @Nullable + @Override + public Set getStringSet(String k, @Nullable Set set) { + if (editor.innerMap.containsKey(k)) { + return (Set) editor.innerMap.get(k); + } else { + return set; + } + } + + @Override + public int getInt(String k, int i) { + if (editor.innerMap.containsKey(k)) { + return (Integer) editor.innerMap.get(k); + } else { + return i; + } + } + + @Override + public long getLong(String k, long l) { + if (editor.innerMap.containsKey(k)) { + return (Long) editor.innerMap.get(k); + } else { + return l; + } + } + + @Override + public float getFloat(String k, float v) { + if (editor.innerMap.containsKey(k)) { + return (Float) editor.innerMap.get(k); + } else { + return v; + } + } + + @Override + public boolean getBoolean(String k, boolean b) { + if (editor.innerMap.containsKey(k)) { + return (Boolean) editor.innerMap.get(k); + } else { + return b; + } + } + + @Override + public boolean contains(String k) { + return editor.innerMap.containsKey(k); + } + + @Override + public Editor edit() { + return editor; + } + + @Override + public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) { + + } + + @Override + public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) { + + } +} diff --git a/wear/src/test/java/info/nightscout/androidaps/testing/utils/BasalWatchDataExt.java b/wear/src/test/java/info/nightscout/androidaps/testing/utils/BasalWatchDataExt.java new file mode 100644 index 0000000000..9a23f491d9 --- /dev/null +++ b/wear/src/test/java/info/nightscout/androidaps/testing/utils/BasalWatchDataExt.java @@ -0,0 +1,58 @@ +package info.nightscout.androidaps.testing.utils; + +import androidx.annotation.Nullable; + +import java.util.Objects; + +import info.nightscout.androidaps.data.BasalWatchData; + +import static info.nightscout.androidaps.testing.utils.ExtUtil.assertClassHaveSameFields; + +public class BasalWatchDataExt extends BasalWatchData { + + private BasalWatchDataExt() { + super(); + } + + public BasalWatchDataExt(BasalWatchData ref) { + super(); + + // since we do not want modify BasalWatchData - we use this wrapper class + // but we make sure it has same fields + assertClassHaveSameFields(BasalWatchData.class, "startTime,endTime,amount"); + + this.startTime = ref.startTime; + this.endTime = ref.endTime; + this.amount = ref.amount; + } + + public static BasalWatchDataExt build(long startTime, long endTime, double amount) { + BasalWatchDataExt bwd = new BasalWatchDataExt(); + bwd.startTime = startTime; + bwd.endTime = endTime; + bwd.amount = amount; + return bwd; + } + + @Override + public boolean equals(@Nullable Object obj) { + if ((obj instanceof BasalWatchData)||(obj instanceof BasalWatchDataExt)) { + return (this.startTime == ((BasalWatchData) obj).startTime) + && (this.endTime == ((BasalWatchData) obj).endTime) + && (this.amount == ((BasalWatchData) obj).amount); + } else { + return false; + } + } + + @Override + public String toString() { + return startTime+", "+endTime+", "+amount; + } + + @Override + public int hashCode() { + return Objects.hash(startTime, endTime, amount); + } + +} diff --git a/wear/src/test/java/info/nightscout/androidaps/testing/utils/BgWatchDataExt.java b/wear/src/test/java/info/nightscout/androidaps/testing/utils/BgWatchDataExt.java new file mode 100644 index 0000000000..c49125809e --- /dev/null +++ b/wear/src/test/java/info/nightscout/androidaps/testing/utils/BgWatchDataExt.java @@ -0,0 +1,66 @@ +package info.nightscout.androidaps.testing.utils; + +import androidx.annotation.Nullable; + +import java.util.Objects; + +import info.nightscout.androidaps.data.BgWatchData; + +import static info.nightscout.androidaps.testing.utils.ExtUtil.assertClassHaveSameFields; + +public class BgWatchDataExt extends BgWatchData { + + private BgWatchDataExt() { + super(); + } + + public BgWatchDataExt(double aSgv, double aHigh, double aLow, long aTimestamp, int aColor) { + super(aSgv, aHigh, aLow, aTimestamp, aColor); + } + + public BgWatchDataExt(BgWatchData ref) { + super(); + + // since we do not want modify BgWatchDataExt - we use this wrapper class + // but we make sure it has same fields + assertClassHaveSameFields(BgWatchData.class, "sgv,high,low,timestamp,color"); + + this.sgv = ref.sgv; + this.high = ref.high; + this.low = ref.low; + this.timestamp = ref.timestamp; + this.color = ref.color; + } + + public static BgWatchDataExt build(double sgv, long timestamp, int color) { + BgWatchDataExt twd = new BgWatchDataExt(); + twd.sgv = sgv; + twd.timestamp = timestamp; + twd.color = color; + return twd; + } + + @Override + public boolean equals(@Nullable Object obj) { + if ((obj instanceof BgWatchData)||(obj instanceof BgWatchDataExt)) { + return (this.sgv == ((BgWatchData) obj).sgv) + && (this.high == ((BgWatchData) obj).high) + && (this.low == ((BgWatchData) obj).low) + && (this.timestamp == ((BgWatchData) obj).timestamp) + && (this.color == ((BgWatchData) obj).color); + } else { + return false; + } + } + + @Override + public String toString() { + return sgv+", "+high+", "+low+", "+timestamp+", "+color; + } + + @Override + public int hashCode() { + return Objects.hash(sgv, high, low, timestamp, color); + } + +} diff --git a/wear/src/test/java/info/nightscout/androidaps/testing/utils/BolusWatchDataExt.java b/wear/src/test/java/info/nightscout/androidaps/testing/utils/BolusWatchDataExt.java new file mode 100644 index 0000000000..a5553f6a59 --- /dev/null +++ b/wear/src/test/java/info/nightscout/androidaps/testing/utils/BolusWatchDataExt.java @@ -0,0 +1,64 @@ +package info.nightscout.androidaps.testing.utils; + +import androidx.annotation.Nullable; + +import java.util.Objects; + +import info.nightscout.androidaps.data.BolusWatchData; + +import static info.nightscout.androidaps.testing.utils.ExtUtil.assertClassHaveSameFields; + +public class BolusWatchDataExt extends BolusWatchData { + + private BolusWatchDataExt() { + super(); + } + + public BolusWatchDataExt(BolusWatchData ref) { + super(); + + // since we do not want modify BolusWatchData - we use this wrapper class + // but we make sure it has same fields + assertClassHaveSameFields(BolusWatchData.class, "date,bolus,carbs,isSMB,isValid"); + + this.date = ref.date; + this.bolus = ref.bolus; + this.carbs = ref.carbs; + this.isSMB = ref.isSMB; + this.isValid = ref.isValid; + } + + public static BolusWatchDataExt build(long date, double bolus, double carbs, boolean isSMB, boolean isValid) { + BolusWatchDataExt bwd = new BolusWatchDataExt(); + bwd.date = date; + bwd.bolus = bolus; + bwd.carbs = carbs; + bwd.isSMB = isSMB; + bwd.isValid = isValid; + return bwd; + } + + @Override + public boolean equals(@Nullable Object obj) { + if ((obj instanceof BolusWatchData)||(obj instanceof BolusWatchDataExt)) { + return (this.date == ((BolusWatchData) obj).date) + && (this.bolus == ((BolusWatchData) obj).bolus) + && (this.carbs == ((BolusWatchData) obj).carbs) + && (this.isSMB == ((BolusWatchData) obj).isSMB) + && (this.isValid == ((BolusWatchData) obj).isValid); + } else { + return false; + } + } + + @Override + public String toString() { + return date+", "+bolus+", "+carbs+", "+isSMB+", "+isValid; + } + + @Override + public int hashCode() { + return Objects.hash(date, bolus, carbs, isSMB, isValid); + } + +} diff --git a/wear/src/test/java/info/nightscout/androidaps/testing/utils/ExtUtil.java b/wear/src/test/java/info/nightscout/androidaps/testing/utils/ExtUtil.java new file mode 100644 index 0000000000..1559db11ae --- /dev/null +++ b/wear/src/test/java/info/nightscout/androidaps/testing/utils/ExtUtil.java @@ -0,0 +1,28 @@ +package info.nightscout.androidaps.testing.utils; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +class ExtUtil { + + static void assertClassHaveSameFields(Class checkedClass, String commaSeparatedFieldList) { + Set parentFields = new HashSet<>(); + for (Field f : checkedClass.getDeclaredFields()) { + final String fieldName = f.getName(); + // skip runtime-injected fields like $jacocoData + if (fieldName.startsWith("$")) { + continue; + } + parentFields.add(fieldName); + } + + Set knownFields = new HashSet<>(Arrays.asList(commaSeparatedFieldList.split(","))); + assertThat(parentFields, is(knownFields)); + } + +} diff --git a/wear/src/test/java/info/nightscout/androidaps/testing/utils/TempWatchDataExt.java b/wear/src/test/java/info/nightscout/androidaps/testing/utils/TempWatchDataExt.java new file mode 100644 index 0000000000..478c6de966 --- /dev/null +++ b/wear/src/test/java/info/nightscout/androidaps/testing/utils/TempWatchDataExt.java @@ -0,0 +1,66 @@ +package info.nightscout.androidaps.testing.utils; + +import androidx.annotation.Nullable; + +import java.util.Objects; + +import info.nightscout.androidaps.data.TempWatchData; + +import static info.nightscout.androidaps.testing.utils.ExtUtil.assertClassHaveSameFields; + + +public class TempWatchDataExt extends TempWatchData { + + private TempWatchDataExt() { + super(); + } + + public TempWatchDataExt(TempWatchData ref) { + super(); + + // since we do not want modify BolusWatchData - we use this wrapper class + // but we make sure it has same fields + assertClassHaveSameFields(TempWatchData.class, "startTime,startBasal,endTime,endBasal,amount"); + + this.startTime = ref.startTime; + this.startBasal = ref.startBasal; + this.endTime = ref.endTime; + this.endBasal = ref.endBasal; + this.amount = ref.amount; + } + + public static TempWatchDataExt build(long startTime, double startBasal, long endTime, + double endBasal, double amount) { + TempWatchDataExt twd = new TempWatchDataExt(); + twd.startTime = startTime; + twd.startBasal = startBasal; + twd.endTime = endTime; + twd.endBasal = endBasal; + twd.amount = amount; + return twd; + } + + @Override + public boolean equals(@Nullable Object obj) { + if ((obj instanceof TempWatchData)||(obj instanceof TempWatchDataExt)) { + return (this.startTime == ((TempWatchData) obj).startTime) + && (this.startBasal == ((TempWatchData) obj).startBasal) + && (this.endTime == ((TempWatchData) obj).endTime) + && (this.endBasal == ((TempWatchData) obj).endBasal) + && (this.amount == ((TempWatchData) obj).amount); + } else { + return false; + } + } + + @Override + public String toString() { + return startTime+", "+startBasal+", "+endTime+", "+endBasal+", "+amount; + } + + @Override + public int hashCode() { + return Objects.hash(startTime, startBasal, endTime, endBasal, amount); + } + +}