Merge remote-tracking branch 'origin/openhumans_old_db' into HEAD
This commit is contained in:
commit
2c2e01dee6
25 changed files with 1759 additions and 16 deletions
|
@ -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'
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -260,6 +260,19 @@
|
|||
android:exported="true" />
|
||||
|
||||
<activity android:name=".plugins.pump.medtronic.dialog.MedtronicHistoryActivity" />
|
||||
<activity android:name=".plugins.general.openhumans.OpenHumansLoginActivity"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="setup-openhumans"
|
||||
android:scheme="androidaps" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
|
||||
|
||||
|
|
|
@ -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<Any>
|
||||
|
@ -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()
|
||||
|
|
|
@ -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<OHQueueItem, Long> 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<BgReading>();
|
||||
}
|
||||
|
||||
public List<BgReading> 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<TDD, String> 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<TDD> getAllTDDs() {
|
||||
try {
|
||||
return getDaoTDD().queryForAll();
|
||||
} catch (SQLException e) {
|
||||
aapsLogger.error("Unhandled exception", e);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public List<TDD> getTDDsForLastXDays(int days) {
|
||||
List<TDD> tddList;
|
||||
GregorianCalendar gc = new GregorianCalendar();
|
||||
|
@ -628,6 +662,15 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
|
|||
return new ArrayList<TempTarget>();
|
||||
}
|
||||
|
||||
public List<TempTarget> getAllTempTargets() {
|
||||
try {
|
||||
return getDaoTempTargets().queryForAll();
|
||||
} catch (SQLException e) {
|
||||
aapsLogger.error("Unhandled exception", e);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public List<TempTarget> getTemptargetsDataFromTime(long from, long to, boolean ascending) {
|
||||
try {
|
||||
Dao<TempTarget, Long> 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<TemporaryBasal> getAllTemporaryBasals() {
|
||||
try {
|
||||
return getDaoTemporaryBasal().queryForAll();
|
||||
} catch (SQLException e) {
|
||||
aapsLogger.error("Unhandled exception", e);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public List<TemporaryBasal> getTemporaryBasalsDataFromTime(long mills, boolean ascending) {
|
||||
try {
|
||||
List<TemporaryBasal> 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<ExtendedBolus, Long> 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<ExtendedBolus> 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<CareportalEvent> 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<ProfileSwitch> 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,65 @@ 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<OHQueueItem, Long> deleteBuilder = getDaoOpenHumansQueue().deleteBuilder();
|
||||
deleteBuilder.where().le("id", id);
|
||||
deleteBuilder.delete();
|
||||
} catch (SQLException e) {
|
||||
aapsLogger.error("Unhandled exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<OHQueueItem> getAllOHQueueItems() {
|
||||
try {
|
||||
return getDaoOpenHumansQueue().queryForAll();
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = ""
|
||||
)
|
|
@ -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
|
||||
|
||||
}
|
|
@ -37,7 +37,8 @@ import javax.inject.Singleton
|
|||
CoreModule::class,
|
||||
DanaModule::class,
|
||||
DanaRModule::class,
|
||||
DanaRSModule::class
|
||||
DanaRSModule::class,
|
||||
OHUploaderModule::class
|
||||
]
|
||||
)
|
||||
interface AppComponent : AndroidInjector<MainApp> {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
@APS
|
||||
@IntoMap
|
||||
@IntKey(480)
|
||||
abstract fun bindOpenHumansPlugin(plugin: OpenHumansUploader): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
@IntKey(490)
|
||||
abstract fun bindConfigBuilderPlugin(plugin: ConfigBuilderPlugin): PluginBase
|
||||
|
||||
@Qualifier
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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() }
|
|
@ -0,0 +1,43 @@
|
|||
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.MainApp
|
||||
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||
import io.reactivex.Single
|
||||
import javax.inject.Inject
|
||||
|
||||
//TODO OH: make injectable
|
||||
class OHUploadWorker(context: Context, workerParameters: WorkerParameters)
|
||||
: RxWorker(context, workerParameters) {
|
||||
|
||||
@Inject
|
||||
lateinit var sp: SP
|
||||
|
||||
@Inject
|
||||
lateinit var openHumansUploader: OpenHumansUploader
|
||||
|
||||
override fun createWork(): Single<Result> = 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)) {
|
||||
openHumansUploader.uploadData()
|
||||
.andThen(Single.just(Result.success()))
|
||||
.onErrorResumeNext { Single.just(Result.retry()) }
|
||||
} else {
|
||||
Single.just(Result.retry())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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<OAuthTokens> = sendTokenRequest(FormBody.Builder()
|
||||
.add("grant_type", "authorization_code")
|
||||
.add("redirect_uri", redirectUri)
|
||||
.add("code", code)
|
||||
.build())
|
||||
|
||||
fun refreshAccessToken(refreshToken: String): Single<OAuthTokens> = 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<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): Single<PreparedUpload> = 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<Response> {
|
||||
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<String>,
|
||||
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")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
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.fragment.app.Fragment
|
||||
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.extensions.plusAssign
|
||||
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
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
compositeDisposable += rxBus.toObservable(UpdateQueueEvent::class.java)
|
||||
.throttleLatest(5, TimeUnit.SECONDS)
|
||||
.observeOn(Schedulers.io())
|
||||
.map { MainApp.getDbHelper().ohQueueSize }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
queueSizeValue = it
|
||||
updateGUI()
|
||||
}
|
||||
compositeDisposable += rxBus.toObservable(UpdateViewEvent::class.java)
|
||||
.observeOn(Schedulers.io())
|
||||
.map { MainApp.getDbHelper().ohQueueSize }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
queueSizeValue = it
|
||||
updateGUI()
|
||||
}
|
||||
context?.applicationContext?.let { appContext ->
|
||||
WorkManager.getInstance(appContext).getWorkInfosForUniqueWorkLiveData(OpenHumansUploader.WORK_NAME).observe(this, Observer<List<WorkInfo>> {
|
||||
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 { 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()
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
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<Button>(R.id.button)
|
||||
val checkbox = findViewById<CheckBox>(R.id.checkbox)
|
||||
|
||||
button.setOnClickListener { _ ->
|
||||
if (checkbox.isChecked) {
|
||||
CustomTabsIntent.Builder().build().launchUrl(this, Uri.parse(OpenHumansUploader.AUTH_URL))
|
||||
} else {
|
||||
Toast.makeText(this, R.string.you_need_to_accept_the_of_use_first, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
val code = intent.data?.getQueryParameter("code")
|
||||
if (supportFragmentManager.fragments.size == 0 && code != null) {
|
||||
ExchangeAuthTokenDialog(code).show(supportFragmentManager, "ExchangeAuthTokenDialog")
|
||||
}
|
||||
}
|
||||
|
||||
//TODO OH: make injectable
|
||||
class ExchangeAuthTokenDialog : DaggerDialogFragment() {
|
||||
|
||||
@Inject
|
||||
lateinit var openHumansUploader: OpenHumansUploader
|
||||
|
||||
private var disposable: Disposable? = null
|
||||
|
||||
init {
|
||||
isCancelable = false
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return AlertDialog.Builder(activity!!)
|
||||
.setTitle(R.string.completing_login)
|
||||
.setMessage(R.string.please_wait)
|
||||
.create()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
disposable = openHumansUploader.login(arguments?.getString("authToken")!!).subscribeOn(Schedulers.io()).subscribe({
|
||||
dismiss()
|
||||
SetupDoneDialog().show(fragmentManager!!, "SetupDoneDialog")
|
||||
}, {
|
||||
dismiss()
|
||||
ErrorDialog(it.message).show(fragmentManager!!, "ErrorDialog")
|
||||
})
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
disposable?.dispose()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
operator fun invoke(authToken: String): ExchangeAuthTokenDialog {
|
||||
val dialog = ExchangeAuthTokenDialog()
|
||||
val args = Bundle()
|
||||
args.putString("authToken", authToken)
|
||||
dialog.arguments = args
|
||||
return dialog
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorDialog : DialogFragment() {
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val message = arguments?.getString("message")
|
||||
val shownMessage = if (message == null) getString(R.string.there_was_an_error)
|
||||
else "${getString(R.string.there_was_an_error)}\n\n$message"
|
||||
return AlertDialog.Builder(activity!!)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(shownMessage)
|
||||
.setPositiveButton(R.string.close, null)
|
||||
.create()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
operator fun invoke(message: String?): ErrorDialog {
|
||||
val dialog = ErrorDialog()
|
||||
val args = Bundle()
|
||||
args.putString("message", message)
|
||||
dialog.arguments = args
|
||||
return dialog
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SetupDoneDialog : DialogFragment() {
|
||||
|
||||
init {
|
||||
isCancelable = false
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return AlertDialog.Builder(activity!!)
|
||||
.setTitle(R.string.successfully_logged_in)
|
||||
.setMessage(R.string.setup_will_continue_in_background)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.close) { _, _ ->
|
||||
activity!!.run {
|
||||
setResult(Activity.RESULT_OK)
|
||||
activity!!.finish()
|
||||
}
|
||||
}
|
||||
.create()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,612 @@
|
|||
package info.nightscout.androidaps.plugins.general.openhumans
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.WindowManager
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.work.*
|
||||
import dagger.android.HasAndroidInjector
|
||||
import info.nightscout.androidaps.BuildConfig
|
||||
import info.nightscout.androidaps.MainApp
|
||||
import info.nightscout.androidaps.R
|
||||
import info.nightscout.androidaps.db.*
|
||||
import info.nightscout.androidaps.events.EventPreferenceChange
|
||||
import info.nightscout.androidaps.interfaces.PluginBase
|
||||
import info.nightscout.androidaps.interfaces.PluginDescription
|
||||
import info.nightscout.androidaps.interfaces.PluginType
|
||||
import info.nightscout.androidaps.logging.AAPSLogger
|
||||
import info.nightscout.androidaps.logging.LTag
|
||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
||||
import info.nightscout.androidaps.utils.extensions.plusAssign
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.security.MessageDigest
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class OpenHumansUploader @Inject constructor(
|
||||
injector: HasAndroidInjector,
|
||||
resourceHelper: ResourceHelper,
|
||||
aapsLogger: AAPSLogger,
|
||||
val sp: SP,
|
||||
val rxBus: RxBusWrapper,
|
||||
val context: Context
|
||||
) : PluginBase(
|
||||
PluginDescription()
|
||||
.mainType(PluginType.GENERAL)
|
||||
.pluginName(R.string.open_humans)
|
||||
.shortName(R.string.open_humans_short)
|
||||
.description(R.string.donate_your_data_to_science)
|
||||
.fragmentClass(OpenHumansFragment::class.qualifiedName)
|
||||
.preferencesId(R.xml.pref_openhumans),
|
||||
aapsLogger, resourceHelper, injector) {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val OPEN_HUMANS_URL = "https://www.openhumans.org"
|
||||
private const val CLIENT_ID = "oie6DvnaEOagTxSoD6BukkLPwDhVr6cMlN74Ihz1"
|
||||
private const val CLIENT_SECRET = "jR0N8pkH1jOwtozHc7CsB1UPcJzFN95ldHcK4VGYIApecr8zGJox0v06xLwPLMASScngT12aIaIHXAVCJeKquEXAWG1XekZdbubSpccgNiQBmuVmIF8nc1xSKSNJltCf"
|
||||
private const val REDIRECT_URL = "androidaps://setup-openhumans"
|
||||
const val AUTH_URL = "https://www.openhumans.org/direct-sharing/projects/oauth2/authorize/?client_id=$CLIENT_ID&response_type=code"
|
||||
const val WORK_NAME = "Open Humans"
|
||||
private const val COPY_NOTIFICATION_ID = 3122
|
||||
private const val FAILURE_NOTIFICATION_ID = 3123
|
||||
private const val SUCCESS_NOTIFICATION_ID = 3124
|
||||
private const val SIGNED_OUT_NOTIFICATION_ID = 3125
|
||||
}
|
||||
|
||||
private val openHumansAPI = OpenHumansAPI(OPEN_HUMANS_URL, CLIENT_ID, CLIENT_SECRET, REDIRECT_URL)
|
||||
private val FILE_NAME_DATE_FORMAT = SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.US).apply { timeZone = TimeZone.getTimeZone("UTC") }
|
||||
|
||||
private var isSetup
|
||||
get() = sp.getBoolean("openhumans_is_setup", false)
|
||||
set(value) = sp.putBoolean("openhumans_is_setup", value)
|
||||
private var oAuthTokens: OpenHumansAPI.OAuthTokens?
|
||||
get() {
|
||||
return if (sp.contains("openhumans_access_token") && sp.contains("openhumans_refresh_token") && sp.contains("openhumans_expires_at")) {
|
||||
OpenHumansAPI.OAuthTokens(
|
||||
accessToken = sp.getStringOrNull("openhumans_access_token", null)!!,
|
||||
refreshToken = sp.getStringOrNull("openhumans_refresh_token", null)!!,
|
||||
expiresAt = sp.getLong("openhumans_expires_at", 0)
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
set(value) {
|
||||
if (value != null) {
|
||||
sp.putString("openhumans_access_token", value.accessToken)
|
||||
sp.putString("openhumans_refresh_token", value.refreshToken)
|
||||
sp.putLong("openhumans_expires_at", value.expiresAt)
|
||||
} else {
|
||||
sp.remove("openhumans_access_token")
|
||||
sp.remove("openhumans_refresh_token")
|
||||
sp.remove("openhumans_expires_at")
|
||||
sp.remove("openhumans_expires_at")
|
||||
}
|
||||
}
|
||||
var projectMemberId: String?
|
||||
get() = sp.getStringOrNull("openhumans_project_member_id", null)
|
||||
private set(value) {
|
||||
if (value == null) sp.remove("openhumans_project_member_id")
|
||||
else sp.putString("openhumans_project_member_id", value)
|
||||
}
|
||||
private var uploadCounter: Int
|
||||
get() = sp.getInt("openhumans_counter", 1)
|
||||
set(value) = sp.putInt("openhumans_counter", value)
|
||||
private val appId: UUID
|
||||
get() {
|
||||
val id = sp.getStringOrNull("openhumans_appid", null)
|
||||
if (id == null) {
|
||||
val generated = UUID.randomUUID()
|
||||
sp.putString("openhumans_appid", generated.toString())
|
||||
return generated
|
||||
} else {
|
||||
return UUID.fromString(id)
|
||||
}
|
||||
}
|
||||
|
||||
private var copyDisposable: Disposable? = null
|
||||
|
||||
private val wakeLock = (context.getSystemService(Context.POWER_SERVICE) as PowerManager)
|
||||
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AndroidAPS::OpenHumans")
|
||||
|
||||
val preferenceChangeDisposable = CompositeDisposable()
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setupNotificationChannel()
|
||||
if (isSetup) scheduleWorker(false)
|
||||
preferenceChangeDisposable += rxBus.toObservable(EventPreferenceChange::class.java).subscribe {
|
||||
onSharedPreferenceChanged(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
copyDisposable?.dispose()
|
||||
cancelWorker()
|
||||
preferenceChangeDisposable.clear()
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
fun enqueueBGReading(bgReading: BgReading) = insertQueueItem("BgReadings") {
|
||||
put("date", bgReading.date)
|
||||
put("isValid", bgReading.isValid)
|
||||
put("value", bgReading.value)
|
||||
put("direction", bgReading.direction)
|
||||
put("raw", bgReading.raw)
|
||||
put("source", bgReading.source)
|
||||
put("nsId", bgReading._id)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun enqueueCareportalEvent(careportalEvent: CareportalEvent, deleted: Boolean = false) = insertQueueItem("CareportalEvents") {
|
||||
put("date", careportalEvent.date)
|
||||
put("isValid", careportalEvent.isValid)
|
||||
put("source", careportalEvent.source)
|
||||
put("nsId", careportalEvent._id)
|
||||
put("eventType", careportalEvent.eventType)
|
||||
val data = JSONObject(careportalEvent.json)
|
||||
val reducedData = JSONObject()
|
||||
if (data.has("mgdl")) reducedData.put("mgdl", data.getDouble("mgdl"))
|
||||
if (data.has("glucose")) reducedData.put("glucose", data.getDouble("glucose"))
|
||||
if (data.has("units")) reducedData.put("units", data.getString("units"))
|
||||
if (data.has("created_at")) reducedData.put("created_at", data.getString("created_at"))
|
||||
if (data.has("glucoseType")) reducedData.put("glucoseType", data.getString("glucoseType"))
|
||||
if (data.has("duration")) reducedData.put("duration", data.getInt("duration"))
|
||||
if (data.has("mills")) reducedData.put("mills", data.getLong("mills"))
|
||||
if (data.has("eventType")) reducedData.put("eventType", data.getString("eventType"))
|
||||
put("data", reducedData)
|
||||
put("isDeletion", deleted)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun enqueueExtendedBolus(extendedBolus: ExtendedBolus, deleted: Boolean = false) = insertQueueItem("ExtendedBoluses") {
|
||||
put("date", extendedBolus.date)
|
||||
put("isValid", extendedBolus.isValid)
|
||||
put("source", extendedBolus.source)
|
||||
put("nsId", extendedBolus._id)
|
||||
put("pumpId", extendedBolus.pumpId)
|
||||
put("insulin", extendedBolus.insulin)
|
||||
put("durationInMinutes", extendedBolus.durationInMinutes)
|
||||
put("isDeletion", deleted)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun enqueueProfileSwitch(profileSwitch: ProfileSwitch, deleted: Boolean = false) = insertQueueItem("ProfileSwitches") {
|
||||
put("date", profileSwitch.date)
|
||||
put("isValid", profileSwitch.isValid)
|
||||
put("source", profileSwitch.source)
|
||||
put("nsId", profileSwitch._id)
|
||||
put("isCPP", profileSwitch.isCPP)
|
||||
put("timeshift", profileSwitch.timeshift)
|
||||
put("percentage", profileSwitch.percentage)
|
||||
put("profile", JSONObject(profileSwitch.profileJson))
|
||||
put("profilePlugin", profileSwitch.profilePlugin)
|
||||
put("durationInMinutes", profileSwitch.durationInMinutes)
|
||||
put("isDeletion", deleted)
|
||||
}
|
||||
|
||||
fun enqueueTotalDailyDose(tdd: TDD) = insertQueueItem("TotalDailyDoses") {
|
||||
put("double", tdd.date)
|
||||
put("double", tdd.bolus)
|
||||
put("double", tdd.basal)
|
||||
put("double", tdd.total)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun enqueueTemporaryBasal(temporaryBasal: TemporaryBasal, deleted: Boolean = false) = insertQueueItem("TemporaryBasals") {
|
||||
put("date", temporaryBasal.date)
|
||||
put("isValid", temporaryBasal.isValid)
|
||||
put("source", temporaryBasal.source)
|
||||
put("nsId", temporaryBasal._id)
|
||||
put("pumpId", temporaryBasal.pumpId)
|
||||
put("durationInMinutes", temporaryBasal.durationInMinutes)
|
||||
put("durationInMinutes", temporaryBasal.durationInMinutes)
|
||||
put("isAbsolute", temporaryBasal.isAbsolute)
|
||||
put("percentRate", temporaryBasal.percentRate)
|
||||
put("absoluteRate", temporaryBasal.absoluteRate)
|
||||
put("isDeletion", deleted)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun enqueueTempTarget(tempTarget: TempTarget, deleted: Boolean = false) = insertQueueItem("TempTargets") {
|
||||
put("date", tempTarget.date)
|
||||
put("isValid", tempTarget.isValid)
|
||||
put("source", tempTarget.source)
|
||||
put("nsId", tempTarget._id)
|
||||
put("low", tempTarget.low)
|
||||
put("high", tempTarget.high)
|
||||
put("reason", tempTarget.reason)
|
||||
put("durationInMinutes", tempTarget.durationInMinutes)
|
||||
put("isDeletion", deleted)
|
||||
}
|
||||
|
||||
fun enqueueSMBData(profile: JSONObject, glucoseStatus: JSONObject, iobData: JSONArray, mealData: JSONObject, currentTemp: JSONObject, autosensData: JSONObject, smbAllowed: Boolean, smbAlwaysAllowed: Boolean, result: JSONObject) = insertQueueItem("APSData") {
|
||||
put("algorithm", "SMB")
|
||||
put("profile", profile)
|
||||
put("glucoseStatus", glucoseStatus)
|
||||
put("iobData", iobData)
|
||||
put("mealData", mealData)
|
||||
put("currentTemp", currentTemp)
|
||||
put("autosensData", autosensData)
|
||||
put("smbAllowed", smbAllowed)
|
||||
put("smbAlwaysAllowed", smbAlwaysAllowed)
|
||||
put("result", result)
|
||||
}
|
||||
|
||||
fun enqueueAMAData(profile: JSONObject, glucoseStatus: JSONObject, iobData: JSONArray, mealData: JSONObject, currentTemp: JSONObject, autosensData: JSONObject, result: JSONObject) = insertQueueItem("APSData") {
|
||||
put("algorithm", "AMA")
|
||||
put("profile", profile)
|
||||
put("glucoseStatus", glucoseStatus)
|
||||
put("iobData", iobData)
|
||||
put("mealData", mealData)
|
||||
put("currentTemp", currentTemp)
|
||||
put("autosensData", autosensData)
|
||||
put("result", result)
|
||||
}
|
||||
|
||||
fun enqueueMAData(profile: JSONObject, glucoseStatus: JSONObject, iobData: JSONObject, mealData: JSONObject, currentTemp: JSONObject, result: JSONObject) = insertQueueItem("APSData") {
|
||||
put("algorithm", "MA")
|
||||
put("profile", profile)
|
||||
put("glucoseStatus", glucoseStatus)
|
||||
put("iobData", iobData)
|
||||
put("mealData", mealData)
|
||||
put("currentTemp", currentTemp)
|
||||
put("result", result)
|
||||
}
|
||||
|
||||
private fun insertQueueItem(file: String, structureVersion: Int = 1, generator: JSONObject.() -> Unit) {
|
||||
if (oAuthTokens != null && this.isEnabled(PluginType.GENERAL)) {
|
||||
try {
|
||||
val jsonObject = JSONObject()
|
||||
jsonObject.put("structureVersion", structureVersion)
|
||||
jsonObject.put("queuedOn", System.currentTimeMillis())
|
||||
generator(jsonObject)
|
||||
val queueItem = OHQueueItem(
|
||||
file = file,
|
||||
content = jsonObject.toString()
|
||||
)
|
||||
MainApp.getDbHelper().createOrUpdate(queueItem)
|
||||
rxBus.send(OpenHumansFragment.UpdateQueueEvent)
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun login(authCode: String): Completable =
|
||||
openHumansAPI.exchangeAuthToken(authCode)
|
||||
.doOnSuccess {
|
||||
oAuthTokens = it
|
||||
}
|
||||
.flatMap { openHumansAPI.getProjectMemberId(it.accessToken) }
|
||||
.doOnSuccess {
|
||||
projectMemberId = it
|
||||
copyExistingDataToQueue()
|
||||
rxBus.send(OpenHumansFragment.UpdateViewEvent)
|
||||
}
|
||||
.doOnError {
|
||||
aapsLogger.error("Failed to login to Open Humans", it)
|
||||
}
|
||||
.ignoreElement()
|
||||
|
||||
fun logout() {
|
||||
cancelWorker()
|
||||
copyDisposable?.dispose()
|
||||
isSetup = false
|
||||
oAuthTokens = null
|
||||
projectMemberId = null
|
||||
MainApp.getDbHelper().clearOpenHumansQueue()
|
||||
rxBus.send(OpenHumansFragment.UpdateViewEvent)
|
||||
}
|
||||
|
||||
private fun copyExistingDataToQueue() {
|
||||
copyDisposable?.dispose()
|
||||
var currentProgress = 0L
|
||||
var maxProgress = 0L
|
||||
val increaseCounter = {
|
||||
currentProgress++
|
||||
//Updating the notification for every item drastically slows down the operation
|
||||
if (currentProgress % 1000L == 0L) showOngoingNotification(maxProgress, currentProgress)
|
||||
}
|
||||
copyDisposable = Completable.fromCallable { MainApp.getDbHelper().clearOpenHumansQueue() }
|
||||
.andThen(Single.defer { Single.just(MainApp.getDbHelper().countOfAllRows) })
|
||||
.doOnSuccess { maxProgress = it }
|
||||
.flatMapObservable { Observable.defer { Observable.fromIterable(MainApp.getDbHelper().allBgReadings) } }
|
||||
.map { enqueueBGReading(it); increaseCounter() }
|
||||
.ignoreElements()
|
||||
.andThen(Observable.defer { Observable.fromIterable(MainApp.getDbHelper().allCareportalEvents) })
|
||||
.map { enqueueCareportalEvent(it); increaseCounter() }
|
||||
.ignoreElements()
|
||||
.andThen(Observable.defer { Observable.fromIterable(MainApp.getDbHelper().allExtendedBoluses) })
|
||||
.map { enqueueExtendedBolus(it); increaseCounter() }
|
||||
.ignoreElements()
|
||||
.andThen(Observable.defer { Observable.fromIterable(MainApp.getDbHelper().allProfileSwitches) })
|
||||
.map { enqueueProfileSwitch(it); increaseCounter() }
|
||||
.ignoreElements()
|
||||
.andThen(Observable.defer { Observable.fromIterable(MainApp.getDbHelper().allTDDs) })
|
||||
.map { enqueueTotalDailyDose(it); increaseCounter() }
|
||||
.ignoreElements()
|
||||
.andThen(Observable.defer { Observable.fromIterable(MainApp.getDbHelper().allTemporaryBasals) })
|
||||
.map { enqueueTemporaryBasal(it); increaseCounter() }
|
||||
.ignoreElements()
|
||||
.andThen(Observable.defer { Observable.fromIterable(MainApp.getDbHelper().allTempTargets) })
|
||||
.map { enqueueTempTarget(it); increaseCounter() }
|
||||
.ignoreElements()
|
||||
.doOnSubscribe {
|
||||
wakeLock.acquire(TimeUnit.MINUTES.toMillis(20))
|
||||
showOngoingNotification()
|
||||
}
|
||||
.doOnComplete {
|
||||
isSetup = true
|
||||
scheduleWorker(false)
|
||||
showSetupFinishedNotification()
|
||||
}
|
||||
.doOnError {
|
||||
showSetupFailedNotification()
|
||||
}
|
||||
.doFinally {
|
||||
copyDisposable = null
|
||||
NotificationManagerCompat.from(context).cancel(COPY_NOTIFICATION_ID)
|
||||
wakeLock.release()
|
||||
}
|
||||
.onErrorComplete()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
private fun showOngoingNotification(maxProgress: Long? = null, currentProgress: Long? = null) {
|
||||
val notification = NotificationCompat.Builder(context, "OpenHumans")
|
||||
.setContentTitle(resourceHelper.gs(R.string.finishing_open_humans_setup))
|
||||
.setContentText(resourceHelper.gs(R.string.this_may_take_a_while))
|
||||
.setStyle(NotificationCompat.BigTextStyle())
|
||||
.setProgress(maxProgress?.toInt() ?: 0, currentProgress?.toInt()
|
||||
?: 0, maxProgress == null || currentProgress == null)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(false)
|
||||
.setSmallIcon(R.drawable.notif_icon)
|
||||
.build()
|
||||
NotificationManagerCompat.from(context).notify(COPY_NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
private fun showSetupFinishedNotification() {
|
||||
val notification = NotificationCompat.Builder(context, "OpenHumans")
|
||||
.setContentTitle(resourceHelper.gs(R.string.setup_finished))
|
||||
.setContentText(resourceHelper.gs(R.string.your_phone_is_upload_data))
|
||||
.setStyle(NotificationCompat.BigTextStyle())
|
||||
.setSmallIcon(R.drawable.notif_icon)
|
||||
.build()
|
||||
val notificationManager = NotificationManagerCompat.from(context)
|
||||
notificationManager.notify(SUCCESS_NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
private fun showSetupFailedNotification() {
|
||||
val notification = NotificationCompat.Builder(context, "OpenHumans")
|
||||
.setContentTitle(resourceHelper.gs(R.string.setup_failed))
|
||||
.setContentText(resourceHelper.gs(R.string.there_was_an_error))
|
||||
.setStyle(NotificationCompat.BigTextStyle())
|
||||
.setSmallIcon(R.drawable.notif_icon)
|
||||
.build()
|
||||
val notificationManager = NotificationManagerCompat.from(context)
|
||||
notificationManager.notify(FAILURE_NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
fun uploadData(): Completable = gatherData()
|
||||
.flatMap { data -> refreshAccessTokensIfNeeded().map { accessToken -> accessToken to data } }
|
||||
.flatMap { uploadFile(it.first, it.second).andThen(Single.just(it.second)) }
|
||||
.flatMapCompletable {
|
||||
if (it.highestQueueId != null) {
|
||||
removeUploadedEntriesFromQueue(it.highestQueueId)
|
||||
} else {
|
||||
Completable.complete()
|
||||
}
|
||||
}
|
||||
.doOnError {
|
||||
if (it is OpenHumansAPI.OHHttpException && it.code == 401 && it.detail == "Invalid token.") {
|
||||
handleSignOut()
|
||||
}
|
||||
aapsLogger.error("Error while uploading to Open Humans", it)
|
||||
}
|
||||
.doOnComplete {
|
||||
aapsLogger.info(LTag.OHUPLOADER, "Upload successful")
|
||||
rxBus.send(OpenHumansFragment.UpdateQueueEvent)
|
||||
}
|
||||
.doOnSubscribe {
|
||||
aapsLogger.info(LTag.OHUPLOADER, "Starting upload")
|
||||
}
|
||||
|
||||
private fun uploadFile(accessToken: String, uploadData: UploadData) = Completable.defer {
|
||||
openHumansAPI.prepareFileUpload(accessToken, uploadData.fileName, uploadData.metadata)
|
||||
.flatMap { openHumansAPI.uploadFile(it.uploadURL, uploadData.content).andThen(Single.just(it.fileId)) }
|
||||
.flatMapCompletable { openHumansAPI.completeFileUpload(accessToken, it) }
|
||||
}
|
||||
|
||||
private fun refreshAccessTokensIfNeeded() = Single.defer {
|
||||
val oAuthTokens = this.oAuthTokens!!
|
||||
if (oAuthTokens.expiresAt <= System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1)) {
|
||||
openHumansAPI.refreshAccessToken(oAuthTokens.refreshToken)
|
||||
.doOnSuccess { this.oAuthTokens = it }
|
||||
.map { it.accessToken }
|
||||
} else {
|
||||
Single.just(oAuthTokens.accessToken)
|
||||
}
|
||||
}
|
||||
|
||||
private fun gatherData() = Single.defer {
|
||||
val items = MainApp.getDbHelper().allOHQueueItems
|
||||
val baos = ByteArrayOutputStream()
|
||||
val zos = ZipOutputStream(baos)
|
||||
val tags = mutableListOf<String>()
|
||||
|
||||
items.groupBy { it.file }.forEach { entry ->
|
||||
tags.add(entry.key)
|
||||
val jsonArray = JSONArray()
|
||||
entry.value.map { it.content }.forEach { jsonArray.put(JSONObject(it)) }
|
||||
zos.writeFile("${entry.key}.json", jsonArray.toString().toByteArray())
|
||||
}
|
||||
|
||||
val applicationInfo = JSONObject()
|
||||
applicationInfo.put("versionName", BuildConfig.VERSION_NAME)
|
||||
applicationInfo.put("versionCode", BuildConfig.VERSION_CODE)
|
||||
val hasGitInfo = !BuildConfig.HEAD.endsWith("NoGitSystemAvailable", true)
|
||||
val customRemote = !BuildConfig.REMOTE.equals("https://github.com/MilosKozak/AndroidAPS.git", true)
|
||||
applicationInfo.put("hasGitInfo", hasGitInfo)
|
||||
applicationInfo.put("customRemote", customRemote)
|
||||
applicationInfo.put("applicationId", appId.toString())
|
||||
zos.writeFile("ApplicationInfo.json", applicationInfo.toString().toByteArray())
|
||||
tags.add("ApplicationInfo")
|
||||
|
||||
val preferences = JSONObject(sp.getAll().filterKeys { it.isAllowedKey() })
|
||||
zos.writeFile("Preferences.json", preferences.toString().toByteArray())
|
||||
tags.add("Preferences")
|
||||
|
||||
val deviceInfo = JSONObject()
|
||||
deviceInfo.put("brand", Build.BRAND)
|
||||
deviceInfo.put("device", Build.DEVICE)
|
||||
deviceInfo.put("manufacturer", Build.MANUFACTURER)
|
||||
deviceInfo.put("model", Build.MODEL)
|
||||
deviceInfo.put("product", Build.PRODUCT)
|
||||
zos.writeFile("DeviceInfo.json", deviceInfo.toString().toByteArray())
|
||||
tags.add("DeviceInfo")
|
||||
|
||||
val displayMetrics = DisplayMetrics()
|
||||
(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.getMetrics(displayMetrics)
|
||||
val displayInfo = JSONObject()
|
||||
displayInfo.put("height", displayMetrics.heightPixels)
|
||||
displayInfo.put("width", displayMetrics.widthPixels)
|
||||
displayInfo.put("density", displayMetrics.density)
|
||||
displayInfo.put("scaledDensity", displayMetrics.scaledDensity)
|
||||
displayInfo.put("xdpi", displayMetrics.xdpi)
|
||||
displayInfo.put("ydpi", displayMetrics.ydpi)
|
||||
zos.writeFile("DisplayInfo.json", displayInfo.toString().toByteArray())
|
||||
tags.add("DisplayInfo")
|
||||
|
||||
val uploadNumber = this.uploadCounter++
|
||||
val uploadDate = Date()
|
||||
val uploadInfo = JSONObject()
|
||||
uploadInfo.put("fileVersion", 1)
|
||||
uploadInfo.put("counter", uploadNumber)
|
||||
uploadInfo.put("timestamp", uploadDate.time)
|
||||
uploadInfo.put("utcOffset", TimeZone.getDefault().getOffset(uploadDate.time))
|
||||
zos.writeFile("UploadInfo.json", uploadInfo.toString().toByteArray())
|
||||
tags.add("UploadInfo")
|
||||
|
||||
zos.close()
|
||||
val bytes = baos.toByteArray()
|
||||
|
||||
Single.just(UploadData(
|
||||
fileName = "upload-num$uploadNumber-ver1-date${FILE_NAME_DATE_FORMAT.format(uploadDate)}-appid${appId.toString().replace("-", "")}.zip",
|
||||
metadata = OpenHumansAPI.FileMetadata(
|
||||
tags = tags,
|
||||
description = "AndroidAPS Database Upload",
|
||||
md5 = MessageDigest.getInstance("MD5").digest(bytes).toHexString(),
|
||||
creationDate = uploadDate.time
|
||||
),
|
||||
content = bytes,
|
||||
highestQueueId = items.map { it.id }.max()
|
||||
))
|
||||
}
|
||||
|
||||
private fun ZipOutputStream.writeFile(name: String, bytes: ByteArray) {
|
||||
putNextEntry(ZipEntry(name))
|
||||
write(bytes)
|
||||
closeEntry()
|
||||
}
|
||||
|
||||
private fun removeUploadedEntriesFromQueue(highestId: Long) = Completable.fromCallable {
|
||||
MainApp.getDbHelper().removeAllOHQueueItemsWithIdSmallerThan(highestId)
|
||||
}
|
||||
|
||||
private fun handleSignOut() {
|
||||
val notification = NotificationCompat.Builder(context, "OpenHumans")
|
||||
.setContentTitle(resourceHelper.gs(R.string.you_have_been_signed_out_of_open_humans))
|
||||
.setContentText(resourceHelper.gs(R.string.click_here_to_sign_in_again_if_this_wasnt_on_purpose))
|
||||
.setStyle(NotificationCompat.BigTextStyle())
|
||||
.setSmallIcon(R.drawable.notif_icon)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
Intent(context, OpenHumansLoginActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
},
|
||||
0
|
||||
))
|
||||
.build()
|
||||
NotificationManagerCompat.from(context).notify(SIGNED_OUT_NOTIFICATION_ID, notification)
|
||||
logout()
|
||||
}
|
||||
|
||||
private fun cancelWorker() {
|
||||
WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME)
|
||||
}
|
||||
|
||||
private fun scheduleWorker(replace: Boolean) {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.setRequiresCharging(sp.getBoolean("key_oh_charging_only", false))
|
||||
.build()
|
||||
val workRequest = PeriodicWorkRequestBuilder<OHUploadWorker>(1, TimeUnit.DAYS)
|
||||
.setConstraints(constraints)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.HOURS)
|
||||
.build()
|
||||
WorkManager.getInstance(context).enqueueUniquePeriodicWork(WORK_NAME, if (replace) ExistingPeriodicWorkPolicy.REPLACE else ExistingPeriodicWorkPolicy.KEEP, workRequest)
|
||||
}
|
||||
|
||||
private fun setupNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val notificationManagerCompat = NotificationManagerCompat.from(context)
|
||||
notificationManagerCompat.createNotificationChannel(NotificationChannel(
|
||||
"OpenHumans",
|
||||
resourceHelper.gs(R.string.open_humans),
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
private class UploadData(
|
||||
val fileName: String,
|
||||
val metadata: OpenHumansAPI.FileMetadata,
|
||||
val content: ByteArray,
|
||||
val highestQueueId: Long?
|
||||
)
|
||||
|
||||
private val HEX_DIGITS = "0123456789ABCDEF".toCharArray()
|
||||
|
||||
private fun ByteArray.toHexString(): String {
|
||||
val stringBuilder = StringBuilder()
|
||||
map { it.toInt() }.forEach {
|
||||
stringBuilder.append(HEX_DIGITS[(it shr 4) and 0x0F])
|
||||
stringBuilder.append(HEX_DIGITS[it and 0x0F])
|
||||
}
|
||||
return stringBuilder.toString()
|
||||
}
|
||||
|
||||
private fun onSharedPreferenceChanged(event: EventPreferenceChange) {
|
||||
if (event.changedKey == "key_oh_charging_only" && isSetup) scheduleWorker(true)
|
||||
}
|
||||
}
|
10
app/src/main/res/drawable/open_humans.xml
Normal file
10
app/src/main/res/drawable/open_humans.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector android:height="100.5272dp" android:viewportHeight="108.36626"
|
||||
android:viewportWidth="107.79796" android:width="100dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#ff9161" android:pathData="m59.75,62.316c5.179,-2.256 8.801,-7.417 8.801,-13.427 0,-8.086 -6.555,-14.641 -14.641,-14.641 -8.086,0 -14.641,6.555 -14.641,14.641 0,6.01 3.622,11.171 8.801,13.427 -7.849,1.589 -14.555,6.318 -18.76,12.817 5.968,6.896 14.774,11.272 24.589,11.272 9.821,0 18.633,-4.382 24.601,-11.286 -4.205,-6.491 -10.907,-11.215 -18.75,-12.803z"/>
|
||||
<path android:fillColor="#ff9161" android:pathData="M21.689,33.33 L10.002,21.643c-5.155,7 -8.677,15.271 -10.002,24.25l16.523,0c0.968,-4.535 2.741,-8.776 5.166,-12.563z"/>
|
||||
<path android:fillColor="#ff9161" android:pathData="m91.275,45.893l16.523,0C106.473,36.909 102.947,28.634 97.787,21.631L86.101,33.317c2.429,3.79 4.205,8.035 5.174,12.576z"/>
|
||||
<path android:fillColor="#ff9161" android:pathData="M86.305,10.106C79.304,4.91 71.02,1.351 62.022,0l0,15.422l13.059,5.908z"/>
|
||||
<path android:fillColor="#ff9161" android:pathData="M45.754,15.339L45.754,0.003c-8.995,1.354 -17.276,4.915 -24.274,10.113l10.963,10.963z"/>
|
||||
<path android:fillColor="#4bc0c7" android:pathData="m26.558,80.554c-4.881,-5.002 -8.405,-11.333 -9.971,-18.394l-16.546,0c4.001,26.128 26.629,46.206 53.858,46.206 27.229,0 49.857,-20.077 53.858,-46.206l-16.546,0c-1.564,7.053 -5.082,13.378 -9.955,18.378 -6.946,7.127 -16.643,11.56 -27.357,11.56 -10.706,0 -20.396,-4.427 -27.341,-11.544z"/>
|
||||
</vector>
|
58
app/src/main/res/layout/activity_open_humans_login.xml
Normal file
58
app/src/main/res/layout/activity_open_humans_login.xml
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/uploaded_data"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Headline" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/the_following_data_will_be_uploaded_to_your_open_humans_account"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/terms_of_use"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Headline" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/open_humans_terms"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/i_understand_and_agree" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
63
app/src/main/res/layout/fragment_open_humans.xml
Normal file
63
app/src/main/res/layout/fragment_open_humans.xml
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:contentDescription="@null"
|
||||
android:paddingBottom="16dp"
|
||||
app:srcCompat="@drawable/open_humans" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/member_id"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
tools:text="Project Member ID: 5151515" />
|
||||
|
||||
<TextView
|
||||
android:layout_marginTop="8dp"
|
||||
android:id="@+id/queue_size"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
tools:text="Queue Size: 155" />
|
||||
|
||||
<TextView
|
||||
android:layout_marginTop="8dp"
|
||||
android:id="@+id/worker_state"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
tools:text="Worker State: Running" />
|
||||
|
||||
<Button
|
||||
android:layout_marginTop="16dp"
|
||||
android:id="@+id/login"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/logout"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/logout" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
|
@ -1407,4 +1407,32 @@
|
|||
<string name="basalpctfromtdd_label">% of basal</string>
|
||||
<string name="dpvdefaultprofile">DPV Default profile</string>
|
||||
|
||||
<string name="open_humans">Open Humans</string>
|
||||
<string name="finishing_open_humans_setup">Finishing Open Humans setup…</string>
|
||||
<string name="this_may_take_a_while">This may take a while. Do not turn your phone off.</string>
|
||||
<string name="setup_finished">Setup finished</string>
|
||||
<string name="your_phone_is_upload_data">Your phone is uploading data to Open Humans now.</string>
|
||||
<string name="setup_failed">Setup failed</string>
|
||||
<string name="there_was_an_error">There was an error.</string>
|
||||
<string name="open_humans_terms">This is an open source tool that will copy your data to Open Humans. We retain no rights to share your data with third parties without your explicit authorization. The data the project and app receive are identified via a random user ID and will only be securely transmitted to an Open Humans account with your authorization of that process. You can stop uploading and delete your upload data at any time via www.openhumans.org.</string>
|
||||
<string name="i_understand_and_agree">I understand and agree.</string>
|
||||
<string name="login">Login</string>
|
||||
<string name="logout">Logout</string>
|
||||
<string name="project_member_id">Project Member ID: %s</string>
|
||||
<string name="queue_size">Queue Size: %d</string>
|
||||
<string name="terms_of_use">Terms of Use</string>
|
||||
<string name="not_logged_in">Not logged in</string>
|
||||
<string name="you_need_to_accept_the_of_use_first">You need to accept the terms of use first.</string>
|
||||
<string name="successfully_logged_in">Successfully logged in</string>
|
||||
<string name="setup_will_continue_in_background">The setup will be completed in background now. Thanks for uploading your data.</string>
|
||||
<string name="completing_login">Completing login…</string>
|
||||
<string name="donate_your_data_to_science">Donate your data to science</string>
|
||||
<string name="open_humans_short">OH</string>
|
||||
<string name="you_have_been_signed_out_of_open_humans">You have been signed out of Open Humans</string>
|
||||
<string name="click_here_to_sign_in_again_if_this_wasnt_on_purpose">Click here to sign in a again if this wasn\'t on purpose.</string>
|
||||
<string name="only_upload_if_connected_to_wifi">Only upload if connected to WiFi</string>
|
||||
<string name="only_upload_if_charging">Only upload if charging</string>
|
||||
<string name="worker_state">Worker State: %s</string>
|
||||
<string name="uploaded_data">Uploaded Data</string>
|
||||
<string name="the_following_data_will_be_uploaded_to_your_open_humans_account">The following data will be uploaded to your Open Humans account: Glucose values, careportal events (except notes), extended boluses, profile switches, total daily doses, temporary basals, temp targets, preferences, application version, device model and screen dimensions. Secret or private information such as your Nightscout URL oder API secret will not be uploaded.</string>
|
||||
</resources>
|
||||
|
|
18
app/src/main/res/xml/pref_openhumans.xml
Normal file
18
app/src/main/res/xml/pref_openhumans.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceCategory android:title="@string/open_humans">
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="true"
|
||||
android:key="key_oh_wifi_only"
|
||||
android:title="@string/only_upload_if_connected_to_wifi" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="key_oh_charging_only"
|
||||
android:title="@string/only_upload_if_charging" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
|
@ -3,7 +3,9 @@ package info.nightscout.androidaps.events
|
|||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
|
||||
class EventPreferenceChange : Event {
|
||||
private var changedKey: String? = null
|
||||
|
||||
var changedKey: String? = null
|
||||
private set
|
||||
|
||||
constructor(key: String) {
|
||||
changedKey = key
|
||||
|
|
|
@ -17,6 +17,7 @@ enum class LTag(val tag: String, val defaultValue : Boolean = true, val requires
|
|||
LOCATION("LOCATION"),
|
||||
NOTIFICATION("NOTIFICATION"),
|
||||
NSCLIENT("NSCLIENT"),
|
||||
OHUPLOADER("OHUPLOADER"),
|
||||
PUMP("PUMP"),
|
||||
PUMPBTCOMM("PUMPBTCOMM", defaultValue = true),
|
||||
PUMPCOMM("PUMPCOMM"),
|
||||
|
|
Loading…
Reference in a new issue