diff --git a/app/build.gradle b/app/build.gradle
index 893f13632c..5a2f94c52d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -302,11 +302,11 @@ dependencies {
// new for tidepool
- implementation 'com.squareup.okhttp3:okhttp:4.2.2'
- implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2'
- implementation "com.squareup.retrofit2:retrofit:2.6.2"
- implementation "com.squareup.retrofit2:adapter-rxjava2:2.6.2"
- implementation "com.squareup.retrofit2:converter-gson:2.6.2"
+ implementation 'com.squareup.okhttp3:okhttp:4.5.0'
+ implementation 'com.squareup.okhttp3:logging-interceptor:4.5.0'
+ implementation "com.squareup.retrofit2:retrofit:2.8.1"
+ implementation "com.squareup.retrofit2:adapter-rxjava2:2.8.1"
+ implementation "com.squareup.retrofit2:converter-gson:2.8.1"
// Phone checker
implementation 'com.scottyab:rootbeer-lib:0.0.7'
@@ -316,6 +316,15 @@ dependencies {
androidTestImplementation 'androidx.test:rules:1.3.0-alpha03'
androidTestImplementation 'com.google.code.findbugs:jsr305:3.0.2'
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 e688720d78..1df838eab5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -282,6 +282,19 @@
android:label="@string/title_activity_rileylink_settings"
android:theme="@style/Theme.AppCompat.NoTitle" />
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java
index b1db737e62..e24d50c841 100644
--- a/app/src/main/java/info/nightscout/androidaps/MainApp.java
+++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java
@@ -54,6 +54,7 @@ import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils;
import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin;
import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin;
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload;
+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;
@@ -224,6 +225,7 @@ public class MainApp extends Application {
pluginsList.add(StatuslinePlugin.initPlugin(this));
pluginsList.add(PersistentNotificationPlugin.getPlugin());
pluginsList.add(NSClientPlugin.getPlugin());
+ pluginsList.add(OpenHumansUploader.INSTANCE);
// if (engineeringMode) pluginsList.add(TidepoolPlugin.INSTANCE);
pluginsList.add(MaintenancePlugin.initPlugin(this));
pluginsList.add(AutomationPlugin.INSTANCE);
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 073f21e708..25c6939bbf 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;
@@ -23,6 +24,7 @@ import org.slf4j.LoggerFactory;
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;
@@ -50,6 +52,7 @@ import info.nightscout.androidaps.interfaces.ProfileInterface;
import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload;
+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.danaR.activities.DanaRNSHistorySync;
@@ -86,8 +89,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 = 11;
+ private static final int DATABASE_VERSION = 12;
public static Long earliestDataChange = null;
@@ -135,6 +139,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
TableUtils.createTableIfNotExists(connectionSource, InsightHistoryOffset.class);
TableUtils.createTableIfNotExists(connectionSource, InsightBolusID.class);
TableUtils.createTableIfNotExists(connectionSource, InsightPumpID.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() + " " +
@@ -174,6 +179,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) {
log.error("Can't drop databases", e);
throw new RuntimeException(e);
@@ -354,6 +360,10 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return getDao(InsightHistoryOffset.class);
}
+ private Dao getDaoOpenHumansQueue() throws SQLException {
+ return getDao(OHQueueItem.class);
+ }
+
public static long roundDateToSec(long date) {
long rounded = date - date % 1000;
if (rounded != date)
@@ -369,6 +379,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
BgReading old = getDaoBgReadings().queryForId(bgReading.date);
if (old == null) {
getDaoBgReadings().create(bgReading);
+ OpenHumansUploader.INSTANCE.queueBGReading(bgReading);
if (L.isEnabled(L.DATABASE))
log.debug("BG: New record from: " + from + " " + bgReading.toString());
scheduleBgChange(bgReading);
@@ -379,6 +390,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
log.debug("BG: Similiar found: " + old.toString());
old.copyFrom(bgReading);
getDaoBgReadings().update(old);
+ OpenHumansUploader.INSTANCE.queueBGReading(old);
if (L.isEnabled(L.DATABASE))
log.debug("BG: Updating record from: " + from + " New data: " + old.toString());
scheduleBgChange(bgReading);
@@ -394,6 +406,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
bgReading.date = roundDateToSec(bgReading.date);
try {
getDaoBgReadings().update(bgReading);
+ OpenHumansUploader.INSTANCE.queueBGReading(bgReading);
} catch (SQLException e) {
log.error("Unhandled exception", e);
}
@@ -503,11 +516,21 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return new ArrayList();
}
+ public List getAllBgReadings() {
+ try {
+ return getDaoBgReadings().queryForAll();
+ } catch (SQLException e) {
+ log.error("Unhandled exception", e);
+ }
+ return Collections.emptyList();
+ }
+
// ------------------- TDD handling -----------------------
public void createOrUpdateTDD(TDD tdd) {
try {
Dao dao = getDaoTDD();
dao.createOrUpdate(tdd);
+ OpenHumansUploader.INSTANCE.queueTotalDailyDose(tdd);
} catch (SQLException e) {
ToastUtils.showToastInUiThread(MainApp.instance(), "createOrUpdate-Exception");
log.error("Unhandled exception", e);
@@ -529,6 +552,15 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return tddList;
}
+ public List getAllTDDs() {
+ try {
+ return getDaoTDD().queryForAll();
+ } catch (SQLException e) {
+ log.error("Unhandled exception", e);
+ }
+ return Collections.emptyList();
+ }
+
public List getTDDsForLastXDays(int days) {
List tddList;
GregorianCalendar gc = new GregorianCalendar();
@@ -636,6 +668,15 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return new ArrayList();
}
+ public List getAllTempTargets() {
+ try {
+ return getDaoTempTargets().queryForAll();
+ } catch (SQLException e) {
+ log.error("Unhandled exception", e);
+ }
+ return Collections.emptyList();
+ }
+
public List getTemptargetsDataFromTime(long from, long to, boolean ascending) {
try {
Dao daoTempTargets = getDaoTempTargets();
@@ -665,6 +706,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.INSTANCE.queueTempTarget(old);
if (L.isEnabled(L.DATABASE))
log.debug("TEMPTARGET: Updating record by date from: " + Source.getString(tempTarget.source) + " " + old.toString());
scheduleTemporaryTargetChange();
@@ -685,6 +727,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.INSTANCE.queueTempTarget(old);
if (L.isEnabled(L.DATABASE))
log.debug("TEMPTARGET: Updating record by _id from: " + Source.getString(tempTarget.source) + " " + old.toString());
scheduleTemporaryTargetChange();
@@ -700,6 +743,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
}
if (tempTarget.source == Source.USER) {
getDaoTempTargets().create(tempTarget);
+ OpenHumansUploader.INSTANCE.queueTempTarget(tempTarget);
if (L.isEnabled(L.DATABASE))
log.debug("TEMPTARGET: New record from: " + Source.getString(tempTarget.source) + " " + tempTarget.toString());
scheduleTemporaryTargetChange();
@@ -714,6 +758,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
public void delete(TempTarget tempTarget) {
try {
getDaoTempTargets().delete(tempTarget);
+ OpenHumansUploader.INSTANCE.queueTempTarget(tempTarget, true);
scheduleTemporaryTargetChange();
} catch (SQLException e) {
log.error("Unhandled exception", e);
@@ -896,6 +941,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
log.debug("TEMPBASAL: Updated record with Pump Data : " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
getDaoTemporaryBasal().update(old);
+ OpenHumansUploader.INSTANCE.queueTemporaryBasal(old);
updateEarliestDataChange(tempBasal.date);
scheduleTemporaryBasalChange();
@@ -904,6 +950,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
}
getDaoTemporaryBasal().create(tempBasal);
+ OpenHumansUploader.INSTANCE.queueTemporaryBasal(tempBasal);
if (L.isEnabled(L.DATABASE))
log.debug("TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
updateEarliestDataChange(tempBasal.date);
@@ -922,6 +969,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.INSTANCE.queueTemporaryBasal(old);
if (L.isEnabled(L.DATABASE))
log.debug("TEMPBASAL: Updating record by date from: " + Source.getString(tempBasal.source) + " " + old.toString());
updateEarliestDataChange(oldDate);
@@ -945,6 +993,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.INSTANCE.queueTemporaryBasal(old);
if (L.isEnabled(L.DATABASE))
log.debug("TEMPBASAL: Updating record by _id from: " + Source.getString(tempBasal.source) + " " + old.toString());
updateEarliestDataChange(oldDate);
@@ -955,6 +1004,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
}
}
getDaoTemporaryBasal().create(tempBasal);
+ OpenHumansUploader.INSTANCE.queueTemporaryBasal(tempBasal);
if (L.isEnabled(L.DATABASE))
log.debug("TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
updateEarliestDataChange(tempBasal.date);
@@ -963,6 +1013,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
}
if (tempBasal.source == Source.USER) {
getDaoTemporaryBasal().create(tempBasal);
+ OpenHumansUploader.INSTANCE.queueTemporaryBasal(tempBasal);
if (L.isEnabled(L.DATABASE))
log.debug("TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
updateEarliestDataChange(tempBasal.date);
@@ -978,6 +1029,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
public void delete(TemporaryBasal tempBasal) {
try {
getDaoTemporaryBasal().delete(tempBasal);
+ OpenHumansUploader.INSTANCE.queueTemporaryBasal(tempBasal, true);
updateEarliestDataChange(tempBasal.date);
} catch (SQLException e) {
log.error("Unhandled exception", e);
@@ -985,6 +1037,15 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
scheduleTemporaryBasalChange();
}
+ public List getAllTemporaryBasals() {
+ try {
+ return getDaoTemporaryBasal().queryForAll();
+ } catch (SQLException e) {
+ log.error("Unhandled exception", e);
+ }
+ return Collections.emptyList();
+ }
+
public List getTemporaryBasalsDataFromTime(long mills, boolean ascending) {
try {
List tempbasals;
@@ -1183,6 +1244,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
// and then is record updated with pumpId
if (extendedBolus.pumpId == 0) {
getDaoExtendedBolus().createOrUpdate(extendedBolus);
+ OpenHumansUploader.INSTANCE.queueExtendedBolus(extendedBolus);
} else {
QueryBuilder queryBuilder = getDaoExtendedBolus().queryBuilder();
Where where = queryBuilder.where();
@@ -1194,6 +1256,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return false;
}
getDaoExtendedBolus().createOrUpdate(extendedBolus);
+ OpenHumansUploader.INSTANCE.queueExtendedBolus(extendedBolus);
}
if (L.isEnabled(L.DATABASE))
log.debug("EXTENDEDBOLUS: New record from: " + Source.getString(extendedBolus.source) + " " + extendedBolus.log());
@@ -1209,6 +1272,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
getDaoExtendedBolus().delete(old); // need to delete/create because date may change too
old.copyFrom(extendedBolus);
getDaoExtendedBolus().create(old);
+ OpenHumansUploader.INSTANCE.queueExtendedBolus(old);
if (L.isEnabled(L.DATABASE))
log.debug("EXTENDEDBOLUS: Updating record by date from: " + Source.getString(extendedBolus.source) + " " + old.log());
updateEarliestDataChange(oldDate);
@@ -1232,6 +1296,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
getDaoExtendedBolus().delete(old); // need to delete/create because date may change too
old.copyFrom(extendedBolus);
getDaoExtendedBolus().create(old);
+ OpenHumansUploader.INSTANCE.queueExtendedBolus(old);
if (L.isEnabled(L.DATABASE))
log.debug("EXTENDEDBOLUS: Updating record by _id from: " + Source.getString(extendedBolus.source) + " " + old.log());
updateEarliestDataChange(oldDate);
@@ -1242,6 +1307,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
}
}
getDaoExtendedBolus().create(extendedBolus);
+ OpenHumansUploader.INSTANCE.queueExtendedBolus(extendedBolus);
if (L.isEnabled(L.DATABASE))
log.debug("EXTENDEDBOLUS: New record from: " + Source.getString(extendedBolus.source) + " " + extendedBolus.log());
updateEarliestDataChange(extendedBolus.date);
@@ -1250,6 +1316,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
}
if (extendedBolus.source == Source.USER) {
getDaoExtendedBolus().create(extendedBolus);
+ OpenHumansUploader.INSTANCE.queueExtendedBolus(extendedBolus);
if (L.isEnabled(L.DATABASE))
log.debug("EXTENDEDBOLUS: New record from: " + Source.getString(extendedBolus.source) + " " + extendedBolus.log());
updateEarliestDataChange(extendedBolus.date);
@@ -1262,6 +1329,15 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return false;
}
+ public List getAllExtendedBoluses() {
+ try {
+ return getDaoExtendedBolus().queryForAll();
+ } catch (SQLException e) {
+ log.error("Unhandled exception", e);
+ }
+ return Collections.emptyList();
+ }
+
public ExtendedBolus getExtendedBolusByPumpId(long pumpId) {
try {
return getDaoExtendedBolus().queryBuilder()
@@ -1276,6 +1352,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
public void delete(ExtendedBolus extendedBolus) {
try {
getDaoExtendedBolus().delete(extendedBolus);
+ OpenHumansUploader.INSTANCE.queueExtendedBolus(extendedBolus, true);
updateEarliestDataChange(extendedBolus.date);
} catch (SQLException e) {
log.error("Unhandled exception", e);
@@ -1382,6 +1459,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
careportalEvent.date = careportalEvent.date - careportalEvent.date % 1000;
try {
getDaoCareportalEvents().createOrUpdate(careportalEvent);
+ OpenHumansUploader.INSTANCE.queueCareportalEvent(careportalEvent);
} catch (SQLException e) {
log.error("Unhandled exception", e);
}
@@ -1391,6 +1469,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
public void delete(CareportalEvent careportalEvent) {
try {
getDaoCareportalEvents().delete(careportalEvent);
+ OpenHumansUploader.INSTANCE.queueCareportalEvent(careportalEvent, true);
} catch (SQLException e) {
log.error("Unhandled exception", e);
}
@@ -1406,6 +1485,15 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return null;
}
+ public List getAllCareportalEvents() {
+ try {
+ return getDaoCareportalEvents().queryForAll();
+ } catch (SQLException e) {
+ log.error("Unhandled exception", e);
+ }
+ return Collections.emptyList();
+ }
+
@Nullable
public CareportalEvent getLastCareportalEvent(String event) {
try {
@@ -1608,6 +1696,15 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return new ArrayList<>();
}
+ public List getAllProfileSwitches() {
+ try {
+ return getDaoProfileSwitch().queryForAll();
+ } catch (SQLException e) {
+ log.error("Unhandled exception", e);
+ }
+ return Collections.emptyList();
+ }
+
@Nullable
private ProfileSwitch getLastProfileSwitchWithoutDuration() {
try {
@@ -1679,6 +1776,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
profileSwitch.profileName = old.profileName; // preserver profileName to prevent multiple CPP extension
getDaoProfileSwitch().delete(old); // need to delete/create because date may change too
getDaoProfileSwitch().create(profileSwitch);
+ OpenHumansUploader.INSTANCE.queueProfileSwitch(profileSwitch);
if (L.isEnabled(L.DATABASE))
log.debug("PROFILESWITCH: Updating record by date from: " + Source.getString(profileSwitch.source) + " " + old.toString());
scheduleProfileSwitchChange();
@@ -1699,6 +1797,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
getDaoProfileSwitch().delete(old); // need to delete/create because date may change too
old.copyFrom(profileSwitch);
getDaoProfileSwitch().create(old);
+ OpenHumansUploader.INSTANCE.queueProfileSwitch(old);
if (L.isEnabled(L.DATABASE))
log.debug("PROFILESWITCH: Updating record by _id from: " + Source.getString(profileSwitch.source) + " " + old.toString());
scheduleProfileSwitchChange();
@@ -1709,6 +1808,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
// look for already added percentage from NS
profileSwitch.profileName = PercentageSplitter.pureName(profileSwitch.profileName);
getDaoProfileSwitch().create(profileSwitch);
+ OpenHumansUploader.INSTANCE.queueProfileSwitch(profileSwitch);
if (L.isEnabled(L.DATABASE))
log.debug("PROFILESWITCH: New record from: " + Source.getString(profileSwitch.source) + " " + profileSwitch.toString());
scheduleProfileSwitchChange();
@@ -1716,6 +1816,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
}
if (profileSwitch.source == Source.USER) {
getDaoProfileSwitch().create(profileSwitch);
+ OpenHumansUploader.INSTANCE.queueProfileSwitch(profileSwitch);
if (L.isEnabled(L.DATABASE))
log.debug("PROFILESWITCH: New record from: " + Source.getString(profileSwitch.source) + " " + profileSwitch.toString());
scheduleProfileSwitchChange();
@@ -1730,6 +1831,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
public void delete(ProfileSwitch profileSwitch) {
try {
getDaoProfileSwitch().delete(profileSwitch);
+ OpenHumansUploader.INSTANCE.queueProfileSwitch(profileSwitch, true);
scheduleProfileSwitchChange();
} catch (SQLException e) {
log.error("Unhandled exception", e);
@@ -1911,5 +2013,40 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return null;
}
- // ---------------- Food handling ---------------
+ // ---------------- Open Humans Queue handling ---------------
+
+ public void clearOpenHumansQueue() {
+ try {
+ TableUtils.clearTable(connectionSource, OHQueueItem.class);
+ } catch (SQLException e) {
+ log.error("Unhandled exception", e);
+ }
+ }
+
+ public void createOrUpdate(OHQueueItem item) {
+ try {
+ getDaoOpenHumansQueue().createOrUpdate(item);
+ } catch (SQLException e) {
+ log.error("Unhandled exception", e);
+ }
+ }
+
+ public void removeAllOHQueueItemsWithIdSmallerThan(long id) {
+ try {
+ DeleteBuilder deleteBuilder = getDaoOpenHumansQueue().deleteBuilder();
+ deleteBuilder.where().le("id", id);
+ deleteBuilder.delete();
+ } catch (SQLException e) {
+ log.error("Unhandled exception", e);
+ }
+ }
+
+ public List getAllOHQueueItems() {
+ try {
+ return getDaoOpenHumansQueue().queryForAll();
+ } catch (SQLException e) {
+ log.error("Unhandled exception", e);
+ }
+ return Collections.emptyList();
+ }
}
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/logging/L.java b/app/src/main/java/info/nightscout/androidaps/logging/L.java
index 25b6750844..bc253d6ba2 100644
--- a/app/src/main/java/info/nightscout/androidaps/logging/L.java
+++ b/app/src/main/java/info/nightscout/androidaps/logging/L.java
@@ -100,6 +100,7 @@ public class L {
public static final String UI = "UI";
public static final String LOCATION = "LOCATION";
public static final String SMS = "SMS";
+ public static final String OPENHUMANS = "OPENHUMANS";
private static void initialize() {
logElements = new ArrayList<>();
@@ -128,6 +129,7 @@ public class L {
logElements.add(new LogElement(PUMPQUEUE, true));
logElements.add(new LogElement(SMS, true));
logElements.add(new LogElement(UI, true));
+ logElements.add(new LogElement(OPENHUMANS, true));
}
}
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..44f6b70b49
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OHUploadWorker.kt
@@ -0,0 +1,24 @@
+package info.nightscout.androidaps.plugins.general.openhumans
+
+import android.content.Context
+import android.net.wifi.WifiManager
+import androidx.work.RxWorker
+import androidx.work.WorkerParameters
+import info.nightscout.androidaps.utils.SP
+import io.reactivex.Single
+
+class OHUploadWorker(
+ val context: Context,
+ workerParameters: WorkerParameters
+) : RxWorker(context, workerParameters) {
+
+ override fun createWork() = Single.defer {
+ val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
+ if (SP.getBoolean("key_oh_wifi_only", true) && wifiManager.isWifiEnabled && wifiManager.connectionInfo.networkId != -1)
+ OpenHumansUploader.uploadData()
+ .andThen(Single.just(Result.success()))
+ .onErrorResumeNext { Single.just(Result.retry()) }
+ else Single.just(Result.retry())
+ }
+
+}
\ 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..235ee64174
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OpenHumansAPI.kt
@@ -0,0 +1,183 @@
+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 org.json.JSONArray
+import org.json.JSONObject
+import java.io.IOException
+import java.text.SimpleDateFormat
+import java.util.*
+import okhttp3.RequestBody.Companion
+import okio.BufferedSink
+
+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) = sendTokenRequest(FormBody.Builder()
+ .add("grant_type", "authorization_code")
+ .add("redirect_uri", redirectUri)
+ .add("code", code)
+ .build())
+
+ fun refreshAccessToken(refreshToken: String) = sendTokenRequest(FormBody.Builder()
+ .add("grant_type", "refresh_token")
+ .add("redirect_uri", redirectUri)
+ .add("code", 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) = 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) = 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) = 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) = 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..89ae94a813
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OpenHumansFragment.kt
@@ -0,0 +1,23 @@
+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 androidx.fragment.app.Fragment
+import info.nightscout.androidaps.R
+
+class OpenHumansFragment : Fragment() {
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ val view = inflater.inflate(R.layout.fragment_open_humans, container, false)
+ val button = view.findViewById