diff --git a/app/build.gradle b/app/build.gradle
index c633d46524..203319de43 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -352,6 +352,15 @@ dependencies {
kapt "com.google.dagger:dagger-compiler:$dagger_version"
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
+
+ //WorkManager
+ implementation 'androidx.work:work-runtime:2.3.4'
+ implementation 'androidx.work:work-runtime-ktx:2.3.4'
+ implementation 'androidx.work:work-rxjava2:2.3.4'
+
+ implementation 'com.google.androidbrowserhelper:androidbrowserhelper:1.1.0'
+
+ implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
}
/*
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 531e88e83a..96763d8274 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -260,6 +260,19 @@
android:exported="true" />
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt
index e01ea6da78..d7ffdc5358 100644
--- a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt
@@ -32,6 +32,7 @@ import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin
import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin
import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus
+import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
import info.nightscout.androidaps.plugins.general.tidepool.TidepoolPlugin
import info.nightscout.androidaps.plugins.general.wear.WearPlugin
@@ -58,6 +59,7 @@ import info.nightscout.androidaps.utils.sharedPreferences.SP
import javax.inject.Inject
class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeListener, HasAndroidInjector {
+
private var pluginId = -1
@Inject lateinit var rxBus: RxBusWrapper
@@ -98,6 +100,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
@Inject lateinit var passwordCheck: PasswordCheck
@Inject lateinit var nsSettingStatus: NSSettingsStatus
+ @Inject lateinit var openHumansUploader: OpenHumansUploader
// TODO why?
@Inject lateinit var androidInjector: DispatchingAndroidInjector
@@ -183,6 +186,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
addPreferencesFromResource(R.xml.pref_alerts, rootKey) // TODO not organized well
addPreferencesFromResource(R.xml.pref_datachoices, rootKey)
addPreferencesFromResourceIfEnabled(maintenancePlugin, rootKey)
+ addPreferencesFromResourceIfEnabled(openHumansUploader, rootKey)
}
initSummary(preferenceScreen, pluginId != -1)
preprocessPreferences()
diff --git a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java
index d96fda8d3b..12103067c5 100644
--- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java
+++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java
@@ -9,6 +9,7 @@ import androidx.annotation.Nullable;
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.dao.CloseableIterator;
import com.j256.ormlite.dao.Dao;
+import com.j256.ormlite.stmt.DeleteBuilder;
import com.j256.ormlite.stmt.PreparedQuery;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.stmt.Where;
@@ -21,6 +22,7 @@ import org.json.JSONObject;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
+import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.concurrent.Executors;
@@ -53,6 +55,8 @@ import info.nightscout.androidaps.logging.LTag;
import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryBgData;
+import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader;
+import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData;
import info.nightscout.androidaps.plugins.pump.insight.database.InsightBolusID;
import info.nightscout.androidaps.plugins.pump.insight.database.InsightHistoryOffset;
@@ -74,6 +78,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
@Inject AAPSLogger aapsLogger;
@Inject RxBusWrapper rxBus;
@Inject VirtualPumpPlugin virtualPumpPlugin;
+ @Inject OpenHumansUploader openHumansUploader;
public static final String DATABASE_NAME = "AndroidAPSDb";
public static final String DATABASE_BGREADINGS = "BgReadings";
@@ -87,8 +92,9 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
public static final String DATABASE_INSIGHT_HISTORY_OFFSETS = "InsightHistoryOffsets";
public static final String DATABASE_INSIGHT_BOLUS_IDS = "InsightBolusIDs";
public static final String DATABASE_INSIGHT_PUMP_IDS = "InsightPumpIDs";
+ public static final String DATABASE_OPEN_HUMANS_QUEUE = "OpenHumansQueue";
- private static final int DATABASE_VERSION = 12;
+ private static final int DATABASE_VERSION = 13;
public static Long earliestDataChange = null;
@@ -141,6 +147,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
TableUtils.createTableIfNotExists(connectionSource, InsightBolusID.class);
TableUtils.createTableIfNotExists(connectionSource, InsightPumpID.class);
TableUtils.createTableIfNotExists(connectionSource, OmnipodHistoryRecord.class);
+ TableUtils.createTableIfNotExists(connectionSource, OHQueueItem.class);
database.execSQL("INSERT INTO sqlite_sequence (name, seq) SELECT \"" + DATABASE_INSIGHT_BOLUS_IDS + "\", " + System.currentTimeMillis() + " " +
"WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = \"" + DATABASE_INSIGHT_BOLUS_IDS + "\")");
database.execSQL("INSERT INTO sqlite_sequence (name, seq) SELECT \"" + DATABASE_INSIGHT_PUMP_IDS + "\", " + System.currentTimeMillis() + " " +
@@ -180,6 +187,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
database.execSQL("UPDATE sqlite_sequence SET seq = " + System.currentTimeMillis() + " WHERE name = \"" + DATABASE_INSIGHT_BOLUS_IDS + "\"");
database.execSQL("UPDATE sqlite_sequence SET seq = " + System.currentTimeMillis() + " WHERE name = \"" + DATABASE_INSIGHT_PUMP_IDS + "\"");
}
+ TableUtils.createTableIfNotExists(connectionSource, OHQueueItem.class);
} catch (SQLException e) {
aapsLogger.error("Can't drop databases", e);
throw new RuntimeException(e);
@@ -366,6 +374,10 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return getDao(OmnipodHistoryRecord.class);
}
+ private Dao getDaoOpenHumansQueue() throws SQLException {
+ return getDao(OHQueueItem.class);
+ }
+
public long roundDateToSec(long date) {
long rounded = date - date % 1000;
if (rounded != date)
@@ -380,6 +392,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
BgReading old = getDaoBgReadings().queryForId(bgReading.date);
if (old == null) {
getDaoBgReadings().create(bgReading);
+ openHumansUploader.enqueueBGReading(bgReading);
aapsLogger.debug(LTag.DATABASE, "BG: New record from: " + from + " " + bgReading.toString());
scheduleBgChange(bgReading);
return true;
@@ -388,6 +401,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
aapsLogger.debug(LTag.DATABASE, "BG: Similiar found: " + old.toString());
old.copyFrom(bgReading);
getDaoBgReadings().update(old);
+ openHumansUploader.enqueueBGReading(old);
aapsLogger.debug(LTag.DATABASE, "BG: Updating record from: " + from + " New data: " + old.toString());
scheduleBgHistoryChange(old.date); // trigger cache invalidation
return false;
@@ -402,6 +416,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
bgReading.date = roundDateToSec(bgReading.date);
try {
getDaoBgReadings().update(bgReading);
+ openHumansUploader.enqueueBGReading(bgReading);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
@@ -496,11 +511,21 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return new ArrayList();
}
+ public List getAllBgReadings() {
+ try {
+ return getDaoBgReadings().queryForAll();
+ } catch (SQLException e) {
+ aapsLogger.error("Unhandled exception", e);
+ }
+ return Collections.emptyList();
+ }
+
// ------------------- TDD handling -----------------------
public void createOrUpdateTDD(TDD tdd) {
try {
Dao dao = getDaoTDD();
dao.createOrUpdate(tdd);
+ openHumansUploader.enqueueTotalDailyDose(tdd);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
@@ -521,6 +546,15 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return tddList;
}
+ public List getAllTDDs() {
+ try {
+ return getDaoTDD().queryForAll();
+ } catch (SQLException e) {
+ aapsLogger.error("Unhandled exception", e);
+ }
+ return Collections.emptyList();
+ }
+
public List getTDDsForLastXDays(int days) {
List tddList;
GregorianCalendar gc = new GregorianCalendar();
@@ -628,6 +662,15 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return new ArrayList();
}
+ public List getAllTempTargets() {
+ try {
+ return getDaoTempTargets().queryForAll();
+ } catch (SQLException e) {
+ aapsLogger.error("Unhandled exception", e);
+ }
+ return Collections.emptyList();
+ }
+
public List getTemptargetsDataFromTime(long from, long to, boolean ascending) {
try {
Dao daoTempTargets = getDaoTempTargets();
@@ -657,6 +700,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
getDaoTempTargets().delete(old); // need to delete/create because date may change too
old.copyFrom(tempTarget);
getDaoTempTargets().create(old);
+ openHumansUploader.enqueueTempTarget(old);
aapsLogger.debug(LTag.DATABASE, "TEMPTARGET: Updating record by date from: " + Source.getString(tempTarget.source) + " " + old.toString());
scheduleTemporaryTargetChange();
return true;
@@ -676,6 +720,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
getDaoTempTargets().delete(old); // need to delete/create because date may change too
old.copyFrom(tempTarget);
getDaoTempTargets().create(old);
+ openHumansUploader.enqueueTempTarget(old);
aapsLogger.debug(LTag.DATABASE, "TEMPTARGET: Updating record by _id from: " + Source.getString(tempTarget.source) + " " + old.toString());
scheduleTemporaryTargetChange();
return true;
@@ -689,6 +734,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
}
if (tempTarget.source == Source.USER) {
getDaoTempTargets().create(tempTarget);
+ openHumansUploader.enqueueTempTarget(tempTarget);
aapsLogger.debug(LTag.DATABASE, "TEMPTARGET: New record from: " + Source.getString(tempTarget.source) + " " + tempTarget.toString());
scheduleTemporaryTargetChange();
return true;
@@ -702,6 +748,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
public void delete(TempTarget tempTarget) {
try {
getDaoTempTargets().delete(tempTarget);
+ openHumansUploader.enqueueTempTarget(tempTarget, true);
scheduleTemporaryTargetChange();
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
@@ -856,6 +903,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: Updated record with Pump Data : " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
getDaoTemporaryBasal().update(old);
+ openHumansUploader.enqueueTemporaryBasal(old);
updateEarliestDataChange(tempBasal.date);
scheduleTemporaryBasalChange();
@@ -864,6 +912,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
}
getDaoTemporaryBasal().create(tempBasal);
+ openHumansUploader.enqueueTemporaryBasal(tempBasal);
aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
updateEarliestDataChange(tempBasal.date);
scheduleTemporaryBasalChange();
@@ -881,6 +930,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
getDaoTemporaryBasal().delete(old); // need to delete/create because date may change too
old.copyFrom(tempBasal);
getDaoTemporaryBasal().create(old);
+ openHumansUploader.enqueueTemporaryBasal(old);
aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: Updating record by date from: " + Source.getString(tempBasal.source) + " " + old.toString());
updateEarliestDataChange(oldDate);
updateEarliestDataChange(old.date);
@@ -903,6 +953,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
getDaoTemporaryBasal().delete(old); // need to delete/create because date may change too
old.copyFrom(tempBasal);
getDaoTemporaryBasal().create(old);
+ openHumansUploader.enqueueTemporaryBasal(old);
aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: Updating record by _id from: " + Source.getString(tempBasal.source) + " " + old.toString());
updateEarliestDataChange(oldDate);
updateEarliestDataChange(old.date);
@@ -912,6 +963,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
}
}
getDaoTemporaryBasal().create(tempBasal);
+ openHumansUploader.enqueueTemporaryBasal(tempBasal);
aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
updateEarliestDataChange(tempBasal.date);
scheduleTemporaryBasalChange();
@@ -919,6 +971,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
}
if (tempBasal.source == Source.USER) {
getDaoTemporaryBasal().create(tempBasal);
+ openHumansUploader.enqueueTemporaryBasal(tempBasal);
aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
updateEarliestDataChange(tempBasal.date);
scheduleTemporaryBasalChange();
@@ -933,6 +986,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
public void delete(TemporaryBasal tempBasal) {
try {
getDaoTemporaryBasal().delete(tempBasal);
+ openHumansUploader.enqueueTemporaryBasal(tempBasal, true);
updateEarliestDataChange(tempBasal.date);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
@@ -940,6 +994,15 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
scheduleTemporaryBasalChange();
}
+ public List getAllTemporaryBasals() {
+ try {
+ return getDaoTemporaryBasal().queryForAll();
+ } catch (SQLException e) {
+ aapsLogger.error("Unhandled exception", e);
+ }
+ return Collections.emptyList();
+ }
+
public List getTemporaryBasalsDataFromTime(long mills, boolean ascending) {
try {
List tempbasals;
@@ -1135,6 +1198,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
// and then is record updated with pumpId
if (extendedBolus.pumpId == 0) {
getDaoExtendedBolus().createOrUpdate(extendedBolus);
+ openHumansUploader.enqueueExtendedBolus(extendedBolus);
} else {
QueryBuilder queryBuilder = getDaoExtendedBolus().queryBuilder();
Where where = queryBuilder.where();
@@ -1146,6 +1210,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return false;
}
getDaoExtendedBolus().createOrUpdate(extendedBolus);
+ openHumansUploader.enqueueExtendedBolus(extendedBolus);
}
aapsLogger.debug(LTag.DATABASE, "EXTENDEDBOLUS: New record from: " + Source.getString(extendedBolus.source) + " " + extendedBolus.log());
updateEarliestDataChange(extendedBolus.date);
@@ -1161,6 +1226,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
old.copyFrom(extendedBolus);
getDaoExtendedBolus().create(old);
aapsLogger.debug(LTag.DATABASE, "EXTENDEDBOLUS: Updating record by date from: " + Source.getString(extendedBolus.source) + " " + old.log());
+ openHumansUploader.enqueueExtendedBolus(old);
updateEarliestDataChange(oldDate);
updateEarliestDataChange(old.date);
scheduleExtendedBolusChange();
@@ -1183,6 +1249,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
old.copyFrom(extendedBolus);
getDaoExtendedBolus().create(old);
aapsLogger.debug(LTag.DATABASE, "EXTENDEDBOLUS: Updating record by _id from: " + Source.getString(extendedBolus.source) + " " + old.log());
+ openHumansUploader.enqueueExtendedBolus(old);
updateEarliestDataChange(oldDate);
updateEarliestDataChange(old.date);
scheduleExtendedBolusChange();
@@ -1192,6 +1259,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
}
getDaoExtendedBolus().create(extendedBolus);
aapsLogger.debug(LTag.DATABASE, "EXTENDEDBOLUS: New record from: " + Source.getString(extendedBolus.source) + " " + extendedBolus.log());
+ openHumansUploader.enqueueExtendedBolus(extendedBolus);
updateEarliestDataChange(extendedBolus.date);
scheduleExtendedBolusChange();
return true;
@@ -1199,6 +1267,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
if (extendedBolus.source == Source.USER) {
getDaoExtendedBolus().create(extendedBolus);
aapsLogger.debug(LTag.DATABASE, "EXTENDEDBOLUS: New record from: " + Source.getString(extendedBolus.source) + " " + extendedBolus.log());
+ openHumansUploader.enqueueExtendedBolus(extendedBolus);
updateEarliestDataChange(extendedBolus.date);
scheduleExtendedBolusChange();
return true;
@@ -1209,6 +1278,15 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return false;
}
+ public List getAllExtendedBoluses() {
+ try {
+ return getDaoExtendedBolus().queryForAll();
+ } catch (SQLException e) {
+ aapsLogger.error("Unhandled exception", e);
+ }
+ return Collections.emptyList();
+ }
+
public ExtendedBolus getExtendedBolusByPumpId(long pumpId) {
try {
return getDaoExtendedBolus().queryBuilder()
@@ -1223,6 +1301,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
public void delete(ExtendedBolus extendedBolus) {
try {
getDaoExtendedBolus().delete(extendedBolus);
+ openHumansUploader.enqueueExtendedBolus(extendedBolus, true);
updateEarliestDataChange(extendedBolus.date);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
@@ -1343,6 +1422,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
careportalEvent.date = careportalEvent.date - careportalEvent.date % 1000;
try {
getDaoCareportalEvents().createOrUpdate(careportalEvent);
+ openHumansUploader.enqueueCareportalEvent(careportalEvent);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
@@ -1352,6 +1432,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
public void delete(CareportalEvent careportalEvent) {
try {
getDaoCareportalEvents().delete(careportalEvent);
+ openHumansUploader.enqueueCareportalEvent(careportalEvent, true);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
@@ -1367,6 +1448,15 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return null;
}
+ public List getAllCareportalEvents() {
+ try {
+ return getDaoCareportalEvents().queryForAll();
+ } catch (SQLException e) {
+ aapsLogger.error("Unhandled exception", e);
+ }
+ return Collections.emptyList();
+ }
+
@Nullable
public CareportalEvent getLastCareportalEvent(String event) {
try {
@@ -1571,6 +1661,14 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return false;
}
+ public List getAllProfileSwitches() {
+ try {
+ return getDaoProfileSwitch().queryForAll();
+ } catch (SQLException e) {
+ aapsLogger.error("Unhandled exception", e);
+ }
+ return Collections.emptyList();
+ }
@Nullable
private ProfileSwitch getLastProfileSwitchWithoutDuration() {
try {
@@ -1643,6 +1741,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
getDaoProfileSwitch().delete(old); // need to delete/create because date may change too
getDaoProfileSwitch().create(profileSwitch);
aapsLogger.debug(LTag.DATABASE, "PROFILESWITCH: Updating record by date from: " + Source.getString(profileSwitch.source) + " " + old.toString());
+ openHumansUploader.enqueueProfileSwitch(profileSwitch);
scheduleProfileSwitchChange();
return true;
}
@@ -1662,6 +1761,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
old.copyFrom(profileSwitch);
getDaoProfileSwitch().create(old);
aapsLogger.debug(LTag.DATABASE, "PROFILESWITCH: Updating record by _id from: " + Source.getString(profileSwitch.source) + " " + old.toString());
+ openHumansUploader.enqueueProfileSwitch(old);
scheduleProfileSwitchChange();
return true;
}
@@ -1671,12 +1771,14 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
profileSwitch.profileName = PercentageSplitter.pureName(profileSwitch.profileName);
getDaoProfileSwitch().create(profileSwitch);
aapsLogger.debug(LTag.DATABASE, "PROFILESWITCH: New record from: " + Source.getString(profileSwitch.source) + " " + profileSwitch.toString());
+ openHumansUploader.enqueueProfileSwitch(profileSwitch);
scheduleProfileSwitchChange();
return true;
}
if (profileSwitch.source == Source.USER) {
getDaoProfileSwitch().create(profileSwitch);
aapsLogger.debug(LTag.DATABASE, "PROFILESWITCH: New record from: " + Source.getString(profileSwitch.source) + " " + profileSwitch.toString());
+ openHumansUploader.enqueueProfileSwitch(profileSwitch);
scheduleProfileSwitchChange();
return true;
}
@@ -1689,6 +1791,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
public void delete(ProfileSwitch profileSwitch) {
try {
getDaoProfileSwitch().delete(profileSwitch);
+ openHumansUploader.enqueueProfileSwitch(profileSwitch, true);
scheduleProfileSwitchChange();
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
@@ -1938,4 +2041,69 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return arrow;
}
+ // ---------------- Open Humans Queue handling ---------------
+
+ public void clearOpenHumansQueue() {
+ try {
+ TableUtils.clearTable(connectionSource, OHQueueItem.class);
+ } catch (SQLException e) {
+ aapsLogger.error("Unhandled exception", e);
+ }
+ }
+
+ public void createOrUpdate(OHQueueItem item) {
+ try {
+ getDaoOpenHumansQueue().createOrUpdate(item);
+ } catch (SQLException e) {
+ aapsLogger.error("Unhandled exception", e);
+ }
+ }
+
+ public void removeAllOHQueueItemsWithIdSmallerThan(long id) {
+ try {
+ DeleteBuilder deleteBuilder = getDaoOpenHumansQueue().deleteBuilder();
+ deleteBuilder.where().le("id", id);
+ deleteBuilder.delete();
+ } catch (SQLException e) {
+ aapsLogger.error("Unhandled exception", e);
+ }
+ }
+
+ public List getAllOHQueueItems(Long maxEntries) {
+ try {
+ return getDaoOpenHumansQueue()
+ .queryBuilder()
+ .orderBy("id", true)
+ .limit(maxEntries)
+ .query();
+ } catch (SQLException e) {
+ aapsLogger.error("Unhandled exception", e);
+ }
+ return Collections.emptyList();
+ }
+
+ public long getOHQueueSize() {
+ try {
+ return getDaoOpenHumansQueue().countOf();
+ } catch (SQLException e) {
+ aapsLogger.error("Unhandled exception", e);
+ }
+ return 0L;
+ }
+
+ public long getCountOfAllRows() {
+ try {
+ return getDaoBgReadings().countOf()
+ + getDaoCareportalEvents().countOf()
+ + getDaoExtendedBolus().countOf()
+ + getDaoCareportalEvents().countOf()
+ + getDaoProfileSwitch().countOf()
+ + getDaoTDD().countOf()
+ + getDaoTemporaryBasal().countOf()
+ + getDaoTempTargets().countOf();
+ } catch (SQLException e) {
+ aapsLogger.error("Unhandled exception", e);
+ }
+ return 0L;
+ }
}
diff --git a/app/src/main/java/info/nightscout/androidaps/db/OHQueueItem.kt b/app/src/main/java/info/nightscout/androidaps/db/OHQueueItem.kt
new file mode 100644
index 0000000000..1a99df9a47
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/db/OHQueueItem.kt
@@ -0,0 +1,14 @@
+package info.nightscout.androidaps.db
+
+import com.j256.ormlite.field.DatabaseField
+import com.j256.ormlite.table.DatabaseTable
+
+@DatabaseTable(tableName = DatabaseHelper.DATABASE_OPEN_HUMANS_QUEUE)
+data class OHQueueItem @JvmOverloads constructor(
+ @DatabaseField(generatedId = true)
+ val id: Long = 0,
+ @DatabaseField
+ val file: String = "",
+ @DatabaseField
+ val content: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt
index 3c2e89b348..8edce1ece4 100644
--- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt
+++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt
@@ -7,6 +7,7 @@ import info.nightscout.androidaps.activities.*
import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity
import info.nightscout.androidaps.plugins.general.maintenance.activities.LogSettingActivity
import info.nightscout.androidaps.plugins.general.maintenance.activities.PrefImportListActivity
+import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansLoginActivity
import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity
import info.nightscout.androidaps.plugins.general.smsCommunicator.activities.SmsCommunicatorOtpActivity
import info.nightscout.androidaps.plugins.pump.common.dialog.RileyLinkBLEScanActivity
@@ -40,4 +41,6 @@ abstract class ActivitiesModule {
@ContributesAndroidInjector abstract fun contributesSurveyActivity(): SurveyActivity
@ContributesAndroidInjector abstract fun contributesDefaultProfileActivity(): ProfileHelperActivity
@ContributesAndroidInjector abstract fun contributesPrefImportListActivity(): PrefImportListActivity
+ @ContributesAndroidInjector abstract fun contributesOpenHumansLoginActivity(): OpenHumansLoginActivity
+
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppComponent.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppComponent.kt
index a748e57eb1..9edf530e56 100644
--- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppComponent.kt
+++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppComponent.kt
@@ -37,7 +37,8 @@ import javax.inject.Singleton
CoreModule::class,
DanaModule::class,
DanaRModule::class,
- DanaRSModule::class
+ DanaRSModule::class,
+ OHUploaderModule::class
]
)
interface AppComponent : AndroidInjector {
diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt
index 07c8cd1495..0af077c632 100644
--- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt
+++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt
@@ -20,6 +20,8 @@ import info.nightscout.androidaps.plugins.general.automation.dialogs.EditTrigger
import info.nightscout.androidaps.plugins.general.food.FoodFragment
import info.nightscout.androidaps.plugins.general.maintenance.MaintenanceFragment
import info.nightscout.androidaps.plugins.general.nsclient.NSClientFragment
+import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansFragment
+import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansLoginActivity
import info.nightscout.androidaps.plugins.general.overview.OverviewFragment
import info.nightscout.androidaps.plugins.general.overview.dialogs.EditQuickWizardDialog
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorFragment
@@ -67,20 +69,29 @@ abstract class FragmentsModule {
@ContributesAndroidInjector abstract fun contributesMedtronicFragment(): MedtronicFragment
@ContributesAndroidInjector abstract fun contributesNSProfileFragment(): NSProfileFragment
@ContributesAndroidInjector abstract fun contributesNSClientFragment(): NSClientFragment
- @ContributesAndroidInjector abstract fun contributesSmsCommunicatorFragment(): SmsCommunicatorFragment
+ @ContributesAndroidInjector
+ abstract fun contributesSmsCommunicatorFragment(): SmsCommunicatorFragment
@ContributesAndroidInjector abstract fun contributesWearFragment(): WearFragment
@ContributesAndroidInjector abstract fun contributesTidepoolFragment(): TidepoolFragment
@ContributesAndroidInjector abstract fun contributesTreatmentsFragment(): TreatmentsFragment
- @ContributesAndroidInjector abstract fun contributesTreatmentsBolusFragment(): TreatmentsBolusFragment
- @ContributesAndroidInjector abstract fun contributesTreatmentsTemporaryBasalsFragment(): TreatmentsTemporaryBasalsFragment
- @ContributesAndroidInjector abstract fun contributesTreatmentsTempTargetFragment(): TreatmentsTempTargetFragment
- @ContributesAndroidInjector abstract fun contributesTreatmentsExtendedBolusesFragment(): TreatmentsExtendedBolusesFragment
- @ContributesAndroidInjector abstract fun contributesTreatmentsCareportalFragment(): TreatmentsCareportalFragment
- @ContributesAndroidInjector abstract fun contributesTreatmentsProfileSwitchFragment(): TreatmentsProfileSwitchFragment
+ @ContributesAndroidInjector
+ abstract fun contributesTreatmentsBolusFragment(): TreatmentsBolusFragment
+ @ContributesAndroidInjector
+ abstract fun contributesTreatmentsTemporaryBasalsFragment(): TreatmentsTemporaryBasalsFragment
+ @ContributesAndroidInjector
+ abstract fun contributesTreatmentsTempTargetFragment(): TreatmentsTempTargetFragment
+ @ContributesAndroidInjector
+ abstract fun contributesTreatmentsExtendedBolusesFragment(): TreatmentsExtendedBolusesFragment
+ @ContributesAndroidInjector
+ abstract fun contributesTreatmentsCareportalFragment(): TreatmentsCareportalFragment
+ @ContributesAndroidInjector
+ abstract fun contributesTreatmentsProfileSwitchFragment(): TreatmentsProfileSwitchFragment
@ContributesAndroidInjector abstract fun contributesVirtualPumpFragment(): VirtualPumpFragment
+ @ContributesAndroidInjector abstract fun contributesOpenHumansFragment(): OpenHumansFragment
+
@ContributesAndroidInjector abstract fun contributesCalibrationDialog(): CalibrationDialog
@ContributesAndroidInjector abstract fun contributesCarbsDialog(): CarbsDialog
@ContributesAndroidInjector abstract fun contributesCareDialog(): CareDialog
@@ -104,9 +115,15 @@ abstract class FragmentsModule {
@ContributesAndroidInjector abstract fun contributesWizardDialog(): WizardDialog
@ContributesAndroidInjector abstract fun contributesWizardInfoDialog(): WizardInfoDialog
+ @ContributesAndroidInjector
+ abstract fun contributesExchangeAuthTokenDialot(): OpenHumansLoginActivity.ExchangeAuthTokenDialog
+
@ContributesAndroidInjector abstract fun contributesPasswordCheck(): PasswordCheck
- @ContributesAndroidInjector abstract fun contributesRileyLinkStatusGeneral(): RileyLinkStatusGeneralFragment
- @ContributesAndroidInjector abstract fun contributesRileyLinkStatusHistoryFragment(): RileyLinkStatusHistoryFragment
- @ContributesAndroidInjector abstract fun contributesRileyLinkStatusDeviceMedtronic(): RileyLinkStatusDeviceMedtronic
+ @ContributesAndroidInjector
+ abstract fun contributesRileyLinkStatusGeneral(): RileyLinkStatusGeneralFragment
+ @ContributesAndroidInjector
+ abstract fun contributesRileyLinkStatusHistoryFragment(): RileyLinkStatusHistoryFragment
+ @ContributesAndroidInjector
+ abstract fun contributesRileyLinkStatusDeviceMedtronic(): RileyLinkStatusDeviceMedtronic
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/OHUploaderModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/OHUploaderModule.kt
new file mode 100644
index 0000000000..bbf0765f96
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/OHUploaderModule.kt
@@ -0,0 +1,12 @@
+package info.nightscout.androidaps.dependencyInjection
+
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import info.nightscout.androidaps.plugins.general.openhumans.OHUploadWorker
+
+@Module
+@Suppress("unused")
+abstract class OHUploaderModule {
+
+ @ContributesAndroidInjector abstract fun contributesOHUploadWorkerInjector(): OHUploadWorker
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt
index c7033eec2d..75c834977a 100644
--- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt
+++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt
@@ -25,6 +25,7 @@ import info.nightscout.androidaps.plugins.general.dataBroadcaster.DataBroadcastP
import info.nightscout.androidaps.plugins.general.food.FoodPlugin
import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin
import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin
+import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader
import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin
import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
@@ -334,9 +335,15 @@ abstract class PluginsModule {
abstract fun bindRandomBgPlugin(plugin: RandomBgPlugin): PluginBase
@Binds
- @AllConfigs
+ @NotNSClient
@IntoMap
@IntKey(480)
+ abstract fun bindOpenHumansPlugin(plugin: OpenHumansUploader): PluginBase
+
+ @Binds
+ @AllConfigs
+ @IntoMap
+ @IntKey(490)
abstract fun bindConfigBuilderPlugin(plugin: ConfigBuilderPlugin): PluginBase
@Qualifier
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.java
index c6b5afbdeb..a5b4fdff9b 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.java
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.java
@@ -19,6 +19,7 @@ import java.nio.charset.StandardCharsets;
import javax.annotation.Nullable;
import javax.inject.Inject;
+import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader;
import dagger.android.HasAndroidInjector;
import info.nightscout.androidaps.Constants;
import info.nightscout.androidaps.R;
@@ -45,6 +46,7 @@ public class DetermineBasalAdapterAMAJS {
@Inject SP sp;
@Inject ProfileFunction profileFunction;
@Inject TreatmentsPlugin treatmentsPlugin;
+ @Inject OpenHumansUploader openHumansUploader;
private ScriptReader mScriptReader;
@@ -132,7 +134,9 @@ public class DetermineBasalAdapterAMAJS {
String result = NativeJSON.stringify(rhino, scope, jsResult, null, null).toString();
aapsLogger.debug(LTag.APS, "Result: " + result);
try {
- determineBasalResultAMA = new DetermineBasalResultAMA(injector, jsResult, new JSONObject(result));
+ JSONObject resultJson = new JSONObject(result);
+ openHumansUploader.enqueueAMAData(mProfile, mGlucoseStatus, mIobData, mMealData, mCurrentTemp, mAutosensData, resultJson);
+ determineBasalResultAMA = new DetermineBasalResultAMA(injector, jsResult, resultJson);
} catch (JSONException e) {
aapsLogger.error(LTag.APS, "Unhandled exception", e);
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java
index 35ba0d3abe..058ae596bd 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java
@@ -19,6 +19,7 @@ import java.nio.charset.StandardCharsets;
import javax.annotation.Nullable;
import javax.inject.Inject;
+import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader;
import dagger.android.HasAndroidInjector;
import info.nightscout.androidaps.Constants;
import info.nightscout.androidaps.R;
@@ -51,6 +52,7 @@ public class DetermineBasalAdapterSMBJS {
@Inject ProfileFunction profileFunction;
@Inject TreatmentsPlugin treatmentsPlugin;
@Inject ActivePluginProvider activePluginProvider;
+ @Inject OpenHumansUploader openHumansUploader;
private ScriptReader mScriptReader;
@@ -160,7 +162,9 @@ public class DetermineBasalAdapterSMBJS {
String result = NativeJSON.stringify(rhino, scope, jsResult, null, null).toString();
aapsLogger.debug(LTag.APS, "Result: " + result);
try {
- determineBasalResultSMB = new DetermineBasalResultSMB(injector, new JSONObject(result));
+ JSONObject resultJson = new JSONObject(result);
+ openHumansUploader.enqueueSMBData(mProfile, mGlucoseStatus, mIobData, mMealData, mCurrentTemp, mAutosensData, mMicrobolusAllowed, mSMBAlwaysAllowed, resultJson);
+ determineBasalResultSMB = new DetermineBasalResultSMB(injector, resultJson);
} catch (JSONException e) {
aapsLogger.error(LTag.APS, "Unhandled exception", e);
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/AllowedPreferenceKeys.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/AllowedPreferenceKeys.kt
new file mode 100644
index 0000000000..04045becdb
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/AllowedPreferenceKeys.kt
@@ -0,0 +1,209 @@
+package info.nightscout.androidaps.plugins.general.openhumans
+
+import java.util.*
+
+fun String.isAllowedKey() = if (startsWith("ConfigBuilder_")) true else allowedKeys.contains(this.toUpperCase(Locale.ROOT))
+
+private val allowedKeys = """
+ absorption
+ absorption_maxtime
+ openapsama_autosens_period
+ autosens_max
+ autosens_min
+ absorption
+ openapsama_min_5m_carbimpact
+ absorption_cutoff
+ autosens_max
+ autosens_min
+ absorption
+ openapsama_min_5m_carbimpact
+ absorption_cutoff
+ autosens_max
+ autosens_min
+ age
+ location
+ dexcomg5_nsupload
+ dexcomg5_xdripupload
+ dexcom_lognssensorchange
+ danars_bolusspeed
+ danar_useextended
+ danar_visualizeextendedaspercentage"
+ bt_watchdog
+ danar_useextended
+ danar_visualizeextendedaspercentage"
+ bt_watchdog
+ DanaRProfile
+ danarprofile_dia
+ blescannner
+ danars_bolusspeed
+ bt_watchdog
+ danars_bolusspeed
+ bt_watchdog
+ enable_fabric
+ insight_log_reservoir_changes
+ insight_log_tube_changes
+ insight_log_site_changes
+ insight_log_battery_changes
+ insight_log_operating_mode_changes
+ insight_log_alerts
+ insight_enable_tbr_emulation
+ insight_min_recovery_duration
+ insight_max_recovery_duration
+ insight_disconnect_delay
+ insight_log_reservoir_changes
+ insight_log_tube_changes
+ insight_log_site_changes
+ insight_log_battery_changes
+ insight_log_operating_mode_changes
+ insight_log_alerts
+ insight_enable_tbr_emulation
+ insight_min_recovery_duration
+ insight_max_recovery_duration
+ insight_disconnect_delay
+ InsulinOrefFreePeak
+ insulin_oref_peak
+ language
+ aps_general
+ aps_mode
+ loop_openmode_min_change
+ maintenance
+ maintenance_logs_amount
+ pref_medtronic_pump_type
+ pref_medtronic_frequency
+ pref_medtronic_max_basal
+ pref_medtronic_max_bolus
+ pref_medtronic_bolus_delay
+ pref_medtronic_encoding
+ pref_medtronic_battery_type
+ pref_medtronic_bolus_debug
+ ns_logappstartedevent
+ nsalarm_urgent_high
+ nsalarm_high
+ nsalarm_low
+ nsalarm_urgent_low
+ nsalarm_staledata
+ nsalarm_staledatavalue
+ nsalarm_urgent_staledata
+ nsalarm_urgent_staledatavalue
+ ns_wifionly
+ ns_wifi_ssids
+ ns_allowroaming
+ ns_chargingonly
+ ns_autobackfill
+ ns_create_announcements_from_errors
+ nsclient_localbroadcasts
+ ns_upload_only
+ ns_noupload
+ ns_sync_use_absolute
+ openapsama
+ openapsma_max_basal
+ openapsma_max_iob
+ openapsama_useautosens
+ autosens_adjust_targets
+ openapsama_min_5m_carbimpact
+ always_use_shortavg
+ openapsama_max_daily_safety_multiplier
+ openapsama_current_basal_safety_multiplier
+ bolussnooze_dia_divisor
+ openaps
+ openapsma_max_basal
+ openapsma_max_iob
+ always_use_shortavg
+ bolussnooze_dia_divisor
+ openapssmb
+ openapsma_max_basal
+ openapsmb_max_iob
+ openapsama_useautosens
+ use_smb
+ enableSMB_with_COB
+ enableSMB_with_temptarget
+ enableSMB_with_high_temptarget
+ enableSMB_always
+ enableSMB_after_carbs
+ smbmaxminutes
+ use_uam
+ high_temptarget_raises_sensitivity
+ low_temptarget_lowers_sensitivity
+ always_use_shortavg
+ openapsama_max_daily_safety_multiplier
+ openapsama_current_basal_safety_multiplier
+ others
+ eatingsoon_duration
+ eatingsoon_target
+ activity_duration
+ activity_target
+ hypo_duration
+ hypo_target
+ fill_button1
+ fill_button2
+ fill_button3
+ low_mark
+ high_mark
+ short_tabtitles
+ enable_missed_bg_readings
+ missed_bg_readings_threshold
+ enable_pump_unreachable_alert
+ raise_urgent_alarms_as_android_notification
+ keep_screen_on
+ show_treatment_button
+ show_wizard_button
+ show_insulin_button
+ insulin_button_increment_1
+ insulin_button_increment_2
+ insulin_button_increment_3
+ show_carbs_button
+ carbs_button_increment_1
+ carbs_button_increment_2
+ carbs_button_increment_3
+ show_cgm_button
+ show_calibration_button
+ show_notes_entry_dialogs
+ quickwizard
+ key_advancedsettings
+ boluswizard_percentage
+ key_usersuperbolus
+ key_show_statuslights
+ key_show_statuslights_extended
+ key_statuslights_res_warning
+ key_statuslights_res_critical
+ key_statuslights_bat_warning
+ key_statuslights_bat_critical
+ dexcomg5_nsupload
+ dexcomg5_xdripupload
+ treatmentssafety
+ treatmentssafety_maxbolus
+ treatmentssafety_maxcarbs
+ smscommunicator
+ smscommunicator_remotecommandsallowed
+ tidepool_upload_screen
+ tidepool_upload_cgm
+ tidepool_upload_bolus
+ tidepool_upload_bg
+ tidepool_upload_tbr
+ tidepool_upload_profile
+ tidepool_dev_servers
+ tidepool_only_while_charging
+ tidepool_only_while_unmetered
+ virtualpump
+ virtualpump_uploadstatus
+ virtualpump_type
+ wearplugin
+ wearcontrol
+ wearplugin
+ wearwizard_bg
+ wearwizard_tt
+ wearwizard_trend
+ wearwizard_cob
+ wearwizard_bolusiob
+ wearwizard_basaliob
+ wearplugin
+ wear_detailediob
+ wear_detailed_delta
+ wear_showbgi
+ wear_predictions
+ wearplugin
+ wear_notifySMB
+ xdripstatus
+ xdripstatus_detailediob
+ xdripstatus_showbgi
+""".trimIndent().split("\n").filterNot { it.isBlank() }.map { it.toUpperCase() }
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OHUploadWorker.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OHUploadWorker.kt
new file mode 100644
index 0000000000..7ce4d784e2
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OHUploadWorker.kt
@@ -0,0 +1,67 @@
+package info.nightscout.androidaps.plugins.general.openhumans
+
+import android.app.Notification
+import android.content.Context
+import android.net.wifi.WifiManager
+import androidx.core.app.NotificationCompat
+import androidx.work.ForegroundInfo
+import androidx.work.RxWorker
+import androidx.work.WorkerParameters
+import info.nightscout.androidaps.MainApp
+import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader.Companion.NOTIFICATION_CHANNEL
+import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader.Companion.UPLOAD_NOTIFICATION_ID
+import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.utils.sharedPreferences.SP
+import io.reactivex.Single
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+class OHUploadWorker(context: Context, workerParameters: WorkerParameters)
+ : RxWorker(context, workerParameters) {
+
+ @Inject
+ lateinit var sp: SP
+
+ @Inject
+ lateinit var openHumansUploader: OpenHumansUploader
+
+ @Inject
+ lateinit var resourceHelper: ResourceHelper
+
+ override fun createWork(): Single = Single.defer {
+
+ // Here we inject every time we create work
+ // We could build our own WorkerFactory with dagger but this will create conflicts with other Workers
+ // (see https://medium.com/wonderquill/how-to-pass-custom-parameters-to-rxworker-worker-using-dagger-2-f4cfbc9892ba)
+ // This class will be replaced with new DB
+
+ (applicationContext as MainApp).androidInjector().inject(this)
+
+ val wifiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as? WifiManager
+ val wifiOnly = sp.getBoolean("key_oh_wifi_only", true)
+ val isConnectedToWifi = wifiManager?.isWifiEnabled ?: false && wifiManager?.connectionInfo?.networkId != -1
+ if (!wifiOnly || (wifiOnly && isConnectedToWifi)) {
+ setForegroundAsync(createForegroundInfo())
+ openHumansUploader.uploadDataSegmentally()
+ .andThen(Single.just(Result.success()))
+ .onErrorResumeNext { Single.just(Result.retry()) }
+ } else {
+ Single.just(Result.retry())
+ }
+ }
+
+ private fun createForegroundInfo(): ForegroundInfo {
+ val title = resourceHelper.gs(info.nightscout.androidaps.R.string.open_humans)
+
+ val notification: Notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL)
+ .setContentTitle(title)
+ .setTicker(title)
+ .setContentText(resourceHelper.gs(info.nightscout.androidaps.R.string.your_phone_is_upload_data))
+ .setSmallIcon(info.nightscout.androidaps.R.drawable.notif_icon)
+ .setOngoing(true)
+ .setProgress(0, 0 , true)
+ .build()
+ return ForegroundInfo(UPLOAD_NOTIFICATION_ID, notification)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OpenHumansAPI.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OpenHumansAPI.kt
new file mode 100644
index 0000000000..70dc24f533
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OpenHumansAPI.kt
@@ -0,0 +1,191 @@
+package info.nightscout.androidaps.plugins.general.openhumans
+
+import android.annotation.SuppressLint
+import android.util.Base64
+import io.reactivex.Completable
+import io.reactivex.Single
+import io.reactivex.disposables.Disposables
+import okhttp3.*
+import okio.BufferedSink
+import org.json.JSONArray
+import org.json.JSONObject
+import java.io.IOException
+import java.text.SimpleDateFormat
+import java.util.*
+
+class OpenHumansAPI(
+ private val baseUrl: String,
+ clientId: String,
+ clientSecret: String,
+ private val redirectUri: String
+) {
+
+ private val authHeader = "Basic " + Base64.encodeToString("$clientId:$clientSecret".toByteArray(), Base64.NO_WRAP)
+ private val client = OkHttpClient()
+
+ fun exchangeAuthToken(code: String): Single = sendTokenRequest(FormBody.Builder()
+ .add("grant_type", "authorization_code")
+ .add("redirect_uri", redirectUri)
+ .add("code", code)
+ .build())
+
+ fun refreshAccessToken(refreshToken: String): Single = sendTokenRequest(FormBody.Builder()
+ .add("grant_type", "refresh_token")
+ .add("redirect_uri", redirectUri)
+ .add("refresh_token", refreshToken)
+ .build())
+
+ private fun sendTokenRequest(body: FormBody) = Request.Builder()
+ .url("$baseUrl/oauth2/token/")
+ .addHeader("Authorization", authHeader)
+ .post(body)
+ .build()
+ .toSingle()
+ .map { response ->
+ response.use { _ ->
+ val body = response.body
+ val jsonObject = body?.let { JSONObject(it.string()) }
+ if (!response.isSuccessful) throw OHHttpException(response.code, response.message, jsonObject?.getString("error"))
+ if (jsonObject == null) throw OHHttpException(response.code, response.message, "No body")
+ if (!jsonObject.has("expires_in")) throw OHMissingFieldException("expires_in")
+ OAuthTokens(
+ accessToken = jsonObject.getString("access_token")
+ ?: throw OHMissingFieldException("access_token"),
+ refreshToken = jsonObject.getString("refresh_token")
+ ?: throw OHMissingFieldException("refresh_token"),
+ expiresAt = response.sentRequestAtMillis + jsonObject.getInt("expires_in") * 1000L
+ )
+ }
+ }
+
+ fun getProjectMemberId(accessToken: String): Single = Request.Builder()
+ .url("$baseUrl/api/direct-sharing/project/exchange-member/?access_token=$accessToken")
+ .get()
+ .build()
+ .toSingle()
+ .map {
+ it.jsonBody.getString("project_member_id")
+ ?: throw OHMissingFieldException("project_member_id")
+ }
+
+ fun prepareFileUpload(accessToken: String, fileName: String, metadata: FileMetadata): Single = Request.Builder()
+ .url("$baseUrl/api/direct-sharing/project/files/upload/direct/?access_token=$accessToken")
+ .post(FormBody.Builder()
+ .add("filename", fileName)
+ .add("metadata", metadata.toJSON().toString())
+ .build())
+ .build()
+ .toSingle()
+ .map {
+ val json = it.jsonBody
+ PreparedUpload(
+ fileId = json.getString("id") ?: throw OHMissingFieldException("id"),
+ uploadURL = json.getString("url") ?: throw OHMissingFieldException("url")
+ )
+ }
+
+ fun uploadFile(url: String, content: ByteArray): Completable = Request.Builder()
+ .url(url)
+ .put(object : RequestBody() {
+ override fun contentType(): MediaType? = null
+
+ override fun contentLength() = content.size.toLong()
+
+ override fun writeTo(sink: BufferedSink) {
+ sink.write(content)
+ }
+ })
+ .build()
+ .toSingle()
+ .doOnSuccess { response ->
+ response.use { _ ->
+ if (!response.isSuccessful) throw OHHttpException(response.code, response.message, null)
+ }
+ }
+ .ignoreElement()
+
+ fun completeFileUpload(accessToken: String, fileId: String): Completable = Request.Builder()
+ .url("$baseUrl/api/direct-sharing/project/files/upload/complete/?access_token=$accessToken")
+ .post(FormBody.Builder()
+ .add("file_id", fileId)
+ .build())
+ .build()
+ .toSingle()
+ .doOnSuccess { it.jsonBody }
+ .ignoreElement()
+
+ private fun Request.toSingle() = Single.create {
+ val call = client.newCall(this)
+ call.enqueue(object : Callback {
+ override fun onFailure(call: Call, e: IOException) {
+ it.tryOnError(e)
+ }
+
+ override fun onResponse(call: Call, response: Response) {
+ it.onSuccess(response)
+ }
+ })
+ it.setDisposable(Disposables.fromRunnable { call.cancel() })
+ }
+
+ private val Response.jsonBody
+ get() = use { _ ->
+ val jsonObject = body?.let { JSONObject(it.string()) }
+ ?: throw OHHttpException(code, message, null)
+ if (!isSuccessful) throw OHHttpException(code, message, jsonObject.getString("detail"))
+ jsonObject
+ }
+
+ data class OAuthTokens(
+ val accessToken: String,
+ val refreshToken: String,
+ val expiresAt: Long
+ )
+
+ data class FileMetadata(
+ val tags: List,
+ val description: String,
+ val md5: String? = null,
+ val creationDate: Long? = null,
+ val startDate: Long? = null,
+ val endDate: Long? = null
+ ) {
+
+ fun toJSON(): JSONObject {
+ val jsonObject = JSONObject()
+ jsonObject.put("tags", JSONArray().apply { tags.forEach { put(it) } })
+ jsonObject.put("description", description)
+ jsonObject.put("md5", md5)
+ creationDate?.let { jsonObject.put("creation_date", iso8601DateFormatter.format(Date(it))) }
+ startDate?.let { jsonObject.put("start_date", iso8601DateFormatter.format(Date(it))) }
+ endDate?.let { jsonObject.put("end_date", iso8601DateFormatter.format(Date(it))) }
+ return jsonObject
+ }
+ }
+
+ data class PreparedUpload(
+ val fileId: String,
+ val uploadURL: String
+ )
+
+ data class OHHttpException(
+ val code: Int,
+ val meaning: String,
+ val detail: String?
+ ) : RuntimeException() {
+
+ override val message: String get() = toString()
+ }
+
+ data class OHMissingFieldException(
+ val name: String
+ ) : RuntimeException() {
+
+ override val message: String get() = toString()
+ }
+
+ companion object {
+ @SuppressLint("SimpleDateFormat")
+ private val iso8601DateFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OpenHumansFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OpenHumansFragment.kt
new file mode 100644
index 0000000000..e22130d998
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OpenHumansFragment.kt
@@ -0,0 +1,120 @@
+package info.nightscout.androidaps.plugins.general.openhumans
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.TextView
+import androidx.lifecycle.Observer
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
+import dagger.android.support.DaggerFragment
+import info.nightscout.androidaps.MainApp
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.events.Event
+import info.nightscout.androidaps.plugins.bus.RxBusWrapper
+import info.nightscout.androidaps.utils.alertDialogs.OKDialog
+import info.nightscout.androidaps.utils.extensions.plusAssign
+import info.nightscout.androidaps.utils.resources.ResourceHelper
+import io.reactivex.BackpressureStrategy
+import io.reactivex.Single
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.schedulers.Schedulers
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+class OpenHumansFragment : DaggerFragment() {
+
+ private var viewsCreated = false
+ private var login: Button? = null
+ private var logout: Button? = null
+ private var memberId: TextView? = null
+ private var queueSize: TextView? = null
+ private var workerState: TextView? = null
+ private var queueSizeValue = 0L
+ private val compositeDisposable = CompositeDisposable()
+
+ @Inject
+ lateinit var rxBus: RxBusWrapper
+
+ @Inject
+ lateinit var openHumansUploader: OpenHumansUploader
+
+ @Inject
+ lateinit var resourceHelper: ResourceHelper
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ compositeDisposable += Single.fromCallable { MainApp.getDbHelper().ohQueueSize }
+ .subscribeOn(Schedulers.io())
+ .repeatWhen { rxBus.toObservable(UpdateViewEvent::class.java)
+ .cast(Any::class.java)
+ .mergeWith(rxBus.toObservable(UpdateQueueEvent::class.java)
+ .throttleLatest(5, TimeUnit.SECONDS))
+ .toFlowable(BackpressureStrategy.LATEST) }
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({
+ queueSizeValue = it
+ updateGUI()
+ }, {})
+ context?.applicationContext?.let { appContext ->
+ WorkManager.getInstance(appContext).getWorkInfosForUniqueWorkLiveData(OpenHumansUploader.WORK_NAME).observe(this, Observer> {
+ val workInfo = it.lastOrNull()
+ if (workInfo == null) {
+ workerState?.visibility = View.GONE
+ } else {
+ workerState?.visibility = View.VISIBLE
+ workerState?.text = getString(R.string.worker_state, workInfo.state.toString())
+ }
+ })
+ }
+ }
+
+ override fun onDestroy() {
+ compositeDisposable.clear()
+ super.onDestroy()
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ val view = inflater.inflate(R.layout.fragment_open_humans, container, false)
+ login = view.findViewById(R.id.login)
+ logout = view.findViewById(R.id.logout)
+ memberId = view.findViewById(R.id.member_id)
+ queueSize = view.findViewById(R.id.queue_size)
+ workerState = view.findViewById(R.id.worker_state)
+ login!!.setOnClickListener { startActivity(Intent(context, OpenHumansLoginActivity::class.java)) }
+ logout!!.setOnClickListener {
+ activity?.let { activity -> OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.oh_logout_confirmation), Runnable { openHumansUploader.logout() }) }
+ }
+ viewsCreated = true
+ updateGUI()
+ return view
+ }
+
+ override fun onDestroyView() {
+ viewsCreated = false
+ login = null
+ logout = null
+ memberId = null
+ queueSize = null
+ super.onDestroyView()
+ }
+
+ fun updateGUI() {
+ if (viewsCreated) {
+ queueSize!!.text = getString(R.string.queue_size, queueSizeValue)
+ val projectMemberId = openHumansUploader.projectMemberId
+ memberId!!.text = getString(R.string.project_member_id, projectMemberId
+ ?: getString(R.string.not_logged_in))
+ login!!.visibility = if (projectMemberId == null) View.VISIBLE else View.GONE
+ logout!!.visibility = if (projectMemberId != null) View.VISIBLE else View.GONE
+ }
+ }
+
+ object UpdateViewEvent : Event()
+
+ object UpdateQueueEvent : Event()
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OpenHumansLoginActivity.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OpenHumansLoginActivity.kt
new file mode 100644
index 0000000000..9220457bbb
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OpenHumansLoginActivity.kt
@@ -0,0 +1,138 @@
+package info.nightscout.androidaps.plugins.general.openhumans
+
+import android.app.Activity
+import android.app.Dialog
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.widget.Button
+import android.widget.CheckBox
+import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
+import androidx.browser.customtabs.CustomTabsIntent
+import androidx.fragment.app.DialogFragment
+import dagger.android.support.DaggerDialogFragment
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.activities.NoSplashAppCompatActivity
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
+import javax.inject.Inject
+
+class OpenHumansLoginActivity : NoSplashAppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_open_humans_login)
+ val button = findViewById