From 6c0853d49a8849d9a37211b6ed12ec368d8f0272 Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Wed, 10 Apr 2019 00:33:05 +0200 Subject: [PATCH 001/152] kotlin support --- app/build.gradle | 7 +++++++ build.gradle | 2 ++ 2 files changed, 9 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index 5cdb5bfd6a..8637ac27d8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,6 +10,8 @@ buildscript { } } apply plugin: "com.android.application" +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-android' apply plugin: 'com.google.gms.google-services' apply plugin: "io.fabric" apply plugin: "jacoco-android" @@ -27,6 +29,7 @@ ext { repositories { maven { url 'https://maven.fabric.io/public' } jcenter { url "https://jcenter.bintray.com/" } + mavenCentral() } def generateGitBuild = { -> @@ -75,6 +78,9 @@ android { moduleName "BleCommandUtil" } } + kotlinOptions { + jvmTarget = '1.8' + } lintOptions { // TODO remove once wear dependency com.google.android.gms:play-services-wearable:7.3.0 // has been upgraded (requiring significant code changes), which currently fails release @@ -233,6 +239,7 @@ dependencies { androidTestImplementation "org.mockito:mockito-core:2.8.47" androidTestImplementation "com.google.dexmaker:dexmaker:${dexmakerVersion}" androidTestImplementation "com.google.dexmaker:dexmaker-mockito:${dexmakerVersion}" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } task unzip(type: Copy) { diff --git a/build.gradle b/build.gradle index 57745d34ab..6826c29a13 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.3.21' repositories { google() jcenter() @@ -16,6 +17,7 @@ buildscript { // in the individual module build.gradle files classpath 'com.jakewharton:butterknife-gradle-plugin:9.0.0-SNAPSHOT' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } From bd5d31a4a31f290bbba304dd8b321c5b24a161bd Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Wed, 10 Apr 2019 00:36:02 +0200 Subject: [PATCH 002/152] check version Java -> Kotlin --- .../nightscout/androidaps/MainActivity.java | 4 +- .../versionChecker/VersionCheckerUtils.kt | 63 +++++++++++ .../androidaps/utils/VersionChecker.java | 100 ------------------ 3 files changed, 65 insertions(+), 102 deletions(-) create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt delete mode 100644 app/src/main/java/info/nightscout/androidaps/utils/VersionChecker.java diff --git a/app/src/main/java/info/nightscout/androidaps/MainActivity.java b/app/src/main/java/info/nightscout/androidaps/MainActivity.java index f441a8b243..7ce5ae2beb 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/MainActivity.java @@ -51,6 +51,7 @@ import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus; +import info.nightscout.androidaps.plugins.general.versionChecker.VersionCheckerUtilsKt; import info.nightscout.androidaps.setupwizard.SetupWizardActivity; import info.nightscout.androidaps.tabs.TabPageAdapter; import info.nightscout.androidaps.utils.AndroidPermission; @@ -59,7 +60,6 @@ import info.nightscout.androidaps.utils.LocaleHelper; import info.nightscout.androidaps.utils.OKDialog; import info.nightscout.androidaps.utils.PasswordProtection; import info.nightscout.androidaps.utils.SP; -import info.nightscout.androidaps.utils.VersionChecker; public class MainActivity extends AppCompatActivity { private static Logger log = LoggerFactory.getLogger(L.CORE); @@ -115,7 +115,7 @@ public class MainActivity extends AppCompatActivity { public void onPageScrollStateChanged(int state) { } }); - VersionChecker.check(); + VersionCheckerUtilsKt.checkVersion(); FabricPrivacy.setUserStats(); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt new file mode 100644 index 0000000000..e2d79e99a9 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt @@ -0,0 +1,63 @@ +package info.nightscout.androidaps.plugins.general.versionChecker + +import android.content.Context +import android.net.ConnectivityManager +import info.nightscout.androidaps.BuildConfig +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification +import org.apache.http.HttpResponse +import org.apache.http.client.methods.HttpGet +import org.apache.http.impl.client.DefaultHttpClient +import org.slf4j.LoggerFactory +import java.io.IOException +import java.io.InputStream + +// check network connection +fun isConnected(): Boolean { + val connMgr = MainApp.instance().applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + return connMgr.activeNetworkInfo?.isConnected ?: false +} + +// convert inputstream to String +@Throws(IOException::class) +fun InputStream.findVersion(): String? { + var version: String? = null + val regex = "(.*)version(.*)\"(((\\d+)\\.)+(\\d+))\"(.*)" + + this.bufferedReader().forEachLine { + if (regex.toRegex().matches(it)) { + version = regex.toRegex().matchEntire(it)?.groupValues?.getOrNull(3) + return@forEachLine + } + } + return version +} + +private val log = LoggerFactory.getLogger(L.CORE) + +@Suppress("DEPRECATION") +fun checkVersion() = if (isConnected()) { + Thread { + try { + val request = HttpGet("https://raw.githubusercontent.com/MilosKozak/AndroidAPS/master/app/build.gradle") + val response: HttpResponse = DefaultHttpClient().execute(request) + val version: String? = response.entity.content?.findVersion() + val comparison = version?.compareTo(BuildConfig.VERSION_NAME.replace("\"", "")) ?: 0 + if (comparison == 0) { + log.debug("Version equal to master of fetch failed") + } else if (comparison > 0) { + log.debug("Version outdated. Found $version") + val notification = Notification(Notification.NEWVERSIONDETECTED, String.format(MainApp.gs(R.string.versionavailable), version.toString()), Notification.LOW) + MainApp.bus().post(EventNewNotification(notification)) + } else { + log.debug("Version newer than master. Are you developer?") + } + } catch (e: IOException) { + log.debug("Github master version check error: $e") + } + }.start() +} else + log.debug("Github master version no checked. No connectivity") \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/VersionChecker.java b/app/src/main/java/info/nightscout/androidaps/utils/VersionChecker.java deleted file mode 100644 index fd636b4596..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/utils/VersionChecker.java +++ /dev/null @@ -1,100 +0,0 @@ -package info.nightscout.androidaps.utils; - -import android.net.ConnectivityManager; -import android.net.NetworkInfo; - -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.DefaultHttpClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import info.nightscout.androidaps.BuildConfig; -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; -import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; - -import static android.content.Context.CONNECTIVITY_SERVICE; - -public class VersionChecker { - private static Logger log = LoggerFactory.getLogger(L.CORE); - - public static void check() { - if (isConnected()) - new Thread(() -> { - HttpClient client = new DefaultHttpClient(); - HttpGet request = new HttpGet("https://raw.githubusercontent.com/MilosKozak/AndroidAPS/master/app/build.gradle"); - HttpResponse response; - - try { - response = client.execute(request); - InputStream inputStream = response.getEntity().getContent(); - - if (inputStream != null) { - String result = findLine(inputStream); - if (result != null) { - int compare = result.compareTo(BuildConfig.VERSION_NAME.replace("\"", "")); - if (compare == 0) { - log.debug("Version equal to master"); - return; - } else if (compare > 0) { - log.debug("Version outdated. Found " + result); - Notification notification = new Notification(Notification.NEWVERSIONDETECTED, String.format(MainApp.gs(R.string.versionavailable), result), Notification.LOW); - MainApp.bus().post(new EventNewNotification(notification)); - return; - } else { - log.debug("Version newer than master. Are you developer?"); - return; - } - } - } - - log.debug("Github master version not found"); - - } catch (IOException e) { - e.printStackTrace(); - log.debug("Github master version check error"); - } - }).start(); - else - log.debug("Github master version no checked. No connectivity"); - } - - // convert inputstream to String - private static String findLine(InputStream inputStream) throws IOException { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); - String line; - String regex = "(.*)version(.*)\"(((\\d+)\\.)+(\\d+))\"(.*)"; - Pattern p = Pattern.compile(regex); - - while ((line = bufferedReader.readLine()) != null) { - Matcher m = p.matcher(line); - if (m.matches()) { - log.debug("+++ " + line); - return m.group(3); - } else { - log.debug("--- " + line); - } - } - inputStream.close(); - return null; - } - - // check network connection - public static boolean isConnected() { - ConnectivityManager connMgr = (ConnectivityManager) MainApp.instance().getApplicationContext().getSystemService(CONNECTIVITY_SERVICE); - NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); - return networkInfo != null && networkInfo.isConnected(); - } - -} From c34cf576e5f386b3872ecafd34fb41c4ba62aeff Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Wed, 10 Apr 2019 00:41:48 +0200 Subject: [PATCH 003/152] VersionCheckerPlugin nightly stub --- .../versionChecker/VersionCheckerPlugin.kt | 22 +++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 2 files changed, 23 insertions(+) create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt new file mode 100644 index 0000000000..99268bcdd9 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt @@ -0,0 +1,22 @@ +package info.nightscout.androidaps.plugins.general.versionChecker + +import info.nightscout.androidaps.R +import info.nightscout.androidaps.interfaces.* + +/** + * Usually we would have a class here. + * Instead of having a class we can use an object directly inherited from PluginBase. + * This is a lazy loading singleton only loaded when actually used. + * */ + +object VersionCheckerPlugin : PluginBase(PluginDescription() + .mainType(PluginType.CONSTRAINTS) + .neverVisible(true) + .alwaysEnabled(true) + .showInList(false) + .pluginName(R.string.versionChecker)), ConstraintsInterface { + + override fun isClosedLoopAllowed(value: Constraint): Constraint { + return value + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 79d626f9be..3d7d2a3c60 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1326,6 +1326,7 @@ Wrong code. Command cancelled. Not configured Profile switch created + Version Checker %1$d day %1$d days From 85cc61934cc33a7f6316e52d50908918d5dbddeb Mon Sep 17 00:00:00 2001 From: Johannes Mockenhaupt Date: Wed, 10 Apr 2019 14:49:23 +0200 Subject: [PATCH 004/152] add test --- .../VersionCheckerUtilsKtTest.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt new file mode 100644 index 0000000000..237012e74c --- /dev/null +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt @@ -0,0 +1,19 @@ +package info.nightscout.androidaps.plugins.general.versionChecker + +import org.junit.Assert.assertEquals +import org.junit.Test + +class VersionCheckerUtilsKtTest { + @Test + fun findVersionMatches() { + val buildGradle = """blabla + | android { + | aosenuthoae + | } + | version = "2.2.2" + | appName = "Aaoeu" + """.trimMargin() + val detectedVersion: String? = buildGradle.byteInputStream().findVersion() + assertEquals("2.2.2", detectedVersion) + } +} \ No newline at end of file From a33afc99f611cc581251cf4d258da27ef191b606 Mon Sep 17 00:00:00 2001 From: Johannes Mockenhaupt Date: Wed, 10 Apr 2019 14:50:26 +0200 Subject: [PATCH 005/152] JUST PLAYING AROUND --- .../versionChecker/VersionCheckerUtils.kt | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt index e2d79e99a9..a7d167d801 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt @@ -24,16 +24,12 @@ fun isConnected(): Boolean { // convert inputstream to String @Throws(IOException::class) fun InputStream.findVersion(): String? { - var version: String? = null - val regex = "(.*)version(.*)\"(((\\d+)\\.)+(\\d+))\"(.*)" - - this.bufferedReader().forEachLine { - if (regex.toRegex().matches(it)) { - version = regex.toRegex().matchEntire(it)?.groupValues?.getOrNull(3) - return@forEachLine - } - } - return version + val regex = "(.*)version(.*)\"(((\\d+)\\.)+(\\d+))\"(.*)".toRegex() + return bufferedReader() + .readLines() + .filter { regex.matches(it) } + .mapNotNull { regex.matchEntire(it)?.groupValues?.getOrNull(3) } + .firstOrNull() } private val log = LoggerFactory.getLogger(L.CORE) From cefee4827bd37d5745b06a515fabe1a958cc7502 Mon Sep 17 00:00:00 2001 From: Johannes Mockenhaupt Date: Wed, 10 Apr 2019 14:58:01 +0200 Subject: [PATCH 006/152] TDDing --- .../VersionCheckerUtilsKtTest.kt | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt index 237012e74c..d5af3a9409 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt @@ -5,7 +5,7 @@ import org.junit.Test class VersionCheckerUtilsKtTest { @Test - fun findVersionMatches() { + fun findVersionMatchesRegularVersion() { val buildGradle = """blabla | android { | aosenuthoae @@ -16,4 +16,25 @@ class VersionCheckerUtilsKtTest { val detectedVersion: String? = buildGradle.byteInputStream().findVersion() assertEquals("2.2.2", detectedVersion) } + + // 04. Break stuff.mp3 like it's 1999. Again. Pizza delivery! For i c wiener ... + //@Test + fun findVersionMatchesCustomVersion() { + val buildGradle = """blabla + | android { + | aosenuthoae + | } + | version = "2.2.2-nefarious-underground-mod" + | appName = "Aaoeu" + """.trimMargin() + val detectedVersion: String? = buildGradle.byteInputStream().findVersion() + assertEquals("2.2.2", detectedVersion) + } + + @Test + fun findVersionMatchesDoesNotMatchErrorResponse() { + val buildGradle = """Balls! No build.gradle here. Move along""" + val detectedVersion: String? = buildGradle.byteInputStream().findVersion() + assertEquals(null, detectedVersion) + } } \ No newline at end of file From d786a2a89871ce75846bd4e272cea6ef90397226 Mon Sep 17 00:00:00 2001 From: Tebbe Ubben Date: Wed, 10 Apr 2019 16:15:18 +0200 Subject: [PATCH 007/152] Insight: Set SQLite Sequence to current time millis on table creation --- .../java/info/nightscout/androidaps/db/DatabaseHelper.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java index c23d797c8c..b024f64711 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java @@ -131,6 +131,8 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { TableUtils.createTableIfNotExists(connectionSource, InsightHistoryOffset.class); TableUtils.createTableIfNotExists(connectionSource, InsightBolusID.class); TableUtils.createTableIfNotExists(connectionSource, InsightPumpID.class); + 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); } catch (SQLException e) { log.error("Can't create database", e); throw new RuntimeException(e); @@ -151,6 +153,8 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { TableUtils.createTableIfNotExists(connectionSource, InsightHistoryOffset.class); TableUtils.createTableIfNotExists(connectionSource, InsightBolusID.class); TableUtils.createTableIfNotExists(connectionSource, InsightPumpID.class); + 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); } else { log.info(DatabaseHelper.class.getName(), "onUpgrade"); TableUtils.dropTable(connectionSource, TempTarget.class, true); From 04618156cc03c50bf901f0d9d8afb1ca4c9d2168 Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Thu, 11 Apr 2019 12:43:52 +0200 Subject: [PATCH 008/152] restructure and test version checker --- .../versionChecker/VersionCheckerUtils.kt | 36 +++++--- .../VersionCheckerUtilsKtTest.kt | 88 ++++++++++++++++++- 2 files changed, 108 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt index a7d167d801..c1378451df 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt @@ -23,7 +23,7 @@ fun isConnected(): Boolean { // convert inputstream to String @Throws(IOException::class) -fun InputStream.findVersion(): String? { +inline fun InputStream.findVersion(): String? { val regex = "(.*)version(.*)\"(((\\d+)\\.)+(\\d+))\"(.*)".toRegex() return bufferedReader() .readLines() @@ -41,19 +41,31 @@ fun checkVersion() = if (isConnected()) { val request = HttpGet("https://raw.githubusercontent.com/MilosKozak/AndroidAPS/master/app/build.gradle") val response: HttpResponse = DefaultHttpClient().execute(request) val version: String? = response.entity.content?.findVersion() - val comparison = version?.compareTo(BuildConfig.VERSION_NAME.replace("\"", "")) ?: 0 - if (comparison == 0) { - log.debug("Version equal to master of fetch failed") - } else if (comparison > 0) { - log.debug("Version outdated. Found $version") - val notification = Notification(Notification.NEWVERSIONDETECTED, String.format(MainApp.gs(R.string.versionavailable), version.toString()), Notification.LOW) - MainApp.bus().post(EventNewNotification(notification)) - } else { - log.debug("Version newer than master. Are you developer?") - } + compareWithCurrentVersion(version, BuildConfig.VERSION_NAME) } catch (e: IOException) { log.debug("Github master version check error: $e") } }.start() } else - log.debug("Github master version no checked. No connectivity") \ No newline at end of file + log.debug("Github master version no checked. No connectivity") + +fun compareWithCurrentVersion(newVersion: String?, currentVersion: String) { + val comparison = newVersion?.versionStrip()?.compareTo(currentVersion.versionStrip()) ?: 0 + when { + comparison == 0 -> log.debug("Version equal to master of fetch failed") + comparison > 0 -> { + log.debug("Version ${currentVersion} outdated. Found $newVersion") + val notification = Notification(Notification.NEWVERSIONDETECTED, String.format(MainApp.gs(R.string.versionavailable), newVersion.toString()), Notification.LOW) + MainApp.bus().post(EventNewNotification(notification)) + } + else -> log.debug("Version newer than master. Are you developer?") + } +} + + fun String.versionStrip() = this.mapNotNull { + when (it) { + in '0'..'9' -> it + '.' -> it + else -> null + } + }.joinToString (separator = "") \ No newline at end of file diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt index d5af3a9409..503c8d46be 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt @@ -1,8 +1,18 @@ package info.nightscout.androidaps.plugins.general.versionChecker +import com.squareup.otto.Bus +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.logging.L import org.junit.Assert.assertEquals import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers +import org.mockito.Mockito.* +import org.powermock.api.mockito.PowerMockito +import org.powermock.core.classloader.annotations.PrepareForTest +import org.powermock.modules.junit4.PowerMockRunner +@RunWith(PowerMockRunner::class) class VersionCheckerUtilsKtTest { @Test fun findVersionMatchesRegularVersion() { @@ -17,9 +27,10 @@ class VersionCheckerUtilsKtTest { assertEquals("2.2.2", detectedVersion) } - // 04. Break stuff.mp3 like it's 1999. Again. Pizza delivery! For i c wiener ... - //@Test - fun findVersionMatchesCustomVersion() { + + // In case we merge a "x.x.x-dev" into master, don't see it as update. + @Test + fun `should return null on non-digit versions on master`() { val buildGradle = """blabla | android { | aosenuthoae @@ -28,7 +39,7 @@ class VersionCheckerUtilsKtTest { | appName = "Aaoeu" """.trimMargin() val detectedVersion: String? = buildGradle.byteInputStream().findVersion() - assertEquals("2.2.2", detectedVersion) + assertEquals(null, detectedVersion) } @Test @@ -37,4 +48,73 @@ class VersionCheckerUtilsKtTest { val detectedVersion: String? = buildGradle.byteInputStream().findVersion() assertEquals(null, detectedVersion) } + + @Test + fun testVersionStrip() { + assertEquals("2.2.2", "2.2.2".versionStrip()) + assertEquals("2.2.2", "2.2.2-dev".versionStrip()) + assertEquals("2.2.2", "2.2.2dev".versionStrip()) + assertEquals("2.2.2", """"2.2.2"""".versionStrip()) + } + + @Test + @PrepareForTest(MainApp::class, L::class) + fun `should find update1`() { + val bus = prepareCompareTests() + compareWithCurrentVersion(newVersion = "2.2.3", currentVersion = "2.2.1") + verify(bus, times(1)).post(any()) + } + + @Test + @PrepareForTest(MainApp::class, L::class) + fun `should find update2`() { + val bus = prepareCompareTests() + compareWithCurrentVersion(newVersion = "2.2.3", currentVersion = "2.2.1-dev") + verify(bus, times(1)).post(any()) + } + + @Test + @PrepareForTest(MainApp::class, L::class) + fun `should find update3`() { + val bus = prepareCompareTests() + compareWithCurrentVersion(newVersion = "2.2.3", currentVersion = "2.1") + verify(bus, times(1)).post(any()) + } + + @Test + @PrepareForTest(MainApp::class, L::class) + fun `should find update4`() { + val bus = prepareCompareTests() + + compareWithCurrentVersion(newVersion = "2.2", currentVersion = "2.1.1") + + verify(bus, times(1)).post(any()) + } + + @Test + @PrepareForTest(MainApp::class, L::class) + fun `should find update5`() { + val bus = prepareCompareTests() + compareWithCurrentVersion(newVersion = "2.2.1", currentVersion = "2.2-dev") + verify(bus, times(1)).post(any()) + } + + @Test + @PrepareForTest(MainApp::class, L::class) + fun `should find update6`() { + val bus = prepareCompareTests() + compareWithCurrentVersion(newVersion = "2.2.1", currentVersion = "2.2dev") + verify(bus, times(1)).post(any()) + } + + + private fun prepareCompareTests(): Bus { + PowerMockito.mockStatic(MainApp::class.java) + val mainApp = mock(MainApp::class.java) + `when`(MainApp.instance()).thenReturn(mainApp) + val bus = mock(Bus::class.java) + `when`(MainApp.bus()).thenReturn(bus) + `when`(MainApp.gs(ArgumentMatchers.anyInt())).thenReturn("some dummy string") + return bus + } } \ No newline at end of file From f739980988cf54571733cf14291af0e39d359bdd Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Mon, 15 Apr 2019 08:04:56 +0200 Subject: [PATCH 009/152] testing and SP logic --- .../versionChecker/VersionCheckerUtils.kt | 41 +++++++---- app/src/main/res/values/strings.xml | 1 + .../VersionCheckerUtilsKtTest.kt | 71 +++++++++++++++---- 3 files changed, 86 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt index c1378451df..b8cfa7fffc 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt @@ -8,6 +8,7 @@ import info.nightscout.androidaps.R import info.nightscout.androidaps.logging.L import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification import info.nightscout.androidaps.plugins.general.overview.notifications.Notification +import info.nightscout.androidaps.utils.SP import org.apache.http.HttpResponse import org.apache.http.client.methods.HttpGet import org.apache.http.impl.client.DefaultHttpClient @@ -50,22 +51,34 @@ fun checkVersion() = if (isConnected()) { log.debug("Github master version no checked. No connectivity") fun compareWithCurrentVersion(newVersion: String?, currentVersion: String) { - val comparison = newVersion?.versionStrip()?.compareTo(currentVersion.versionStrip()) ?: 0 + val comparison: Int? = newVersion?.versionStrip()?.compareTo(currentVersion.versionStrip()) when { - comparison == 0 -> log.debug("Version equal to master of fetch failed") - comparison > 0 -> { - log.debug("Version ${currentVersion} outdated. Found $newVersion") - val notification = Notification(Notification.NEWVERSIONDETECTED, String.format(MainApp.gs(R.string.versionavailable), newVersion.toString()), Notification.LOW) - MainApp.bus().post(EventNewNotification(notification)) - } + comparison == null -> onVersionNotDetectable() + comparison == 0 -> onSameVersionDetected() + comparison > 0 -> onNewVersionDetected(currentVersion = currentVersion, newVersion = newVersion) else -> log.debug("Version newer than master. Are you developer?") } } - fun String.versionStrip() = this.mapNotNull { - when (it) { - in '0'..'9' -> it - '.' -> it - else -> null - } - }.joinToString (separator = "") \ No newline at end of file +fun onSameVersionDetected() { + SP.remove(R.string.key_new_version_available_since) +} + +fun onVersionNotDetectable() { + log.debug("fetch failed, ignore and smartcast to non-null") +} + +fun onNewVersionDetected(currentVersion: String, newVersion: String?) { + log.debug("Version ${currentVersion} outdated. Found $newVersion") + val notification = Notification(Notification.NEWVERSIONDETECTED, String.format(MainApp.gs(R.string.versionavailable), newVersion.toString()), Notification.LOW) + MainApp.bus().post(EventNewNotification(notification)) + SP.putLong(R.string.key_new_version_available_since, System.currentTimeMillis()) +} + +fun String.versionStrip() = this.mapNotNull { + when (it) { + in '0'..'9' -> it + '.' -> it + else -> null + } +}.joinToString(separator = "") \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3d7d2a3c60..feaab74c41 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1327,6 +1327,7 @@ Not configured Profile switch created Version Checker + new_version_available_since %1$d day %1$d days diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt index 503c8d46be..b1b62b589d 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt @@ -2,7 +2,9 @@ package info.nightscout.androidaps.plugins.general.versionChecker import com.squareup.otto.Bus import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.utils.SP import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith @@ -58,33 +60,33 @@ class VersionCheckerUtilsKtTest { } @Test - @PrepareForTest(MainApp::class, L::class) + @PrepareForTest(MainApp::class, L::class, SP::class) fun `should find update1`() { - val bus = prepareCompareTests() + val bus = prepareBus() compareWithCurrentVersion(newVersion = "2.2.3", currentVersion = "2.2.1") verify(bus, times(1)).post(any()) } @Test - @PrepareForTest(MainApp::class, L::class) + @PrepareForTest(MainApp::class, L::class, SP::class) fun `should find update2`() { - val bus = prepareCompareTests() + val bus = prepareBus() compareWithCurrentVersion(newVersion = "2.2.3", currentVersion = "2.2.1-dev") verify(bus, times(1)).post(any()) } @Test - @PrepareForTest(MainApp::class, L::class) + @PrepareForTest(MainApp::class, L::class, SP::class) fun `should find update3`() { - val bus = prepareCompareTests() + val bus = prepareBus() compareWithCurrentVersion(newVersion = "2.2.3", currentVersion = "2.1") verify(bus, times(1)).post(any()) } @Test - @PrepareForTest(MainApp::class, L::class) + @PrepareForTest(MainApp::class, L::class, SP::class) fun `should find update4`() { - val bus = prepareCompareTests() + val bus = prepareBus() compareWithCurrentVersion(newVersion = "2.2", currentVersion = "2.1.1") @@ -92,29 +94,72 @@ class VersionCheckerUtilsKtTest { } @Test - @PrepareForTest(MainApp::class, L::class) + @PrepareForTest(MainApp::class, L::class, SP::class) fun `should find update5`() { - val bus = prepareCompareTests() + val bus = prepareBus() compareWithCurrentVersion(newVersion = "2.2.1", currentVersion = "2.2-dev") verify(bus, times(1)).post(any()) } @Test - @PrepareForTest(MainApp::class, L::class) + @PrepareForTest(MainApp::class, L::class, SP::class) fun `should find update6`() { - val bus = prepareCompareTests() + val bus = prepareBus() + val sp = prepareSP() compareWithCurrentVersion(newVersion = "2.2.1", currentVersion = "2.2dev") verify(bus, times(1)).post(any()) } + @Test + @PrepareForTest(MainApp::class, L::class, SP::class) + fun `find same version`() { + val buildGradle = """blabla + | android { + | aosenuthoae + | } + | version = "2.2.2" + | appName = "Aaoeu" + """.trimMargin() + val bus = prepareBus() + compareWithCurrentVersion(buildGradle.byteInputStream().findVersion(), currentVersion = "2.2.2") + verify(bus, times(0)).post(any()) + verify(SP, times(1)).remove(R.string.key_new_version_available_since) + } - private fun prepareCompareTests(): Bus { + @Test + @PrepareForTest(MainApp::class, L::class, SP::class) + fun `find higher version`() { + val buildGradle = """blabla + | android { + | aosenuthoae + | } + | version = "3.0" + | appName = "Aaoeu" + """.trimMargin() + val bus = prepareBus() + compareWithCurrentVersion(buildGradle.byteInputStream().findVersion(), currentVersion = "2.2.2") + verify(bus, times(1)).post(any()) + } + + + private fun prepareBus(): Bus { PowerMockito.mockStatic(MainApp::class.java) val mainApp = mock(MainApp::class.java) `when`(MainApp.instance()).thenReturn(mainApp) val bus = mock(Bus::class.java) `when`(MainApp.bus()).thenReturn(bus) `when`(MainApp.gs(ArgumentMatchers.anyInt())).thenReturn("some dummy string") + prepareSP() return bus } + + private fun prepareSP() { + PowerMockito.mockStatic(SP::class.java) + } + + private fun prepareLogging() { + PowerMockito.mockStatic(L::class.java) + `when`(L.isEnabled(any())).thenReturn(true) + } + } \ No newline at end of file From 78f2da405f975a13820a217d89a2d9b6a8f71535 Mon Sep 17 00:00:00 2001 From: Lee Braiden Date: Mon, 15 Apr 2019 19:09:56 +0100 Subject: [PATCH 010/152] Fix potential NPE in AlarmSoundService destructor. --- .../nightscout/androidaps/services/AlarmSoundService.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/services/AlarmSoundService.java b/app/src/main/java/info/nightscout/androidaps/services/AlarmSoundService.java index e95cd7c479..4917789db1 100644 --- a/app/src/main/java/info/nightscout/androidaps/services/AlarmSoundService.java +++ b/app/src/main/java/info/nightscout/androidaps/services/AlarmSoundService.java @@ -75,8 +75,11 @@ public class AlarmSoundService extends Service { @Override public void onDestroy() { - player.stop(); - player.release(); + if (player != null) { + player.stop(); + player.release(); + } + if (L.isEnabled(L.CORE)) log.debug("onDestroy"); } From ee4fdcd6401585a15578c1e11cbdceda93acef5c Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 16 Apr 2019 11:15:40 +0200 Subject: [PATCH 011/152] Test for uncommited changes in gradle --- app/build.gradle | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 46a2ee19dd..23705b9bee 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -67,6 +67,26 @@ def generateDate = { -> return stringBuilder.toString() } +def isMaster = { -> + return !version.contains('-') +} + +def allCommited = { -> + StringBuilder stringBuilder = new StringBuilder(); + try { + def stdout = new ByteArrayOutputStream() + exec { + commandLine 'git', 'status' + standardOutput = stdout + } + String commitObject = stdout.toString().trim() + stringBuilder.append(commitObject) + } catch (ignored) { + return false; // NoGitSystemAvailable + } + return stringBuilder.toString().contains("nothing to commit") +} + tasks.matching { it instanceof Test }.all { testLogging.events = ["failed", "skipped", "started"] testLogging.exceptionFormat = "full" @@ -83,7 +103,7 @@ android { version "2.2.3-dev" buildConfigField "String", "VERSION", '"' + version + '"' buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"' - buildConfigField "String", "REMOTE", '"' + generateGitRemote()+ '"' + buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"' buildConfigField "String", "HEAD", '"' + generateGitBuild() + '"' testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" // if you change minSdkVersion to less than 11, you need to change executeTask for wear @@ -120,7 +140,7 @@ android { resValue "string", "app_name", "AndroidAPS" versionName version manifestPlaceholders = [ - appIcon: "@mipmap/ic_launcher", + appIcon : "@mipmap/ic_launcher", appIconRound: "@mipmap/ic_launcher_round" ] } @@ -130,7 +150,7 @@ android { resValue "string", "app_name", "Pumpcontrol" versionName version + "-pumpcontrol" manifestPlaceholders = [ - appIcon: "@mipmap/ic_pumpcontrol", + appIcon : "@mipmap/ic_pumpcontrol", appIconRound: "@null" ] } @@ -140,7 +160,7 @@ android { resValue "string", "app_name", "NSClient" versionName version + "-nsclient" manifestPlaceholders = [ - appIcon: "@mipmap/ic_yellowowl", + appIcon : "@mipmap/ic_yellowowl", appIconRound: "@null" ] } @@ -150,7 +170,7 @@ android { resValue "string", "app_name", "NSClient2" versionName version + "-nsclient" manifestPlaceholders = [ - appIcon: "@mipmap/ic_yellowowl", + appIcon : "@mipmap/ic_yellowowl", appIconRound: "@null" ] } @@ -165,7 +185,7 @@ android { unitTests.includeAndroidResources = true } - useLibrary "org.apache.http.legacy" + useLibrary "org.apache.http.legacy" } allprojects { @@ -274,3 +294,12 @@ task full_clean(type: Delete) { clean.dependsOn full_clean preBuild.dependsOn copyLibs + +printf('--------------\n') +printf('isMaster: %s\n', isMaster().toString()) +printf('allCommited: %s\n', allCommited().toString()) +printf('--------------\n') +if (isMaster() && !allCommited()) { + throw new GradleException('There are uncommitted changes or git system is not available. Clone sources again as described in wiki and do not allow gradle update') +} + From 952a6fe5097ee67a92157ecb0233abeb730f0651 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 16 Apr 2019 20:36:18 +0200 Subject: [PATCH 012/152] fix GlucoseStatus calculation --- .../androidaps/data/GlucoseStatus.java | 178 ----------------- .../androidaps/data/QuickWizardEntry.java | 1 + .../DetermineBasalAdapterAMAJS.java | 2 +- .../aps/openAPSAMA/OpenAPSAMAPlugin.java | 2 +- .../openAPSMA/DetermineBasalAdapterMAJS.java | 2 +- .../aps/openAPSMA/OpenAPSMAPlugin.java | 2 +- .../DetermineBasalAdapterSMBJS.java | 2 +- .../aps/openAPSSMB/OpenAPSSMBPlugin.java | 2 +- .../Dialogs/NewNSTreatmentDialog.java | 2 +- .../general/overview/OverviewFragment.java | 2 +- .../overview/dialogs/CalibrationDialog.java | 2 +- .../PersistentNotificationPlugin.java | 2 +- .../SmsCommunicatorPlugin.java | 2 +- .../wearintegration/WatchUpdaterService.java | 12 +- .../iob/iobCobCalculator/GlucoseStatus.java | 180 ++++++++++++++++++ .../androidaps/utils/BolusWizard.java | 2 +- .../androidaps/data/GlucoseStatusTest.java | 1 + .../androidaps/utils/BolusWizardTest.java | 2 +- 18 files changed, 196 insertions(+), 202 deletions(-) delete mode 100644 app/src/main/java/info/nightscout/androidaps/data/GlucoseStatus.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java diff --git a/app/src/main/java/info/nightscout/androidaps/data/GlucoseStatus.java b/app/src/main/java/info/nightscout/androidaps/data/GlucoseStatus.java deleted file mode 100644 index be3e981fb4..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/data/GlucoseStatus.java +++ /dev/null @@ -1,178 +0,0 @@ -package info.nightscout.androidaps.data; - -import android.support.annotation.Nullable; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; - -import info.nightscout.androidaps.db.BgReading; -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.DecimalFormatter; -import info.nightscout.androidaps.utils.Round; - -/** - * Created by mike on 04.01.2017. - */ - -public class GlucoseStatus { - private static Logger log = LoggerFactory.getLogger(GlucoseStatus.class); - public double glucose = 0d; - public double delta = 0d; - public double avgdelta = 0d; - public double short_avgdelta = 0d; - public double long_avgdelta = 0d; - public long date = 0L; - - - public String log() { - return "Glucose: " + DecimalFormatter.to0Decimal(glucose) + " mg/dl " + - "Delta: " + DecimalFormatter.to0Decimal(delta) + " mg/dl" + - "Short avg. delta: " + " " + DecimalFormatter.to2Decimal(short_avgdelta) + " mg/dl " + - "Long avg. delta: " + DecimalFormatter.to2Decimal(long_avgdelta) + " mg/dl"; - } - - public GlucoseStatus() { - } - - public GlucoseStatus round() { - this.glucose = Round.roundTo(this.glucose, 0.1); - this.delta = Round.roundTo(this.delta, 0.01); - this.avgdelta = Round.roundTo(this.avgdelta, 0.01); - this.short_avgdelta = Round.roundTo(this.short_avgdelta, 0.01); - this.long_avgdelta = Round.roundTo(this.long_avgdelta, 0.01); - return this; - } - - - @Nullable - public static GlucoseStatus getGlucoseStatusData() { - return getGlucoseStatusData(false); - } - - @Nullable - public static GlucoseStatus getGlucoseStatusData(boolean allowOldData) { - // load 45min - //long fromtime = DateUtil.now() - 60 * 1000L * 45; - //List data = MainApp.getDbHelper().getBgreadingsDataFromTime(fromtime, false); - - List data = IobCobCalculatorPlugin.getPlugin().getBgReadings(); - - if (data == null) { - if (L.isEnabled(L.GLUCOSE)) - log.debug("data=null"); - return null; - } - - int sizeRecords = data.size(); - if (sizeRecords == 0) { - if (L.isEnabled(L.GLUCOSE)) - log.debug("sizeRecords==0"); - return null; - } - - if (data.get(0).date < DateUtil.now() - 7 * 60 * 1000L && !allowOldData) { - if (L.isEnabled(L.GLUCOSE)) - log.debug("olddata"); - return null; - } - - BgReading now = data.get(0); - long now_date = now.date; - double change; - - if (sizeRecords == 1) { - GlucoseStatus status = new GlucoseStatus(); - status.glucose = now.value; - status.short_avgdelta = 0d; - status.delta = 0d; - status.long_avgdelta = 0d; - status.avgdelta = 0d; // for OpenAPS MA - status.date = now_date; - if (L.isEnabled(L.GLUCOSE)) - log.debug("sizeRecords==1"); - return status.round(); - } - - ArrayList now_value_list = new ArrayList<>(); - ArrayList last_deltas = new ArrayList<>(); - ArrayList short_deltas = new ArrayList<>(); - ArrayList long_deltas = new ArrayList<>(); - - // Use the latest sgv value in the now calculations - now_value_list.add(now.value); - - for (int i = 1; i < sizeRecords; i++) { - if (data.get(i).value > 38) { - BgReading then = data.get(i); - long then_date = then.date; - double avgdelta; - long minutesago; - - minutesago = Math.round((now_date - then_date) / (1000d * 60)); - // multiply by 5 to get the same units as delta, i.e. mg/dL/5m - change = now.value - then.value; - avgdelta = change / minutesago * 5; - - if (L.isEnabled(L.GLUCOSE)) - log.debug(then.toString() + " minutesago=" + minutesago + " avgdelta=" + avgdelta); - - // use the average of all data points in the last 2.5m for all further "now" calculations - if (0 < minutesago && minutesago < 2.5) { - // Keep and average all values within the last 2.5 minutes - now_value_list.add(then.value); - now.value = average(now_value_list); - // short_deltas are calculated from everything ~5-15 minutes ago - } else if (2.5 < minutesago && minutesago < 17.5) { - //console.error(minutesago, avgdelta); - short_deltas.add(avgdelta); - // last_deltas are calculated from everything ~5 minutes ago - if (2.5 < minutesago && minutesago < 7.5) { - last_deltas.add(avgdelta); - } - // long_deltas are calculated from everything ~20-40 minutes ago - } else if (17.5 < minutesago && minutesago < 42.5) { - long_deltas.add(avgdelta); - } else { - // Do not process any more records after >= 42.5 minutes - break; - } - } - } - - GlucoseStatus status = new GlucoseStatus(); - status.glucose = now.value; - status.date = now_date; - - status.short_avgdelta = average(short_deltas); - - if (last_deltas.isEmpty()) { - status.delta = status.short_avgdelta; - } else { - status.delta = average(last_deltas); - } - - status.long_avgdelta = average(long_deltas); - status.avgdelta = status.short_avgdelta; // for OpenAPS MA - - if (L.isEnabled(L.GLUCOSE)) - log.debug(status.log()); - return status.round(); - } - - public static double average(ArrayList array) { - double sum = 0d; - - if (array.size() == 0) - return 0d; - - for (Double value : array) { - sum += value; - } - return sum / array.size(); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/data/QuickWizardEntry.java b/app/src/main/java/info/nightscout/androidaps/data/QuickWizardEntry.java index 4262fe2133..0bfcbb1002 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/QuickWizardEntry.java +++ b/app/src/main/java/info/nightscout/androidaps/data/QuickWizardEntry.java @@ -12,6 +12,7 @@ import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.TempTarget; import info.nightscout.androidaps.interfaces.TreatmentsInterface; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.CobInfo; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.java index 7eb9a2d81d..5b5c8cb63d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.java @@ -20,7 +20,7 @@ import java.lang.reflect.InvocationTargetException; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; import info.nightscout.androidaps.data.Profile; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.java index 3447c7c4f4..1f7deebea4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.java @@ -6,7 +6,7 @@ import org.slf4j.LoggerFactory; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; import info.nightscout.androidaps.data.Profile; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/DetermineBasalAdapterMAJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/DetermineBasalAdapterMAJS.java index 4baabf22ab..ae1f390ded 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/DetermineBasalAdapterMAJS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/DetermineBasalAdapterMAJS.java @@ -17,7 +17,7 @@ import java.lang.reflect.InvocationTargetException; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; import info.nightscout.androidaps.data.Profile; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAPlugin.java index 24b4cb7929..2dd78bdb82 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAPlugin.java @@ -6,7 +6,7 @@ import org.slf4j.LoggerFactory; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; import info.nightscout.androidaps.data.Profile; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java index edaddd62c9..bfb75503c1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java @@ -20,7 +20,7 @@ import java.lang.reflect.InvocationTargetException; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; import info.nightscout.androidaps.data.Profile; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.java index 73914662d7..728e3145d6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.java @@ -6,7 +6,7 @@ import org.slf4j.LoggerFactory; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; import info.nightscout.androidaps.data.Profile; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/careportal/Dialogs/NewNSTreatmentDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/careportal/Dialogs/NewNSTreatmentDialog.java index e94cc658ca..57414927b4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/careportal/Dialogs/NewNSTreatmentDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/careportal/Dialogs/NewNSTreatmentDialog.java @@ -39,7 +39,7 @@ import java.util.List; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.db.BgReading; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java index c4d8655ca8..6d41303540 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java @@ -56,7 +56,7 @@ import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; -import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.QuickWizardEntry; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/CalibrationDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/CalibrationDialog.java index 53c571aad7..0ab1b98ff6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/CalibrationDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/CalibrationDialog.java @@ -18,7 +18,7 @@ import java.text.DecimalFormat; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.utils.NumberPicker; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.java index 0287c21260..987d9374bd 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.java @@ -20,7 +20,7 @@ import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainActivity; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.DatabaseHelper; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.java index 7703561aa9..181bdd86d2 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.java @@ -20,7 +20,7 @@ import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; -import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.ProfileStore; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java index 86b56ba130..d121c5c55d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java @@ -32,17 +32,11 @@ import com.google.android.gms.wearable.PutDataRequest; import com.google.android.gms.wearable.Wearable; import com.google.android.gms.wearable.WearableListenerService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; - import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.BgReading; @@ -55,15 +49,11 @@ import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.TreatmentsInterface; import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus; import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.plugins.general.wear.ActionStringHandler; import info.nightscout.androidaps.plugins.general.wear.WearPlugin; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; -import info.nightscout.androidaps.plugins.treatments.Treatment; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.DecimalFormatter; import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.utils.SafeParse; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java new file mode 100644 index 0000000000..94fab37d8b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java @@ -0,0 +1,180 @@ +package info.nightscout.androidaps.plugins.iob.iobCobCalculator; + +import android.support.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +import info.nightscout.androidaps.db.BgReading; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.DecimalFormatter; +import info.nightscout.androidaps.utils.Round; + +/** + * Created by mike on 04.01.2017. + */ + +public class GlucoseStatus { + private static Logger log = LoggerFactory.getLogger(GlucoseStatus.class); + public double glucose = 0d; + public double delta = 0d; + public double avgdelta = 0d; + public double short_avgdelta = 0d; + public double long_avgdelta = 0d; + public long date = 0L; + + + public String log() { + return "Glucose: " + DecimalFormatter.to0Decimal(glucose) + " mg/dl " + + "Delta: " + DecimalFormatter.to0Decimal(delta) + " mg/dl" + + "Short avg. delta: " + " " + DecimalFormatter.to2Decimal(short_avgdelta) + " mg/dl " + + "Long avg. delta: " + DecimalFormatter.to2Decimal(long_avgdelta) + " mg/dl"; + } + + public GlucoseStatus() { + } + + public GlucoseStatus round() { + this.glucose = Round.roundTo(this.glucose, 0.1); + this.delta = Round.roundTo(this.delta, 0.01); + this.avgdelta = Round.roundTo(this.avgdelta, 0.01); + this.short_avgdelta = Round.roundTo(this.short_avgdelta, 0.01); + this.long_avgdelta = Round.roundTo(this.long_avgdelta, 0.01); + return this; + } + + + @Nullable + public static GlucoseStatus getGlucoseStatusData() { + return getGlucoseStatusData(false); + } + + @Nullable + public static GlucoseStatus getGlucoseStatusData(boolean allowOldData) { + // load 45min + //long fromtime = DateUtil.now() - 60 * 1000L * 45; + //List data = MainApp.getDbHelper().getBgreadingsDataFromTime(fromtime, false); + + synchronized (IobCobCalculatorPlugin.getPlugin().dataLock) { + + List data = IobCobCalculatorPlugin.getPlugin().getBgReadings(); + + if (data == null) { + if (L.isEnabled(L.GLUCOSE)) + log.debug("data=null"); + return null; + } + + int sizeRecords = data.size(); + if (sizeRecords == 0) { + if (L.isEnabled(L.GLUCOSE)) + log.debug("sizeRecords==0"); + return null; + } + + if (data.get(0).date < DateUtil.now() - 7 * 60 * 1000L && !allowOldData) { + if (L.isEnabled(L.GLUCOSE)) + log.debug("olddata"); + return null; + } + + BgReading now = data.get(0); + long now_date = now.date; + double change; + + if (sizeRecords == 1) { + GlucoseStatus status = new GlucoseStatus(); + status.glucose = now.value; + status.short_avgdelta = 0d; + status.delta = 0d; + status.long_avgdelta = 0d; + status.avgdelta = 0d; // for OpenAPS MA + status.date = now_date; + if (L.isEnabled(L.GLUCOSE)) + log.debug("sizeRecords==1"); + return status.round(); + } + + ArrayList now_value_list = new ArrayList<>(); + ArrayList last_deltas = new ArrayList<>(); + ArrayList short_deltas = new ArrayList<>(); + ArrayList long_deltas = new ArrayList<>(); + + // Use the latest sgv value in the now calculations + now_value_list.add(now.value); + + for (int i = 1; i < sizeRecords; i++) { + if (data.get(i).value > 38) { + BgReading then = data.get(i); + long then_date = then.date; + double avgdelta; + long minutesago; + + minutesago = Math.round((now_date - then_date) / (1000d * 60)); + // multiply by 5 to get the same units as delta, i.e. mg/dL/5m + change = now.value - then.value; + avgdelta = change / minutesago * 5; + + if (L.isEnabled(L.GLUCOSE)) + log.debug(then.toString() + " minutesago=" + minutesago + " avgdelta=" + avgdelta); + + // use the average of all data points in the last 2.5m for all further "now" calculations + if (0 < minutesago && minutesago < 2.5) { + // Keep and average all values within the last 2.5 minutes + now_value_list.add(then.value); + now.value = average(now_value_list); + // short_deltas are calculated from everything ~5-15 minutes ago + } else if (2.5 < minutesago && minutesago < 17.5) { + //console.error(minutesago, avgdelta); + short_deltas.add(avgdelta); + // last_deltas are calculated from everything ~5 minutes ago + if (2.5 < minutesago && minutesago < 7.5) { + last_deltas.add(avgdelta); + } + // long_deltas are calculated from everything ~20-40 minutes ago + } else if (17.5 < minutesago && minutesago < 42.5) { + long_deltas.add(avgdelta); + } else { + // Do not process any more records after >= 42.5 minutes + break; + } + } + } + + GlucoseStatus status = new GlucoseStatus(); + status.glucose = now.value; + status.date = now_date; + + status.short_avgdelta = average(short_deltas); + + if (last_deltas.isEmpty()) { + status.delta = status.short_avgdelta; + } else { + status.delta = average(last_deltas); + } + + status.long_avgdelta = average(long_deltas); + status.avgdelta = status.short_avgdelta; // for OpenAPS MA + + if (L.isEnabled(L.GLUCOSE)) + log.debug(status.log()); + return status.round(); + } + } + + public static double average(ArrayList array) { + double sum = 0d; + + if (array.size() == 0) + return 0d; + + for (Double value : array) { + sum += value; + } + return sum / array.size(); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/utils/BolusWizard.java b/app/src/main/java/info/nightscout/androidaps/utils/BolusWizard.java index e47a74e17f..cd789ee225 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/BolusWizard.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/BolusWizard.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.TempTarget; diff --git a/app/src/test/java/info/nightscout/androidaps/data/GlucoseStatusTest.java b/app/src/test/java/info/nightscout/androidaps/data/GlucoseStatusTest.java index 8bcdf0ed26..cdbd276bae 100644 --- a/app/src/test/java/info/nightscout/androidaps/data/GlucoseStatusTest.java +++ b/app/src/test/java/info/nightscout/androidaps/data/GlucoseStatusTest.java @@ -16,6 +16,7 @@ import java.util.List; import info.AAPSMocker; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.db.BgReading; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.general.nsclient.data.NSSgv; import info.nightscout.androidaps.utils.DateUtil; diff --git a/app/src/test/java/info/nightscout/androidaps/utils/BolusWizardTest.java b/app/src/test/java/info/nightscout/androidaps/utils/BolusWizardTest.java index 945c813674..2143a7101c 100644 --- a/app/src/test/java/info/nightscout/androidaps/utils/BolusWizardTest.java +++ b/app/src/test/java/info/nightscout/androidaps/utils/BolusWizardTest.java @@ -9,7 +9,7 @@ import org.powermock.modules.junit4.PowerMockRunner; import info.AAPSMocker; import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.interfaces.PumpInterface; From a6bd81a1b9f5bc77fc5329177dfb0cfed9f03857 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 16 Apr 2019 21:28:21 +0200 Subject: [PATCH 013/152] fix tests --- .../plugins/iob/iobCobCalculator/GlucoseStatus.java | 2 +- .../iob/iobCobCalculator/IobCobCalculatorPlugin.java | 6 +++++- .../plugins/iob/iobCobCalculator/IobCobOref1Thread.java | 2 +- .../plugins/iob/iobCobCalculator/IobCobThread.java | 2 +- app/src/test/java/info/AAPSMocker.java | 5 ++++- .../iob/iobCobCalculatorPlugin}/GlucoseStatusTest.java | 7 ++----- 6 files changed, 14 insertions(+), 10 deletions(-) rename app/src/test/java/info/nightscout/androidaps/{data => plugins/iob/iobCobCalculatorPlugin}/GlucoseStatusTest.java (97%) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java index 94fab37d8b..191ce9e1cd 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java @@ -59,7 +59,7 @@ public class GlucoseStatus { //long fromtime = DateUtil.now() - 60 * 1000L * 45; //List data = MainApp.getDbHelper().getBgreadingsDataFromTime(fromtime, false); - synchronized (IobCobCalculatorPlugin.getPlugin().dataLock) { + synchronized (IobCobCalculatorPlugin.getPlugin().getDataLock()) { List data = IobCobCalculatorPlugin.getPlugin().getBgReadings(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.java index 4897ff55ac..94b687b863 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.java @@ -65,7 +65,7 @@ public class IobCobCalculatorPlugin extends PluginBase { private volatile List bgReadings = null; // newest at index 0 private volatile List bucketed_data = null; - final Object dataLock = new Object(); + private final Object dataLock = new Object(); boolean stopCalculationTrigger = false; private Thread thread = null; @@ -108,6 +108,10 @@ public class IobCobCalculatorPlugin extends PluginBase { return bucketed_data; } + public Object getDataLock() { + return dataLock; + } + // roundup to whole minute public static long roundUpTime(long time) { if (time % 60000 == 0) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java index 492f3c0ff1..c483ffd2a6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java @@ -96,7 +96,7 @@ public class IobCobOref1Thread extends Thread { long oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable); - synchronized (iobCobCalculatorPlugin.dataLock) { + synchronized (iobCobCalculatorPlugin.getDataLock()) { if (bgDataReload) { iobCobCalculatorPlugin.loadBgData(end); iobCobCalculatorPlugin.createBucketedData(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java index 2aacb4f453..9ddf18abb0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java @@ -95,7 +95,7 @@ public class IobCobThread extends Thread { long oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable); - synchronized (iobCobCalculatorPlugin.dataLock) { + synchronized (iobCobCalculatorPlugin.getDataLock()) { if (bgDataReload) { iobCobCalculatorPlugin.loadBgData(end); iobCobCalculatorPlugin.createBucketedData(); diff --git a/app/src/test/java/info/AAPSMocker.java b/app/src/test/java/info/AAPSMocker.java index 850affa224..50bd20d198 100644 --- a/app/src/test/java/info/AAPSMocker.java +++ b/app/src/test/java/info/AAPSMocker.java @@ -282,10 +282,13 @@ public class AAPSMocker { PowerMockito.when(ProfileFunctions.getInstance().getProfileName()).thenReturn(TESTPROFILENAME); } - public static void mockIobCobCalculatorPlugin() { + public static IobCobCalculatorPlugin mockIobCobCalculatorPlugin() { PowerMockito.mockStatic(IobCobCalculatorPlugin.class); IobCobCalculatorPlugin iobCobCalculatorPlugin = PowerMockito.mock(IobCobCalculatorPlugin.class); PowerMockito.when(IobCobCalculatorPlugin.getPlugin()).thenReturn(iobCobCalculatorPlugin); + Object dataLock = new Object(); + PowerMockito.when(iobCobCalculatorPlugin.getDataLock()).thenReturn(dataLock); + return iobCobCalculatorPlugin; } private static MockedBus bus = new MockedBus(); diff --git a/app/src/test/java/info/nightscout/androidaps/data/GlucoseStatusTest.java b/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculatorPlugin/GlucoseStatusTest.java similarity index 97% rename from app/src/test/java/info/nightscout/androidaps/data/GlucoseStatusTest.java rename to app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculatorPlugin/GlucoseStatusTest.java index cdbd276bae..19e497b834 100644 --- a/app/src/test/java/info/nightscout/androidaps/data/GlucoseStatusTest.java +++ b/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculatorPlugin/GlucoseStatusTest.java @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.data; +package info.nightscout.androidaps.plugins.iob.iobCobCalculatorPlugin; import org.json.JSONException; import org.json.JSONObject; @@ -134,10 +134,7 @@ public class GlucoseStatusTest { public void initMocking() { AAPSMocker.mockMainApp(); AAPSMocker.mockStrings(); - - PowerMockito.mockStatic(IobCobCalculatorPlugin.class); - iobCobCalculatorPlugin = mock(IobCobCalculatorPlugin.class); - when(IobCobCalculatorPlugin.getPlugin()).thenReturn(iobCobCalculatorPlugin); + iobCobCalculatorPlugin = AAPSMocker.mockIobCobCalculatorPlugin(); PowerMockito.mockStatic(DateUtil.class); when(DateUtil.now()).thenReturn(1514766900000L + T.mins(1).msecs()); From 39412ae032acdd8a13b9364fca648ad584a946f6 Mon Sep 17 00:00:00 2001 From: Tebbe Ubben Date: Wed, 17 Apr 2019 17:31:10 +0200 Subject: [PATCH 014/152] Fix initial sqlite_sequence for Insight driver --- .../nightscout/androidaps/db/DatabaseHelper.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java index b024f64711..ad16cb90f6 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java @@ -131,8 +131,10 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { TableUtils.createTableIfNotExists(connectionSource, InsightHistoryOffset.class); TableUtils.createTableIfNotExists(connectionSource, InsightBolusID.class); TableUtils.createTableIfNotExists(connectionSource, InsightPumpID.class); - 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); + 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() + " " + + "WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = \"" + DATABASE_INSIGHT_PUMP_IDS + "\")"); } catch (SQLException e) { log.error("Can't create database", e); throw new RuntimeException(e); @@ -153,8 +155,10 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { TableUtils.createTableIfNotExists(connectionSource, InsightHistoryOffset.class); TableUtils.createTableIfNotExists(connectionSource, InsightBolusID.class); TableUtils.createTableIfNotExists(connectionSource, InsightPumpID.class); - 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); + 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() + " " + + "WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = \"" + DATABASE_INSIGHT_PUMP_IDS + "\")"); } else { log.info(DatabaseHelper.class.getName(), "onUpgrade"); TableUtils.dropTable(connectionSource, TempTarget.class, true); From 0eee1c341274e216baac47be71e2af2587a09195 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Wed, 17 Apr 2019 20:31:00 +0200 Subject: [PATCH 015/152] New Crowdin translations (#1745) * New translations strings.xml (Russian) * New translations strings.xml (Russian) --- app/src/main/res/values-ru/strings.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e970c21d48..26d330f64b 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1,11 +1,11 @@ - Безопасность назначений + Безопасность терапии Макс разрешенный болюс [U] ед. макс разрешенные углеводы (г) опции - синхронисировать назначения с NS + Синхронизировать назначения с NS обнулить базы Вы действительно хотите обнулить базы данных? Выход @@ -116,7 +116,7 @@ профиль NS простой профиль ВремБазал - назначения + Терапия виртуальная помпа Портал лечения / назначений помпа @@ -154,7 +154,7 @@ калькулятор применено ограничение! подтверждение - введите новое назначение + Ввести новое назначение: болюс болюс: базал @@ -690,7 +690,7 @@ Получение времени помпы повторное использование Контроль с часов - Поставить временные цели и ввести назначения с часов. + Ставить временные цели и вводить назначения с часов. Истекло время ожидания соединения Еда грамм @@ -1044,7 +1044,7 @@ Context | Edit Context AndroidAPS перезапущен Найдены сохраненные параметры Внимание: Если вы активируете подключение к невиртуальной помпе, AndroidAPS скопирует настройки базала в профиль помпы, перезаписывая существующие настройки, хранящиеся в ней. Убедитесь, что настройки базала в AndroidAPS корректны. Если вы не уверены или не хотите перезаписать настройки базала на помпу, нажмите отменить и повторите подключение в другое время. - Данные назначений неполные + Данные терапии неполные Параметры обслуживания Адрес электронной почты Недопустимый e-mail From 9be0ffffa9f37274c22833f1c112fb1ffd260277 Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Wed, 17 Apr 2019 21:28:32 +0200 Subject: [PATCH 016/152] verify static calls on mocked classes --- .../VersionCheckerUtilsKtTest.kt | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt index b1b62b589d..d711c4f958 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt @@ -63,24 +63,43 @@ class VersionCheckerUtilsKtTest { @PrepareForTest(MainApp::class, L::class, SP::class) fun `should find update1`() { val bus = prepareBus() + compareWithCurrentVersion(newVersion = "2.2.3", currentVersion = "2.2.1") + verify(bus, times(1)).post(any()) + + PowerMockito.verifyStatic(SP::class.java, times(1)) + SP.putLong(eq(R.string.key_new_version_available_since), ArgumentMatchers.anyLong()) + PowerMockito.verifyNoMoreInteractions(SP::class.java) + } @Test @PrepareForTest(MainApp::class, L::class, SP::class) fun `should find update2`() { val bus = prepareBus() + compareWithCurrentVersion(newVersion = "2.2.3", currentVersion = "2.2.1-dev") + verify(bus, times(1)).post(any()) + + PowerMockito.verifyStatic(SP::class.java, times(1)) + SP.putLong(eq(R.string.key_new_version_available_since), ArgumentMatchers.anyLong()) + PowerMockito.verifyNoMoreInteractions(SP::class.java) } @Test @PrepareForTest(MainApp::class, L::class, SP::class) fun `should find update3`() { val bus = prepareBus() + compareWithCurrentVersion(newVersion = "2.2.3", currentVersion = "2.1") + verify(bus, times(1)).post(any()) + + PowerMockito.verifyStatic(SP::class.java, times(1)) + SP.putLong(eq(R.string.key_new_version_available_since), ArgumentMatchers.anyLong()) + PowerMockito.verifyNoMoreInteractions(SP::class.java) } @Test @@ -91,6 +110,10 @@ class VersionCheckerUtilsKtTest { compareWithCurrentVersion(newVersion = "2.2", currentVersion = "2.1.1") verify(bus, times(1)).post(any()) + + PowerMockito.verifyStatic(SP::class.java, times(1)) + SP.putLong(eq(R.string.key_new_version_available_since), ArgumentMatchers.anyLong()) + PowerMockito.verifyNoMoreInteractions(SP::class.java) } @Test @@ -98,16 +121,25 @@ class VersionCheckerUtilsKtTest { fun `should find update5`() { val bus = prepareBus() compareWithCurrentVersion(newVersion = "2.2.1", currentVersion = "2.2-dev") + verify(bus, times(1)).post(any()) + + PowerMockito.verifyStatic(SP::class.java, times(1)) + SP.putLong(eq(R.string.key_new_version_available_since), ArgumentMatchers.anyLong()) + PowerMockito.verifyNoMoreInteractions(SP::class.java) } @Test @PrepareForTest(MainApp::class, L::class, SP::class) fun `should find update6`() { val bus = prepareBus() - val sp = prepareSP() compareWithCurrentVersion(newVersion = "2.2.1", currentVersion = "2.2dev") + verify(bus, times(1)).post(any()) + + PowerMockito.verifyStatic(SP::class.java, times(1)) + SP.putLong(eq(R.string.key_new_version_available_since), ArgumentMatchers.anyLong()) + PowerMockito.verifyNoMoreInteractions(SP::class.java) } @Test @@ -122,8 +154,12 @@ class VersionCheckerUtilsKtTest { """.trimMargin() val bus = prepareBus() compareWithCurrentVersion(buildGradle.byteInputStream().findVersion(), currentVersion = "2.2.2") + verify(bus, times(0)).post(any()) - verify(SP, times(1)).remove(R.string.key_new_version_available_since) + + PowerMockito.verifyStatic(SP::class.java, times(1)) + SP.remove(eq(R.string.key_new_version_available_since)) + PowerMockito.verifyNoMoreInteractions(SP::class.java) } @Test @@ -138,7 +174,12 @@ class VersionCheckerUtilsKtTest { """.trimMargin() val bus = prepareBus() compareWithCurrentVersion(buildGradle.byteInputStream().findVersion(), currentVersion = "2.2.2") + verify(bus, times(1)).post(any()) + + PowerMockito.verifyStatic(SP::class.java, times(1)) + SP.putLong(eq(R.string.key_new_version_available_since), ArgumentMatchers.anyLong()) + PowerMockito.verifyNoMoreInteractions(SP::class.java) } From 84573cc0ccdf6797532d177888f04043d1a70433 Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Wed, 17 Apr 2019 21:29:16 +0200 Subject: [PATCH 017/152] refactor constraints checker --- .../info/nightscout/androidaps/MainApp.java | 2 +- .../androidaps/data/ConstraintChecker.java | 67 +++++++++---------- .../versionChecker/VersionCheckerPlugin.kt | 21 +++++- .../interfaces/ConstraintsCheckerTest.java | 2 +- 4 files changed, 54 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index 8f626365d9..088f0a4bc3 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -119,7 +119,7 @@ public class MainApp extends Application { log.debug("onCreate"); sInstance = this; sResources = getResources(); - sConstraintsChecker = new ConstraintChecker(this); + sConstraintsChecker = new ConstraintChecker(); sDatabaseHelper = OpenHelperManager.getHelper(sInstance, DatabaseHelper.class); try { diff --git a/app/src/main/java/info/nightscout/androidaps/data/ConstraintChecker.java b/app/src/main/java/info/nightscout/androidaps/data/ConstraintChecker.java index 68664033d0..b1267e81bc 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/ConstraintChecker.java +++ b/app/src/main/java/info/nightscout/androidaps/data/ConstraintChecker.java @@ -1,7 +1,11 @@ package info.nightscout.androidaps.data; +import android.support.annotation.NonNull; + import java.util.ArrayList; +import javax.annotation.Nonnull; + import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.interfaces.Constraint; @@ -15,13 +19,6 @@ import info.nightscout.androidaps.interfaces.PluginType; public class ConstraintChecker implements ConstraintsInterface { - private MainApp mainApp; - - public ConstraintChecker(MainApp mainApp) { - this.mainApp = mainApp; - } - - public Constraint isLoopInvokationAllowed() { return isLoopInvocationAllowed(new Constraint<>(true)); } @@ -79,9 +76,9 @@ public class ConstraintChecker implements ConstraintsInterface { } @Override - public Constraint isLoopInvocationAllowed(Constraint value) { + public Constraint isLoopInvocationAllowed(@NonNull Constraint value) { - ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); for (PluginBase p : constraintsPlugins) { ConstraintsInterface constraint = (ConstraintsInterface) p; if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; @@ -91,9 +88,9 @@ public class ConstraintChecker implements ConstraintsInterface { } @Override - public Constraint isClosedLoopAllowed(Constraint value) { + public Constraint isClosedLoopAllowed(@NonNull Constraint value) { - ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); for (PluginBase p : constraintsPlugins) { ConstraintsInterface constraint = (ConstraintsInterface) p; if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; @@ -103,9 +100,9 @@ public class ConstraintChecker implements ConstraintsInterface { } @Override - public Constraint isAutosensModeEnabled(Constraint value) { + public Constraint isAutosensModeEnabled(@NonNull Constraint value) { - ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); for (PluginBase p : constraintsPlugins) { ConstraintsInterface constraint = (ConstraintsInterface) p; if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; @@ -115,9 +112,9 @@ public class ConstraintChecker implements ConstraintsInterface { } @Override - public Constraint isAMAModeEnabled(Constraint value) { + public Constraint isAMAModeEnabled(@NonNull Constraint value) { - ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); for (PluginBase p : constraintsPlugins) { ConstraintsInterface constrain = (ConstraintsInterface) p; if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; @@ -127,9 +124,9 @@ public class ConstraintChecker implements ConstraintsInterface { } @Override - public Constraint isSMBModeEnabled(Constraint value) { + public Constraint isSMBModeEnabled(@NonNull Constraint value) { - ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); for (PluginBase p : constraintsPlugins) { ConstraintsInterface constraint = (ConstraintsInterface) p; if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; @@ -139,9 +136,9 @@ public class ConstraintChecker implements ConstraintsInterface { } @Override - public Constraint isUAMEnabled(Constraint value) { + public Constraint isUAMEnabled(@NonNull Constraint value) { - ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); for (PluginBase p : constraintsPlugins) { ConstraintsInterface constraint = (ConstraintsInterface) p; if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; @@ -151,8 +148,8 @@ public class ConstraintChecker implements ConstraintsInterface { } @Override - public Constraint isAdvancedFilteringEnabled(Constraint value) { - ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + public Constraint isAdvancedFilteringEnabled(@NonNull Constraint value) { + ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); for (PluginBase p : constraintsPlugins) { ConstraintsInterface constraint = (ConstraintsInterface) p; if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; @@ -162,8 +159,8 @@ public class ConstraintChecker implements ConstraintsInterface { } @Override - public Constraint isSuperBolusEnabled(Constraint value) { - ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + public Constraint isSuperBolusEnabled(@NonNull Constraint value) { + ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); for (PluginBase p : constraintsPlugins) { ConstraintsInterface constraint = (ConstraintsInterface) p; if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; @@ -173,8 +170,8 @@ public class ConstraintChecker implements ConstraintsInterface { } @Override - public Constraint applyBasalConstraints(Constraint absoluteRate, Profile profile) { - ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + public Constraint applyBasalConstraints(@NonNull Constraint absoluteRate, Profile profile) { + ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); for (PluginBase p : constraintsPlugins) { ConstraintsInterface constraint = (ConstraintsInterface) p; if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; @@ -184,8 +181,8 @@ public class ConstraintChecker implements ConstraintsInterface { } @Override - public Constraint applyBasalPercentConstraints(Constraint percentRate, Profile profile) { - ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + public Constraint applyBasalPercentConstraints(@NonNull Constraint percentRate, Profile profile) { + ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); for (PluginBase p : constraintsPlugins) { ConstraintsInterface constrain = (ConstraintsInterface) p; if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; @@ -195,8 +192,8 @@ public class ConstraintChecker implements ConstraintsInterface { } @Override - public Constraint applyBolusConstraints(Constraint insulin) { - ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + public Constraint applyBolusConstraints(@NonNull Constraint insulin) { + ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); for (PluginBase p : constraintsPlugins) { ConstraintsInterface constrain = (ConstraintsInterface) p; if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; @@ -206,8 +203,8 @@ public class ConstraintChecker implements ConstraintsInterface { } @Override - public Constraint applyExtendedBolusConstraints(Constraint insulin) { - ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + public Constraint applyExtendedBolusConstraints(@NonNull Constraint insulin) { + ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); for (PluginBase p : constraintsPlugins) { ConstraintsInterface constrain = (ConstraintsInterface) p; if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; @@ -217,8 +214,8 @@ public class ConstraintChecker implements ConstraintsInterface { } @Override - public Constraint applyCarbsConstraints(Constraint carbs) { - ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + public Constraint applyCarbsConstraints(@NonNull Constraint carbs) { + ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); for (PluginBase p : constraintsPlugins) { ConstraintsInterface constrain = (ConstraintsInterface) p; if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; @@ -228,8 +225,8 @@ public class ConstraintChecker implements ConstraintsInterface { } @Override - public Constraint applyMaxIOBConstraints(Constraint maxIob) { - ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + public Constraint applyMaxIOBConstraints(@NonNull Constraint maxIob) { + ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); for (PluginBase p : constraintsPlugins) { ConstraintsInterface constrain = (ConstraintsInterface) p; if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt index 99268bcdd9..e3410549ee 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt @@ -17,6 +17,25 @@ object VersionCheckerPlugin : PluginBase(PluginDescription() .pluginName(R.string.versionChecker)), ConstraintsInterface { override fun isClosedLoopAllowed(value: Constraint): Constraint { - return value + return if (isVeryOldVersion()) + Constraint(false) + else + value } + + override fun applyMaxIOBConstraints(maxIob: Constraint): Constraint { + return if (isOldVersion()) + Constraint(0.toDouble()) + else + maxIob + } + + private fun isOldVersion(): Boolean { + return true + } + + private fun isVeryOldVersion(): Boolean { + return true + } + } diff --git a/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintsCheckerTest.java b/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintsCheckerTest.java index ee03ef73d7..fbbd72f064 100644 --- a/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintsCheckerTest.java +++ b/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintsCheckerTest.java @@ -289,7 +289,7 @@ public class ConstraintsCheckerTest { //SafetyPlugin when(ConfigBuilderPlugin.getPlugin().getActivePump()).thenReturn(pump); - constraintChecker = new ConstraintChecker(mainApp); + constraintChecker = new ConstraintChecker(); safetyPlugin = SafetyPlugin.getPlugin(); objectivesPlugin = ObjectivesPlugin.getPlugin(); From 4bde759a503c44800ec694095ffaeb5dc0263dd2 Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Wed, 17 Apr 2019 22:23:35 +0200 Subject: [PATCH 018/152] version checker plugin logic --- .../info/nightscout/androidaps/MainApp.java | 2 + .../overview/notifications/Notification.java | 1 + .../versionChecker/VersionCheckerPlugin.kt | 47 ++++++++++++++----- app/src/main/res/values/strings.xml | 5 ++ 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index 088f0a4bc3..e390d12007 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -50,6 +50,7 @@ import info.nightscout.androidaps.plugins.general.nsclient.receivers.DBAccessRec import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin; import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin; import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin; +import info.nightscout.androidaps.plugins.general.versionChecker.VersionCheckerPlugin; import info.nightscout.androidaps.plugins.general.wear.WearPlugin; import info.nightscout.androidaps.plugins.general.xdripStatusline.StatuslinePlugin; import info.nightscout.androidaps.plugins.insulin.InsulinOrefFreePeakPlugin; @@ -179,6 +180,7 @@ public class MainApp extends Application { if (Config.OTHERPROFILES) pluginsList.add(LocalProfilePlugin.getPlugin()); pluginsList.add(TreatmentsPlugin.getPlugin()); if (Config.SAFETY) pluginsList.add(SafetyPlugin.getPlugin()); + if (Config.SAFETY) pluginsList.add(VersionCheckerPlugin.INSTANCE); if (Config.SAFETY) pluginsList.add(StorageConstraintPlugin.getPlugin()); if (Config.APS) pluginsList.add(ObjectivesPlugin.getPlugin()); pluginsList.add(SourceXdripPlugin.getPlugin()); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/Notification.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/Notification.java index 3b6bcf615d..f7a4c32360 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/Notification.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/Notification.java @@ -74,6 +74,7 @@ public class Notification { public static final int DST_LOOP_DISABLED = 49; public static final int DST_IN_24H = 50; public static final int DISKFULL = 51; + public static final int OLDVERSION = 52; public int id; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt index e3410549ee..d80b3cc7d0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt @@ -1,7 +1,12 @@ package info.nightscout.androidaps.plugins.general.versionChecker +import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.R import info.nightscout.androidaps.interfaces.* +import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification +import info.nightscout.androidaps.utils.SP +import java.util.concurrent.TimeUnit /** * Usually we would have a class here. @@ -17,25 +22,43 @@ object VersionCheckerPlugin : PluginBase(PluginDescription() .pluginName(R.string.versionChecker)), ConstraintsInterface { override fun isClosedLoopAllowed(value: Constraint): Constraint { - return if (isVeryOldVersion()) - Constraint(false) + checkWarning() + return if (isOldVersion(GRACE_PERIOD_VERY_OLD)) + value.set(false, MainApp.gs(R.string.very_old_version), this) else value } - override fun applyMaxIOBConstraints(maxIob: Constraint): Constraint { - return if (isOldVersion()) - Constraint(0.toDouble()) - else - maxIob + private fun checkWarning() { + val now = System.currentTimeMillis() + if (isOldVersion(GRACE_PERIOD_WARNING) && shouldWarnAgain(now)) { + // store last notification time + SP.putLong(R.string.key_last_versionchecker_waring, now) + + //notify + val message = MainApp.gs(R.string.new_version_warning, Math.round(now / TimeUnit.DAYS.toMillis(1).toDouble())) + val notification = Notification(Notification.OLDVERSION, message, Notification.NORMAL) + MainApp.bus().post(EventNewNotification(notification)) + } } - private fun isOldVersion(): Boolean { - return true + private fun shouldWarnAgain(now: Long) = + now > SP.getLong(R.string.key_last_versionchecker_waring, 0) + WARN_EVERY + + override fun applyMaxIOBConstraints(maxIob: Constraint): Constraint = + if (isOldVersion(GRACE_PERIOD_OLD)) + maxIob.set(0.toDouble(), MainApp.gs(R.string.old_version), this) + else + maxIob + + private fun isOldVersion(gracePeriod: Long): Boolean { + val now = System.currentTimeMillis() + return now > SP.getLong(R.string.key_new_version_available_since, 0) + gracePeriod } - private fun isVeryOldVersion(): Boolean { - return true - } + val WARN_EVERY = TimeUnit.DAYS.toMillis(1) + val GRACE_PERIOD_WARNING = TimeUnit.DAYS.toMillis(30) + val GRACE_PERIOD_OLD = TimeUnit.DAYS.toMillis(60) + val GRACE_PERIOD_VERY_OLD = TimeUnit.DAYS.toMillis(90) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index feaab74c41..b149bd0f40 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1328,6 +1328,11 @@ Profile switch created Version Checker new_version_available_since + last_versionchecker_waring + old version + very old version + New version for at least %1$d days available! Fallback to LGS after 60 days, loop will be disabled after 90 days + %1$d day %1$d days From 092400f7a01a52847cbf2507ee00188b84de217d Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Wed, 17 Apr 2019 22:45:21 +0200 Subject: [PATCH 019/152] check regularly --- .../nightscout/androidaps/MainActivity.java | 8 ++++++- .../versionChecker/VersionCheckerPlugin.kt | 21 ++++++++++++++++--- app/src/main/res/values/strings.xml | 4 +++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/MainActivity.java b/app/src/main/java/info/nightscout/androidaps/MainActivity.java index 7ce5ae2beb..31d462a007 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/MainActivity.java @@ -48,7 +48,9 @@ import info.nightscout.androidaps.events.EventFeatureRunning; import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.events.EventRefreshGui; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus; import info.nightscout.androidaps.plugins.general.versionChecker.VersionCheckerUtilsKt; @@ -115,7 +117,11 @@ public class MainActivity extends AppCompatActivity { public void onPageScrollStateChanged(int state) { } }); - VersionCheckerUtilsKt.checkVersion(); + + //Check here if loop plugin is disabled. Else check via constraints + if (!LoopPlugin.getPlugin().isEnabled(PluginType.LOOP)) + VersionCheckerUtilsKt.checkVersion(); + FabricPrivacy.setUserStats(); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt index d80b3cc7d0..7be99e2369 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt @@ -23,6 +23,7 @@ object VersionCheckerPlugin : PluginBase(PluginDescription() override fun isClosedLoopAllowed(value: Constraint): Constraint { checkWarning() + checkUpdate() return if (isOldVersion(GRACE_PERIOD_VERY_OLD)) value.set(false, MainApp.gs(R.string.very_old_version), this) else @@ -33,7 +34,7 @@ object VersionCheckerPlugin : PluginBase(PluginDescription() val now = System.currentTimeMillis() if (isOldVersion(GRACE_PERIOD_WARNING) && shouldWarnAgain(now)) { // store last notification time - SP.putLong(R.string.key_last_versionchecker_waring, now) + SP.putLong(R.string.key_last_versionchecker_warning, now) //notify val message = MainApp.gs(R.string.new_version_warning, Math.round(now / TimeUnit.DAYS.toMillis(1).toDouble())) @@ -42,8 +43,21 @@ object VersionCheckerPlugin : PluginBase(PluginDescription() } } + private fun checkUpdate() { + val now = System.currentTimeMillis() + if (shouldCheckVersionAgain(now)) { + // store last notification time + SP.putLong(R.string.key_last_versioncheck, now) + + checkVersion() + } + } + + private fun shouldCheckVersionAgain(now: Long) = + now > SP.getLong(R.string.key_last_versioncheck, 0) + CHECK_EVERY + private fun shouldWarnAgain(now: Long) = - now > SP.getLong(R.string.key_last_versionchecker_waring, 0) + WARN_EVERY + now > SP.getLong(R.string.key_last_versionchecker_warning, 0) + WARN_EVERY override fun applyMaxIOBConstraints(maxIob: Constraint): Constraint = if (isOldVersion(GRACE_PERIOD_OLD)) @@ -53,9 +67,10 @@ object VersionCheckerPlugin : PluginBase(PluginDescription() private fun isOldVersion(gracePeriod: Long): Boolean { val now = System.currentTimeMillis() - return now > SP.getLong(R.string.key_new_version_available_since, 0) + gracePeriod + return now > SP.getLong(R.string.key_new_version_available_since, 0) + gracePeriod } + val CHECK_EVERY = TimeUnit.DAYS.toMillis(1) val WARN_EVERY = TimeUnit.DAYS.toMillis(1) val GRACE_PERIOD_WARNING = TimeUnit.DAYS.toMillis(30) val GRACE_PERIOD_OLD = TimeUnit.DAYS.toMillis(60) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b149bd0f40..9aa326361b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1328,7 +1328,9 @@ Profile switch created Version Checker new_version_available_since - last_versionchecker_waring + last_versionchecker_waring + key_last_versioncheck + old version very old version New version for at least %1$d days available! Fallback to LGS after 60 days, loop will be disabled after 90 days From 6c6b23066bf1fe1ed45bf34d560e6606f7daf9a1 Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Thu, 18 Apr 2019 00:33:24 +0200 Subject: [PATCH 020/152] mocking the time --- .../versionChecker/VersionCheckerUtilsKtTest.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt index d711c4f958..29725e4d9e 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt @@ -14,6 +14,7 @@ import org.powermock.api.mockito.PowerMockito import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner + @RunWith(PowerMockRunner::class) class VersionCheckerUtilsKtTest { @Test @@ -183,6 +184,15 @@ class VersionCheckerUtilsKtTest { } + @Test + @PrepareForTest(System::class) + fun `set time`() { + PowerMockito.spy(System::class.java) + PowerMockito.`when`(System.currentTimeMillis()).thenReturn(100L) + + assertEquals(100L, System.currentTimeMillis()) + } + private fun prepareBus(): Bus { PowerMockito.mockStatic(MainApp::class.java) val mainApp = mock(MainApp::class.java) @@ -199,8 +209,8 @@ class VersionCheckerUtilsKtTest { } private fun prepareLogging() { - PowerMockito.mockStatic(L::class.java) - `when`(L.isEnabled(any())).thenReturn(true) + PowerMockito.mockStatic(L::class.java) + `when`(L.isEnabled(any())).thenReturn(true) } } \ No newline at end of file From cb5197950e7c32fda4cb12a2d7779101899dfb35 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 18 Apr 2019 14:05:19 +0200 Subject: [PATCH 021/152] New translations strings.xml (Portuguese) --- app/src/main/res/values-pt/strings.xml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 6499f63e59..33d67541f7 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -9,6 +9,7 @@ Reinicializar base de dados Quer realmente reiniciar a base de dados? Sair + Usar bólus prolongado de >200%% Dispositivo Bluetooth DanaR Usar sempre valores absolutos de basal Por favor, reinicie o seu telefone ou reinicie o AndroidAPS a partir das Configurações do Sistema \ncaso contrário, o AndroidAPS não terá registro (importante para controlar e verificar se os algoritmos estão a funcionar corretamente)! @@ -313,6 +314,7 @@ Parar STOP PRESSIONADO À espera da bomba + Vão ser administradas %1$.2fU Configuração da visualização e monitoramento, e análise de rácios e basals Verificar se a BG está disponível no Nightscout, e se os dados de insulina da bomba estão a ser carregados A iniciar um open loop @@ -335,11 +337,23 @@ Loop desactivado Loop activado %1$.2f limitado a %2$.2f + O valor %1$s está fora dos limites permitidos O comando remoto não é permitido + O bólus remoto não está disponível. Tente novamente mais tarde. + Para começar a basal %1$.2fU/h durante%2$d min responda com o código %3$s + Para mudar o perfil para %1$s %2$d%% responda com o código %3$s + Para começar o bólus estendido %1$.2fU/h para %2$d min responda com o código %3$s + Para começar a basal %1$d% U/h durante %2$d min responda com o código %3$s Para suspender o loop por %1$d minutos resposta com código %2$s Basal temporária %1$.2fU/h para %2$d min iniciada com êxito + Bólus estendido %1$.2fU/h para %2$d min iniciado com êxito + Basal temporária %1$d% U/h durante%2$d min iniciada com êxito Início basal temp falhou + Falha ao iniciar o bólus estendido + Para parar a basal temporária responda com o código %1$s + Para parar o bólus temporário responda com o código %1$s Basal temp cancelada + Bólus estendido cancelado Não foi possivel cancelar a basal temp Falhou o cancelamento do bolus extendido Comando desconhecido ou resposta errada @@ -450,6 +464,7 @@ Perfil Valor padrão: 3 Por segurança é o valor limite estabelecido por OpenAPS. O que faz é limitar a basal a x3 a basal mázima. Se necessário modificar este valor, por favor ter em conta que os dados apontam para que os limites de segurança sejam - 3 x max diario ou 4x valor actual (qual seja menor) como valores máximos. Valor padrão: 4 Esta é a outra parte dos valores limites de segurança - 3 x max diário ou 4x valor actual - do OpenAPS. Isto define que não importa o valor da basal máxima definido na bomba, o valor máximo da basal temporária não pode ser maior que 4 x o valor da basal definida na bomba. O objectivo é evitar que sejam determinadas basais temporárias demasiado elevadas antes que se perceba como o algoritmo funciona. 4x é um valor que a maior parte das pessoas nunca necessitará de alterar pois o mais provável é necessitar de alterar outras definições para não necessitar de \"ultrapassar\" este limite de segurança. + Valor predefinido: 1.2\n Este é um multiplicador para autosens (e em breve autotune) que coloca um limite máximo de 20%% aos rácios superiores e inferiores de autosens, o que por sua vez calcula o quão alto autosens pode ajustar a basal, quão pode baixar o Factor de Sensibilidade (FSI) e baixar o valor alvo de glicose no sangue. Valor padrão: 0.7\nO outro lado dos limites de segurança do autosens. Coloca um tecto no quão baixo autosens pode ajustar as basais, e quão alto pode ajustar os valores de ISF e valor alvo de glucose no sangue (BG). Autosens também ajusta os alvos Valor padrão: true\nÉ usado para permitir que autosens possa ajustar os valores alvo de glucose no sangue (BG), além de ISF e basais. @@ -462,6 +477,7 @@ Número de telefone não é válido SMS número de telefone inválido Calibração + Enviar calibração %1$.1f para o xDrip? xDrip+ não está instalado Calibração enviada para o xDrip Calibração enviada. Recepção têm de estar activada no xDrip. @@ -602,6 +618,7 @@ Configurações de absorção Tempo máx. absorção refeição [h] Tempo em horas, espectável para que todos os hidratos de carbono da refeição sejam absorvidos + Visualizar bólus prolongado como %% SAGE IAGE CAGE @@ -714,6 +731,8 @@ Enviar dados Glic. para xDrip+ Seleccionar 640g/Eversense como fonte no xDrip+ Glic NSCliente + Valor da basal alterado para o valor mínimo suportado: %1$s + Valor da basal alterado para o valor máximo suportado: %1$s Cálculos Glic Cálculo de Bólus IOB Cálculo de Basal IOB @@ -731,6 +750,8 @@ Modo fechado ativado IOB máxima definida correctamente Glicemia disponivel desde a fonte selecionada + Valores das basais não definidos por horas: %1$s + Perfil inválido: %1$s A programar a bomba para injectar o bolus Actualizar Estado @@ -1072,6 +1093,7 @@ Alterações do modo de funcionamento de registo Alertas de registo Ativar a emulação TBR + Usar bólus prolongados em vez de basais temporárias para contornar o limite de 250%% Atraso de desconexão [s] Número de série Lançar versão de software @@ -1111,11 +1133,13 @@ Basal temporária: %1$d%% para %2$d / %3$d minutos Estendido: %1$.2f / %2$.2f U por %3$d min Multionda: %1$.2f / %2$.2f U por %3$d min + TDD: %1$.2f Reser:%1$.2fU Bat.: %1$d%% Duração máxima da recuperação [s] Duração mínima da recuperação [s] Duração da recuperação + Tempo de operação excedido - reset bluetooth == ∑ %1$s U U/h g/U @@ -1124,6 +1148,8 @@ Criar evento \"Mudança de Sensor\" automaticamente no NS aquando do início do sensor Tomato (MiaoMiao) Tomato + Horário de Verão em 24h ou menos + Horário de Verão a menos de 3 horas - Closed Loop desligado restrição de armazenamento interno Liberte pelo menos %1$d MB do armazenamento interno! Loop desativado! Formato incorrecto From 619514e159feda8e86b3b7a8acd7da0791c3689f Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sun, 21 Apr 2019 10:22:27 +0200 Subject: [PATCH 022/152] fix bugs in strings.xml --- app/src/main/res/values/strings.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 79d626f9be..010005d55f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -152,8 +152,8 @@ Constraints violation Bolus delivery error Tempbasal delivery error - Basal value [%] - % (100% = current) + Basal value [%%] + %% (100%% = current) Accept new temp basal: Treatment Calculator @@ -276,7 +276,7 @@ Pump IOB Daily units Last bolus - %.1fh ago + %1$.1fh ago Invalid input data Value not set properly Reload profile @@ -582,7 +582,7 @@ Please select patient age to setup safety limits I_understand Glimp - %s needs battery optimalization whitelisting for proper performance + %1$s needs battery optimalization whitelisting for proper performance Loop suspended Suspended (%1$d m) Superbolus (%1$d m) @@ -624,9 +624,9 @@ key_statuslights_res_critical Threshold critical reservoir level [U] key_statuslights_bat_warning - Threshold warning battery level [%] + Threshold warning battery level [%%] key_statuslights_bat_critical - Threshold critical battery level [%] + Threshold critical battery level [%%] IOB COB Firmware @@ -869,7 +869,7 @@ Running Cancelling TBR Setting TBR (%1$d%% / %2$d min) - Bolusing (%.1f U) + Bolusing (%1$.1f U) Refreshing Requested operation not supported by pump Unsafe usage: extended or multiwave boluses are active. Loop mode has been set to low-suspend only 6 hours. Only normal boluses are supported in loop mode From 0361ee858944fff8bab395aaa35e35a62a9cc207 Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Mon, 22 Apr 2019 14:10:12 +0200 Subject: [PATCH 023/152] update logic version checker --- .../nightscout/androidaps/MainActivity.java | 2 +- .../info/nightscout/androidaps/MainApp.java | 5 +++ .../versionChecker/VersionCheckerPlugin.kt | 22 ++--------- .../versionChecker/VersionCheckerUtils.kt | 37 +++++++++++++++---- app/src/main/res/values/strings.xml | 4 +- .../VersionCheckerUtilsKtTest.kt | 30 +++++++++++---- 6 files changed, 63 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/MainActivity.java b/app/src/main/java/info/nightscout/androidaps/MainActivity.java index 31d462a007..6779b0a467 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/MainActivity.java @@ -120,7 +120,7 @@ public class MainActivity extends AppCompatActivity { //Check here if loop plugin is disabled. Else check via constraints if (!LoopPlugin.getPlugin().isEnabled(PluginType.LOOP)) - VersionCheckerUtilsKt.checkVersion(); + VersionCheckerUtilsKt.triggerCheckVersion(); FabricPrivacy.setUserStats(); } diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index e390d12007..37372ae0d4 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -89,6 +89,8 @@ import info.nightscout.androidaps.services.Intents; import info.nightscout.androidaps.utils.FabricPrivacy; import io.fabric.sdk.android.Fabric; +import static info.nightscout.androidaps.plugins.general.versionChecker.VersionCheckerUtilsKt.triggerCheckVersion; + public class MainApp extends Application { private static Logger log = LoggerFactory.getLogger(L.CORE); @@ -149,6 +151,9 @@ public class MainApp extends Application { registerLocalBroadcastReceiver(); + //trigger here to see the new version on app start after an update + triggerCheckVersion(); + if (pluginsList == null) { pluginsList = new ArrayList<>(); // Register all tabs in app here diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt index 7be99e2369..fc773aaaaf 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt @@ -23,7 +23,7 @@ object VersionCheckerPlugin : PluginBase(PluginDescription() override fun isClosedLoopAllowed(value: Constraint): Constraint { checkWarning() - checkUpdate() + triggerCheckVersion() return if (isOldVersion(GRACE_PERIOD_VERY_OLD)) value.set(false, MainApp.gs(R.string.very_old_version), this) else @@ -34,7 +34,7 @@ object VersionCheckerPlugin : PluginBase(PluginDescription() val now = System.currentTimeMillis() if (isOldVersion(GRACE_PERIOD_WARNING) && shouldWarnAgain(now)) { // store last notification time - SP.putLong(R.string.key_last_versionchecker_warning, now) + SP.putLong(R.string.key_last_versionchecker_plugin_warning, now) //notify val message = MainApp.gs(R.string.new_version_warning, Math.round(now / TimeUnit.DAYS.toMillis(1).toDouble())) @@ -43,21 +43,8 @@ object VersionCheckerPlugin : PluginBase(PluginDescription() } } - private fun checkUpdate() { - val now = System.currentTimeMillis() - if (shouldCheckVersionAgain(now)) { - // store last notification time - SP.putLong(R.string.key_last_versioncheck, now) - - checkVersion() - } - } - - private fun shouldCheckVersionAgain(now: Long) = - now > SP.getLong(R.string.key_last_versioncheck, 0) + CHECK_EVERY - private fun shouldWarnAgain(now: Long) = - now > SP.getLong(R.string.key_last_versionchecker_warning, 0) + WARN_EVERY + now > SP.getLong(R.string.key_last_versionchecker_plugin_warning, 0) + WARN_EVERY override fun applyMaxIOBConstraints(maxIob: Constraint): Constraint = if (isOldVersion(GRACE_PERIOD_OLD)) @@ -67,10 +54,9 @@ object VersionCheckerPlugin : PluginBase(PluginDescription() private fun isOldVersion(gracePeriod: Long): Boolean { val now = System.currentTimeMillis() - return now > SP.getLong(R.string.key_new_version_available_since, 0) + gracePeriod + return now > SP.getLong(R.string.key_last_time_this_version_detected, 0) + gracePeriod } - val CHECK_EVERY = TimeUnit.DAYS.toMillis(1) val WARN_EVERY = TimeUnit.DAYS.toMillis(1) val GRACE_PERIOD_WARNING = TimeUnit.DAYS.toMillis(30) val GRACE_PERIOD_OLD = TimeUnit.DAYS.toMillis(60) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt index b8cfa7fffc..1b01ea70de 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt @@ -15,6 +15,7 @@ import org.apache.http.impl.client.DefaultHttpClient import org.slf4j.LoggerFactory import java.io.IOException import java.io.InputStream +import java.util.concurrent.TimeUnit // check network connection fun isConnected(): Boolean { @@ -35,8 +36,16 @@ inline fun InputStream.findVersion(): String? { private val log = LoggerFactory.getLogger(L.CORE) + +fun triggerCheckVersion() { + // If we are good, only check once every day. + if(System.currentTimeMillis() > SP.getLong(R.string.key_last_time_this_version_detected, 0) + CHECK_EVERY){ + checkVersion() + } +} + @Suppress("DEPRECATION") -fun checkVersion() = if (isConnected()) { +private fun checkVersion() = if (isConnected()) { Thread { try { val request = HttpGet("https://raw.githubusercontent.com/MilosKozak/AndroidAPS/master/app/build.gradle") @@ -56,12 +65,17 @@ fun compareWithCurrentVersion(newVersion: String?, currentVersion: String) { comparison == null -> onVersionNotDetectable() comparison == 0 -> onSameVersionDetected() comparison > 0 -> onNewVersionDetected(currentVersion = currentVersion, newVersion = newVersion) - else -> log.debug("Version newer than master. Are you developer?") + else -> onOlderVersionDetected() } } +private fun onOlderVersionDetected() { + log.debug("Version newer than master. Are you developer?") + SP.putLong(R.string.key_last_time_this_version_detected, System.currentTimeMillis()) +} + fun onSameVersionDetected() { - SP.remove(R.string.key_new_version_available_since) + SP.putLong(R.string.key_last_time_this_version_detected, System.currentTimeMillis()) } fun onVersionNotDetectable() { @@ -69,10 +83,13 @@ fun onVersionNotDetectable() { } fun onNewVersionDetected(currentVersion: String, newVersion: String?) { - log.debug("Version ${currentVersion} outdated. Found $newVersion") - val notification = Notification(Notification.NEWVERSIONDETECTED, String.format(MainApp.gs(R.string.versionavailable), newVersion.toString()), Notification.LOW) - MainApp.bus().post(EventNewNotification(notification)) - SP.putLong(R.string.key_new_version_available_since, System.currentTimeMillis()) + val now = System.currentTimeMillis() + if(now > SP.getLong(R.string.key_last_versionchecker_warning, 0) + WARN_EVERY) { + log.debug("Version ${currentVersion} outdated. Found $newVersion") + val notification = Notification(Notification.NEWVERSIONDETECTED, String.format(MainApp.gs(R.string.versionavailable), newVersion.toString()), Notification.LOW) + MainApp.bus().post(EventNewNotification(notification)) + SP.putLong(R.string.key_last_versionchecker_warning, now) + } } fun String.versionStrip() = this.mapNotNull { @@ -81,4 +98,8 @@ fun String.versionStrip() = this.mapNotNull { '.' -> it else -> null } -}.joinToString(separator = "") \ No newline at end of file +}.joinToString(separator = "") + + +val CHECK_EVERY = TimeUnit.DAYS.toMillis(1) +val WARN_EVERY = TimeUnit.DAYS.toMillis(1) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9aa326361b..6722bc7786 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1327,9 +1327,9 @@ Not configured Profile switch created Version Checker - new_version_available_since + last_time_this_version_detected last_versionchecker_waring - key_last_versioncheck + last_versionchecker_plugin_waring old version very old version diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt index 29725e4d9e..2d07e40c1d 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt @@ -70,7 +70,9 @@ class VersionCheckerUtilsKtTest { verify(bus, times(1)).post(any()) PowerMockito.verifyStatic(SP::class.java, times(1)) - SP.putLong(eq(R.string.key_new_version_available_since), ArgumentMatchers.anyLong()) + SP.getLong(eq(R.string.key_last_versionchecker_warning), ArgumentMatchers.anyLong()) + PowerMockito.verifyStatic(SP::class.java, times(1)) + SP.putLong(eq(R.string.key_last_versionchecker_warning), ArgumentMatchers.anyLong()) PowerMockito.verifyNoMoreInteractions(SP::class.java) } @@ -85,7 +87,9 @@ class VersionCheckerUtilsKtTest { verify(bus, times(1)).post(any()) PowerMockito.verifyStatic(SP::class.java, times(1)) - SP.putLong(eq(R.string.key_new_version_available_since), ArgumentMatchers.anyLong()) + SP.getLong(eq(R.string.key_last_versionchecker_warning), ArgumentMatchers.anyLong()) + PowerMockito.verifyStatic(SP::class.java, times(1)) + SP.putLong(eq(R.string.key_last_versionchecker_warning), ArgumentMatchers.anyLong()) PowerMockito.verifyNoMoreInteractions(SP::class.java) } @@ -99,7 +103,9 @@ class VersionCheckerUtilsKtTest { verify(bus, times(1)).post(any()) PowerMockito.verifyStatic(SP::class.java, times(1)) - SP.putLong(eq(R.string.key_new_version_available_since), ArgumentMatchers.anyLong()) + SP.getLong(eq(R.string.key_last_versionchecker_warning), ArgumentMatchers.anyLong()) + PowerMockito.verifyStatic(SP::class.java, times(1)) + SP.putLong(eq(R.string.key_last_versionchecker_warning), ArgumentMatchers.anyLong()) PowerMockito.verifyNoMoreInteractions(SP::class.java) } @@ -113,7 +119,9 @@ class VersionCheckerUtilsKtTest { verify(bus, times(1)).post(any()) PowerMockito.verifyStatic(SP::class.java, times(1)) - SP.putLong(eq(R.string.key_new_version_available_since), ArgumentMatchers.anyLong()) + SP.getLong(eq(R.string.key_last_versionchecker_warning), ArgumentMatchers.anyLong()) + PowerMockito.verifyStatic(SP::class.java, times(1)) + SP.putLong(eq(R.string.key_last_versionchecker_warning), ArgumentMatchers.anyLong()) PowerMockito.verifyNoMoreInteractions(SP::class.java) } @@ -126,7 +134,9 @@ class VersionCheckerUtilsKtTest { verify(bus, times(1)).post(any()) PowerMockito.verifyStatic(SP::class.java, times(1)) - SP.putLong(eq(R.string.key_new_version_available_since), ArgumentMatchers.anyLong()) + SP.getLong(eq(R.string.key_last_versionchecker_warning), ArgumentMatchers.anyLong()) + PowerMockito.verifyStatic(SP::class.java, times(1)) + SP.putLong(eq(R.string.key_last_versionchecker_warning), ArgumentMatchers.anyLong()) PowerMockito.verifyNoMoreInteractions(SP::class.java) } @@ -139,7 +149,9 @@ class VersionCheckerUtilsKtTest { verify(bus, times(1)).post(any()) PowerMockito.verifyStatic(SP::class.java, times(1)) - SP.putLong(eq(R.string.key_new_version_available_since), ArgumentMatchers.anyLong()) + SP.getLong(eq(R.string.key_last_versionchecker_warning), ArgumentMatchers.anyLong()) + PowerMockito.verifyStatic(SP::class.java, times(1)) + SP.putLong(eq(R.string.key_last_versionchecker_warning), ArgumentMatchers.anyLong()) PowerMockito.verifyNoMoreInteractions(SP::class.java) } @@ -159,7 +171,7 @@ class VersionCheckerUtilsKtTest { verify(bus, times(0)).post(any()) PowerMockito.verifyStatic(SP::class.java, times(1)) - SP.remove(eq(R.string.key_new_version_available_since)) + SP.putLong(eq(R.string.key_last_time_this_version_detected), ArgumentMatchers.anyLong()) PowerMockito.verifyNoMoreInteractions(SP::class.java) } @@ -179,7 +191,9 @@ class VersionCheckerUtilsKtTest { verify(bus, times(1)).post(any()) PowerMockito.verifyStatic(SP::class.java, times(1)) - SP.putLong(eq(R.string.key_new_version_available_since), ArgumentMatchers.anyLong()) + SP.getLong(eq(R.string.key_last_versionchecker_warning), ArgumentMatchers.anyLong()) + PowerMockito.verifyStatic(SP::class.java, times(1)) + SP.putLong(eq(R.string.key_last_versionchecker_warning), ArgumentMatchers.anyLong()) PowerMockito.verifyNoMoreInteractions(SP::class.java) } From 249121f80de6b31e01ae50ffbd1a9ffc80d421de Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Tue, 23 Apr 2019 21:22:44 +0200 Subject: [PATCH 024/152] outdated on install edge case --- .../plugins/general/versionChecker/VersionCheckerUtils.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt index 1b01ea70de..8791030abb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt @@ -38,6 +38,12 @@ private val log = LoggerFactory.getLogger(L.CORE) fun triggerCheckVersion() { + + if(!SP.contains(R.string.key_last_time_this_version_detected)) { + // On a new installation, set it as 30 days old in order to warn that there is a new version. + SP.putLong(R.string.key_last_time_this_version_detected, System.currentTimeMillis() - TimeUnit.DAYS.toMillis(30)) + } + // If we are good, only check once every day. if(System.currentTimeMillis() > SP.getLong(R.string.key_last_time_this_version_detected, 0) + CHECK_EVERY){ checkVersion() From 0cab8c1d898e57c466741373c7d44e62cbdce9c1 Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Tue, 23 Apr 2019 21:40:10 +0200 Subject: [PATCH 025/152] warn at date --- .../plugins/general/versionChecker/VersionCheckerPlugin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt index fc773aaaaf..2a4c08a585 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt @@ -37,7 +37,7 @@ object VersionCheckerPlugin : PluginBase(PluginDescription() SP.putLong(R.string.key_last_versionchecker_plugin_warning, now) //notify - val message = MainApp.gs(R.string.new_version_warning, Math.round(now / TimeUnit.DAYS.toMillis(1).toDouble())) + val message = MainApp.gs(R.string.new_version_warning, Math.round((now - SP.getLong(R.string.key_last_time_this_version_detected, now)) / TimeUnit.DAYS.toMillis(1).toDouble())) val notification = Notification(Notification.OLDVERSION, message, Notification.NORMAL) MainApp.bus().post(EventNewNotification(notification)) } From ee82976fc5ab6a87d9d1b37a08ce6f6432ffe340 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Wed, 24 Apr 2019 09:07:12 +0200 Subject: [PATCH 026/152] New Crowdin translations (#1752) * New translations strings.xml (Portuguese) * New translations strings.xml (Portuguese) * New translations strings.xml (Slovak) * New translations strings.xml (French) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Polish) * New translations strings.xml (Korean) * New translations strings.xml (Portuguese) * New translations strings.xml (Italian) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Greek) * New translations strings.xml (French) * New translations strings.xml (Romanian) * New translations strings.xml (Turkish) * New translations strings.xml (Swedish) * New translations strings.xml (Spanish) * New translations strings.xml (Dutch) * New translations strings.xml (Russian) * New translations strings.xml (Lithuanian) * New translations strings.xml (German) * New translations strings.xml (Czech) * New translations strings.xml (Bulgarian) * New translations strings.xml (Afrikaans) * New translations strings.xml (Slovak) * New translations strings.xml (Russian) * New translations strings.xml (Russian) * New translations strings.xml (German) * New translations strings.xml (Russian) * New translations strings.xml (German) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Lithuanian) * New translations strings.xml (Slovak) * New translations strings.xml (Polish) * New translations strings.xml (Polish) * New translations strings.xml (Polish) * New translations strings.xml (Polish) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Czech) * New translations strings.xml (Polish) * New translations strings.xml (Lithuanian) * New translations strings.xml (Lithuanian) * New translations strings.xml (French) * New translations strings.xml (Bulgarian) * New translations strings.xml (Greek) * New translations strings.xml (Portuguese) --- app/src/main/res/values-af/strings.xml | 7 ---- app/src/main/res/values-bg/strings.xml | 18 +++++---- app/src/main/res/values-cs/strings.xml | 18 +++++---- app/src/main/res/values-de/strings.xml | 16 +++++--- app/src/main/res/values-el/strings.xml | 18 +++++---- app/src/main/res/values-es/strings.xml | 7 ---- app/src/main/res/values-fr/strings.xml | 18 +++++---- app/src/main/res/values-it/strings.xml | 5 --- app/src/main/res/values-ko/strings.xml | 5 --- app/src/main/res/values-lt/strings.xml | 20 ++++++---- app/src/main/res/values-nl/strings.xml | 7 ---- app/src/main/res/values-pl/strings.xml | 51 ++++++++++++++++++++++---- app/src/main/res/values-pt/strings.xml | 22 ++++++----- app/src/main/res/values-ro/strings.xml | 7 ---- app/src/main/res/values-ru/strings.xml | 18 +++++---- app/src/main/res/values-sk/strings.xml | 18 +++++---- app/src/main/res/values-sv/strings.xml | 7 ---- app/src/main/res/values-tr/strings.xml | 5 --- app/src/main/res/values-zh/strings.xml | 39 ++++++++++++++++---- 19 files changed, 177 insertions(+), 129 deletions(-) diff --git a/app/src/main/res/values-af/strings.xml b/app/src/main/res/values-af/strings.xml index 1200a8e2e3..a0a0048ca1 100644 --- a/app/src/main/res/values-af/strings.xml +++ b/app/src/main/res/values-af/strings.xml @@ -147,8 +147,6 @@ Beperkings skending Bolus aflewering fout Tempbasal aflewering fout - Basale waarde [%] - % (100% =huidig) Aanvaar nuwe tydelike basale: Behandeling Rekenaar @@ -248,7 +246,6 @@ Pomp IAB Daaglikse eenhede Laaste bolus - %.1fh gelede Ongeldige invoer Waarde nie behoorlik gestel Herlaai profiel @@ -528,7 +525,6 @@ Insulien weerstandige volwasse Kies asseblief pasiënt ouderdom om veiligheids limiete te stel Glimp - %s benodig battery optimisering whitelisting vir behoorlike prestasie Lus opgeskort Opgeskort (%1$d m) Superbolus (%1$d m) @@ -564,8 +560,6 @@ Wys status vlae vir cage, iage, sage, stoor en battery vlak op tuisskerm. Drempel waarskuwings stoor vlak [U] Drempel kritieke stoor vlak [U] - Drempel waarskuwing battery vlak [%] - Drempel kritieke battery vlak [%] IAB KOB Firmware @@ -764,7 +758,6 @@ Loop tans Kanselleer TBR Stel TBR (%1$d%% / %2$d min) - Bolusing (%.1f U) Verfris Versoekte bewerking nie ondersteun deur pomp Onveilige gebruik: uitgebreide of multigolf boluses is aktief. Lus modus is gestel om laag te opskort net 6 ure. Net gewone boluses is ondersteun in lus modus diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 61b87d0f13..8453a50ffe 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -147,8 +147,8 @@ Нарушено ограничение Грешка при подаване на болус Грешка при подаване на временен базал - Стойност на базал [%] - % (100% = текущ) + Базална стойност [%%] + %% (100%% = текущ) Приложи нов временен базал: Болус Калкулатор @@ -248,7 +248,7 @@ IOB на помпата Инсулин за деня Последен болус: - преди %.1fч + преди %1$.1fч Грешни входящи данни Неправилна стойност Презареди профил @@ -528,7 +528,7 @@ Възрастни с голяма инсулинова резистентност Изберете възраст за определяне лимитите на безопасност Glimp - %s се нуждае от зключване на оптимизиране на батерията за пълна функционалност + %1$s се нуждае от изключване от списъка за оптимизиране на батерията за пълна функционалност Loop изключен Изключен (%1$d мин) Суперболус (%1$d мин) @@ -564,8 +564,8 @@ Покажи статус светлини за канула, инсулин, сензор, резервоар и батерията на началния екран. Ниво за аларма за останал инсулин в резервоара [Е] Критично ниво на останал инсулин в резервоар [Е] - Аларма за заряд на батерия [%] - Критично ниво на батерията под [%] + Ниско ниво на батерията под [%%] + Критично ниво на батерията под [%%] IOB СОВ Фърмуер @@ -764,7 +764,7 @@ Изпълнява Отказва временен базал Задава временен базал (%1$d%% / %2$d min) - Болус (%.1f U) + Болус (%1$.1f Е) Обновява Исканата операция не се поддържа от помпата Опасно използване: удължени или многовълнови болуси са активни. Помпата е изключена за само 6 часа. Само нормални болуси се поддържат. @@ -1156,6 +1156,10 @@ Грешен код. Командата не е изпълнена. Не е конфигуриран Създаден запис - Промяна на профил + Проверка на версията + стара версия + много стара версия + Има нова версия от най-малко %1$d дни! Връщане към LGS след 60 дни, цикълът ще бъде изключен след 90 дни %1$d дeн %1$d дни diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 61864568d3..96a66c5b8b 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -147,8 +147,8 @@ Mimo povolený rozsah Chyba podání bolusu Chyba nastavování dočasného bazálu - Hodnota bazálu [%] - % (100% = současný) + Hodnota bazálu [%%] + %% (100%% = současný) Spustit nový dočasný bazál: Bolus Kalkulačka @@ -248,7 +248,7 @@ IOB z pumpy Jednotek za den Poslední bolus - před %.1fh + před %1$.1fh Chybná vstupní data Hodnota nenastavena správně Obnovit profil @@ -528,7 +528,7 @@ Dospělý s nízkou citlivostí Vyberte věk pacienta pro nastavení bezpečnostních limitů Glimp - %s potřebuje vypnout optimalizace baterie pro optimální výkon + %1$s potřebuje vypnout optimalizace baterie pro optimální výkon Smyčka pozastavena Pozastaveno (%1$d min) Superbolus (%1$d m) @@ -564,8 +564,8 @@ Povolte stavové indikátory pro stáří kanyly, inzulínu, senzoru, zásobníku a baterie na domovské obrazovce. Úroveň varování stavu zásobníku [U] Úroveň kritického varování stavu zásobníku [U] - Úroveň varování stavu baterie [%] - Úroveň kritického varování stavu baterie [%] + Úroveň varování stavu baterie [%%] + Úroveň kritického varování stavu baterie [%%] IOB COB Firmware @@ -764,7 +764,7 @@ Běží Rušení dočasného bazálu Nastavování doč. bazálu (%1$d%% / %2$d min) - Bolus (%.1f U) + Bolus (%1$.1f U) Obnovování Požadovaná operace není pumpou podporována Nebezpečné použití: extended nebo multiwave bolus je aktivní. Pumpa byla vypnuta jen na 6 hodin. Povolené jsou pouze normální bolusy. @@ -1156,6 +1156,10 @@ Nesprávný kód. Příkaz zrušen. Není nakonfigurováno Přepnutí profilu vytvořeno + Kontrola verze + stará verze + velmi stará verze + Nová verze je dostupná minimálně %1$d dní! Přepnutí na ochranu před nízkou glykémií po 60 dnech, vypnutí smyčky po 90 dnech %1$d den %1$d dnů diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index ed46e6a8ea..2d0a15d6ac 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -147,8 +147,8 @@ Beschränkungen wurden verletzt oder Limit erreicht. Bolus-Abgabefehler TBR Abgabe-Fehler - Basal-Wert [%] - % (100% = aktuell) + Basal-Wert [%%] + %% (100%% = aktuell) Akzeptiere neue TBR: Bolus Rechner @@ -248,7 +248,7 @@ Pumpen-IOB Tägliche Einheiten Letzter Bolus: - vor %.1f h + vor %1$.1f Stunde Ungültige Eingabedaten Wert nicht korrekt gesetzt Profil neuladen @@ -529,7 +529,7 @@ maxIOB = durchschnittlicher Essensbolus + 3 x maximale Basalrate Insulinresistenter Erwachsener Bitte wähle das Patientenalter, um die Sicherheits-Limits festzulegen Glimp - %s benötigt eine deaktivierte Akku-Leistungsoptimierung, um korrekt arbeiten zu können. + %1$s benötigt eine deaktivierte Akku-Leistungsoptimierung, um korrekt arbeiten zu können. Loop pausiert Pausiert (%1$d min) Superbolus (%1$d m) @@ -565,8 +565,8 @@ maxIOB = durchschnittlicher Essensbolus + 3 x maximale Basalrate Statusanzeige für CAGE, IAGE, SAGE, Reservoir- und Batteriestand auf dem Homescreen. Warnschwelle Reservoirstand [IE] Warnschwelle kritischer Reservoirstand [IE] - Warnschwelle Batteriestand [%] - Warnschwelle kritischer Batteriestand [%] + Warnschwelle Batteriestand [%%] + Warnschwelle kritischer Batteriestand [%%] IOB COB Firmware @@ -1158,6 +1158,10 @@ Unerwartetes Verhalten. Falscher Code. Befehl wurde abgebrochen. Nicht konfiguriert Profilwechsel wurde erstellt + Versionsprüfer + Vorherige Version + sehr alte Version + Neue Version für mindestens %1$d Tage verfügbar! Rückfall zur sensorunterstützten Pumpentherapie nach 60 Tagen, Loop wird nach 90 Tagen deaktiviert %1$d Tag %1$d Tage diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 155afd10d0..9872c77080 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -147,8 +147,8 @@ Παραβίαση Περιορισμών Σφάλμα παράδοσης Bolus Σφάλμα παράδοσης Προσ Ρυθμού - Τιμή Βασικού [%] - % (100% = τρέχων) + Τιμή Βασικού [%%] + %% (100%% = τρέχων) Αποδοχή νέου Προσ Ρυθμού: Θεραπεία Υπολογιστής @@ -248,7 +248,7 @@ IOB αντλίας Μονάδες ανά ημέρα Τελευταίο Bolus: - πριν από %.1fh + %1$.1fώρες πριν Μη έγκυρα δεδομένα Η τιμή δεν μπήκε σωστά Ξαναφορτώστε το προφίλ @@ -528,7 +528,7 @@ Αντίσταση ινσουλίνης ενηλίκων Παρακαλώ επιλέξτε ηλικία χρήστη για να ορισθούν όρια ασφαλείας Glimp - Το %s πρέπει να απενεργοποιήσει τη βελτιστοποίηση της μπαταρίας για βέλτιστη απόδοση + %1$s χρειάζεται λίστα βελτιστοποίησης μπαταρίας για σωστή απόδοση Κύκλωμα σε αναστολή Αναστολή (%1$d m) Superbolus (%1$d m) @@ -564,8 +564,8 @@ Ενεργοποίηση του φωτισμού κατάστασης για cage, iage, sage, reservoir και επίπεδο μπεταρίας στην αρχική οθόνη. Όριο προειδοποίησης χαμηλής αμπούλας [U] Όριο προειδοποίησης πολύ χαμηλής αμπούλας [U] - Όριο προειδοποίησης χαμηλής μπαταρίας [%] - Όριο προειδοποίησης πολύ χαμηλής μπαταρίας [%] + Όριο προειδοποίησης χαμηλής μπαταρίας [%%] + Όριο προειδοποίησης πολύ χαμηλής μπαταρίας [%%] IOB COB Έκδοση @@ -764,7 +764,7 @@ Εκτελείτε Ακυρώνεται TBR Ρύθμιση TBR (%1$d%% / %2$d λεπτά) - Bolus (%.1f U) + Bolus (%1$.1f U) Ανανέωση Η λειτουργία που ζητήθηκε δεν υποστηρίζεται από την αντλία Επικίνδυνη χρήση: Εκτεταμένο ή πολλαπλά bolus είναι ενεργό. Το κύκλωμα απενεργοποιήθηκε λόγω low-suspend μόνο για 6 ώρες. Επιτρέπονται μόνο κανονικά bolus @@ -1156,6 +1156,10 @@ Λάθος κωδικός. Η εντολή ακυρώθηκε. Δεν έχει ρυθμιστεί Δημιουργήθηκε αλλαγή προφίλ + Έλεγχος Έκδοσης + παλιά έκδοση + πολύ παλιά έκδοση + Νέα έκδοση για τουλάχιστον %1$d ημέρες διαθέσιμη! Επιστροφή σε LGS μετά από 60 ημέρες, το κύκλωμα θα απενεργοποιηθεί μετά από 90 ημέρες %1$d ημέρα %1$d ημέρες diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 9d800ccc59..93d2498e5a 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -143,8 +143,6 @@ Violación restricciones Error de administración del bolo Basal Temporal no emitida - Valor basal [%] - % (100% = actual) Aceptar nueva basal temporal: Tratamiento Calculadora @@ -244,7 +242,6 @@ Bomba IOB Unidades diarias Último bolo - Hace %.1fh Datos inválidos Valor no establecido correctamente Recargar Perfil @@ -503,7 +500,6 @@ Adulto resistente a la insulina Por favor elige la edad del paciente para ajustar los límites de seguridad Glimp - %s necesita optimización de batería lista blanca para funcionar bien Loop desactivado Desactivado (%1$d m) Superbolo (%1$d m) @@ -539,8 +535,6 @@ Habilitar las luces de estado para tiempo de la canula, tiempo de la insulina, tiempo del sensor y nivel de bateria en la pantalla de inicio. Umbral de advertencia de nivel de reservorio [U] Umbral crítico de nivel de reservorio [U] - Umbral de advertencia de nivel de pila [%] - Umbral crítico de nivel de batería [%] IOB COB Firmware @@ -734,7 +728,6 @@ Funcionando Cancelando TBR Poniendo TBR (%1$d%% / %2$d min) - Emitiendo bolo (%.1f U) Actualizando Acción requerida no disponible por la bomba Uso inseguro: bolo extendido o multionda activo. El modo del lazo ha sido fijado a sólo suspensión en baja glucosa durante 6 horas. En modo lazo sólo se soportan los bolo estándar. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 0b5ef3c187..0ae7423e5d 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -147,8 +147,8 @@ Violation des restrictions Erreur injection bolus Erreur injection basal temporaire - Valeur Basal [%] - % (100% = actuel) + Valeur de Basal [%%] + %% (100%% = actuel) Accepter nouveau basal temporaire : Traitement Calculatrice @@ -249,7 +249,7 @@ L\'ENSEMBLE DES RISQUES LIÉS À LA QUALITÉ ET À LA PERFORMANCE DU PROGRAMME S Insuline Active pompe Unités journalières Dernier bolus - %.1fh passées + il y a %1$.1fh Données saisies invalides Valeur pas correctement définie Actualiser le profil @@ -529,7 +529,7 @@ L\'ENSEMBLE DES RISQUES LIÉS À LA QUALITÉ ET À LA PERFORMANCE DU PROGRAMME S Adulte résistant à l\'insuline Veuillez svp sélectionner l’âge du patient pour définir les limites de sécurité Glimp - %s a besoin de la liste blanche de l’optimisation batterie pour une bonne performance + %1$s a besoin d\'optimisation de la batterie (whitelisting) pour une performance correcte La Boucle est suspendue Suspendu (%1$d m) Superbolus (%1$d m) @@ -565,8 +565,8 @@ L\'ENSEMBLE DES RISQUES LIÉS À LA QUALITÉ ET À LA PERFORMANCE DU PROGRAMME S Activer les lumières d\'état pour AgeC, AgeI, AgeS, niveaux du réservoir et de batterie sur l\'écran d\'accueil. Seuil d\'avertissement de niveau du réservoir [U] Seuil critique de niveau du réservoir [U] - Seuil d’avertissement du niveau de batterie [%] - Seuil critique du niveau de la batterie [%] + Seuil d’avertissement du niveau de batterie [%%] + Seuil critique du niveau de la batterie [%%] IA GA Firmware @@ -765,7 +765,7 @@ L\'ENSEMBLE DES RISQUES LIÉS À LA QUALITÉ ET À LA PERFORMANCE DU PROGRAMME S En cours d\'exécution Annulation en cours du TBR Paramétrer TBR (%1$d%% / %2$d min) - Injection du bolus en cours (%.1f U) + Injection du Bolus en cours (%1$.1f U) Actualisation en cours L\'opération demandée n\'est pas prise en charge par la pompe Utilisation dangereuse : les bolus étendus ou carrés sont actifs. Le mode Boucle a été programmé pour des suspensions seulement pour 6 heures. Uniquement les bolus normaux sont pris en charge par le mode Boucle @@ -1157,6 +1157,10 @@ L\'ENSEMBLE DES RISQUES LIÉS À LA QUALITÉ ET À LA PERFORMANCE DU PROGRAMME S Code incorrect. Commande annulée. Non configuré Changement de profil effectué + Vérificateur de version + ancienne version + très ancienne version + Une nouvelle version est disponible depuis au moins %1$d jours ! Retour au traitement par pompe assistée par capteur (Arrêt par Glycémie Basse (AGB), Low Glucose Suspend (LGS) ) après 60 jours et la Boucle sera désactivée après 90 jours %1$d jour %1$d jours diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index cbb98e7289..5d942f4818 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -143,8 +143,6 @@ Vincolo Violato Errore di erogazione del bolo Errore di somministrazione basale temporale - Valore Basale [%] - % (100% = corrente) Accettare il nuovo basale temp Boli Calcolatore @@ -244,7 +242,6 @@ IOB Microinfusore Unita\' giornaliera Ultimo Bolo - %.1fh fa Data Errata Valore non impostato correttamente Aggiorna Profilo @@ -502,7 +499,6 @@ Insulina resistente Si prega di selezionare l\'età paziente e di impostare i limiti di sicurezza Glimp - %s ha bisogno di whitelisting di ottimizzazione della batteria per un buon funzionamento Sospensione Loop Sospendi (%1$d m) Superbolo (%1$d m) @@ -722,7 +718,6 @@ In esecuzione Annullamento di TBR L\'impostazione di TBR (%1$d%% / %2$d min) - Bolusing (%.1f U) Aggiornando Richiesta operazione non supportata dal micro L\'utilizzo non sicuro: estesi o multiwave boli sono attivi. Modalità loop è stato impostato basso-sospendere solo 6 ore. Solo normali boli sono supportati in modalità loop diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 80b2994a0e..46b451b602 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -143,8 +143,6 @@ 제한 위반 Bolus 주입 에러 임시Basal 주입 에러 - Basal값[%] - % (100% = 현재) 새 임시Basal 적용: 관리 계산기 @@ -244,7 +242,6 @@ 펌프 IOB 일 인슐린 총량 최근 Bolus: - %.1f시간 전 사용할수 없는 입력 데이터 값이 제대로 설정되지 않았습니다 프로파일 새로고침 @@ -502,7 +499,6 @@ 인슐린 저항성 높은 성인 안전제한을 설정하기 위해 당뇨인의 나이를 선택하세요 Glimp - 최적의 성능을 위해 %s에서 배터리 최적화를 해제해야합니다. Loop 일시중지 일시중지중 (%d분) Superbolus (%1$d 분) @@ -722,7 +718,6 @@ 실행중 임시기초주입 취소중 임시기초주입 설정중 (%1$d%% / %2$d분) - Bolus 주입중(%.1f U) 새로고침중 요청하신 동작은 펌프에서 지원하지 않습니다. 위험한 사용: 확장Bolus 혹은 멀티웨이브Bolus가 활성화 됩니다. Loop 모드가 저혈당 방지로 오직 6시간만 설정됩니다. Loop 모드에선 일반 Bolus만 지원합니다. diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 11d4733c9b..f95cd7627f 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -12,7 +12,7 @@ Naudoti ištęstinį bolusą >200%% DanaR Bluetooth įrenginys Visada naudoti bazės absoliučias vertes - Prašome iš naujo paleisti telefoną arba sistemos nustatymuose iš naujo paleisti AndroidAPS \nkitaip AndroidAPS negalės registruoti prisijungimų (svarbu stebint ir tikrinant, ar algoritmai veikia tinkamai)! + Prašome iš naujo paleisti telefoną arba sistemos nustatymuose iš naujo paleisti AndroidAPS \nkitaip AndroidAPS negalės registruoti prisijungimų (svarbu stebėti ir tikrinti ar algoritmai veikia tinkamai)! Mygtukai greitesniam pagrindinių funkcijų paleidimui Papildomų įrašų žurnalas. Naudojama aktyvių įskiepių konfigūravimui @@ -147,8 +147,8 @@ Apribojimų pažeidimas Boluso suleidimo klaida Laikinos bazės suleidimo klaida - Valandinė bazė [%] - % (100 % = dabartinis) + Valandinė bazė [%%] + %%(100%% - pasirinkta) Patvirtinti naują laikiną bazę: Terapija Skaičiuotuvas @@ -248,7 +248,7 @@ Pompos AIO Paros insulinas Paskutinis bolusas - prieš %.1fval + Prieš %1$.1f valandų Neteisingai įvesti duomenys Vertė nėra tinkamai nustatyta Atnaujinti profilį @@ -528,7 +528,7 @@ Insulinui rezistentiškas suaugęs Pasirinkite paciento amžių saugumo riboms nustatyti Glimp - %s įtraukite baterijos optimizavimą į baltąjį sąrašą, tinkamam veikimui užtikrinti + %1$s įtraukite į baterijos optimizavimo baltąjį sąrašą tinkamam veikimui užtikrinti Ciklas sustabdytas Sustabdyta (%1$d m) Superbolusas (%1$d m) @@ -564,8 +564,8 @@ Pradžios ekrane rodyti spalvotus indikatorius adatos, insulino, sensoriaus naudojimo trukmei bei baterijos įkrovimo lygiui. Įspėjimo apie žemą rezervuaro lygį riba [U] Įspėjimo apie kritiškai žemą rezervuaro lygį riba [U] - Įspėjimo apie žemą baterijos įkrovimo lygį riba [%] - Įspėjimo apie kritiškai žemą baterijos įkrovimo lygį riba [%] + Įspėjimo apie žemą baterijos įkrovimo lygį riba [%%] + Įspėjimo apie kritiškai žemą baterijos įkrovimo lygį riba [%%] AIO AAO Programinė įranga @@ -764,7 +764,7 @@ Vykdoma Laikina bazė atšaukiama Nustatyti laikiną bazę (%1$d%% / %2$d min) - Bolusas (%.1f vv) + Leidžiamas bolusas (%1$.1f vv) Atnaujinama Pompa negali įvykdyti prašomos operacijos Nesaugu: aktyvus ištęstinis arba dvibangis bolusas. Ciklas nustatytas stabdymui prie žemo tik 6 valandoms. Aktyvūs tik paprasti bolusai. @@ -1156,6 +1156,10 @@ Neteisingas kodas. Komanda atšaukta. Nesukonfigūruota Profilio perjungimas sukurtas + Versijos tikrintuvas + sena versija + labai sena versija + Nauja versija pasiekiama mažiausiai %1$d dienų! Po 60 d. grįšite prie stabdymo prie žemo, po 90 d. ciklas bus išjungtas. %1$d diena %1$d diena diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 405918431f..313c3fdde5 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -145,8 +145,6 @@ In strijd met beperkingen Bolus toedien storing Tijdelijk basaal toedien storing - Basale waarde [%] - % (100% = actueel) Accepteer nieuw tijdelijk basaal: Bolus Bolus wizard @@ -246,7 +244,6 @@ Pomp IOB Dag totaal Laatste bolus - %.1fu geleden Verkeerde ingave Waarde niet correct ingesteld Herlaad profiel @@ -506,7 +503,6 @@ Stel in en gebruik tijdelijk en standaard tijdelijke streefdoelen (bv. bij sport insuline resistente volwassene Kies leeftijd van de patient voor veiligheids limieten Glimp - %s benodigd batterij optimalistaie vrijwaring voor goede werking Loop pauzeren Gepauzeerd (%1$d m) Superbolus (%1$d m) @@ -542,8 +538,6 @@ Stel in en gebruik tijdelijk en standaard tijdelijke streefdoelen (bv. bij sport Statusindicatoren inschakelen voor cage, iage, sage, reservoir en batterijniveau op het beginscherm. Drempel waarschuwing reservoir niveau [E] Drempel alarm reservoir niveau [E] - Drempel waarschuwing batterij niveau [%] - Drempel alarm batterij niveau [%] IOB COB Firmware @@ -737,7 +731,6 @@ Stel in en gebruik tijdelijk en standaard tijdelijke streefdoelen (bv. bij sport Actief Annuleren van TB Instellen TBR (%1$d%% / %2$d min) - Bolus (%.1f E) Vernieuwen Gevraagde is niet mogelijk met de pomp Opgelet: verlengde en multi wave bolussen zijn actief. Loop is overgeschakeld naar low-suspend modus gedurende 6 uur. Alleen gewone bolussen worden ondersteund in loop modus diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 18f711530c..b06da87522 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -9,6 +9,7 @@ Zresetuj bazy danych Na pewno chcesz zresetować bazy danych? Wyjście + Używaj przedłużonych bolusów dla >200%% Urządzenie Bluetooth DanaR Zawsze używaj wartości bezwzględnych bazy Proszę uruchomić ponownie telefon lub zrestartować AndroidAPS w ustawieniach systemu telefonu \ninaczej AndroidAPS nie będzie otrzymywał logowań (ważne ze względu na śledzenie i sprawdzanie poprawności działania algorytmów)! @@ -146,8 +147,8 @@ Naruszenie ograniczeń Błąd podania bolusa Błąd podania bazy tymczasowej - Wartość bazy [%] - % (100% = aktualny) + Wartość bazy [%%] + %% (100%% = aktualny) Akceptuj nową bazę tymczasową: Leczenie Kalkulator @@ -247,7 +248,7 @@ IOB w pompie Jednostki dzienne Ostatni bolus - %.1fh temu + %1$.1fh temu Błędne dane wejściowe Wartość ustawiona nieprawidłowo Załaduj profil ponownie @@ -313,6 +314,7 @@ Stop NACIŚNIĘTY STOP Czekam na pompę + Zamierzam dostarczyć %1$.2fU Konfiguracja wizualizacji i monitorowania, analiza dawek i wartości bazowych Sprawdź czy BG (pomiary cukru) są dostępne w Nightscout, i dane pompy insulinowej są ładowane Uruchamiam otwartą pętle (OpenLoop) @@ -335,12 +337,26 @@ Pętla (Loop) jest wyłączona Pętla (Loop) jest włączona %1$.2f ograniczone do %2$.2f + Wartość %1$s jest poza dopuszczalną granicą Zdalne komendy nie są dozwolone + Bolus zdalny niedostępny. Spróbuj ponownie później. + Aby rozpocząć bazę %1$.2fU/h przez %2$d min. odpowiedz kodem %3$s + Aby przełączyć profil na %1$s %2$d%% odpowiedz kodem %3$s + Aby rozpocząć bolus przedłużony %1$.2fU przez %2$d min. odpowiedz kodem %3$s + Aby rozpocząć bolus przedłużony %1$d%% przez %2$d min. odpowiedz kodem %3$s Aby wstrzymać pętle na %1$d minut odpowiedz kodem %2$s Tymczasowa baza %1$.2fU/h przez %2$d min rozpoczęta + Bolus przedłużony %1$.2fU na %2$d min. rozpoczęty pomyślnie + Tymczasowa baza %1$d%% przez %2$d min. pomyślnie rozpoczęta Rozpoczęcie tymczasowej bazy nie powiodło się + Nie powiodło się podanie bolusa przedłużonego + Aby zatrzymać bazę tymczasową wprowadź kod %1$s + Aby zatrzymać bolus przedłużony wprowadź kod %1$s Baza tymczasowa anulowana + Przedłużony bolus anulowano Anulowanie tymczasowej bazy nie powiodło się + Anulowanie bolusa przedłużonego nie powiodło się + Nieznane polecenie lub błędna odpowiedź Bolus zdefiniowany Ustaw szybkie bolusy Tekst przycisku: @@ -448,6 +464,7 @@ Profil Wartość domyślna: 3 To jest kluczowy współczynnik bezpieczeństwa OpenAPS. Ogranicza dawki bazowe do 3x twojej największej dawki bazowej. Prawdopodobnie nie będziesz musiał tego zmieniać, ale powinieneś być świadomy tego, co się rozumie przez “3x max dzienna; 4x aktualna” dla współczynników bezpieczeństwa. Wartość domyślna: 4 To jest drugi kluczowy współczynnik bezpieczeństwa OpenAPS, i druga wartość z “3x max dzienna; 4x aktualna”. To oznacza, że Twoja dawka bazowa niezależnie od maksymalnej bazy ustawionej na pompie nie może być większa niż ta ustawiona wartość razy twoja aktualna dawka bazowa. To ma na celu przestrzec ludzi przed ustawieniem zbyt wysokiej dawki maksymalnej przed zrozumieniem jak działa algorytm. Ponownie, wartość domyślna to 4x; większość ludzi nie będzie potrzebowała tego zmienić i raczej będą potrzebować dostosować inne ustawienia, jeśli czują, że zbliżają się do współczynnika bezpieczeństwa. + Wartość domyślna: 1.2\nTo jest mnożnik dla autosens (wkrótce autotune), ustawia 20%% maks. limit dla najwyższej wartości jaką autosens może przyjąć, co z kolei określa jak wysoko autosens może zwiększyć bazę, jak nisko może ustawić współczynnik wrażliwości insuliny ISF, i jak nisko może ustawić docelową wartość glikemii. Wartość domyślna: 0.7\nDruga strona limitu bezpieczeństwa dla autosens, ustawia mnożnik minimalny dla wartości, do jakiej autosens może obniżyć bazę, i jak wysoko może ustawić ISF i wartość docelową BG. Autosens wpływa na wartości docelowe Domyślnie włączone\nTa funkcja pozwala wpływać na zmianę wartości docelowe BG, wartości bazy i oraz współczynnika wrażliwości na insulinę ISF. @@ -460,6 +477,7 @@ Nieprawidłowy numer telefonu Nieprawidłowy numer telefonu SMS Kalibracja + Wysłać kalibrację %1$.1f do xDrip? xDrip+ nie zainstalowany Kalibracja przesłana do xDrip Kalibracja wysłana. Odbiór musi być dozwolony w xDrip. @@ -510,7 +528,7 @@ Osoba dorosła insulino odporna Proszę wybierz wiek pacjenta w celu ustawienia wartości limitów bezpieczeństwa Glimp - %s Potrzebuję wpisu na białą listę optymalizacji baterii, w celu zapewnienia poprawnego działania + %1$s potrzebuję wyłączenia z optymalizacji baterii, w celu zapewnienia poprawnego działania Pętla wstrzymana Wstrzymana (%1$d m) Superbolus (%1$d m) @@ -546,8 +564,8 @@ Włącz diody stanu dla cage, iage, sage, rezerwuar i poziom baterii na ekranie głównym. Próg ostrzeżenia o poziomie zbiornika [U] Próg ostrzeżenia o krytycznym poziomie zbiornika [U] - Próg ostrzeżenia o poziomie baterii [%] - Próg ostrzeżenia o krytycznym poziomie baterii [%] + Próg ostrzeżenia o poziomie baterii [%%] + Próg ostrzeżenia o krytycznym poziomie baterii [%%] IOB COB Firmware @@ -601,6 +619,7 @@ Ustawienia wchłaniania Maks. czas wchłaniania posiłku [h] Oczekiwany czas w godzinach, w którym wszystkie węglowodany zostają wchłonięte + Wizualizacja bolusa przedłużonego jako %% SAGE IAGE CAGE @@ -713,6 +732,8 @@ Prześlij dane BG do xDrip+ W xDrip+ wybierz źródło danych 640g/Eversense NSClient BG + Wartość bazy zastąpiona minimalną obsługiwaną wartością: %1$s + Wartość bazy zastąpiona maksymalną obsługiwaną wartością: %1$s Obliczenia BG Obliczenia Bolus IOB Obliczenia IOB @@ -730,6 +751,8 @@ Tryb zamknięty włączony Maks. IOB ustawione poprawnie BG dostępne z wybranego źródła + Wartości bazy nie są ustawione w pełnych godzinach: %1$s + Nieprawidłowy profil: %1$s Programowanie pompy do podawania bolusa Odśwież Stan @@ -742,7 +765,7 @@ W działaniu Anulowanie TBR Ustawianie TBR (%1$d%% / %2$d min) - Podawanie bolusa (%.1f U) + Podawanie bolusa (%1$.1f U) Odświeżanie Żądana operacja nie jest obsługiwana przez pompę Niebezpieczne użycie: aktywne są bolusy przedłużone lub wielofalowe. Tryb pętli został ustawiony tylko na 6 godzinne zawieszenie z uwagi na niski poziom (BG). W trybie pętli obsługiwane są tylko normalne bolusy @@ -1065,11 +1088,13 @@ Wycisz Alarm pompy Melduj zmianę miejsca wkłucia + Zapisz zmianę zbiornika Melduj zmianę drenu Melduj zmiany baterii Melduj zmiany trybu pracy Melduj ostrzeżenia Włącz emulację TBR + Użyj rozszerzonych bolusów zamiast TBR by ominąć limit 250%% bazy Opóźnienie rozłączenie [s] Numer seryjny Wersja oprogramowania @@ -1124,6 +1149,18 @@ Podaj \"Zmiana Sensora\" do NS do NS automatycznie przy uruchomieniu sensora Tomato (MiaoMiao) Tomato + Zmiana na czas letni w ciągu 24 godzin lub krócej + Zmiana czasu nastąpiła mniej niż 3 godziny temu - Zamknięta pętla wyłączona + limit wielkości pamięci wewnętrznej + Zwolnij co najmniej %1$d MB z pamięci wewnętrznej! Pętla zatrzymana! + Błędny format + Zły kod. Polecenie anulowano. + Nie skonfigurowano + Zmiana profilu wykonana + Kontroler wersji + stara wersja + bardzo stara wersja + Nowa wersja dostępna co najmniej %1$d dni! Powrót do LGS (zawieszania podawania bazy przy niskim poziomie) po 60 dniach, pętla zostanie wyłączona po 90 dniach %1$d dzień %1$d dni diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 33d67541f7..36ffb7c5e9 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -147,8 +147,8 @@ Violação das restrições Erro na entrega do bolus Erro na entrega da basal temporária - Valor da Basal [%] - % (100% = atual) + Valor da Basal [%%] + %% (100%% = actual) Aceitar nova basal temporária: Tratamento Calculadora @@ -248,7 +248,7 @@ IOB Bomba Unidades diárias Último bólus - %.1fh atrás + %1$.1fh atrás Entrada Inválida Valor não definido corretamente Recarregar perfil @@ -528,7 +528,7 @@ Adulto resistente insulina Por favor seleccione a idade do diabético para definir os limites de segurança Glimp - %s necessita de autorizar a não optimização da bateria para assegurar a performance necessária + %1$s necessita de autorizar a não optimização da bateria para assegurar a performance necessária Loop suspenso Suspendido (%1$d m) Superbólus (%1$d m) @@ -564,8 +564,8 @@ Ativar as luzes de status para idade da cânula, idade da insulina, idade do sensor, reservatório e bateria no ecrã inicial. Limite de aviso de nível de reservatório [U] Limite crítico de nível de reservatório [U] - Limite de aviso de nível de bateria [%] - Limite crítico de nível de bateria [%] + Limite de aviso de nível de bateria [%%] + Limite crítico de nível de bateria [%%] IOB COB Firmware @@ -764,7 +764,7 @@ A correr Cancelar TBR A definir TBR (%1$d%% / %2$d min) - Injectando (%.1f U) + Injectando (%1$.1f U) A actualizar Operação solicitada não suportada pela bomba Ultilização nao segura: bolus estendudos ou multionda estão activos. Modo do loop foi alterado para apenas suspender em limite inferior por 6h. Apenas são suportados bolus normais em modo loop @@ -1129,7 +1129,7 @@ Actualizar Integração de bomba para bombas Accu-Chek Insight Não inserido - Última conexão:%1$d minutos atrás + Última ligação:%1$d minutos atrás Basal temporária: %1$d%% para %2$d / %3$d minutos Estendido: %1$.2f / %2$.2f U por %3$d min Multionda: %1$.2f / %2$.2f U por %3$d min @@ -1139,7 +1139,7 @@ Duração máxima da recuperação [s] Duração mínima da recuperação [s] Duração da recuperação - Tempo de operação excedido - reset bluetooth + Tempo de operação excedido - reinicar bluetooth == ∑ %1$s U U/h g/U @@ -1156,6 +1156,10 @@ Código errado. Comando cancelado. Não configurado Troca de perfil criada + Verificador de Versão + versão antiga + versão muito antiga + Nova versão para pelo menos %1$d dias disponíveis! Voltar para o LGS após 60 dias, o loop será desativado após 90 dias %1$d dia %1$d dias diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 67868f930a..12267b972b 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -145,8 +145,6 @@ Încălcare a unei limite Eroare la livrarea bolusului Eroare la setare bazală temporară - Valoare bazală [%] - % (100% = curent) Acceptă noua bazală temporară: Tratament Calculator @@ -246,7 +244,6 @@ IOB din pompă Unități zilnic Ultimul bolus - %.1f o în urmă Date de intrare incorecte Valoare setată incorect Reîncarcă profilul @@ -505,7 +502,6 @@ Adult rezistent la insulină Vă rog să selectați vârsta pacientului în vederea stabilirii valorilor maxime admise de siguranță Glimp - %s necesită excluderea din lista de optimizare a bateriei pentru funcționare corespunzătoare Buclă suspendată Suspendat (%1$d min) Superbolus (%1$d min) @@ -541,8 +537,6 @@ Activați indicatori pentru CAGE, IAGE, SAGE, nivel baterie și rezervor pe ecranul principal. Pragul de avertisment pentru insulina din rezervor [U] Pragul critic al nivelului insulinei în rezervor [U] - Pragul de avertizare nivel baterie [%] - Pragul critic al nivelului baterie [%] IOB COB Firmware @@ -736,7 +730,6 @@ Rulează Se oprește TBR Se setează TBR (%1$d%% / %2$d min) - Bolusare (%.1f U) Reîncărcare Operațiunea nu este suportată de pompă Folosire nesigură: un bolus extins sau multiwave este activ. Modul buclă este setat să funcționeze în low-suspend pentru 6 ore. Doar bolusurile normale sunt posibile. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 26d330f64b..aaa07dfec0 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -147,8 +147,8 @@ ограничение нарушено Ошибка подачи болюса Ошибка подачи врем базала - величина базала (%) - % (100% = текущий) + Величина базала [%%] + %% (100%% = текущее) принять новый врем базал: болюс калькулятор @@ -248,7 +248,7 @@ активный инсулин на помпе суточные единицы предыдущий болюс - %.1fч назад + %1$.1fч назад введенные данные неверны величина не задана должным образом обновить профиль @@ -528,7 +528,7 @@ Инсулинорезистентный взрослый выберите возраст пациента для определения ограничителей безопасности Glimp - %s необходимо включить в белый список для корректной работы + %1$s необходимо включить в белый список оптимизации батареи для корректной работы ЗЦ остановлен Остановлен на(%1$d m) Суперболюс(%1$d m) @@ -564,8 +564,8 @@ Включить на главном экране индикаторы отработанного времени для канюли помпы, инсулина, сенсора, резервуара, а также показать уровень аккумулятора. Порог уровня наполненности резервуара для оповещения [ед.] Порог критического уровня наполненности резервуара [U] - Порог предупреждения о разрядке батареи [%] - Порог предупреждения о критическом уровне разрядки батареи [%] + Порог предупреждения о разрядке батареи [%%] + Порог предупреждения о критическом уровне разрядки батареи [%%] IOB акт инс акт углев прошивка @@ -764,7 +764,7 @@ Выполняется Отмена врем базала TBR Установка врем базала TBR на (%1$d%% / %2$d мин) - Введение болюса (%.1f ед) + Введение болюса (%1$.1f ед) Обновление Запрашиваемая операция не поддерживается помпой Небезопасное использование: Удлиненный или многоволновой болюс активны. Режим цикла Loop установлен на приостановку при низкой гликемии только на 6 часов. В режиме loop поддерживаются только обычные болюсы @@ -1158,4 +1158,8 @@ Context | Edit Context Неверный код. Команда отменена. Не сконфигурировано Переключатель профиля создан + Проверка версии + старая версия + очень старая версия + Новая версия доступна не менее %1$d дней! После 60 дней переход на остановку при низкой гликемии LGS, замкнутый цикл будет отключен через 90 дней diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index d2cdb050cd..f9dacaef8b 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -147,8 +147,8 @@ Mimo povolený rozsah Chyba podania bolusu Chyba podávania dočasného bazálu - Hodnota bazálu [%] - % (100% = aktuálny) + Hodnota bazálu [%%] + %% (100%% = aktuálny) Povoliť nový dočasný bazál: Bolus Kalkulačka @@ -248,7 +248,7 @@ IOB z pumpy Jednotiek za deň Posledný bolus - pred %.1fh + pred %1$.1fh Chybné vstupné dáta Hodnota nenastavená správne Obnoviť profil @@ -528,7 +528,7 @@ Dospelý z nízkou citlivosťou Vyberte vek pacienta pre nastavenie bezpečnostných limitov Glimp - %s potrebuje vypnúť optimalizáciu baterie pre optimálny výkon + %1$s potrebuje vypnúť optimalizáciu batérie pre optimálny výkon Uzavretý okruh pozastavený Pozastavený (%1$d min) Superbolus (%1$d m) @@ -564,8 +564,8 @@ Povoliť indikátory stavu pre CAGE, IAGE, SAGE, Reservoir a Battery Level na domovskej obrazovke. Prah upozornenia na úroveň hladiny zásobníka [U] Prah kritickej úrovne hladiny zásobníka [U] - Prah upozornenia na úroveň batérie [%] - Prah kritickej úrovne batérie [%] + Prah upozornenia na úroveň batérie [%%] + Prah kritickej úrovne batérie [%%] IOB COB Firmware @@ -764,7 +764,7 @@ V prevádzke Rušenie dočasného bazálu Nastavovanie doč. bazálu (%1$d%% / %2$d min) - Podávanie bolusu (%.1f U) + Podávanie bolusu (%1$.1f U) Aktualizuje sa stav Požadovaná operácia nie je pumpou podporovaná Nebezpečné použitie: predĺžený alebo multiwave bolus je aktívny. Pumpa bola vypnutá len na 6 hodín. Povolené sú iba normálne bolusy. @@ -1156,6 +1156,10 @@ Nesprávný kód. Príkaz zrušený. Nie je nakonfigurované Prepnutie profilu vytvorené + Kontrola verzie + stará verzia + veľmi stará verzia + Nová verzia dostupná najmenej %1$d dní! Návrat k liečbe inzulínovou pumpou s podporou senzora po 60 dňoch, okruh bude deaktivovaný po 90 dňoch. %1$d deň %1$d dní diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 74378fb8d9..409b7f21a6 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -148,8 +148,6 @@ Eversense-appen. Begränsning nådd Fel vid bolusleverans Fel vid justering av temp basal - Basal [%] - % (100% = profilens värde) Acceptera ny temp basal: Behandling Kalkylator @@ -249,7 +247,6 @@ Eversense-appen. Pumpens IOB Enheter per dag Senaste bolus - %.1f tim sedan Fel på inmatning Misslyckades med inställning Ladda om profil @@ -529,7 +526,6 @@ Eversense-appen. Insulinresistent vuxen Vg ange personens ålder för inställningar av gränser Glimp - %s behöver kunna kringgå batterisparfunktionerna för att fungera korrekt Loop pausad Pausad (%1$d min) Superbolus (%1$d min) @@ -565,8 +561,6 @@ Eversense-appen. Aktivera statusindikationerna för cage, iage, sage, reservoar och batterinivå på hemskärmen. Varningsnivå för reservoar [U] Akut varningsnivå för reservoar [U] - Varningsnivå för batteri [%] - Akut varningsnivå för batteri [%] IOB COB Firmwareversion @@ -765,7 +759,6 @@ Eversense-appen. Körs Avbryter temp basal Sätter temp basal (%1$d%% / %2$d min) - Levererar bolus (%.1f enheter) Uppdaterar Önskad åtgärd stöds inte av pumpen Osäker användning: Förlängd- eller kombibolus är aktiv. Loop mode är satt till att bromsa vid lågt BG i 6 timmar. Endast normala bolusar tillåtna i loopläge. diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index dcc3af86e3..d858630fff 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -143,8 +143,6 @@ Kısıtlamalar ihlali Bolus teslimat hatası Geçici Bazal teslimat hatası - Bazal değeri [%] - % ( 100% = aktuel) Yeni geçici bazal oranını kabul et: Tedavi Hesap makinesi @@ -244,7 +242,6 @@ IOB(Aktif insülin) pompa Günlük birimleri Son bolus - %.1fs önce Geçersiz bilgi girişi Değer düzgün ayarlanmamış Dosyayı yeniden yükle @@ -503,7 +500,6 @@ Aktif Karbonhidratın ne kadar hızlı sindirildiğine ve KŞ\'nin beklenenden d Yetişkin İnsülin direnci Güvenlik sınırlarını ayarlamak için lütfen hasta yaşını seçiniz Glimp - %s, düzgün bir şekilde çalışması için devre dışı bırakılmış pil performans ayarlaması gerektirir Döngü durduldu Askıya alındı (%1$d m) Superbolus (%1$d m) @@ -725,7 +721,6 @@ Aktif Karbonhidratın ne kadar hızlı sindirildiğine ve KŞ\'nin beklenenden d Çalışıyor TBR (Geçici Bazal Oranı) İptal ediliyor TBR(Geçici Bazal Oranı) ayarı (%1$d%% / %2$d dak) - Bolus (%.1f U) teslim ediliyor Yenileniyor... İstenen işlem pompası tarafından desteklenmiyor Güvensiz kullanım: yayım veya çoklu dalgalı (kare dalgalı) boluslar aktiftir. Döngü modu 6 saat içinde ekstra insülin vermeyecektir. Döngü modunda sadece normal boluslar desteklenir diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 2e1874e5ef..7e4b22eacd 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -53,6 +53,7 @@ 使用您的 WearOS 手表监视和控制 AndroidAPS。 在 xDrip + 表盘上显示有关闭环的信息。 远程控制 AndroidAPS 使用 SMS 短信命令。 + 返回 开始 校验 单位 @@ -145,8 +146,6 @@ 违反约束条件 大剂量输注错误 临时基础输注错误 - 基础率数值 [%] - % (100% = 当前) 接受新的临时基础率 治疗 计算器 @@ -246,7 +245,6 @@ 泵 IOB 每日单位数 上次大剂量 - %.1fh 前 无效的输入数据 数值设定的不正确 重新加载配置文件 @@ -264,6 +262,10 @@ 要输注大剂量胰岛素%1$.2fU 回复如下代码 %2$s 要发送校准值 %1$.2f 回复如下代码 %2$s 大剂量输注失败 + 已经成功输注大剂量%1$.2fU + 将要输注 %1$.2fU + 已经成功输注大剂量%1$.2fU + 正在输注 %1$.2fU 通过SMS短信允许远程命令 手指 传感器 @@ -331,11 +333,24 @@ 闭环被启用 %1$.2f 超过 %2$.2f的限制 远程命令没有被允许 + 远程大剂量不可用。请稍后再试。 + 要开始基础率 %1$.2fU/h 持续时间%2$d 分钟,请回复如下代码 %3$s + 要切换配置文件到 %1$s %2$d%% 请回复代码 %3$s + 要开始扩展大剂量 %1$.2fU/h 持续时间%2$d 分钟,请回复如下代码 %3$s + 要开始基础率 %1$d%% 持续时间%2$d 分钟,请回复如下代码 %3$s 要暂停闭环 %1$d 分钟请回复如下代码 %2$s 临时基础率 %1$.2fU/h 持续 %2$d 分钟启用成功了 + 扩展大剂量 %1$.2fU/h 持续时间 %2$d 分钟已经启用成功了 + 临时基础率 %1$d%% 持续时间 %2$d 分钟 启用成功了 开始临时基础率失败了 + 开始扩展大剂量失败了 + 要停止临时基础率,请回复如下代码 %1$s + 要停止扩展大剂量,请回复如下代码 %1$s 临时基础率取消了 + 扩展大剂量已经取消了 取消临时基础率失败 + 取消扩展大剂量失败 + 未知的命令或者错误的回复 快速向导 快速向导设置 按钮文本: @@ -505,7 +520,6 @@ 胰岛素抵抗成人 请选择患者年龄以设置安全限制 Glimp - %s 需要加入电池优化白名单才能正常运行 闭环暂停了 暂停了 (%1$d m) 超级大剂量 (%1$d m) @@ -541,8 +555,6 @@ 在主屏幕上启用cage(碳水时间)、iage(胰岛素时间)、sage(探头使用时间)、储药器和电池电量的状态指示灯。 储药器药量低于阈值[U] 警告 储药器药量低于阈值[U] 严重警告 - 电池电量低于阈值[%] 警告 - 电池电量低于阈值[%] 严重警告 IOB活性胰岛素 COB活性碳水 固件 @@ -550,6 +562,7 @@ 蓝牙状态 关于 缺少 SMS 短信权限 + 缺少手机状态权限 xDrip 状态 (手表) xDrip 状态线 (手表) xdrip @@ -735,7 +748,6 @@ 正在运行 正在取消临时基础率 正在设置临时基础率 (%1$d%% / %2$d 分钟) - 正在输注大剂量 (%.1f U) 正在刷新 泵不支持请求的操作 不安全的用法: 扩展大剂量(方波) 或者双波被启用了,闭环只支持常规大剂量, 闭环已经被设置为6个小时的低血糖暂停模式(low-suspend only 6 hours). @@ -1001,6 +1013,7 @@ %1$d.目标 Poctech 从 Poctech app 接收血糖值。 + 从番茄app(喵喵设备) 接收血糖值 高临时目标增加灵敏度 =100 mg/dl (5.5 mmol/l) 时提高灵敏度]]> 低临时目标降低灵敏度 @@ -1057,6 +1070,7 @@ 静音 泵警报 记录位置更换 + 记录储药器更换 记录管路更换 记录电池更换 记录操作模式更改 @@ -1100,6 +1114,7 @@ 最后一次连接: %1$d 分钟前 临时基础率: %1$d%% for %2$d / %3$d 分钟 扩展: %1$.2f / %2$.2f U 为 %3$d 分钟 + 多波: %1$.2f / %2$.2f U 持续时间 %3$d 分钟 TDD(日输注总量): %1$.2f Reser.: %1$.2fU 电池: %1$d%% @@ -1113,6 +1128,16 @@ /U 记录传感器更换至NS服务器 在传感器启动时在 NS服务器 自动创建事件 \"传感器更改\" + 番茄(喵喵) + 番茄 + 在24小时内或更少时间内更夏令时时间 + 不到3小时前夏令时时间变化了-已禁用闭环 + 内部储存空间不足 + 至少 剩余%1$d MB 内部存储!闭环已禁用! + 格式错误 + 错误的代码。命令取消 + 未配置 + 配置文件切换已创建 %1$d 天 From ffbb37348108efb6b4b43e30329508927cb3dfef Mon Sep 17 00:00:00 2001 From: Tebbe Ubben Date: Wed, 24 Apr 2019 22:36:29 +0200 Subject: [PATCH 027/152] Insight: Update sqlite_sequence on DB update --- .../androidaps/db/DatabaseHelper.java | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java index 1b2c21bf18..1f071a2f18 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java @@ -45,9 +45,9 @@ import info.nightscout.androidaps.events.EventTempTargetChange; import info.nightscout.androidaps.interfaces.ProfileInterface; import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData; -import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.pump.danaR.activities.DanaRNSHistorySync; import info.nightscout.androidaps.plugins.pump.danaR.comm.RecordTypes; import info.nightscout.androidaps.plugins.pump.insight.database.InsightBolusID; @@ -83,7 +83,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { public static final String DATABASE_INSIGHT_BOLUS_IDS = "InsightBolusIDs"; public static final String DATABASE_INSIGHT_PUMP_IDS = "InsightPumpIDs"; - private static final int DATABASE_VERSION = 10; + private static final int DATABASE_VERSION = 11; public static Long earliestDataChange = null; @@ -147,19 +147,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { this.oldVersion = oldVersion; this.newVersion = newVersion; - if (oldVersion == 7 && newVersion == 8) { - log.debug("Upgrading database from v7 to v8"); - } else if (oldVersion == 8 && newVersion == 9) { - log.debug("Upgrading database from v8 to v9"); - } else if (oldVersion == 9 && newVersion == 10) { - TableUtils.createTableIfNotExists(connectionSource, InsightHistoryOffset.class); - TableUtils.createTableIfNotExists(connectionSource, InsightBolusID.class); - TableUtils.createTableIfNotExists(connectionSource, InsightPumpID.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() + " " + - "WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = \"" + DATABASE_INSIGHT_PUMP_IDS + "\")"); - } else { + if (oldVersion < 7) { log.info(DatabaseHelper.class.getName(), "onUpgrade"); TableUtils.dropTable(connectionSource, TempTarget.class, true); TableUtils.dropTable(connectionSource, BgReading.class, true); @@ -170,6 +158,17 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { TableUtils.dropTable(connectionSource, CareportalEvent.class, true); TableUtils.dropTable(connectionSource, ProfileSwitch.class, true); onCreate(database, connectionSource); + } else if (oldVersion < 10) { + TableUtils.createTableIfNotExists(connectionSource, InsightHistoryOffset.class); + TableUtils.createTableIfNotExists(connectionSource, InsightBolusID.class); + TableUtils.createTableIfNotExists(connectionSource, InsightPumpID.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() + " " + + "WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = \"" + DATABASE_INSIGHT_PUMP_IDS + "\")"); + } else if (oldVersion < 11) { + 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 + "\""); } } catch (SQLException e) { log.error("Can't drop databases", e); From 4d66e0c1be793c428005a20067b901f4c8d3f599 Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Thu, 25 Apr 2019 00:32:40 +0200 Subject: [PATCH 028/152] 2.3 bump --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index f3a1139a38..f4f4ca7add 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -83,7 +83,7 @@ android { targetSdkVersion 25 multiDexEnabled true versionCode 1500 - version "2.2.3-dev" + version "2.3" buildConfigField "String", "VERSION", '"' + version + '"' buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"' buildConfigField "String", "REMOTE", '"' + generateGitRemote()+ '"' From 0bab2e6c11df052b22a29727f6fc20b21efa9179 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 25 Apr 2019 14:19:39 +0200 Subject: [PATCH 029/152] 2.3.1-dev --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 676c9b6287..10a52b8ca1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -103,7 +103,7 @@ android { targetSdkVersion 25 multiDexEnabled true versionCode 1500 - version "2.3" + version "2.3.1-dev" buildConfigField "String", "VERSION", '"' + version + '"' buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"' buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"' From 26c236a79e30d656660a47ed07b63e01ee37b04e Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 25 Apr 2019 16:13:21 +0200 Subject: [PATCH 030/152] better check for dev branch --- app/src/main/java/info/nightscout/androidaps/MainApp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index 3259483033..720e07dd15 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -145,7 +145,7 @@ public class MainApp extends Application { File engineeringModeSemaphore = new File(extFilesDir, "engineering_mode"); engineeringMode = engineeringModeSemaphore.exists() && engineeringModeSemaphore.isFile(); - devBranch = BuildConfig.VERSION.contains("dev"); + devBranch = BuildConfig.VERSION.contains("-") || BuildConfig.VERSION.matches(".*[a-zA-Z]+.*"); sBus = L.isEnabled(L.EVENTS) && devBranch ? new LoggingBus(ThreadEnforcer.ANY) : new Bus(ThreadEnforcer.ANY); From 8b18a7ffb341d4d0f1457fe01fdcf9044e9609b9 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 25 Apr 2019 18:59:10 +0200 Subject: [PATCH 031/152] German git --- app/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 676c9b6287..a8d8f2a0fe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -87,7 +87,8 @@ def allCommited = { -> } catch (ignored) { return false; // NoGitSystemAvailable } - return stringBuilder.toString().contains("nothing to commit") + return stringBuilder.toString().contains("nothing to commit") || stringBuilder.toString().contains("nichts zu committen") + } tasks.matching { it instanceof Test }.all { From 6fd8d7ae3cbbd2fc57583056f8a9e960eb32db30 Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Thu, 25 Apr 2019 20:42:34 +0200 Subject: [PATCH 032/152] not language dependent git command --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a8d8f2a0fe..8c153a9e81 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -79,7 +79,7 @@ def allCommited = { -> try { def stdout = new ByteArrayOutputStream() exec { - commandLine 'git', 'status' + commandLine 'git', 'status', '-s' standardOutput = stdout } String commitObject = stdout.toString().trim() @@ -87,7 +87,7 @@ def allCommited = { -> } catch (ignored) { return false; // NoGitSystemAvailable } - return stringBuilder.toString().contains("nothing to commit") || stringBuilder.toString().contains("nichts zu committen") + return stringBuilder.toString().isEmpty() } From f2c18cbc1ff7ceab3659f3e96fb36d64c0d2c956 Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Thu, 25 Apr 2019 22:13:31 +0200 Subject: [PATCH 033/152] Don't warn if next loop invocation is prior to update check --- .../plugins/general/versionChecker/VersionCheckerPlugin.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt index 2a4c08a585..c0335ea64a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt @@ -32,6 +32,13 @@ object VersionCheckerPlugin : PluginBase(PluginDescription() private fun checkWarning() { val now = System.currentTimeMillis() + + if (!SP.contains(R.string.key_last_versionchecker_plugin_warning)) { + SP.putLong(R.string.key_last_versionchecker_plugin_warning, now) + return + } + + if (isOldVersion(GRACE_PERIOD_WARNING) && shouldWarnAgain(now)) { // store last notification time SP.putLong(R.string.key_last_versionchecker_plugin_warning, now) From c35fbad80a57536a85bbee93080bf5acfdc4d185 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Fri, 26 Apr 2019 15:13:10 +0200 Subject: [PATCH 034/152] New translations strings.xml (Romanian) --- app/src/main/res/values-ro/strings.xml | 89 ++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 12267b972b..894641e880 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -9,6 +9,7 @@ Resetează bazele de date Sigur resetați bazele de date? Ieșire + Folosiți bolusuri extinse pentru >200%% Dispozitive bluetooth DanaR Folosește întotdeauna valori absolute ale bazalei Restartați telefonul sau reporniți aplicația AndroidAPS din System Settings \naltfel AndroidAPS nu va putea face loguri (importante pentru verificarea corectitudinii funcționării algoritmilor)! @@ -53,6 +54,7 @@ Monitorizează și controlează aplicația AndroidAPS folosind smartwatchul WearOS. Afișează informații privind starea buclei pe watchface-ul xDrip+. Controlare de la distanță a AndroidAPS folosind comenzi date prin SMS. + Înapoi Start Verifică Unități @@ -145,6 +147,8 @@ Încălcare a unei limite Eroare la livrarea bolusului Eroare la setare bazală temporară + Valoare bazală [%%] + %% (100%% = curent) Acceptă noua bazală temporară: Tratament Calculator @@ -244,6 +248,7 @@ IOB din pompă Unități zilnic Ultimul bolus + %1$.1f o în urmă Date de intrare incorecte Valoare setată incorect Reîncarcă profilul @@ -261,6 +266,10 @@ Pentru a livra un bolus de %1$.2fU răspundeți cu codul %2$s Pentru a trimite calibrarea cu %1$.2f răspundeți cu codul %2$s Bolusare eșuată + Bolusul de %1$.2fU a fost livrat cu succes + Se va livra un bolus de %1$.2fU + Bolusul de %1$.2fU a fost livrat cu succes + Se livrează %1$.2fU Permite comenzi de la distanță, prin SMS Deget Senzor @@ -305,6 +314,7 @@ Stop STOP APĂSAT Se așteaptă pompa + Se va livra un bolus de %1$.2fU Se stabilesc vizualizările și monitorizarea și se analizează bazalele și valorile ratelor Verificați că vedeți glicemia în Nightscout și că datele despre insulină date de pompă sunt înregistrate în site Pornire în mod buclă deschisă @@ -327,12 +337,26 @@ Bucla este dezactivată Bucla este activată %1$.2f este limitată la %2$.2f + Valoarea %1$s este mai mare decât limita stabilită Comanda de la distanță nu este permisă + Bolus de la distanță nu este disponibil. Încearcă din nou mai târziu. + Pentru a iniția bazala de %1$.2fU/h pentru %2$d min trimiteți codul %3$s + Pentru a schimba profilul în %1$s %2$d%% trimiteți codul %3$s + Pentru a stabili bazala de %1$.2fU/h pentru %2$d min trimiteți codul %3$s + Pentru a iniția bazala de %1$d%% pentru %2$d min trimiteți codul %3$s Pentru suspendarea buclei pentru %1$d minute trimiteți codul %2$s Bazala temporară %1$.2fU/h pentru %2$d minute a fost trimisă cu succes + Bolusul extins de %1$.2fU pentru %2$d min a fost inițiat + Bazala temporară %1$d% pentru %2$d minute a fost stabilită cu succes Trimiterea bazalei temporare a eșuat + Pornirea bolusului extins a eșuat + Pentru oprirea bazalei temporare, răspundeți cu codul %1$s + Pentru oprirea bolusului extins, răspundeți cu codul %1$s Bazala temporară a fost anulată + Bolus extins anulat Renunțarea la bazala temporară a eșuat + Anularea bolusului extins a eșuat + Comandă necunoscută sau răspuns greșit AsistentRapid Setări AsistentRapid Text buton: @@ -440,6 +464,7 @@ Profil Valoare implicită: 3\nAceasta este o setare de critică de securitate a OpenAPS. Asta înseamnă că se limitează bazala lade 3x valoarea maximă a bazalelor tale.Cel mai probabil nu veți schimba această valoare, dar trebuie să țineți cont de ce se discută despre “3x max zilnic; 4x curent” ca valori de siguranță. Valoare implicită: 4\nAceasta este cealaltă jumătate a cheii de siguranță a OpenAPS și cealaltă jumătate a \"3x max zilnic; 4x curent\" al setărilor de siguranță.Aceasta înseamnă că bazala dumneavoastră, indiferent de bazala maximă configurată în pompă, nu poate fi mai mare de acest număr înmulțit cu nivelul curent al bazalei active. Această limitare este impusă pentru a evita posibilitatea de a intra pe un teritoriu periculos prin setarea unei bazale maxime excesiv de mari înainte de a înțelege funcționarea algoritmului. Din nou, valoarea implictă este 4x; majoritatea oamenilor nu vor trebui să ajusteze această valoare și vor modifica, mai degrabă, alte valori dacă vor simți că această valoare le stă în cale. + Valoare implicită: 1.2\nAceasta este valoarea limită de multiplicare pentru autosens (și pentru autotune, în curând) pentru a seta o limită maximă de 20%% din cât de mare poate fi raportul autosens, care, la rândul ei, determină cât de mult poate autosens să modifice bazalele, cât de jos poate modifica ISF și cât de jos poate pune ținta glicemiei. Valoare implicită: 0.7\nCealaltă parte a limitelor autosens, care limitează cât de jos poate ajusta bazalele și cât de mult poate mări ISF și țintele de glicemie. Autosens ajustează țintele la Valoare implicită: adevărat\nAceasta se folosește pentru a permite autosens să ajusteze țintele de glicemie, pe lângă modificările asupra ISF și bazalelor. @@ -452,6 +477,7 @@ Numărul de telefon este invalid Număr de telefon SMS invalid Calibrare + Trimite calibrarea %1$.1f către xDrip? xDrip+ nu este instalat. Calibrare trimisă către xDrip Calibrare trimisă. Recepționarea trebuie să fie activată și în xDrip. @@ -502,6 +528,7 @@ Adult rezistent la insulină Vă rog să selectați vârsta pacientului în vederea stabilirii valorilor maxime admise de siguranță Glimp + %1$s necesită excluderea din lista de optimizare a bateriei pentru o funcționare corespunzătoare Buclă suspendată Suspendat (%1$d min) Superbolus (%1$d min) @@ -537,6 +564,8 @@ Activați indicatori pentru CAGE, IAGE, SAGE, nivel baterie și rezervor pe ecranul principal. Pragul de avertisment pentru insulina din rezervor [U] Pragul critic al nivelului insulinei în rezervor [U] + Pragul de avertizare nivel baterie [%%] + Pragul critic al nivelului baterie [%%] IOB COB Firmware @@ -589,6 +618,7 @@ Setări absorbție Timp maxim absorbție masă [o] Timp în ore la care se presupune că toți carbohidrații sunt absorbiți + Afișati bolusul extins ca %% VS VI VC @@ -701,6 +731,8 @@ Trimite date glicemie la xDrip+ Selectați 640g/Eversense ca sursă de date în xDrip+ Glicemie NSClient + Valoarea bazalei a fost înlocuită cu valoarea minimă posibilă: %1$s + Valoarea bazalei a fost înlocuită cu valoarea maximă posibilă: %1$s Calcul glicemie Calcul IOB bolus Calcul IOB bazală @@ -718,6 +750,8 @@ Mod buclă închisă activat IOB maxim configurat corect Glicemie disponibilă din sursa selectată + Valori bazale nesincronizate cu ora: %1$s + Profil invalid: %1$s Se programează pompa pentru livrare bolus Reîncarcă Stare @@ -730,6 +764,7 @@ Rulează Se oprește TBR Se setează TBR (%1$d%% / %2$d min) + Bolusare (%1$.1f U) Reîncărcare Operațiunea nu este suportată de pompă Folosire nesigură: un bolus extins sau multiwave este activ. Modul buclă este setat să funcționeze în low-suspend pentru 6 ore. Doar bolusurile normale sunt posibile. @@ -1052,11 +1087,13 @@ Liniște Alertă a pompei Înregistrează schimbările locului de inserție + Jurnal al schimbărilor rezervorului Înregistrează schimbările canulei Înregistrează schimbările bateriei Înregistrează schimbările modului de operare Înregistrează alertele Activează emularea RBT + Folosește bolus extins în locul RBT pentru a trece de limita de 250%% Întârziere a deconectării [s] Număr de serie Versiunea de software @@ -1071,6 +1108,58 @@ Șterge asocierea Informații despre asociere Reîmprospătare stare + Porniți pompa + Opriți pompa + Modul de funcționare + Stare + Bolus TDD + Bazală TDD + Total TDD + Se recuperează... + Nu există asociere + Ultima conexiune + Pornit + Oprit + %1$d%% pentru %2$d / %3$d min + Bolus extins + Bolus multiwave + %1$.2f / %2$.2f U pentru %3$d min + Permiteți notificarea de terminare RBT\n(setare a pompei) + Nu permiteți notificarea terminării RBT\n(setare a pompei) + Actualizează + Integrare pentru pompele Accu-Chek Insight + Nu este introdus + Ultima conexiune: acum %1$d min + TBR: %1$d%% pentru %2$d / %3$d min + Extins: %1$.2f / %2$.2f U pentru %3$d min + Multiwave: %1$.2f / %2$.2f U pentru %3$d min + TDD: %1$.2f + Rzvr.: %1$.2fU + Bat.: %1$d%% + Timpul maxim de restabilire [s] + Timpul minim de restabilire [s] + Durata restabilirii + Durată timp expirat pe timpul negocierii - resetare bluetooth + == ∑ %1$s U + U/h + g/U + /U + Înregistrează schimbarea senzorului în NS + Crează eveniment ”Schimbare Senzor” în NS la pornirea senzorului + Tomato (MiaoMiao) + Tomato + Schimbare oră vară/iarnă în mai puțin de 24h + Schimbare oră vară/iarnă în mai puțin de 3 ore - buclă dezactivată + restricție de stocare internă + Eliberați cel puțin %1$d MB din spațiunl de stocare al telefonlui! Buclă dezactivată! + Format greșit + Cod greșit. Comandă anulată. + Nu este configurat + Schimbare de profil creată + Verificator versiune + versiune veche + versiune foarte veche + Noua versiune este disponibilă de cel puțin %1$d zile! Revenire la LGS după 60 zile, bucla va fi dezactivată după 90 zile %1$d zi %1$d zi From 3a7b00f424677967a09cd1527253c241d9613517 Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Sun, 28 Apr 2019 14:33:04 +0200 Subject: [PATCH 035/152] Include 39 as valid value --- .../main/java/info/nightscout/androidaps/db/DatabaseHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java index 1f071a2f18..eb51f5e2ba 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java @@ -434,7 +434,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { return null; for (int i = 0; i < bgList.size(); i++) - if (bgList.get(i).value > 39) + if (bgList.get(i).value >= 39) return bgList.get(i); return null; } From 4336b4743178a373b365a468cae4b9c5d17ad978 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Mon, 29 Apr 2019 11:36:30 +0200 Subject: [PATCH 036/152] NPE fixes from Firebase --- .../activities/PreferencesActivity.java | 2 +- .../activities/InsightPairingActivity.java | 19 ++++++++++------ .../plugins/treatments/TreatmentService.java | 5 +++-- .../androidaps/queue/CommandQueue.java | 7 ++++-- .../androidaps/queue/QueueThread.java | 22 ++++++++++--------- app/src/test/java/info/AAPSMocker.java | 1 + 6 files changed, 34 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.java b/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.java index aea7bcfca6..ef2639e9ac 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.java @@ -93,7 +93,7 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre } else if (editTextPref.getText() != null) { ((EditTextPreference) pref).setDialogMessage(editTextPref.getDialogMessage()); pref.setSummary(editTextPref.getText()); - } else if (pref.getKey().contains("smscommunicator_allowednumbers") && TextUtils.isEmpty(editTextPref.getText().trim())) { + } else if (pref.getKey().contains("smscommunicator_allowednumbers") && (editTextPref.getText() == null || TextUtils.isEmpty(editTextPref.getText().trim()))) { pref.setSummary(MainApp.gs(R.string.smscommunicator_allowednumbers_summary)); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingActivity.java index 7ea52ed271..c165dd140e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingActivity.java @@ -160,12 +160,14 @@ public class InsightPairingActivity extends AppCompatActivity implements Insight private void startBLScan() { if (!scanning) { BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - if (!bluetoothAdapter.isEnabled()) bluetoothAdapter.enable(); - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); - intentFilter.addAction(BluetoothDevice.ACTION_FOUND); - registerReceiver(broadcastReceiver, intentFilter); - bluetoothAdapter.startDiscovery(); + if (bluetoothAdapter != null) { + if (!bluetoothAdapter.isEnabled()) bluetoothAdapter.enable(); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); + intentFilter.addAction(BluetoothDevice.ACTION_FOUND); + registerReceiver(broadcastReceiver, intentFilter); + bluetoothAdapter.startDiscovery(); + } scanning = true; } } @@ -173,7 +175,10 @@ public class InsightPairingActivity extends AppCompatActivity implements Insight private void stopBLScan() { if (scanning) { unregisterReceiver(broadcastReceiver); - BluetoothAdapter.getDefaultAdapter().cancelDiscovery(); + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (bluetoothAdapter != null) { + bluetoothAdapter.cancelDiscovery(); + } scanning = false; } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java index 7bb4774911..502b7f38b7 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java @@ -190,8 +190,9 @@ public class TreatmentService extends OrmLiteBaseService { } // prepare task for execution in 1 sec // cancel waiting task to prevent sending multiple posts - if (callback.getPost() != null) - callback.getPost().cancel(false); + ScheduledFuture scheduledFuture = callback.getPost(); + if (scheduledFuture != null) + scheduledFuture.cancel(false); Runnable task = new PostRunnable(); final int sec = 1; callback.setPost(eventWorker.schedule(task, sec, TimeUnit.SECONDS)); diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java index e4fbacc43c..daf8239788 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java @@ -12,6 +12,8 @@ import org.slf4j.LoggerFactory; import java.util.LinkedList; +import javax.annotation.Nullable; + import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; @@ -126,6 +128,7 @@ public class CommandQueue { queue.add(command); } + @Nullable synchronized void pickup() { performing = queue.poll(); } @@ -143,11 +146,11 @@ public class CommandQueue { return queue.size(); } - public Command performing() { + Command performing() { return performing; } - public void resetPerforming() { + void resetPerforming() { performing = null; } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java b/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java index 0962952221..a7e106c042 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java @@ -141,15 +141,17 @@ public class QueueThread extends Thread { // Pickup 1st command and set performing variable if (queue.size() > 0) { queue.pickup(); - if (L.isEnabled(L.PUMPQUEUE)) - log.debug("performing " + queue.performing().status()); - MainApp.bus().post(new EventQueueChanged()); - queue.performing().execute(); - queue.resetPerforming(); - MainApp.bus().post(new EventQueueChanged()); - lastCommandTime = System.currentTimeMillis(); - SystemClock.sleep(100); - continue; + if (queue.performing() != null) { + if (L.isEnabled(L.PUMPQUEUE)) + log.debug("performing " + queue.performing().status()); + MainApp.bus().post(new EventQueueChanged()); + queue.performing().execute(); + queue.resetPerforming(); + MainApp.bus().post(new EventQueueChanged()); + lastCommandTime = System.currentTimeMillis(); + SystemClock.sleep(100); + continue; + } } } @@ -173,7 +175,7 @@ public class QueueThread extends Thread { } } } finally { - if (mWakeLock != null) + if (mWakeLock != null && mWakeLock.isHeld()) mWakeLock.release(); if (L.isEnabled(L.PUMPQUEUE)) log.debug("thread end"); diff --git a/app/src/test/java/info/AAPSMocker.java b/app/src/test/java/info/AAPSMocker.java index 50bd20d198..79e0f89996 100644 --- a/app/src/test/java/info/AAPSMocker.java +++ b/app/src/test/java/info/AAPSMocker.java @@ -150,6 +150,7 @@ public class AAPSMocker { when(MainApp.gs(R.string.pumpsuspended)).thenReturn("Pump suspended"); when(MainApp.gs(R.string.cob)).thenReturn("COB"); when(MainApp.gs(R.string.value_unavailable_short)).thenReturn("n/a"); + when(MainApp.gs(R.string.pumpNotInitialized)).thenReturn("Pump not initialized!"); } public static MainApp mockMainApp() { From 6c7a18067fa3ba80859eb0711b50fe986c2669c6 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Wed, 1 May 2019 22:59:48 +0200 Subject: [PATCH 037/152] remove misplaced @Nullable --- .../main/java/info/nightscout/androidaps/queue/CommandQueue.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java index daf8239788..7928435ed0 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java @@ -128,7 +128,6 @@ public class CommandQueue { queue.add(command); } - @Nullable synchronized void pickup() { performing = queue.poll(); } From a303d71d253f3c8d8e11a585598fafc22f01aec1 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 2 May 2019 21:51:44 +0200 Subject: [PATCH 038/152] scanning = false if no adapter present --- .../plugins/pump/insight/activities/InsightPairingActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingActivity.java index c165dd140e..13fd698236 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingActivity.java @@ -167,8 +167,8 @@ public class InsightPairingActivity extends AppCompatActivity implements Insight intentFilter.addAction(BluetoothDevice.ACTION_FOUND); registerReceiver(broadcastReceiver, intentFilter); bluetoothAdapter.startDiscovery(); + scanning = true; } - scanning = true; } } From 552298496f9a7c39a54001468d6ff9b23ff90b5f Mon Sep 17 00:00:00 2001 From: Tebbe Ubben Date: Sun, 12 May 2019 02:20:00 +0200 Subject: [PATCH 039/152] Pump control: Fix crash when opening Insight paring activity --- .../pump/insight/LocalInsightPlugin.java | 3 +- ..._local.xml => pref_insight_local_full.xml} | 0 .../xml/pref_insight_local_pumpcontrol.xml | 65 +++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) rename app/src/main/res/xml/{pref_insight_local.xml => pref_insight_local_full.xml} (100%) create mode 100644 app/src/main/res/xml/pref_insight_local_pumpcontrol.xml diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightPlugin.java index c5b505a3d3..d673a0b8ce 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightPlugin.java @@ -168,7 +168,8 @@ public class LocalInsightPlugin extends PluginBase implements PumpInterface, Con .mainType(PluginType.PUMP) .description(R.string.description_pump_insight_local) .fragmentClass(LocalInsightFragment.class.getName()) - .preferencesId(R.xml.pref_insight_local)); + .preferencesId(MainApp.instance().getPackageName().equals("info.nightscout.androidaps") + ? R.xml.pref_insight_local_full : R.xml.pref_insight_local_pumpcontrol)); pumpDescription = new PumpDescription(); pumpDescription.setPumpDescription(PumpType.AccuChekInsightBluetooth); diff --git a/app/src/main/res/xml/pref_insight_local.xml b/app/src/main/res/xml/pref_insight_local_full.xml similarity index 100% rename from app/src/main/res/xml/pref_insight_local.xml rename to app/src/main/res/xml/pref_insight_local_full.xml diff --git a/app/src/main/res/xml/pref_insight_local_pumpcontrol.xml b/app/src/main/res/xml/pref_insight_local_pumpcontrol.xml new file mode 100644 index 0000000000..74e5586bf6 --- /dev/null +++ b/app/src/main/res/xml/pref_insight_local_pumpcontrol.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From f4138a2fc0f5b82521644501bd4358ad93a80b74 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sun, 12 May 2019 10:43:32 +0200 Subject: [PATCH 040/152] fix index out of bouns --- .../general/overview/OverviewFragment.java | 23 +++---------------- .../notifications/NotificationStore.java | 21 +++++++++++++---- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java index 6d41303540..0b0724eefd 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java @@ -56,7 +56,6 @@ import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.QuickWizardEntry; @@ -94,19 +93,18 @@ import info.nightscout.androidaps.plugins.general.careportal.OptionsToShow; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus; import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus; +import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity; import info.nightscout.androidaps.plugins.general.overview.dialogs.CalibrationDialog; import info.nightscout.androidaps.plugins.general.overview.dialogs.ErrorHelperActivity; import info.nightscout.androidaps.plugins.general.overview.dialogs.NewCarbsDialog; import info.nightscout.androidaps.plugins.general.overview.dialogs.NewInsulinDialog; import info.nightscout.androidaps.plugins.general.overview.dialogs.NewTreatmentDialog; import info.nightscout.androidaps.plugins.general.overview.dialogs.WizardDialog; -import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity; import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData; -import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationRecyclerViewAdapter; -import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore; import info.nightscout.androidaps.plugins.general.wear.ActionStringHandler; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensData; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.CobInfo; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress; @@ -1064,7 +1062,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, timeView.setText(DateUtil.timeString(new Date())); } - updateNotifications(); + OverviewPlugin.getPlugin().notificationStore.updateNotifications(notificationsView); pumpStatusLayout.setVisibility(View.GONE); loopStatusLayout.setVisibility(View.GONE); @@ -1603,21 +1601,6 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, Profiler.log(log, from, updateGUIStart); } - //Notifications - - void updateNotifications() { - NotificationStore nstore = OverviewPlugin.getPlugin().notificationStore; - nstore.removeExpired(); - nstore.unSnooze(); - if (nstore.store.size() > 0) { - NotificationRecyclerViewAdapter adapter = new NotificationRecyclerViewAdapter(nstore.store); - notificationsView.setAdapter(adapter); - notificationsView.setVisibility(View.VISIBLE); - } else { - notificationsView.setVisibility(View.GONE); - } - } - public static void applyStatuslight(TextView view, String text, double value, double warnThreshold, double urgentThreshold, double invalid, boolean checkAscending) { Function check = checkAscending ? (Double threshold) -> value >= threshold : (Double threshold) -> value <= threshold; if (value != invalid) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.java index 8d327bf29f..16913d26ae 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.java @@ -12,6 +12,8 @@ import android.media.RingtoneManager; import android.net.Uri; import android.os.Build; import android.support.v4.app.NotificationCompat; +import android.support.v7.widget.RecyclerView; +import android.view.View; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,7 +23,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; @@ -97,7 +98,7 @@ public class NotificationStore { return false; } - public synchronized void removeExpired() { + private synchronized void removeExpired() { for (int i = 0; i < store.size(); i++) { Notification n = store.get(i); if (n.validTo.getTime() != 0 && n.validTo.getTime() < System.currentTimeMillis()) { @@ -107,13 +108,13 @@ public class NotificationStore { } } - public void snoozeTo(long timeToSnooze) { + void snoozeTo(long timeToSnooze) { if (L.isEnabled(L.NOTIFICATION)) log.debug("Snoozing alarm until: " + timeToSnooze); SP.putLong("snoozedTo", timeToSnooze); } - public void unSnooze() { + private void unSnooze() { if (Notification.isAlarmForStaleData()) { Notification notification = new Notification(Notification.NSALARM, MainApp.gs(R.string.nsalarm_staledata), Notification.URGENT); SP.putLong("snoozedTo", System.currentTimeMillis()); @@ -160,4 +161,16 @@ public class NotificationStore { } } + public synchronized void updateNotifications(RecyclerView notificationsView) { + removeExpired(); + unSnooze(); + if (store.size() > 0) { + NotificationRecyclerViewAdapter adapter = new NotificationRecyclerViewAdapter(store); + notificationsView.setAdapter(adapter); + notificationsView.setVisibility(View.VISIBLE); + } else { + notificationsView.setVisibility(View.GONE); + } + } + } From 4fda6c4c4f564e08f475d0423d5f685b081c22fe Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sun, 12 May 2019 10:52:25 +0200 Subject: [PATCH 041/152] fix basal rendering --- .../IobCobCalculatorPlugin.java | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.java index 94b687b863..659af5ec56 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.java @@ -391,28 +391,30 @@ public class IobCobCalculatorPlugin extends PluginBase { } public BasalData getBasalData(Profile profile, long time) { - long now = System.currentTimeMillis(); - time = roundUpTime(time); - BasalData retval = basalDataTable.get(time); - if (retval == null) { - retval = new BasalData(); - TemporaryBasal tb = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(time); - retval.basal = profile.getBasal(time); - if (tb != null) { - retval.isTempBasalRunning = true; - retval.tempBasalAbsolute = tb.tempBasalConvertedToAbsolute(time, profile); + synchronized (dataLock) { + long now = System.currentTimeMillis(); + time = roundUpTime(time); + BasalData retval = basalDataTable.get(time); + if (retval == null) { + retval = new BasalData(); + TemporaryBasal tb = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(time); + retval.basal = profile.getBasal(time); + if (tb != null) { + retval.isTempBasalRunning = true; + retval.tempBasalAbsolute = tb.tempBasalConvertedToAbsolute(time, profile); + } else { + retval.isTempBasalRunning = false; + retval.tempBasalAbsolute = retval.basal; + } + if (time < now) { + basalDataTable.append(time, retval); + } + //log.debug(">>> getBasalData Cache miss " + new Date(time).toLocaleString()); } else { - retval.isTempBasalRunning = false; - retval.tempBasalAbsolute = retval.basal; + //log.debug(">>> getBasalData Cache hit " + new Date(time).toLocaleString()); } - if (time < now) { - basalDataTable.append(time, retval); - } - //log.debug(">>> getBasalData Cache miss " + new Date(time).toLocaleString()); - } else { - //log.debug(">>> getBasalData Cache hit " + new Date(time).toLocaleString()); + return retval; } - return retval; } @Nullable From 808a4177ca1c9f3c3de730ec7df1a811da5793f7 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sun, 12 May 2019 14:36:14 +0200 Subject: [PATCH 042/152] fix tests --- .../androidaps/interfaces/ConstraintsCheckerTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintsCheckerTest.java b/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintsCheckerTest.java index fbbd72f064..edfd30180a 100644 --- a/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintsCheckerTest.java +++ b/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintsCheckerTest.java @@ -283,6 +283,8 @@ public class ConstraintsCheckerTest { AAPSMocker.mockSP(); AAPSMocker.mockCommandQueue(); + when(mainApp.getPackageName()).thenReturn("info.nightscout.androidaps"); + // RS constructor when(SP.getString(R.string.key_danars_address, "")).thenReturn(""); From 5a478578e07518b77610134c286b4202f886f2cd Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Wed, 15 May 2019 23:26:07 +0200 Subject: [PATCH 043/152] targetsdk 28 --- app/build.gradle | 4 ++-- app/src/main/AndroidManifest.xml | 7 ++++++- .../plugins/general/overview/dialogs/ErrorDialog.java | 6 +++++- .../overview/notifications/NotificationStore.java | 10 ++++++++-- .../PersistentNotificationPlugin.java | 7 +++++-- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index cef63558b9..5ab2f9ac29 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -97,11 +97,11 @@ tasks.matching { it instanceof Test }.all { } android { - compileSdkVersion 27 + compileSdkVersion 28 defaultConfig { minSdkVersion 21 - targetSdkVersion 25 + targetSdkVersion 28 multiDexEnabled true versionCode 1500 version "2.3.1-dev" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3eaa81ba45..6a893d6606 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,6 +17,7 @@ + @@ -34,7 +35,8 @@ android:label="@string/app_name" android:roundIcon="${appIconRound}" android:supportsRtl="true" - android:theme="@style/AppTheme.NoActionBar"> + android:theme="@style/AppTheme.NoActionBar" + android:fullBackupContent="true"> @@ -281,6 +283,9 @@ android:name=".plugins.pump.insight.activities.InsightPairingInformationActivity" android:label="@string/pairing_information" android:theme="@style/AppTheme" /> + + + diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/ErrorDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/ErrorDialog.java index 209a585384..1093da531b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/ErrorDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/ErrorDialog.java @@ -2,6 +2,7 @@ package info.nightscout.androidaps.plugins.general.overview.dialogs; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; @@ -98,7 +99,10 @@ public class ErrorDialog extends DialogFragment implements View.OnClickListener private void startAlarm() { Intent alarm = new Intent(MainApp.instance().getApplicationContext(), AlarmSoundService.class); alarm.putExtra("soundid", soundId); - MainApp.instance().startService(alarm); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + MainApp.instance().startForegroundService(alarm); + else + MainApp.instance().startService(alarm); } private void stopAlarm() { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.java index 16913d26ae..d669065622 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.java @@ -69,14 +69,20 @@ public class NotificationStore { if (usesChannels && n.soundId != null) { Intent alarm = new Intent(MainApp.instance().getApplicationContext(), AlarmSoundService.class); alarm.putExtra("soundid", n.soundId); - MainApp.instance().startService(alarm); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + MainApp.instance().startForegroundService(alarm); + else + MainApp.instance().startService(alarm); } } else { if (n.soundId != null) { Intent alarm = new Intent(MainApp.instance().getApplicationContext(), AlarmSoundService.class); alarm.putExtra("soundid", n.soundId); - MainApp.instance().startService(alarm); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + MainApp.instance().startForegroundService(alarm); + else + MainApp.instance().startService(alarm); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.java index 987d9374bd..0489f488df 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.java @@ -20,7 +20,6 @@ import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainActivity; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.DatabaseHelper; @@ -37,6 +36,7 @@ import info.nightscout.androidaps.interfaces.PluginDescription; import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; @@ -111,7 +111,10 @@ public class PersistentNotificationPlugin extends PluginBase { } private void triggerNotificationUpdate() { - MainApp.instance().startService(new Intent(MainApp.instance(), DummyService.class)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + MainApp.instance().startForegroundService(new Intent(MainApp.instance(), DummyService.class)); + else + MainApp.instance().startService(new Intent(MainApp.instance(), DummyService.class)); } Notification updateNotification() { From 08c3b5c0952a3cc7205a11a7474ecad95f6cdce1 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Wed, 15 May 2019 23:31:46 +0200 Subject: [PATCH 044/152] make app indexable --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6a893d6606..168ed83bdd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -43,6 +43,7 @@ + From 1ccb895ff9b3d4c22e19ea5edcc5ca632234ec2b Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Wed, 15 May 2019 23:33:56 +0200 Subject: [PATCH 045/152] update libraries --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 5ab2f9ac29..b9b0bf3eaf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -213,7 +213,7 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.google.android.gms:play-services-wearable:16.0.1' - implementation 'com.google.firebase:firebase-core:16.0.8' + implementation 'com.google.firebase:firebase-core:16.0.9' implementation("com.crashlytics.sdk.android:crashlytics:2.9.9@aar") { transitive = true; } @@ -250,7 +250,7 @@ dependencies { // excluding org.json which is provided by Android exclude group: "org.json", module: "json" } - implementation "com.google.code.gson:gson:2.8.2" + implementation "com.google.code.gson:gson:2.8.5" implementation "com.google.guava:guava:24.1-jre" implementation "net.danlew:android.joda:2.9.9.1" From 12260779e69b15b3cb8edce3cfbbd23688dabdf9 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Wed, 15 May 2019 23:39:11 +0200 Subject: [PATCH 046/152] supportLibraryVersion 28 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index b9b0bf3eaf..2fe633746b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,7 +18,7 @@ apply plugin: "jacoco-android" apply plugin: 'com.jakewharton.butterknife' ext { - supportLibraryVersion = "27.1.1" + supportLibraryVersion = "28.0.0" ormLiteVersion = "4.46" powermockVersion = "1.7.3" dexmakerVersion = "1.2" From 8c00a9fadef84ac49f12ef21f425aaa09b624a54 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 16 May 2019 00:21:32 +0200 Subject: [PATCH 047/152] wear libraries update --- app/build.gradle | 2 +- wear/build.gradle | 18 ++++++++++-------- wear/libs/hellocharts-library-1.5.5.jar | Bin 161461 -> 0 bytes 3 files changed, 11 insertions(+), 9 deletions(-) delete mode 100644 wear/libs/hellocharts-library-1.5.5.jar diff --git a/app/build.gradle b/app/build.gradle index 2fe633746b..cd76881ff9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -104,7 +104,7 @@ android { targetSdkVersion 28 multiDexEnabled true versionCode 1500 - version "2.3.1-dev" + version "2.3.1-dev-28" buildConfigField "String", "VERSION", '"' + version + '"' buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"' buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"' diff --git a/wear/build.gradle b/wear/build.gradle index 9f094935fd..9a82e64241 100644 --- a/wear/build.gradle +++ b/wear/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'com.android.application' ext { - wearableVersion = "2.0.1" + wearableVersion = "2.4.0" playServicesWearable = "16.0.1" } @@ -27,12 +27,12 @@ def generateGitBuild = { -> } android { - compileSdkVersion 27 + compileSdkVersion 28 defaultConfig { applicationId "info.nightscout.androidaps" minSdkVersion 23 - targetSdkVersion 23 + targetSdkVersion 28 versionCode 1 versionName "1.0.2" buildConfigField "String", "BUILDVERSION", generateGitBuild() @@ -90,13 +90,15 @@ allprojects { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation files("libs/hellocharts-library-1.5.5.jar") + //implementation files("libs/hellocharts-library-1.5.5.jar") //compile "com.ustwo.android:clockwise-wearable:1.0.2" compileOnly "com.google.android.wearable:wearable:${wearableVersion}" implementation "com.google.android.support:wearable:${wearableVersion}" implementation "com.google.android.gms:play-services-wearable:${playServicesWearable}" - implementation(name:"ustwo-clockwise-debug", ext:"aar") - implementation "com.android.support:support-v4:27.0.1" - implementation 'com.android.support:wear:27.0.1' - implementation "me.denley.wearpreferenceactivity:wearpreferenceactivity:0.5.0" + implementation(name: 'ustwo-clockwise-debug', ext: 'aar') + implementation 'com.android.support:support-v4:28.0.0' + implementation 'com.android.support:percent:28.0.0' + implementation 'com.android.support:wear:28.0.0' + implementation('me.denley.wearpreferenceactivity:wearpreferenceactivity:0.5.0') + implementation('com.github.lecho:hellocharts-library:1.5.8@aar') } diff --git a/wear/libs/hellocharts-library-1.5.5.jar b/wear/libs/hellocharts-library-1.5.5.jar deleted file mode 100644 index 142316706e686114a71b233eeb2fc01d367deb29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 161461 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

O%dhdgnTj!T3%ERF;lzXT-%ni{(9h< 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 Date: Thu, 16 May 2019 00:24:44 +0200 Subject: [PATCH 048/152] update roboelectric --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index cd76881ff9..3ab239c4fa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -270,7 +270,7 @@ dependencies { testImplementation "org.powermock:powermock-module-junit4:${powermockVersion}" testImplementation "joda-time:joda-time:2.9.9" testImplementation "com.google.truth:truth:0.39" - testImplementation 'org.robolectric:robolectric:3.8' + testImplementation 'org.robolectric:robolectric:4.2.1' testImplementation "org.skyscreamer:jsonassert:1.5.0" androidTestImplementation "org.mockito:mockito-core:2.8.47" From a41a678ea2c0fd956853a6fc7b59091650d3fd62 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 16 May 2019 00:31:46 +0200 Subject: [PATCH 049/152] kotlin lib update --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6826c29a13..e0c65bd53e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.21' + ext.kotlin_version = '1.3.31' repositories { google() jcenter() From e2dd781a2078b65db4386979cf6034cde204423e Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 16 May 2019 00:56:00 +0200 Subject: [PATCH 050/152] formating --- app/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3ab239c4fa..d8dacb5ba8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,12 +9,12 @@ buildscript { classpath 'com.dicedmelon.gradle:jacoco-android:0.1.3' } } -apply plugin: "com.android.application" +apply plugin: 'com.android.application' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android' apply plugin: 'com.google.gms.google-services' -apply plugin: "io.fabric" -apply plugin: "jacoco-android" +apply plugin: 'io.fabric' +apply plugin: 'jacoco-android' apply plugin: 'com.jakewharton.butterknife' ext { From 9a76f3386de71996d69a1d43b48392c6d6eb556b Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 16 May 2019 13:57:37 +0200 Subject: [PATCH 051/152] androidx transition --- app/build.gradle | 64 ++++++++++--------- app/src/main/AndroidManifest.xml | 2 +- .../nightscout/androidaps/MainActivity.java | 20 +++--- .../info/nightscout/androidaps/MainApp.java | 6 +- .../activities/HistoryBrowseActivity.java | 6 +- .../activities/SingleFragmentActivity.java | 6 +- .../activities/TDDStatsActivity.java | 2 +- .../androidaps/data/ConstraintChecker.java | 4 +- .../nightscout/androidaps/data/Intervals.java | 4 +- .../data/NonOverlappingIntervals.java | 4 +- .../androidaps/data/OverlappingIntervals.java | 2 +- .../nightscout/androidaps/data/Profile.java | 2 +- .../androidaps/data/ProfileIntervals.java | 4 +- .../androidaps/data/ProfileStore.java | 4 +- .../androidaps/db/DatabaseHelper.java | 2 +- .../androidaps/db/ProfileSwitch.java | 2 +- .../androidaps/events/EventNewBG.java | 2 +- .../events/EventTreatmentChange.java | 2 +- .../androidaps/interfaces/PluginBase.java | 2 +- .../interfaces/ProfileInterface.java | 2 +- .../plugins/aps/loop/LoopPlugin.java | 4 +- .../plugins/common/SubscriberFragment.java | 2 +- .../configBuilder/ConfigBuilderFragment.java | 4 +- .../configBuilder/ConfigBuilderPlugin.java | 2 +- .../DetailedBolusInfoStorage.java | 2 +- .../configBuilder/ProfileFunctions.java | 2 +- .../objectives/ObjectivesFragment.java | 10 +-- .../objectives/objectives/Objective.java | 2 +- .../general/actions/ActionsFragment.java | 4 +- .../general/actions/dialogs/FillDialog.java | 4 +- .../dialogs/NewExtendedBolusDialog.java | 4 +- .../actions/dialogs/NewTempBasalDialog.java | 4 +- .../careportal/CareportalFragment.java | 2 +- .../Dialogs/NewNSTreatmentDialog.java | 4 +- .../plugins/general/food/FoodFragment.java | 6 +- .../plugins/general/food/FoodService.java | 2 +- .../maintenance/ImportExportPrefs.java | 6 +- .../maintenance/MaintenanceFragment.java | 4 +- .../maintenance/MaintenancePlugin.java | 2 +- .../activities/LogSettingActivity.java | 2 +- .../plugins/general/nsclient/NSUpload.java | 4 +- .../broadcasts/BroadcastAckAlarm.java | 2 +- .../nsclient/broadcasts/BroadcastAlarm.java | 2 +- .../broadcasts/BroadcastAnnouncement.java | 2 +- .../nsclient/broadcasts/BroadcastCals.java | 2 +- .../broadcasts/BroadcastClearAlarm.java | 2 +- .../broadcasts/BroadcastDeviceStatus.java | 2 +- .../nsclient/broadcasts/BroadcastFood.java | 2 +- .../nsclient/broadcasts/BroadcastMbgs.java | 2 +- .../nsclient/broadcasts/BroadcastProfile.java | 2 +- .../nsclient/broadcasts/BroadcastSgvs.java | 2 +- .../nsclient/broadcasts/BroadcastStatus.java | 2 +- .../broadcasts/BroadcastTreatment.java | 2 +- .../broadcasts/BroadcastUrgentAlarm.java | 2 +- .../nsclient/data/NSSettingsStatus.java | 2 +- .../nsclient/receivers/RestartReceiver.java | 2 +- .../general/overview/OverviewFragment.java | 18 +++--- .../activities/QuickWizardListActivity.java | 10 +-- .../overview/dialogs/BolusProgressDialog.java | 2 +- .../dialogs/BolusProgressHelperActivity.java | 2 +- .../overview/dialogs/CalibrationDialog.java | 2 +- .../dialogs/EditQuickWizardDialog.java | 2 +- .../general/overview/dialogs/ErrorDialog.java | 2 +- .../overview/dialogs/ErrorHelperActivity.java | 2 +- .../overview/dialogs/NewCarbsDialog.java | 4 +- .../overview/dialogs/NewInsulinDialog.java | 4 +- .../overview/dialogs/NewTreatmentDialog.java | 4 +- .../overview/dialogs/WizardDialog.java | 4 +- .../DismissNotificationService.java | 2 +- .../NotificationRecyclerViewAdapter.java | 6 +- .../notifications/NotificationStore.java | 4 +- .../persistentNotification/DummyService.java | 2 +- .../PersistentNotificationPlugin.java | 6 +- .../general/wear/ActionStringHandler.java | 2 +- .../plugins/general/wear/WearFragment.java | 2 +- .../wearintegration/WatchUpdaterService.java | 2 +- .../xdripStatusline/StatuslinePlugin.java | 2 +- .../plugins/insulin/InsulinFragment.java | 2 +- .../plugins/iob/iobCobCalculator/CobInfo.java | 4 +- .../iob/iobCobCalculator/GlucoseStatus.java | 2 +- .../IobCobCalculatorPlugin.java | 6 +- .../iobCobCalculator/IobCobOref1Thread.java | 2 +- .../iob/iobCobCalculator/IobCobThread.java | 2 +- .../profile/local/LocalProfileFragment.java | 2 +- .../profile/local/LocalProfilePlugin.java | 2 +- .../plugins/profile/ns/NSProfilePlugin.java | 2 +- .../plugins/pump/combo/ComboFragment.java | 2 +- .../plugins/pump/combo/ComboPlugin.java | 8 +-- .../plugins/pump/combo/ComboPump.java | 4 +- .../combo/ruffyscripter/CommandResult.java | 2 +- .../combo/ruffyscripter/RuffyScripter.java | 4 +- .../ruffyscripter/WarningOrErrorCode.java | 2 +- .../ruffyscripter/commands/BaseCommand.java | 2 +- .../commands/ReadHistoryCommand.java | 2 +- .../ruffyscripter/history/PumpHistory.java | 2 +- .../pump/danaR/AbstractDanaRPlugin.java | 2 +- .../plugins/pump/danaR/DanaRFragment.java | 2 +- .../plugins/pump/danaR/DanaRPlugin.java | 4 +- .../pump/danaR/Dialogs/ProfileViewDialog.java | 2 +- .../activities/DanaRHistoryActivity.java | 6 +- .../danaR/comm/MsgStatusBolusExtended.java | 2 +- .../pump/danaR/comm/MsgStatusTempBasal.java | 2 +- .../pump/danaRKorean/DanaRKoreanPlugin.java | 4 +- .../plugins/pump/danaRS/DanaRSPlugin.java | 6 +- .../danaRS/activities/BLEScanActivity.java | 2 +- .../activities/PairingHelperActivity.java | 2 +- .../activities/PairingProgressDialog.java | 2 +- ...acket_Basal_Get_Temporary_Basal_State.java | 2 +- .../plugins/pump/danaRv2/DanaRv2Plugin.java | 4 +- .../comm/MsgStatusBolusExtended_v2.java | 2 +- .../danaRv2/comm/MsgStatusTempBasal_v2.java | 2 +- .../pump/insight/InsightAlertService.java | 2 +- .../pump/insight/LocalInsightFragment.java | 4 +- .../activities/InsightAlertActivity.java | 4 +- .../activities/InsightPairingActivity.java | 10 +-- .../InsightPairingInformationActivity.java | 4 +- .../InsightConnectionService.java | 2 +- .../pump/virtual/VirtualPumpFragment.java | 4 +- .../sensitivity/SensitivityAAPSPlugin.java | 2 +- .../sensitivity/SensitivityOref0Plugin.java | 2 +- .../sensitivity/SensitivityOref1Plugin.java | 2 +- .../SensitivityWeightedAveragePlugin.java | 2 +- .../plugins/source/BGSourceFragment.java | 8 +-- .../plugins/treatments/CarbsGenerator.java | 2 +- .../plugins/treatments/Treatment.java | 2 +- .../plugins/treatments/TreatmentService.java | 2 +- .../treatments/TreatmentsFragment.java | 4 +- .../plugins/treatments/TreatmentsPlugin.java | 2 +- .../treatments/dialogs/WizardInfoDialog.java | 2 +- .../fragments/ProfileViewerDialog.java | 2 +- .../fragments/TreatmentsBolusFragment.java | 12 ++-- .../TreatmentsCareportalFragment.java | 8 +-- .../TreatmentsExtendedBolusesFragment.java | 10 +-- .../TreatmentsProfileSwitchFragment.java | 12 ++-- .../TreatmentsTempTargetFragment.java | 10 +-- .../TreatmentsTemporaryBasalsFragment.java | 12 ++-- .../androidaps/queue/CommandQueue.java | 4 +- .../androidaps/receivers/DataReceiver.java | 2 +- .../receivers/NetworkChangeReceiver.java | 2 +- .../androidaps/setupwizard/SWDefinition.java | 2 +- .../setupwizard/SetupWizardActivity.java | 6 +- .../setupwizard/elements/SWFragment.java | 4 +- .../androidaps/tabs/TabPageAdapter.java | 8 +-- .../androidaps/utils/AndroidPermission.java | 4 +- .../nightscout/androidaps/utils/DateUtil.java | 2 +- .../androidaps/utils/JsonHelper.java | 2 +- .../nightscout/androidaps/utils/OKDialog.java | 4 +- .../androidaps/utils/SingleClickButton.java | 4 +- .../androidaps/utils/TimeListEdit.java | 4 +- .../androidaps/utils/XdripCalibrations.java | 2 +- .../res/layout/activity_historybrowse.xml | 4 +- .../res/layout/activity_insight_pairing.xml | 2 +- app/src/main/res/layout/activity_main.xml | 16 ++--- app/src/main/res/layout/bgsource_fragment.xml | 4 +- app/src/main/res/layout/bgsource_item.xml | 4 +- .../main/res/layout/careportal_fragment.xml | 16 ++--- .../main/res/layout/danar_history_item.xml | 4 +- .../main/res/layout/danar_historyactivity.xml | 2 +- app/src/main/res/layout/food_fragment.xml | 4 +- app/src/main/res/layout/food_item.xml | 4 +- .../main/res/layout/objectives_fragment.xml | 2 +- app/src/main/res/layout/objectives_item.xml | 4 +- app/src/main/res/layout/overview_fragment.xml | 4 +- .../res/layout/overview_fragment_nsclient.xml | 4 +- .../overview_fragment_nsclient_tablet.xml | 4 +- .../layout/overview_fragment_smallheight.xml | 4 +- .../res/layout/overview_notification_item.xml | 4 +- .../overview_quickwizardlist_activity.xml | 2 +- .../layout/overview_quickwizardlist_item.xml | 4 +- .../res/layout/treatments_bolus_fragment.xml | 4 +- .../main/res/layout/treatments_bolus_item.xml | 4 +- .../layout/treatments_careportal_fragment.xml | 4 +- .../res/layout/treatments_careportal_item.xml | 4 +- .../treatments_extendedbolus_fragment.xml | 4 +- .../layout/treatments_extendedbolus_item.xml | 4 +- .../treatments_profileswitch_fragment.xml | 4 +- .../layout/treatments_profileswitch_item.xml | 4 +- .../layout/treatments_tempbasals_fragment.xml | 4 +- .../res/layout/treatments_tempbasals_item.xml | 4 +- .../layout/treatments_temptarget_fragment.xml | 4 +- .../res/layout/treatments_temptarget_item.xml | 4 +- build.gradle | 9 +-- gradle.properties | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- wear/build.gradle | 6 +- .../androidaps/data/ListenerService.java | 11 ++-- .../interaction/actions/AcceptActivity.java | 2 +- .../interaction/utils/VersionPreference.java | 5 +- .../androidaps/watchfaces/BIGChart.java | 4 +- .../androidaps/watchfaces/BaseWatchFace.java | 2 +- .../watchfaces/CircleWatchface.java | 2 +- .../androidaps/watchfaces/Home.java | 2 +- .../androidaps/watchfaces/Home2.java | 2 +- .../androidaps/watchfaces/LargeHome.java | 2 +- .../androidaps/watchfaces/NOChart.java | 8 +-- .../androidaps/watchfaces/Steampunk.java | 2 +- .../main/res/layout/action_confirm_text.xml | 8 +-- 197 files changed, 404 insertions(+), 413 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d8dacb5ba8..f3652457a9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,7 +6,7 @@ buildscript { dependencies { classpath 'io.fabric.tools:gradle:1.+' - classpath 'com.dicedmelon.gradle:jacoco-android:0.1.3' + classpath 'com.dicedmelon.gradle:jacoco-android:0.1.4' } } apply plugin: 'com.android.application' @@ -17,12 +17,16 @@ apply plugin: 'io.fabric' apply plugin: 'jacoco-android' apply plugin: 'com.jakewharton.butterknife' +jacoco { + toolVersion = "0.8.3" +} + ext { supportLibraryVersion = "28.0.0" ormLiteVersion = "4.46" powermockVersion = "1.7.3" dexmakerVersion = "1.2" - butterknifeVersion = "8.8.1" + butterknifeVersion = "10.1.0" } @@ -109,7 +113,7 @@ android { buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"' buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"' buildConfigField "String", "HEAD", '"' + generateGitBuild() + '"' - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // if you change minSdkVersion to less than 11, you need to change executeTask for wear ndk { @@ -219,14 +223,14 @@ dependencies { } libs "MilosKozak:danars-support-lib:master@zip" - implementation "com.android.support:appcompat-v7:${supportLibraryVersion}" - implementation "com.android.support:support-v13:${supportLibraryVersion}" - implementation "com.android.support:support-v4:${supportLibraryVersion}" - implementation "com.android.support:cardview-v7:${supportLibraryVersion}" - implementation "com.android.support:recyclerview-v7:${supportLibraryVersion}" - implementation "com.android.support:gridlayout-v7:${supportLibraryVersion}" - implementation "com.android.support:design:${supportLibraryVersion}" - implementation "com.android.support:percent:${supportLibraryVersion}" + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.legacy:legacy-support-v13:1.0.0' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.recyclerview:recyclerview:1.0.0' + implementation 'androidx.gridlayout:gridlayout:1.0.0' + implementation 'com.google.android.material:material:1.0.0' + implementation 'androidx.percentlayout:percentlayout:1.0.0' implementation "com.wdullaer:materialdatetimepicker:2.3.0" implementation "com.squareup:otto:1.3.7" implementation "com.j256.ormlite:ormlite-core:${ormLiteVersion}" @@ -239,7 +243,7 @@ dependencies { // Graphview cannot be upgraded implementation "com.jjoe64:graphview:4.0.1" implementation "com.joanzapata.iconify:android-iconify-fontawesome:2.1.1" - implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation(name: "android-edittext-validator-v1.3.4-mod", ext: "aar") implementation 'com.madgag.spongycastle:core:1.58.0.0' @@ -279,29 +283,29 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } -task unzip(type: Copy) { - def zipPath = configurations.libs.find { it.name.startsWith("danars") } - def zipFile = file(zipPath) - def outputDir = file("${buildDir}/unpacked/dist") +//task unzip(type: Copy) { +// def zipPath = configurations.libs.find { it.name.startsWith("danars") } +// def zipFile = file(zipPath) +// def outputDir = file("${buildDir}/unpacked/dist") - from zipTree(zipFile) - into outputDir -} +// from zipTree(zipFile) +// into outputDir +//} -task copyLibs(dependsOn: unzip, type: Copy) { - def src = file("${buildDir}/unpacked/dist/danars-support-lib-master") - def target = file("src/main/jniLibs/") +//task copyLibs(dependsOn: unzip, type: Copy) { +// def src = file("${buildDir}/unpacked/dist/danars-support-lib-master") +// def target = file("src/main/jniLibs/") - from src - into target -} +// from src +// into target +//} -task full_clean(type: Delete) { - delete file("src/main/jniLibs") -} +//task full_clean(type: Delete) { +// delete file("src/main/jniLibs") +//} -clean.dependsOn full_clean -preBuild.dependsOn copyLibs +//clean.dependsOn full_clean +//preBuild.dependsOn copyLibs printf('--------------\n') printf('isMaster: %s\n', isMaster().toString()) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 168ed83bdd..deb87d67fe 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -150,7 +150,7 @@ diff --git a/app/src/main/java/info/nightscout/androidaps/MainActivity.java b/app/src/main/java/info/nightscout/androidaps/MainActivity.java index 6779b0a467..b9622daf1b 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/MainActivity.java @@ -7,16 +7,16 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.PersistableBundle; import android.os.PowerManager; -import android.support.annotation.Nullable; -import android.support.design.widget.NavigationView; -import android.support.design.widget.TabLayout; -import android.support.v4.app.ActivityCompat; -import android.support.v4.view.ViewPager; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.ActionBarDrawerToggle; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; +import androidx.annotation.Nullable; +import com.google.android.material.navigation.NavigationView; +import com.google.android.material.tabs.TabLayout; +import androidx.core.app.ActivityCompat; +import androidx.viewpager.widget.ViewPager; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; import android.text.SpannableString; import android.text.method.LinkMovementMethod; import android.text.util.Linkify; diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index 720e07dd15..69d2c18fce 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -4,9 +4,9 @@ import android.app.Application; import android.content.IntentFilter; import android.content.res.Resources; import android.os.SystemClock; -import android.support.annotation.Nullable; -import android.support.annotation.PluralsRes; -import android.support.v4.content.LocalBroadcastManager; +import androidx.annotation.Nullable; +import androidx.annotation.PluralsRes; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.crashlytics.android.Crashlytics; import com.google.firebase.analytics.FirebaseAnalytics; diff --git a/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.java b/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.java index c007d1fced..77ea7715cc 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.java @@ -2,9 +2,9 @@ package info.nightscout.androidaps.activities; import android.os.Bundle; import android.os.SystemClock; -import android.support.v4.content.res.ResourcesCompat; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.PopupMenu; +import androidx.core.content.res.ResourcesCompat; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.PopupMenu; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; import android.view.Menu; diff --git a/app/src/main/java/info/nightscout/androidaps/activities/SingleFragmentActivity.java b/app/src/main/java/info/nightscout/androidaps/activities/SingleFragmentActivity.java index 2a381cd0a8..2c899784bd 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/SingleFragmentActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/activities/SingleFragmentActivity.java @@ -2,9 +2,9 @@ package info.nightscout.androidaps.activities; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v7.app.AppCompatActivity; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.appcompat.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; diff --git a/app/src/main/java/info/nightscout/androidaps/activities/TDDStatsActivity.java b/app/src/main/java/info/nightscout/androidaps/activities/TDDStatsActivity.java index e42d31d7c0..832ca2ad85 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/TDDStatsActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/activities/TDDStatsActivity.java @@ -4,7 +4,7 @@ import android.app.Activity; import android.graphics.Color; import android.graphics.Rect; import android.os.Bundle; -import android.support.v7.widget.LinearLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; import android.text.TextUtils; import android.view.KeyEvent; import android.view.MotionEvent; diff --git a/app/src/main/java/info/nightscout/androidaps/data/ConstraintChecker.java b/app/src/main/java/info/nightscout/androidaps/data/ConstraintChecker.java index b1267e81bc..9d7a49493b 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/ConstraintChecker.java +++ b/app/src/main/java/info/nightscout/androidaps/data/ConstraintChecker.java @@ -1,11 +1,9 @@ package info.nightscout.androidaps.data; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import java.util.ArrayList; -import javax.annotation.Nonnull; - import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.interfaces.Constraint; diff --git a/app/src/main/java/info/nightscout/androidaps/data/Intervals.java b/app/src/main/java/info/nightscout/androidaps/data/Intervals.java index 66d567d9bd..eb1b7154d3 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/Intervals.java +++ b/app/src/main/java/info/nightscout/androidaps/data/Intervals.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.data; -import android.support.annotation.Nullable; -import android.support.v4.util.LongSparseArray; +import androidx.annotation.Nullable; +import androidx.collection.LongSparseArray; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/info/nightscout/androidaps/data/NonOverlappingIntervals.java b/app/src/main/java/info/nightscout/androidaps/data/NonOverlappingIntervals.java index 8e0c286e7e..928833c481 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/NonOverlappingIntervals.java +++ b/app/src/main/java/info/nightscout/androidaps/data/NonOverlappingIntervals.java @@ -1,10 +1,8 @@ package info.nightscout.androidaps.data; -import android.support.annotation.Nullable; -import android.support.v4.util.LongSparseArray; +import androidx.annotation.Nullable; -import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.interfaces.Interval; /** diff --git a/app/src/main/java/info/nightscout/androidaps/data/OverlappingIntervals.java b/app/src/main/java/info/nightscout/androidaps/data/OverlappingIntervals.java index 76c2ff3615..5515ea15e1 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/OverlappingIntervals.java +++ b/app/src/main/java/info/nightscout/androidaps/data/OverlappingIntervals.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.data; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import info.nightscout.androidaps.interfaces.Interval; diff --git a/app/src/main/java/info/nightscout/androidaps/data/Profile.java b/app/src/main/java/info/nightscout/androidaps/data/Profile.java index 23a7e62426..945cf3c7c7 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/Profile.java +++ b/app/src/main/java/info/nightscout/androidaps/data/Profile.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.data; -import android.support.v4.util.LongSparseArray; +import androidx.collection.LongSparseArray; import org.json.JSONArray; import org.json.JSONException; diff --git a/app/src/main/java/info/nightscout/androidaps/data/ProfileIntervals.java b/app/src/main/java/info/nightscout/androidaps/data/ProfileIntervals.java index 8938b53936..1a5c81036c 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/ProfileIntervals.java +++ b/app/src/main/java/info/nightscout/androidaps/data/ProfileIntervals.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.data; -import android.support.annotation.Nullable; -import android.support.v4.util.LongSparseArray; +import androidx.annotation.Nullable; +import androidx.collection.LongSparseArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/data/ProfileStore.java b/app/src/main/java/info/nightscout/androidaps/data/ProfileStore.java index f97d8b5e02..a0b54ec0bb 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/ProfileStore.java +++ b/app/src/main/java/info/nightscout/androidaps/data/ProfileStore.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.data; -import android.support.annotation.Nullable; -import android.support.v4.util.ArrayMap; +import androidx.annotation.Nullable; +import androidx.collection.ArrayMap; import org.json.JSONException; import org.json.JSONObject; diff --git a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java index eb51f5e2ba..1cd2165be9 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.db; import android.content.Context; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper; import com.j256.ormlite.dao.CloseableIterator; diff --git a/app/src/main/java/info/nightscout/androidaps/db/ProfileSwitch.java b/app/src/main/java/info/nightscout/androidaps/db/ProfileSwitch.java index 0e589f0595..5f37c3d96b 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/ProfileSwitch.java +++ b/app/src/main/java/info/nightscout/androidaps/db/ProfileSwitch.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.db; import android.graphics.Color; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import com.j256.ormlite.field.DatabaseField; import com.j256.ormlite.table.DatabaseTable; diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventNewBG.java b/app/src/main/java/info/nightscout/androidaps/events/EventNewBG.java index dc4d434e0a..db3498bed7 100644 --- a/app/src/main/java/info/nightscout/androidaps/events/EventNewBG.java +++ b/app/src/main/java/info/nightscout/androidaps/events/EventNewBG.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.events; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import info.nightscout.androidaps.db.BgReading; diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentChange.java b/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentChange.java index 990f28a388..e1d9d527e1 100644 --- a/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentChange.java +++ b/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentChange.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.events; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import info.nightscout.androidaps.plugins.treatments.Treatment; diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/PluginBase.java b/app/src/main/java/info/nightscout/androidaps/interfaces/PluginBase.java index 01be296539..2032c89b14 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/PluginBase.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/PluginBase.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.interfaces; import android.os.SystemClock; -import android.support.v4.app.FragmentActivity; +import androidx.fragment.app.FragmentActivity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/ProfileInterface.java b/app/src/main/java/info/nightscout/androidaps/interfaces/ProfileInterface.java index e3b368fe86..482278e437 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/ProfileInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/ProfileInterface.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.interfaces; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import info.nightscout.androidaps.data.ProfileStore; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.java index 92378a1d6b..0cbeae2e71 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.java @@ -10,8 +10,8 @@ import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.SystemClock; -import android.support.annotation.NonNull; -import android.support.v4.app.NotificationCompat; +import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; import com.squareup.otto.Subscribe; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/common/SubscriberFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/common/SubscriberFragment.java index 3ac877b42d..1cf6a35f7d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/common/SubscriberFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/common/SubscriberFragment.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.common; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import butterknife.Unbinder; import info.nightscout.androidaps.MainApp; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderFragment.java index 47b5d49357..3b2fa590bf 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderFragment.java @@ -3,8 +3,8 @@ package info.nightscout.androidaps.plugins.configBuilder; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.StringRes; +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderPlugin.java index 3c3531da86..a6c5209d5f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderPlugin.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.configBuilder; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/DetailedBolusInfoStorage.java b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/DetailedBolusInfoStorage.java index b19948fc5e..b81ae37983 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/DetailedBolusInfoStorage.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/DetailedBolusInfoStorage.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.configBuilder; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctions.java b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctions.java index 0360ba4a68..7c92cd98b3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctions.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctions.java @@ -2,7 +2,7 @@ package info.nightscout.androidaps.plugins.configBuilder; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import com.google.firebase.analytics.FirebaseAnalytics; import com.squareup.otto.Subscribe; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.java index b943571089..7600c017c2 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.java @@ -4,11 +4,11 @@ import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.support.annotation.NonNull; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.LinearSmoothScroller; -import android.support.v7.widget.RecyclerView; +import androidx.annotation.NonNull; +import androidx.cardview.widget.CardView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.LinearSmoothScroller; +import androidx.recyclerview.widget.RecyclerView; import android.text.Html; import android.view.LayoutInflater; import android.view.View; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective.java b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective.java index 83abe15688..55aba1b49f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.constraints.objectives.objectives; -import android.support.annotation.StringRes; +import androidx.annotation.StringRes; import java.util.ArrayList; import java.util.Date; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.java index ff99978fed..11428ce204 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.java @@ -5,8 +5,8 @@ import android.app.Activity; import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/dialogs/FillDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/dialogs/FillDialog.java index 94d548a444..e5ba4b9a13 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/dialogs/FillDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/dialogs/FillDialog.java @@ -3,8 +3,8 @@ package info.nightscout.androidaps.plugins.general.actions.dialogs; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.support.v7.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.appcompat.app.AlertDialog; import android.text.Editable; import android.text.Html; import android.text.TextWatcher; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/dialogs/NewExtendedBolusDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/dialogs/NewExtendedBolusDialog.java index 579e9e2f06..9b5b7af515 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/dialogs/NewExtendedBolusDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/dialogs/NewExtendedBolusDialog.java @@ -4,8 +4,8 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.support.v7.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.appcompat.app.AlertDialog; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/dialogs/NewTempBasalDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/dialogs/NewTempBasalDialog.java index 80969b51b0..30b6e60dbd 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/dialogs/NewTempBasalDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/dialogs/NewTempBasalDialog.java @@ -3,8 +3,8 @@ package info.nightscout.androidaps.plugins.general.actions.dialogs; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.support.v7.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.appcompat.app.AlertDialog; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/careportal/CareportalFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/careportal/CareportalFragment.java index 991fc962ee..ab98682fb4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/careportal/CareportalFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/careportal/CareportalFragment.java @@ -4,7 +4,7 @@ package info.nightscout.androidaps.plugins.general.careportal; import android.app.Activity; import android.graphics.Color; import android.os.Bundle; -import android.support.v4.app.FragmentManager; +import androidx.fragment.app.FragmentManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/careportal/Dialogs/NewNSTreatmentDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/careportal/Dialogs/NewNSTreatmentDialog.java index 57414927b4..078b3fc1ce 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/careportal/Dialogs/NewNSTreatmentDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/careportal/Dialogs/NewNSTreatmentDialog.java @@ -3,8 +3,8 @@ package info.nightscout.androidaps.plugins.general.careportal.Dialogs; import android.app.Activity; import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.support.v7.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.appcompat.app.AlertDialog; import android.text.Editable; import android.text.TextWatcher; import android.text.format.DateFormat; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodFragment.java index 6eb55db266..66727f0f7f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodFragment.java @@ -4,9 +4,9 @@ import android.app.Activity; import android.content.DialogInterface; import android.graphics.Paint; import android.os.Bundle; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodService.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodService.java index 2d8d5f847b..3856ecfc22 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodService.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.food; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import com.j256.ormlite.android.apptools.OpenHelperManager; import com.j256.ormlite.android.apptools.OrmLiteBaseService; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.java index 67bf604f41..ea56115347 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.java @@ -8,9 +8,9 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Environment; import android.preference.PreferenceManager; -import android.support.v4.app.ActivityCompat; -import android.support.v4.app.Fragment; -import android.support.v4.content.ContextCompat; +import androidx.core.app.ActivityCompat; +import androidx.fragment.app.Fragment; +import androidx.core.content.ContextCompat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.java index e0e9b58253..6b8863242a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.java @@ -2,8 +2,8 @@ package info.nightscout.androidaps.plugins.general.maintenance; import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v7.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.appcompat.app.AlertDialog; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.java index 35bd9f9d06..1bad189aba 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.maintenance; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.support.v4.content.FileProvider; +import androidx.core.content.FileProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/activities/LogSettingActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/activities/LogSettingActivity.java index 9d7681ae38..852554ed06 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/activities/LogSettingActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/activities/LogSettingActivity.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.plugins.general.maintenance.activities; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.widget.CheckBox; import android.widget.LinearLayout; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSUpload.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSUpload.java index 40a411694b..881593b32a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSUpload.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSUpload.java @@ -7,8 +7,8 @@ import android.content.pm.ResolveInfo; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; -import android.support.annotation.Nullable; -import android.support.v4.content.LocalBroadcastManager; +import androidx.annotation.Nullable; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import org.apache.commons.lang3.StringUtils; import org.json.JSONArray; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastAckAlarm.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastAckAlarm.java index 6873b59515..7179231068 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastAckAlarm.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastAckAlarm.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.nsclient.broadcasts; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastAlarm.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastAlarm.java index 9c1e3e7ae2..ce1de70900 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastAlarm.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastAlarm.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.nsclient.broadcasts; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import org.json.JSONObject; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastAnnouncement.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastAnnouncement.java index b26c055851..f97f750287 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastAnnouncement.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastAnnouncement.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.nsclient.broadcasts; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import org.json.JSONObject; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastCals.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastCals.java index 7d5dcddadc..83f3c0a0e4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastCals.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastCals.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.nsclient.broadcasts; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import org.json.JSONArray; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastClearAlarm.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastClearAlarm.java index fe9a194ca2..3f0586e65e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastClearAlarm.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastClearAlarm.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.nsclient.broadcasts; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import org.json.JSONObject; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastDeviceStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastDeviceStatus.java index 412c321817..4631834d9b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastDeviceStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastDeviceStatus.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.nsclient.broadcasts; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import org.json.JSONArray; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastFood.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastFood.java index 0a8c21251b..35a2315ff2 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastFood.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastFood.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.nsclient.broadcasts; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import org.json.JSONArray; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastMbgs.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastMbgs.java index 9c059ae8a2..248e5d627f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastMbgs.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastMbgs.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.nsclient.broadcasts; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import org.json.JSONArray; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastProfile.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastProfile.java index 9411ed6e9e..d6d051e327 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastProfile.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastProfile.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.nsclient.broadcasts; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastSgvs.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastSgvs.java index 04e957811d..06fd3c0a35 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastSgvs.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastSgvs.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.nsclient.broadcasts; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import org.json.JSONArray; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastStatus.java index c037acc9bb..81de2248e4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastStatus.java @@ -4,7 +4,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastTreatment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastTreatment.java index e4ca7b577f..a364d0d4ae 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastTreatment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastTreatment.java @@ -2,7 +2,7 @@ package info.nightscout.androidaps.plugins.general.nsclient.broadcasts; import android.content.Intent; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import org.json.JSONArray; import org.json.JSONException; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastUrgentAlarm.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastUrgentAlarm.java index 676dbe909d..b6b1c4f8d9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastUrgentAlarm.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/broadcasts/BroadcastUrgentAlarm.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.nsclient.broadcasts; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import org.json.JSONObject; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/data/NSSettingsStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/data/NSSettingsStatus.java index 8511247e42..93cf70dd12 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/data/NSSettingsStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/data/NSSettingsStatus.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.nsclient.data; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import org.json.JSONException; import org.json.JSONObject; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/receivers/RestartReceiver.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/receivers/RestartReceiver.java index 0b221ee456..5024801827 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/receivers/RestartReceiver.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/receivers/RestartReceiver.java @@ -2,7 +2,7 @@ package info.nightscout.androidaps.plugins.general.nsclient.receivers; import android.content.Context; import android.content.Intent; -import android.support.v4.content.WakefulBroadcastReceiver; +import androidx.legacy.content.WakefulBroadcastReceiver; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java index 0b0724eefd..c980c04f6a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.overview; import android.annotation.SuppressLint; import android.app.Activity; import android.app.NotificationManager; -import android.arch.core.util.Function; +import androidx.arch.core.util.Function; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; @@ -12,14 +12,14 @@ import android.graphics.Color; import android.graphics.Paint; import android.os.Bundle; import android.os.Handler; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentActivity; -import android.support.v4.app.FragmentManager; -import android.support.v4.content.res.ResourcesCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.PopupMenu; -import android.support.v7.widget.RecyclerView; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.core.content.res.ResourcesCompat; +import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.appcompat.widget.PopupMenu; +import androidx.recyclerview.widget.RecyclerView; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; import android.util.DisplayMetrics; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.java index d7eb9c86dd..85adbf7e80 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.java @@ -2,11 +2,11 @@ package info.nightscout.androidaps.plugins.general.overview.activities; import android.app.Activity; import android.os.Bundle; -import android.support.v4.app.FragmentManager; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.fragment.app.FragmentManager; +import androidx.appcompat.app.AppCompatActivity; +import androidx.cardview.widget.CardView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/BolusProgressDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/BolusProgressDialog.java index 4189fcdcdb..1b41ea0c44 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/BolusProgressDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/BolusProgressDialog.java @@ -4,7 +4,7 @@ package info.nightscout.androidaps.plugins.general.overview.dialogs; import android.app.Activity; import android.os.Bundle; import android.os.SystemClock; -import android.support.v4.app.DialogFragment; +import androidx.fragment.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/BolusProgressHelperActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/BolusProgressHelperActivity.java index fd1de6ce8c..7ca19d0334 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/BolusProgressHelperActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/BolusProgressHelperActivity.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.plugins.general.overview.dialogs; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; /** * Created by adrian on 09/02/17. diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/CalibrationDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/CalibrationDialog.java index 0ab1b98ff6..7ec20254f2 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/CalibrationDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/CalibrationDialog.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.overview.dialogs; import android.content.Context; import android.os.Bundle; -import android.support.v4.app.DialogFragment; +import androidx.fragment.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.java index 9cd1277961..248ab30d7c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.java @@ -2,7 +2,7 @@ package info.nightscout.androidaps.plugins.general.overview.dialogs; import android.os.Bundle; -import android.support.v4.app.DialogFragment; +import androidx.fragment.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/ErrorDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/ErrorDialog.java index 1093da531b..ea8dd8e4bb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/ErrorDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/ErrorDialog.java @@ -4,7 +4,7 @@ package info.nightscout.androidaps.plugins.general.overview.dialogs; import android.content.Intent; import android.os.Build; import android.os.Bundle; -import android.support.v4.app.DialogFragment; +import androidx.fragment.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/ErrorHelperActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/ErrorHelperActivity.java index 667cadf2b1..86e7ac5461 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/ErrorHelperActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/ErrorHelperActivity.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.plugins.general.overview.dialogs; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; import info.nightscout.androidaps.R; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/NewCarbsDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/NewCarbsDialog.java index 789ced1df3..2a79a17bbd 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/NewCarbsDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/NewCarbsDialog.java @@ -2,8 +2,8 @@ package info.nightscout.androidaps.plugins.general.overview.dialogs; import android.os.Bundle; import android.os.HandlerThread; -import android.support.v4.app.DialogFragment; -import android.support.v7.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.appcompat.app.AlertDialog; import android.text.Editable; import android.text.Html; import android.text.TextWatcher; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/NewInsulinDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/NewInsulinDialog.java index 27e2e33ba7..6344f29ec8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/NewInsulinDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/NewInsulinDialog.java @@ -4,8 +4,8 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.HandlerThread; -import android.support.v4.app.DialogFragment; -import android.support.v7.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.appcompat.app.AlertDialog; import android.text.Editable; import android.text.Html; import android.text.TextWatcher; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/NewTreatmentDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/NewTreatmentDialog.java index 837d206fb3..b8034c7fba 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/NewTreatmentDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/NewTreatmentDialog.java @@ -4,8 +4,8 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.support.v7.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.appcompat.app.AlertDialog; import android.text.Editable; import android.text.Html; import android.text.TextWatcher; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/WizardDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/WizardDialog.java index b4a3de4271..f9d54f9f9a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/WizardDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/WizardDialog.java @@ -5,8 +5,8 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.support.v7.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.appcompat.app.AlertDialog; import android.text.Editable; import android.text.Html; import android.text.TextWatcher; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/DismissNotificationService.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/DismissNotificationService.java index a8d6fca1ba..475aa02332 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/DismissNotificationService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/DismissNotificationService.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.overview.notifications; import android.app.IntentService; import android.app.PendingIntent; import android.content.Intent; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationRecyclerViewAdapter.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationRecyclerViewAdapter.java index de35e4bc8c..6cb5c2fdf4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationRecyclerViewAdapter.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationRecyclerViewAdapter.java @@ -1,8 +1,8 @@ package info.nightscout.androidaps.plugins.general.overview.notifications; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.CardView; -import android.support.v7.widget.RecyclerView; +import androidx.core.content.ContextCompat; +import androidx.cardview.widget.CardView; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.java index d669065622..2a94d2c859 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.java @@ -11,8 +11,8 @@ import android.media.AudioAttributes; import android.media.RingtoneManager; import android.net.Uri; import android.os.Build; -import android.support.v4.app.NotificationCompat; -import android.support.v7.widget.RecyclerView; +import androidx.core.app.NotificationCompat; +import androidx.recyclerview.widget.RecyclerView; import android.view.View; import org.slf4j.Logger; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/DummyService.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/DummyService.java index 863046c0c4..4ad576615c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/DummyService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/DummyService.java @@ -4,7 +4,7 @@ import android.app.Notification; import android.app.Service; import android.content.Intent; import android.os.IBinder; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import com.squareup.otto.Subscribe; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.java index 0489f488df..d4f8688e58 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.java @@ -10,9 +10,9 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Build; -import android.support.v4.app.NotificationCompat; -import android.support.v4.app.RemoteInput; -import android.support.v4.app.TaskStackBuilder; +import androidx.core.app.NotificationCompat; +import androidx.core.app.RemoteInput; +import androidx.core.app.TaskStackBuilder; import com.squareup.otto.Subscribe; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.java index 1a01a783cd..e2ca7ac869 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.general.wear; import android.app.NotificationManager; import android.content.Context; import android.os.HandlerThread; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import java.text.DateFormat; import java.text.DecimalFormat; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearFragment.java index 701784e9d9..9eb157de24 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearFragment.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.plugins.general.wear; import android.os.Bundle; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java index d121c5c55d..6f2f59faa1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java @@ -17,7 +17,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.preference.PreferenceManager; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.util.Log; import com.google.android.gms.common.ConnectionResult; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/xdripStatusline/StatuslinePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/xdripStatusline/StatuslinePlugin.java index fe89cc8304..2421a1561e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/xdripStatusline/StatuslinePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/xdripStatusline/StatuslinePlugin.java @@ -5,7 +5,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceManager; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.squareup.otto.Subscribe; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinFragment.java index bcf445d0d1..02e9256a8a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinFragment.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.plugins.insulin; import android.os.Bundle; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/CobInfo.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/CobInfo.java index 12d5cd5c11..fb99067008 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/CobInfo.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/CobInfo.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.plugins.iob.iobCobCalculator; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import info.nightscout.androidaps.utils.DecimalFormatter; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java index 191ce9e1cd..159c5865af 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.iob.iobCobCalculator; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.java index 659af5ec56..c98de7c8be 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.java @@ -1,9 +1,9 @@ package info.nightscout.androidaps.plugins.iob.iobCobCalculator; import android.os.SystemClock; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.util.LongSparseArray; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.collection.LongSparseArray; import com.squareup.otto.Subscribe; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java index c483ffd2a6..8ca13a6694 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.iob.iobCobCalculator; import android.content.Context; import android.os.PowerManager; import android.os.SystemClock; -import android.support.v4.util.LongSparseArray; +import androidx.collection.LongSparseArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java index 9ddf18abb0..943637fd4e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.iob.iobCobCalculator; import android.content.Context; import android.os.PowerManager; import android.os.SystemClock; -import android.support.v4.util.LongSparseArray; +import androidx.collection.LongSparseArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.java index ec5dc55fa8..9fe86c087f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.profile.local; import android.app.Activity; import android.os.Bundle; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfilePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfilePlugin.java index 06f979876c..089611b40d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfilePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfilePlugin.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.profile.local; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.json.JSONArray; import org.json.JSONException; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/profile/ns/NSProfilePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/profile/ns/NSProfilePlugin.java index 11c38d1492..f741316732 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/profile/ns/NSProfilePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/profile/ns/NSProfilePlugin.java @@ -2,7 +2,7 @@ package info.nightscout.androidaps.plugins.profile.ns; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import org.json.JSONException; import org.json.JSONObject; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboFragment.java index a533821d59..1a924fa9f5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboFragment.java @@ -5,7 +5,7 @@ import android.app.Activity; import android.graphics.Color; import android.graphics.Typeface; import android.os.Bundle; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboPlugin.java index 557ba42168..6edfbd1679 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboPlugin.java @@ -2,10 +2,10 @@ package info.nightscout.androidaps.plugins.pump.combo; import android.content.DialogInterface; import android.os.SystemClock; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.FragmentActivity; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; +import androidx.appcompat.app.AlertDialog; import org.json.JSONObject; import org.slf4j.Logger; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboPump.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboPump.java index cfe459fb58..c5d7fa0115 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboPump.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboPump.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.plugins.pump.combo; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/CommandResult.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/CommandResult.java index 180f10f69d..9febf0e86a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/CommandResult.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/CommandResult.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.combo.ruffyscripter; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import java.util.LinkedList; import java.util.List; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/RuffyScripter.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/RuffyScripter.java index eb01c92c60..19603d99e6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/RuffyScripter.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/RuffyScripter.java @@ -7,8 +7,8 @@ import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.common.base.Joiner; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/WarningOrErrorCode.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/WarningOrErrorCode.java index ae4723046e..7692afe84e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/WarningOrErrorCode.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/WarningOrErrorCode.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.combo.ruffyscripter; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; public class WarningOrErrorCode { @Nullable diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/commands/BaseCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/commands/BaseCommand.java index 6cd89f2938..1de9392cba 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/commands/BaseCommand.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/commands/BaseCommand.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.combo.ruffyscripter.commands; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute; import org.monkey.d.ruffy.ruffy.driver.display.MenuType; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/commands/ReadHistoryCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/commands/ReadHistoryCommand.java index 5f23b7e66d..b50ccaeb2c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/commands/ReadHistoryCommand.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/commands/ReadHistoryCommand.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.combo.ruffyscripter.commands; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute; import org.monkey.d.ruffy.ruffy.driver.display.MenuType; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/history/PumpHistory.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/history/PumpHistory.java index 0fffe13c50..b9e9c704a0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/history/PumpHistory.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ruffyscripter/history/PumpHistory.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.combo.ruffyscripter.history; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import java.util.ArrayList; import java.util.LinkedList; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/AbstractDanaRPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/AbstractDanaRPlugin.java index 749307e1a7..e0d5147de6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/AbstractDanaRPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/AbstractDanaRPlugin.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.danaR; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import org.json.JSONException; import org.json.JSONObject; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRFragment.java index e044a2db27..30e99ebbe2 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRFragment.java @@ -5,7 +5,7 @@ import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Handler; -import android.support.v4.app.FragmentManager; +import androidx.fragment.app.FragmentManager; import android.text.Spanned; import android.view.LayoutInflater; import android.view.View; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRPlugin.java index 2f7484cd6f..4e38767619 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRPlugin.java @@ -5,8 +5,8 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; -import android.support.v4.app.FragmentActivity; -import android.support.v7.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; +import androidx.appcompat.app.AlertDialog; import com.squareup.otto.Subscribe; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/Dialogs/ProfileViewDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/Dialogs/ProfileViewDialog.java index 2b09b180cf..06cfc289ad 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/Dialogs/ProfileViewDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/Dialogs/ProfileViewDialog.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.plugins.pump.danaR.dialogs; import android.os.Bundle; -import android.support.v4.app.DialogFragment; +import androidx.fragment.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRHistoryActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRHistoryActivity.java index 95c9f25e37..617f99071d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRHistoryActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRHistoryActivity.java @@ -4,9 +4,9 @@ import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.cardview.widget.CardView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgStatusBolusExtended.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgStatusBolusExtended.java index 9b27561d5a..aa272b546d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgStatusBolusExtended.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgStatusBolusExtended.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.danaR.comm; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgStatusTempBasal.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgStatusTempBasal.java index 76fa0e9f43..00b3bf5f69 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgStatusTempBasal.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgStatusTempBasal.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.danaR.comm; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/DanaRKoreanPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/DanaRKoreanPlugin.java index 3b4aa0ee4c..44eacef16c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/DanaRKoreanPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/DanaRKoreanPlugin.java @@ -5,8 +5,8 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; -import android.support.v4.app.FragmentActivity; -import android.support.v7.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; +import androidx.appcompat.app.AlertDialog; import com.squareup.otto.Subscribe; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/DanaRSPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/DanaRSPlugin.java index ddbb8547ae..39d110a0c5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/DanaRSPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/DanaRSPlugin.java @@ -5,9 +5,9 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; -import android.support.annotation.Nullable; -import android.support.v4.app.FragmentActivity; -import android.support.v7.app.AlertDialog; +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; +import androidx.appcompat.app.AlertDialog; import com.squareup.otto.Subscribe; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/BLEScanActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/BLEScanActivity.java index 502c18cbc9..e790fe7bfb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/BLEScanActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/BLEScanActivity.java @@ -7,7 +7,7 @@ import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanResult; import android.os.Bundle; import android.os.Handler; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/PairingHelperActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/PairingHelperActivity.java index a98d541963..6282ff8d0f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/PairingHelperActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/PairingHelperActivity.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.danaRS.activities; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; public class PairingHelperActivity extends AppCompatActivity { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/PairingProgressDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/PairingProgressDialog.java index 1d9943a5f5..544b0c862e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/PairingProgressDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/PairingProgressDialog.java @@ -5,7 +5,7 @@ import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; -import android.support.v4.app.DialogFragment; +import androidx.fragment.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Basal_Get_Temporary_Basal_State.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Basal_Get_Temporary_Basal_State.java index 5724c5ff80..117baf23ff 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Basal_Get_Temporary_Basal_State.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Basal_Get_Temporary_Basal_State.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.danaRS.comm; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.cozmo.danar.util.BleCommandUtil; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/DanaRv2Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/DanaRv2Plugin.java index 9e3ca499d0..4fba649cae 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/DanaRv2Plugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/DanaRv2Plugin.java @@ -5,8 +5,8 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; -import android.support.v4.app.FragmentActivity; -import android.support.v7.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; +import androidx.appcompat.app.AlertDialog; import com.squareup.otto.Subscribe; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusBolusExtended_v2.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusBolusExtended_v2.java index 578be6d4b6..a829b3afcb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusBolusExtended_v2.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusBolusExtended_v2.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.danaRv2.comm; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusTempBasal_v2.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusTempBasal_v2.java index a92c534d92..19a0a2b6ce 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusTempBasal_v2.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusTempBasal_v2.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.danaRv2.comm; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/InsightAlertService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/InsightAlertService.java index d851389aaf..26555898c0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/InsightAlertService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/InsightAlertService.java @@ -15,7 +15,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Vibrator; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightFragment.java index 26f5180f47..b973424d71 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightFragment.java @@ -3,8 +3,8 @@ package info.nightscout.androidaps.plugins.pump.insight; import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightAlertActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightAlertActivity.java index c40cb99ed6..46031d8666 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightAlertActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightAlertActivity.java @@ -5,8 +5,8 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AppCompatActivity; +import androidx.core.content.ContextCompat; +import androidx.appcompat.app.AppCompatActivity; import android.text.Html; import android.view.View; import android.view.WindowManager; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingActivity.java index 13fd698236..535d3d8bdb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingActivity.java @@ -10,11 +10,11 @@ import android.content.IntentFilter; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingInformationActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingInformationActivity.java index 8580b30dca..9e6ee03d77 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingInformationActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingInformationActivity.java @@ -5,8 +5,8 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; -import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.widget.TextView; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/connection_service/InsightConnectionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/connection_service/InsightConnectionService.java index e964c9fa20..cecb631776 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/connection_service/InsightConnectionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/connection_service/InsightConnectionService.java @@ -8,7 +8,7 @@ import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.os.PowerManager; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.java index 0b764f5382..be9bf17c85 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.java @@ -4,7 +4,7 @@ package info.nightscout.androidaps.plugins.pump.virtual; import android.app.Activity; import android.os.Bundle; import android.os.Handler; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -20,8 +20,6 @@ import info.nightscout.androidaps.R; import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.plugins.common.SubscriberFragment; -import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; -import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; import info.nightscout.androidaps.plugins.pump.virtual.events.EventVirtualPumpUpdateGui; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.java index 8e33b16302..dfc4bed25d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.sensitivity; -import android.support.v4.util.LongSparseArray; +import androidx.collection.LongSparseArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref0Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref0Plugin.java index 83800e1721..cfcaf0eb22 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref0Plugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref0Plugin.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.sensitivity; -import android.support.v4.util.LongSparseArray; +import androidx.collection.LongSparseArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.java index 3ce5c4861c..bbd987e6bd 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.sensitivity; -import android.support.v4.util.LongSparseArray; +import androidx.collection.LongSparseArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityWeightedAveragePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityWeightedAveragePlugin.java index 5b7268b560..91b69162bb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityWeightedAveragePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityWeightedAveragePlugin.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.sensitivity; -import android.support.v4.util.LongSparseArray; +import androidx.collection.LongSparseArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.java index 211cbb0bb0..269ee2b3f5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.java @@ -4,10 +4,10 @@ import android.app.Activity; import android.content.DialogInterface; import android.graphics.Paint; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/CarbsGenerator.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/CarbsGenerator.java index ac9be50a23..0e7215b368 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/CarbsGenerator.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/CarbsGenerator.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.plugins.treatments; import android.content.Intent; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/Treatment.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/Treatment.java index dbf1a89c37..5be28b148a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/Treatment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/Treatment.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.plugins.treatments; import android.graphics.Color; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import com.j256.ormlite.field.DatabaseField; import com.j256.ormlite.table.DatabaseTable; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java index 502b7f38b7..fd2e73b2e9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java @@ -2,7 +2,7 @@ package info.nightscout.androidaps.plugins.treatments; import android.content.Intent; import android.os.IBinder; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import com.j256.ormlite.android.apptools.OpenHelperManager; import com.j256.ormlite.android.apptools.OrmLiteBaseService; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsFragment.java index d30565b4a7..192952057d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsFragment.java @@ -1,8 +1,8 @@ package info.nightscout.androidaps.plugins.treatments; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java index 2c94d6713d..71cbd65904 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java @@ -2,7 +2,7 @@ package info.nightscout.androidaps.plugins.treatments; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import com.google.firebase.analytics.FirebaseAnalytics; import com.squareup.otto.Subscribe; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/dialogs/WizardInfoDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/dialogs/WizardInfoDialog.java index c38672311b..e068e094f1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/dialogs/WizardInfoDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/dialogs/WizardInfoDialog.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.plugins.treatments.dialogs; import android.os.Bundle; -import android.support.v4.app.DialogFragment; +import androidx.fragment.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/ProfileViewerDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/ProfileViewerDialog.java index c8db29b617..a3b6234a22 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/ProfileViewerDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/ProfileViewerDialog.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.plugins.treatments.fragments; import android.os.Bundle; -import android.support.v4.app.DialogFragment; +import androidx.fragment.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsBolusFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsBolusFragment.java index 26a8494dd7..c2320fc3ce 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsBolusFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsBolusFragment.java @@ -6,12 +6,12 @@ import android.content.DialogInterface; import android.content.Intent; import android.graphics.Paint; import android.os.Bundle; -import android.support.v4.app.FragmentManager; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.fragment.app.FragmentManager; +import androidx.core.content.ContextCompat; +import androidx.appcompat.app.AlertDialog; +import androidx.cardview.widget.CardView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsCareportalFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsCareportalFragment.java index 2f45f67c3b..2ec61597d7 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsCareportalFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsCareportalFragment.java @@ -6,10 +6,10 @@ import android.content.DialogInterface; import android.content.Intent; import android.graphics.Paint; import android.os.Bundle; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.appcompat.app.AlertDialog; +import androidx.cardview.widget.CardView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsExtendedBolusesFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsExtendedBolusesFragment.java index 766190e378..24a5185ef0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsExtendedBolusesFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsExtendedBolusesFragment.java @@ -5,11 +5,11 @@ import android.content.Context; import android.content.DialogInterface; import android.graphics.Paint; import android.os.Bundle; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.core.content.ContextCompat; +import androidx.appcompat.app.AlertDialog; +import androidx.cardview.widget.CardView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsProfileSwitchFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsProfileSwitchFragment.java index c59d5e1836..5d5919f69f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsProfileSwitchFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsProfileSwitchFragment.java @@ -6,12 +6,12 @@ import android.content.DialogInterface; import android.content.Intent; import android.graphics.Paint; import android.os.Bundle; -import android.support.v4.app.FragmentManager; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.fragment.app.FragmentManager; +import androidx.core.content.ContextCompat; +import androidx.appcompat.app.AlertDialog; +import androidx.cardview.widget.CardView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTempTargetFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTempTargetFragment.java index eda62a8318..56b887c295 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTempTargetFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTempTargetFragment.java @@ -6,11 +6,11 @@ import android.content.DialogInterface; import android.content.Intent; import android.graphics.Paint; import android.os.Bundle; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.core.content.ContextCompat; +import androidx.appcompat.app.AlertDialog; +import androidx.cardview.widget.CardView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTemporaryBasalsFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTemporaryBasalsFragment.java index 7b935d354e..6aed7aea43 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTemporaryBasalsFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTemporaryBasalsFragment.java @@ -4,12 +4,12 @@ import android.app.Activity; import android.content.Context; import android.graphics.Paint; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.appcompat.app.AlertDialog; +import androidx.cardview.widget.CardView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java index 7928435ed0..c0a8bd7f56 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.queue; import android.content.Context; import android.content.Intent; import android.os.SystemClock; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; import android.text.Html; import android.text.Spanned; @@ -12,8 +12,6 @@ import org.slf4j.LoggerFactory; import java.util.LinkedList; -import javax.annotation.Nullable; - import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.java index 087cd3a5f5..6c2e7c6920 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.java +++ b/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.java @@ -2,7 +2,7 @@ package info.nightscout.androidaps.receivers; import android.content.Context; import android.content.Intent; -import android.support.v4.content.WakefulBroadcastReceiver; +import androidx.legacy.content.WakefulBroadcastReceiver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java index 9a3108e98c..05adc1af59 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java +++ b/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java @@ -8,7 +8,7 @@ import android.net.NetworkInfo; import android.net.wifi.SupplicantState; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.java b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.java index 1d143d073f..3be94c494e 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.java +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.setupwizard; import android.Manifest; import android.content.Intent; import android.os.Build; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; import com.squareup.otto.Subscribe; diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.java b/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.java index 9049ad7463..777463583d 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.java @@ -3,9 +3,9 @@ package info.nightscout.androidaps.setupwizard; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; -import android.support.v4.app.ActivityCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.widget.LinearLayout; import android.widget.ScrollView; diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWFragment.java b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWFragment.java index a061f57eb6..29149e5b81 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWFragment.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.setupwizard.elements; -import android.support.v4.app.Fragment; -import android.view.View; +import androidx.fragment.app.Fragment; + import android.widget.LinearLayout; import org.slf4j.Logger; diff --git a/app/src/main/java/info/nightscout/androidaps/tabs/TabPageAdapter.java b/app/src/main/java/info/nightscout/androidaps/tabs/TabPageAdapter.java index 53b3700c72..c567df7b49 100644 --- a/app/src/main/java/info/nightscout/androidaps/tabs/TabPageAdapter.java +++ b/app/src/main/java/info/nightscout/androidaps/tabs/TabPageAdapter.java @@ -1,10 +1,10 @@ package info.nightscout.androidaps.tabs; import android.content.Context; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; import android.view.ViewGroup; import org.slf4j.Logger; diff --git a/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.java b/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.java index 3f6c3a3ce0..527a98dda2 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.java @@ -5,8 +5,8 @@ import android.app.Activity; import android.content.Context; import android.content.pm.PackageManager; import android.os.Build; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; diff --git a/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java b/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java index 7cc4424244..9e5782d063 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.utils; -import android.support.v4.util.LongSparseArray; +import androidx.collection.LongSparseArray; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; diff --git a/app/src/main/java/info/nightscout/androidaps/utils/JsonHelper.java b/app/src/main/java/info/nightscout/androidaps/utils/JsonHelper.java index 8f7d9611b1..c130353419 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/JsonHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/JsonHelper.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.utils; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import org.json.JSONException; import org.json.JSONObject; diff --git a/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.java b/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.java index 00b3c29692..98d8845ff8 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.java @@ -5,8 +5,8 @@ import android.content.Context; import android.content.DialogInterface; import android.os.Handler; import android.os.SystemClock; -import android.support.v7.app.AlertDialog; -import android.support.v7.view.ContextThemeWrapper; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.view.ContextThemeWrapper; import android.text.Spanned; import org.slf4j.Logger; diff --git a/app/src/main/java/info/nightscout/androidaps/utils/SingleClickButton.java b/app/src/main/java/info/nightscout/androidaps/utils/SingleClickButton.java index eef4ca57fe..74500ebdbc 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/SingleClickButton.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/SingleClickButton.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.utils; import android.app.Activity; import android.content.Context; import android.os.SystemClock; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.View; @@ -14,7 +14,7 @@ import org.slf4j.LoggerFactory; * Created by mike on 22.12.2017. */ -public class SingleClickButton extends android.support.v7.widget.AppCompatButton implements View.OnClickListener { +public class SingleClickButton extends androidx.appcompat.widget.AppCompatButton implements View.OnClickListener { private static Logger log = LoggerFactory.getLogger(SingleClickButton.class); Context context; diff --git a/app/src/main/java/info/nightscout/androidaps/utils/TimeListEdit.java b/app/src/main/java/info/nightscout/androidaps/utils/TimeListEdit.java index d120e102d1..c8f767f77e 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/TimeListEdit.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/TimeListEdit.java @@ -2,8 +2,8 @@ package info.nightscout.androidaps.utils; import android.content.Context; import android.os.Handler; -import android.support.v4.content.ContextCompat; -import android.support.v4.widget.TextViewCompat; +import androidx.core.content.ContextCompat; +import androidx.core.widget.TextViewCompat; import android.text.Editable; import android.text.TextWatcher; import android.view.Gravity; diff --git a/app/src/main/java/info/nightscout/androidaps/utils/XdripCalibrations.java b/app/src/main/java/info/nightscout/androidaps/utils/XdripCalibrations.java index 99961a156b..f7b8761823 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/XdripCalibrations.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/XdripCalibrations.java @@ -5,7 +5,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ResolveInfo; import android.os.Bundle; -import android.support.v7.app.AlertDialog; +import androidx.appcompat.app.AlertDialog; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/res/layout/activity_historybrowse.xml b/app/src/main/res/layout/activity_historybrowse.xml index 96b2b5871c..84f52f48c8 100644 --- a/app/src/main/res/layout/activity_historybrowse.xml +++ b/app/src/main/res/layout/activity_historybrowse.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/layout/activity_insight_pairing.xml b/app/src/main/res/layout/activity_insight_pairing.xml index d90b5f091d..83412a8645 100644 --- a/app/src/main/res/layout/activity_insight_pairing.xml +++ b/app/src/main/res/layout/activity_insight_pairing.xml @@ -18,7 +18,7 @@ android:text="@string/searching_for_devices" android:textSize="20sp" /> - - - - - + - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/bgsource_fragment.xml b/app/src/main/res/layout/bgsource_fragment.xml index 73829c12d4..6619b6bafa 100644 --- a/app/src/main/res/layout/bgsource_fragment.xml +++ b/app/src/main/res/layout/bgsource_fragment.xml @@ -10,12 +10,12 @@ android:layout_height="match_parent" android:orientation="vertical"> - - + diff --git a/app/src/main/res/layout/bgsource_item.xml b/app/src/main/res/layout/bgsource_item.xml index acd0454327..4cb3be97b8 100644 --- a/app/src/main/res/layout/bgsource_item.xml +++ b/app/src/main/res/layout/bgsource_item.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/layout/careportal_fragment.xml b/app/src/main/res/layout/careportal_fragment.xml index 1e4c72dd72..a9791a1b88 100644 --- a/app/src/main/res/layout/careportal_fragment.xml +++ b/app/src/main/res/layout/careportal_fragment.xml @@ -45,7 +45,7 @@ android:paddingStart="15dp" android:text="@string/careportal_activity_label" /> - - + - - + - - + - - + diff --git a/app/src/main/res/layout/danar_history_item.xml b/app/src/main/res/layout/danar_history_item.xml index 6df07edd62..b03a26f55a 100644 --- a/app/src/main/res/layout/danar_history_item.xml +++ b/app/src/main/res/layout/danar_history_item.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/layout/danar_historyactivity.xml b/app/src/main/res/layout/danar_historyactivity.xml index 8c7f8a8e6e..f3cc2ece29 100644 --- a/app/src/main/res/layout/danar_historyactivity.xml +++ b/app/src/main/res/layout/danar_historyactivity.xml @@ -50,7 +50,7 @@ android:layout_gravity="center_horizontal" android:gravity="center_horizontal" /> - - - + diff --git a/app/src/main/res/layout/food_item.xml b/app/src/main/res/layout/food_item.xml index d4efc403a3..b6b6adbcf8 100644 --- a/app/src/main/res/layout/food_item.xml +++ b/app/src/main/res/layout/food_item.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/layout/objectives_fragment.xml b/app/src/main/res/layout/objectives_fragment.xml index f2b18e703d..56edc011f9 100644 --- a/app/src/main/res/layout/objectives_fragment.xml +++ b/app/src/main/res/layout/objectives_fragment.xml @@ -31,7 +31,7 @@ android:text="Reset" /> - - - + diff --git a/app/src/main/res/layout/overview_fragment.xml b/app/src/main/res/layout/overview_fragment.xml index a21cf30d03..2192ac62f4 100644 --- a/app/src/main/res/layout/overview_fragment.xml +++ b/app/src/main/res/layout/overview_fragment.xml @@ -20,12 +20,12 @@ android:layout_height="wrap_content" android:orientation="vertical"> - - + - - + - - + - - + - - + diff --git a/app/src/main/res/layout/overview_quickwizardlist_activity.xml b/app/src/main/res/layout/overview_quickwizardlist_activity.xml index 227f946486..3ef067d044 100644 --- a/app/src/main/res/layout/overview_quickwizardlist_activity.xml +++ b/app/src/main/res/layout/overview_quickwizardlist_activity.xml @@ -29,7 +29,7 @@ android:layout_height="wrap_content" android:id="@+id/overview_quickwizardactivity_add_button" /> - diff --git a/app/src/main/res/layout/overview_quickwizardlist_item.xml b/app/src/main/res/layout/overview_quickwizardlist_item.xml index 429e2ca2de..5d186ffaf4 100644 --- a/app/src/main/res/layout/overview_quickwizardlist_item.xml +++ b/app/src/main/res/layout/overview_quickwizardlist_item.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/layout/treatments_bolus_fragment.xml b/app/src/main/res/layout/treatments_bolus_fragment.xml index c04aac5f9f..db8e0bbbbb 100644 --- a/app/src/main/res/layout/treatments_bolus_fragment.xml +++ b/app/src/main/res/layout/treatments_bolus_fragment.xml @@ -75,12 +75,12 @@ - - + diff --git a/app/src/main/res/layout/treatments_bolus_item.xml b/app/src/main/res/layout/treatments_bolus_item.xml index 29cbb6fe1d..ff277dcbc0 100644 --- a/app/src/main/res/layout/treatments_bolus_item.xml +++ b/app/src/main/res/layout/treatments_bolus_item.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/layout/treatments_careportal_fragment.xml b/app/src/main/res/layout/treatments_careportal_fragment.xml index e0918d0301..5460c738af 100644 --- a/app/src/main/res/layout/treatments_careportal_fragment.xml +++ b/app/src/main/res/layout/treatments_careportal_fragment.xml @@ -34,12 +34,12 @@ - - + diff --git a/app/src/main/res/layout/treatments_careportal_item.xml b/app/src/main/res/layout/treatments_careportal_item.xml index 9f688d2438..251272cb25 100644 --- a/app/src/main/res/layout/treatments_careportal_item.xml +++ b/app/src/main/res/layout/treatments_careportal_item.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/layout/treatments_extendedbolus_fragment.xml b/app/src/main/res/layout/treatments_extendedbolus_fragment.xml index cf5b425f8d..5c46d9e3a6 100644 --- a/app/src/main/res/layout/treatments_extendedbolus_fragment.xml +++ b/app/src/main/res/layout/treatments_extendedbolus_fragment.xml @@ -9,12 +9,12 @@ android:layout_height="match_parent" android:orientation="vertical"> - - + diff --git a/app/src/main/res/layout/treatments_extendedbolus_item.xml b/app/src/main/res/layout/treatments_extendedbolus_item.xml index 421bbccb81..24230e70ac 100644 --- a/app/src/main/res/layout/treatments_extendedbolus_item.xml +++ b/app/src/main/res/layout/treatments_extendedbolus_item.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/layout/treatments_profileswitch_fragment.xml b/app/src/main/res/layout/treatments_profileswitch_fragment.xml index bb63495dce..f6b0881982 100644 --- a/app/src/main/res/layout/treatments_profileswitch_fragment.xml +++ b/app/src/main/res/layout/treatments_profileswitch_fragment.xml @@ -17,12 +17,12 @@ android:layout_gravity="center_horizontal" android:text="@string/refresheventsfromnightscout" /> - - + diff --git a/app/src/main/res/layout/treatments_profileswitch_item.xml b/app/src/main/res/layout/treatments_profileswitch_item.xml index bcd3836fe2..de457274ff 100644 --- a/app/src/main/res/layout/treatments_profileswitch_item.xml +++ b/app/src/main/res/layout/treatments_profileswitch_item.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/layout/treatments_tempbasals_fragment.xml b/app/src/main/res/layout/treatments_tempbasals_fragment.xml index 062db7a4a8..47c15d6117 100644 --- a/app/src/main/res/layout/treatments_tempbasals_fragment.xml +++ b/app/src/main/res/layout/treatments_tempbasals_fragment.xml @@ -34,12 +34,12 @@ - - + diff --git a/app/src/main/res/layout/treatments_tempbasals_item.xml b/app/src/main/res/layout/treatments_tempbasals_item.xml index 2504292034..f57a3426e0 100644 --- a/app/src/main/res/layout/treatments_tempbasals_item.xml +++ b/app/src/main/res/layout/treatments_tempbasals_item.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/layout/treatments_temptarget_fragment.xml b/app/src/main/res/layout/treatments_temptarget_fragment.xml index 3f3e26e899..c83332fb53 100644 --- a/app/src/main/res/layout/treatments_temptarget_fragment.xml +++ b/app/src/main/res/layout/treatments_temptarget_fragment.xml @@ -17,12 +17,12 @@ android:layout_gravity="center_horizontal" android:text="@string/refresheventsfromnightscout" /> - - + diff --git a/app/src/main/res/layout/treatments_temptarget_item.xml b/app/src/main/res/layout/treatments_temptarget_item.xml index b85ef8a80a..c26fb8c22e 100644 --- a/app/src/main/res/layout/treatments_temptarget_item.xml +++ b/app/src/main/res/layout/treatments_temptarget_item.xml @@ -1,5 +1,5 @@ - - + diff --git a/build.gradle b/build.gradle index e0c65bd53e..398f280881 100644 --- a/build.gradle +++ b/build.gradle @@ -2,21 +2,22 @@ buildscript { ext.kotlin_version = '1.3.31' + ext.butterknifeVersion = '10.1.0' repositories { google() jcenter() - maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } + //maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } maven { url 'https://maven.fabric.io/public' } } dependencies { - classpath 'com.android.tools.build:gradle:3.2.0' + classpath 'com.android.tools.build:gradle:3.4.1' classpath 'com.google.gms:google-services:4.2.0' - classpath 'io.fabric.tools:gradle:1.28.1' + classpath 'io.fabric.tools:gradle:1.29.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files - classpath 'com.jakewharton:butterknife-gradle-plugin:9.0.0-SNAPSHOT' + classpath "com.jakewharton:butterknife-gradle-plugin:$butterknifeVersion" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/gradle.properties b/gradle.properties index 1d3591c8a4..915f0e66f9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,6 @@ # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true \ No newline at end of file +# org.gradle.parallel=true +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c25a2b3e46..219a2e077c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/wear/build.gradle b/wear/build.gradle index 9a82e64241..c26800859a 100644 --- a/wear/build.gradle +++ b/wear/build.gradle @@ -96,9 +96,9 @@ dependencies { implementation "com.google.android.support:wearable:${wearableVersion}" implementation "com.google.android.gms:play-services-wearable:${playServicesWearable}" implementation(name: 'ustwo-clockwise-debug', ext: 'aar') - implementation 'com.android.support:support-v4:28.0.0' - implementation 'com.android.support:percent:28.0.0' - implementation 'com.android.support:wear:28.0.0' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.percentlayout:percentlayout:1.0.0' + implementation 'androidx.wear:wear:1.0.0' implementation('me.denley.wearpreferenceactivity:wearpreferenceactivity:0.5.0') implementation('com.github.lecho:hellocharts-library:1.5.8@aar') } diff --git a/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java b/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java index 094b8aad76..8fc8dcf215 100644 --- a/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java +++ b/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java @@ -10,14 +10,13 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.AsyncTask; -import android.os.Build; import android.os.Bundle; import android.os.SystemClock; import android.preference.PreferenceManager; -import android.support.v4.content.LocalBroadcastManager; -import android.support.v4.app.NotificationCompat; -import android.support.v4.app.NotificationManagerCompat; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + import android.util.Log; @@ -37,8 +36,6 @@ import com.google.android.gms.wearable.NodeApi; import com.google.android.gms.wearable.Wearable; import com.google.android.gms.wearable.WearableListenerService; -import java.util.concurrent.TimeUnit; - import info.nightscout.androidaps.interaction.AAPSPreferences; import info.nightscout.androidaps.R; import info.nightscout.androidaps.interaction.actions.AcceptActivity; diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/AcceptActivity.java b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/AcceptActivity.java index f0ae184dfe..07e4bf02b3 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/AcceptActivity.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/AcceptActivity.java @@ -7,7 +7,7 @@ import android.content.res.Resources; import android.os.Bundle; import android.os.SystemClock; import android.os.Vibrator; -import android.support.v4.app.NotificationManagerCompat; +import androidx.core.app.NotificationManagerCompat; import android.support.wearable.view.DotsPageIndicator; import android.support.wearable.view.GridPagerAdapter; import android.support.wearable.view.GridViewPager; diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/VersionPreference.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/VersionPreference.java index 3bc05af82d..a190b7e65a 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/VersionPreference.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/VersionPreference.java @@ -1,9 +1,8 @@ package info.nightscout.androidaps.interaction.utils; import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.support.annotation.NonNull; + +import androidx.annotation.NonNull; import android.util.AttributeSet; import android.widget.Toast; diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/BIGChart.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/BIGChart.java index ec2e8c6e5e..1717fbf662 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/BIGChart.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/BIGChart.java @@ -17,8 +17,8 @@ import android.os.Bundle; import android.os.PowerManager; import android.os.SystemClock; import android.preference.PreferenceManager; -import android.support.v4.content.ContextCompat; -import android.support.v4.content.LocalBroadcastManager; +import androidx.core.content.ContextCompat; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import android.support.wearable.view.WatchViewStub; import android.support.wearable.watchface.WatchFaceStyle; import android.text.format.DateFormat; diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/BaseWatchFace.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/BaseWatchFace.java index 3c10981923..229eb64f1b 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/BaseWatchFace.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/BaseWatchFace.java @@ -13,7 +13,7 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.PowerManager; import android.preference.PreferenceManager; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import android.support.wearable.view.WatchViewStub; import android.text.format.DateFormat; import android.util.Log; diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/CircleWatchface.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/CircleWatchface.java index c795a03ae4..fffb0a555e 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/CircleWatchface.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/CircleWatchface.java @@ -17,7 +17,7 @@ import android.os.Bundle; import android.os.PowerManager; import android.os.SystemClock; import android.preference.PreferenceManager; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import android.support.wearable.watchface.WatchFaceStyle; import android.util.Log; import android.util.TypedValue; diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home.java index ba557f200b..430a3feb47 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home.java @@ -2,7 +2,7 @@ package info.nightscout.androidaps.watchfaces; import android.content.Intent; import android.graphics.Color; -import android.support.v4.content.ContextCompat; +import androidx.core.content.ContextCompat; import android.support.wearable.watchface.WatchFaceStyle; import android.view.LayoutInflater; diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home2.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home2.java index 367b93a729..41c9fd3029 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home2.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home2.java @@ -2,7 +2,7 @@ package info.nightscout.androidaps.watchfaces; import android.content.Intent; import android.graphics.Color; -import android.support.v4.content.ContextCompat; +import androidx.core.content.ContextCompat; import android.support.wearable.watchface.WatchFaceStyle; import android.view.LayoutInflater; diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/LargeHome.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/LargeHome.java index 2cb09c9b7b..ffb8dce3ad 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/LargeHome.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/LargeHome.java @@ -2,7 +2,7 @@ package info.nightscout.androidaps.watchfaces; import android.content.Intent; import android.graphics.Color; -import android.support.v4.content.ContextCompat; +import androidx.core.content.ContextCompat; import android.support.wearable.watchface.WatchFaceStyle; import android.view.LayoutInflater; diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/NOChart.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/NOChart.java index 991d0c4e80..f799b0cf6e 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/NOChart.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/NOChart.java @@ -13,13 +13,12 @@ import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Shader; -import android.os.Build; import android.os.Bundle; import android.os.PowerManager; import android.os.SystemClock; import android.preference.PreferenceManager; -import android.support.v4.content.ContextCompat; -import android.support.v4.content.LocalBroadcastManager; +import androidx.core.content.ContextCompat; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import android.support.wearable.view.WatchViewStub; import android.support.wearable.watchface.WatchFaceStyle; import android.text.format.DateFormat; @@ -32,7 +31,6 @@ import android.view.WindowInsets; import android.view.WindowManager; import android.widget.RelativeLayout; import android.widget.TextView; -import android.widget.Toast; import com.google.android.gms.wearable.DataMap; import com.ustwo.clockwise.common.WatchFaceTime; @@ -43,13 +41,11 @@ import com.ustwo.clockwise.wearable.WatchFace; import java.util.ArrayList; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.data.BasalWatchData; import info.nightscout.androidaps.data.BgWatchData; import info.nightscout.androidaps.data.ListenerService; import info.nightscout.androidaps.data.TempWatchData; import info.nightscout.androidaps.interaction.menus.MainMenuActivity; -import lecho.lib.hellocharts.view.LineChartView; /** * Created by adrianLxM. diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Steampunk.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Steampunk.java index 3549b2a67d..c42aff1518 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Steampunk.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Steampunk.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.watchfaces; import android.content.Intent; -import android.support.v4.content.ContextCompat; +import androidx.core.content.ContextCompat; import android.support.wearable.watchface.WatchFaceStyle; import android.view.LayoutInflater; import android.view.animation.Animation; diff --git a/wear/src/main/res/layout/action_confirm_text.xml b/wear/src/main/res/layout/action_confirm_text.xml index 291b05116d..9e6355949f 100644 --- a/wear/src/main/res/layout/action_confirm_text.xml +++ b/wear/src/main/res/layout/action_confirm_text.xml @@ -1,4 +1,4 @@ - - @@ -34,7 +34,7 @@ android:textColor="@color/white" /> - + - \ No newline at end of file + \ No newline at end of file From b173c1e79347a935b37c511691fbab3b45b0544a Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Thu, 16 May 2019 15:54:29 +0200 Subject: [PATCH 052/152] download task --- app/build.gradle | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f3652457a9..a3a00e8e8c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,6 +7,7 @@ buildscript { dependencies { classpath 'io.fabric.tools:gradle:1.+' classpath 'com.dicedmelon.gradle:jacoco-android:0.1.4' + classpath 'de.undercouch:gradle-download-task:3.4.3' } } apply plugin: 'com.android.application' @@ -16,6 +17,8 @@ apply plugin: 'com.google.gms.google-services' apply plugin: 'io.fabric' apply plugin: 'jacoco-android' apply plugin: 'com.jakewharton.butterknife' +apply plugin: 'de.undercouch.download' + jacoco { toolVersion = "0.8.3" @@ -208,10 +211,6 @@ allprojects { } } -configurations { - libs -} - dependencies { wearApp project(':wear') @@ -221,7 +220,6 @@ dependencies { implementation("com.crashlytics.sdk.android:crashlytics:2.9.9@aar") { transitive = true; } - libs "MilosKozak:danars-support-lib:master@zip" implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.legacy:legacy-support-v13:1.0.0' @@ -283,29 +281,33 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } -//task unzip(type: Copy) { -// def zipPath = configurations.libs.find { it.name.startsWith("danars") } -// def zipFile = file(zipPath) -// def outputDir = file("${buildDir}/unpacked/dist") -// from zipTree(zipFile) -// into outputDir -//} +task downloadZipFile(type: Download) { + src 'https://github.com/MilosKozak/danars-support-lib/archive/master.zip' + dest new File(buildDir, 'danars.zip') +} -//task copyLibs(dependsOn: unzip, type: Copy) { -// def src = file("${buildDir}/unpacked/dist/danars-support-lib-master") -// def target = file("src/main/jniLibs/") +task downloadAndUnzipFile(dependsOn: downloadZipFile, type: Copy) { + from zipTree(downloadZipFile.dest) + def outputDir = file("${buildDir}/unpacked/dist") + into outputDir +} -// from src -// into target -//} -//task full_clean(type: Delete) { -// delete file("src/main/jniLibs") -//} +task copyLibs(dependsOn: downloadAndUnzipFile, type: Copy) { + def src = file("${buildDir}/unpacked/dist/danars-support-lib-master") + def target = file("src/main/jniLibs/") -//clean.dependsOn full_clean -//preBuild.dependsOn copyLibs + from src + into target +} + +task full_clean(type: Delete) { + delete file("src/main/jniLibs") +} + +clean.dependsOn full_clean +preBuild.dependsOn copyLibs printf('--------------\n') printf('isMaster: %s\n', isMaster().toString()) From d3a371336c6a734b37c7c22df110938adeead0b2 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 16 May 2019 17:39:13 +0200 Subject: [PATCH 053/152] set jvm memory --- gradle.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gradle.properties b/gradle.properties index 915f0e66f9..88510bbf07 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,5 +16,8 @@ # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true + +org.gradle.jvmargs=-Xmx1g + android.enableJetifier=true android.useAndroidX=true \ No newline at end of file From 0dbd52d6cb22236ada60cbf4bad995f31433ff45 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 16 May 2019 18:57:00 +0200 Subject: [PATCH 054/152] set jvm memory 2 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 88510bbf07..1bc51c91af 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,7 +17,7 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true -org.gradle.jvmargs=-Xmx1g +org.gradle.jvmargs=-Xmx2g android.enableJetifier=true android.useAndroidX=true \ No newline at end of file From cafb11f15afb09a22e2dd0e26ff4834688da822b Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 16 May 2019 19:00:44 +0200 Subject: [PATCH 055/152] fix kotlin warning --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index a3a00e8e8c..4facf2fa78 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,8 +11,8 @@ buildscript { } } apply plugin: 'com.android.application' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' apply plugin: 'com.google.gms.google-services' apply plugin: 'io.fabric' apply plugin: 'jacoco-android' From a20efab567578f697f938db7666d21648a5011a9 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 16 May 2019 19:54:35 +0200 Subject: [PATCH 056/152] remove deprecated calls --- .../versionChecker/VersionCheckerUtils.kt | 27 ++++++------------- .../VersionCheckerUtilsKtTest.kt | 10 +++---- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt index 8791030abb..daebb5deb6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt @@ -9,29 +9,21 @@ import info.nightscout.androidaps.logging.L import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification import info.nightscout.androidaps.plugins.general.overview.notifications.Notification import info.nightscout.androidaps.utils.SP -import org.apache.http.HttpResponse -import org.apache.http.client.methods.HttpGet -import org.apache.http.impl.client.DefaultHttpClient import org.slf4j.LoggerFactory import java.io.IOException -import java.io.InputStream +import java.net.URL import java.util.concurrent.TimeUnit + // check network connection fun isConnected(): Boolean { val connMgr = MainApp.instance().applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager return connMgr.activeNetworkInfo?.isConnected ?: false } -// convert inputstream to String -@Throws(IOException::class) -inline fun InputStream.findVersion(): String? { +fun findVersion(file :String?): String? { val regex = "(.*)version(.*)\"(((\\d+)\\.)+(\\d+))\"(.*)".toRegex() - return bufferedReader() - .readLines() - .filter { regex.matches(it) } - .mapNotNull { regex.matchEntire(it)?.groupValues?.getOrNull(3) } - .firstOrNull() + return file?.lines()?.filter { regex.matches(it) }?.mapNotNull { regex.matchEntire(it)?.groupValues?.getOrNull(3) }?.firstOrNull() } private val log = LoggerFactory.getLogger(L.CORE) @@ -39,24 +31,21 @@ private val log = LoggerFactory.getLogger(L.CORE) fun triggerCheckVersion() { - if(!SP.contains(R.string.key_last_time_this_version_detected)) { + if (!SP.contains(R.string.key_last_time_this_version_detected)) { // On a new installation, set it as 30 days old in order to warn that there is a new version. SP.putLong(R.string.key_last_time_this_version_detected, System.currentTimeMillis() - TimeUnit.DAYS.toMillis(30)) } // If we are good, only check once every day. - if(System.currentTimeMillis() > SP.getLong(R.string.key_last_time_this_version_detected, 0) + CHECK_EVERY){ + if (System.currentTimeMillis() > SP.getLong(R.string.key_last_time_this_version_detected, 0) + CHECK_EVERY) { checkVersion() } } -@Suppress("DEPRECATION") private fun checkVersion() = if (isConnected()) { Thread { try { - val request = HttpGet("https://raw.githubusercontent.com/MilosKozak/AndroidAPS/master/app/build.gradle") - val response: HttpResponse = DefaultHttpClient().execute(request) - val version: String? = response.entity.content?.findVersion() + val version: String? = findVersion(URL("https://raw.githubusercontent.com/MilosKozak/AndroidAPS/master/app/build.gradle").readText()) compareWithCurrentVersion(version, BuildConfig.VERSION_NAME) } catch (e: IOException) { log.debug("Github master version check error: $e") @@ -90,7 +79,7 @@ fun onVersionNotDetectable() { fun onNewVersionDetected(currentVersion: String, newVersion: String?) { val now = System.currentTimeMillis() - if(now > SP.getLong(R.string.key_last_versionchecker_warning, 0) + WARN_EVERY) { + if (now > SP.getLong(R.string.key_last_versionchecker_warning, 0) + WARN_EVERY) { log.debug("Version ${currentVersion} outdated. Found $newVersion") val notification = Notification(Notification.NEWVERSIONDETECTED, String.format(MainApp.gs(R.string.versionavailable), newVersion.toString()), Notification.LOW) MainApp.bus().post(EventNewNotification(notification)) diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt index 2d07e40c1d..1e67bb2d4f 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt @@ -26,7 +26,7 @@ class VersionCheckerUtilsKtTest { | version = "2.2.2" | appName = "Aaoeu" """.trimMargin() - val detectedVersion: String? = buildGradle.byteInputStream().findVersion() + val detectedVersion: String? = findVersion(buildGradle) assertEquals("2.2.2", detectedVersion) } @@ -41,14 +41,14 @@ class VersionCheckerUtilsKtTest { | version = "2.2.2-nefarious-underground-mod" | appName = "Aaoeu" """.trimMargin() - val detectedVersion: String? = buildGradle.byteInputStream().findVersion() + val detectedVersion: String? = findVersion(buildGradle) assertEquals(null, detectedVersion) } @Test fun findVersionMatchesDoesNotMatchErrorResponse() { val buildGradle = """Balls! No build.gradle here. Move along""" - val detectedVersion: String? = buildGradle.byteInputStream().findVersion() + val detectedVersion: String? = findVersion(buildGradle) assertEquals(null, detectedVersion) } @@ -166,7 +166,7 @@ class VersionCheckerUtilsKtTest { | appName = "Aaoeu" """.trimMargin() val bus = prepareBus() - compareWithCurrentVersion(buildGradle.byteInputStream().findVersion(), currentVersion = "2.2.2") + compareWithCurrentVersion(findVersion(buildGradle), currentVersion = "2.2.2") verify(bus, times(0)).post(any()) @@ -186,7 +186,7 @@ class VersionCheckerUtilsKtTest { | appName = "Aaoeu" """.trimMargin() val bus = prepareBus() - compareWithCurrentVersion(buildGradle.byteInputStream().findVersion(), currentVersion = "2.2.2") + compareWithCurrentVersion(findVersion(buildGradle), currentVersion = "2.2.2") verify(bus, times(1)).post(any()) From ebd9e1621a381cc70c6c8157f8ef15cef84138d9 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 16 May 2019 22:54:20 +0200 Subject: [PATCH 057/152] New Crowdin translations (#1800) * New translations strings.xml (Dutch) * New translations strings.xml (Dutch) * New translations strings.xml (Dutch) * New translations strings.xml (Dutch) --- app/src/main/res/values-nl/strings.xml | 48 +++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 313c3fdde5..9e37bfd9c6 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -9,6 +9,7 @@ Reset database Wil je echt de database wissen? Afsluiten + Gebruik vertraagde bolussen voor >200%% DanaR Bluetooth apparaat Gebruik altijd absolute basale waarden Herstart je telefoon of herstart AndroidAPS vanuit de systeem instellingen a. u. b. \nanders zal AndroidAPS geen logboek hebben (Belangrijk om te controleren of de algoritmes correct werken)! @@ -53,6 +54,7 @@ AndroidAPS controleren en bedienen met behulp van uw WearOS-horloge. Toon informatie over de Loop op jouw xDrip+ wijzerplaat. Bedien AndroidAPS op afstand met SMS commando\'s. + Terug Start Controleer Eénheden @@ -145,6 +147,8 @@ In strijd met beperkingen Bolus toedien storing Tijdelijk basaal toedien storing + Basaal waarde [%%] + %% (100%% = huidig) Accepteer nieuw tijdelijk basaal: Bolus Bolus wizard @@ -244,6 +248,7 @@ Pomp IOB Dag totaal Laatste bolus + %1$.1fu geleden Verkeerde ingave Waarde niet correct ingesteld Herlaad profiel @@ -261,6 +266,10 @@ Om de bolus %1$.2fU toe te dienen antwoord met de code %2$s Om calibratie %1$.2f te verzenden antwoord met de code %2$s Bolus mislukt + Bolus van %1$.2fE succesvol toegediend + Er worden %1$.2fE toegediend + Bolus van %1$.2fE succesvol toegediend + Toedienen van %1$.2fE Sta SMS commando\'s toe Vingerprik Sensor @@ -305,6 +314,7 @@ Stop STOP INGEDRUKT Wacht op pomp + Er worden %1$.2fE toegediend Opzetten van visualisatie en monitoring en analyzeren van basaal en ratio\'s Controleren van beschikbaarheid BG en insuline pomp data op Nightscout Starten met de Open Loop modus @@ -328,12 +338,26 @@ Stel in en gebruik tijdelijk en standaard tijdelijke streefdoelen (bv. bij sport Loop is uitgeschakeld Loop is ingeschakeld %1$.2f gelimiteerd tot %2$.2f + Waarde %1$s valt buiten de toegestane harde limieten Commando\'s op afstand zijn niet toegestaan + Bolus op afstand niet beschikbaar. Probeer het later opnieuw. + Om een basaal van %1$.2fE/u voor %2$d min te starten antwoord met code %3$s + Om naar profiel %1$s %2$d%% te wisselen antwoord met code %3$s + Om een vertraagde bolus van %1$.2fE voor %2$d min te starten antwoord met code %3$s + Om een basaal van %1$d% voor %2$d min te starten antwoord met code %3$s Om de loop te onderbreken voor %1$d minuten antwoord met de code %2$s Tijdelijk basaal %1$.2fE/u voor %2$d minuten succesvol gestart + Vertraagde bolus %1$.2fE voor %2$d minuten succesvol gestart + Tijdelijk basaal van %1$d%% voor %2$d minuten succesvol gestart Start tijdelijk basaal mislukt + Starten vertraagde bolus is mislukt + Om het tijdelijke basaal te stoppen antwoord met de code %1$s + Om de vertraagde bolus te stoppen antwoord met de code %1$s Tijdelijk basaal afgebroken + Vertraagde bolus is geannuleerd Afbreken van tijdelijk basaal mislukt + Annuleren van vertraagde bolus is mislukt + Onbekende opdracht of verkeerd antwoord Vaste maaltijd Vaste maaltijd instellingen Naam: @@ -441,6 +465,7 @@ Stel in en gebruik tijdelijk en standaard tijdelijke streefdoelen (bv. bij sport Profiel Standaard waarde: 3.0 Dit is een belangrijke veiligheid parameter van OpenAPS. Dit limiteert je basaal met een max van 3 maal je hoogste basale dosis. Normaal hoef je deze niet te veranderen. Het is echter belangrijk dat je de ingebouwde limieten kent. Standaard waarde: 4 Dit is een combinatie van enerzijds het OpenAPS veiligheid limieten en anderzijds van “3 x max dagelijks basaal; 4x actueel”. Dit betekent dat het basaal niet hoger kan ingesteld worden dan het ingestelde nummer keer het actueel basaal waarbij de limiet in de pomp geen invloed heeft. Dit is een veiligheidsmaatregel om te voorkomen dat patiënten in gevaarlijke laag bereik komen zonder te snappen hoe het algoritme werkt. Nogmaals de standaard waarde is 4x; de meeste zullen deze waarde nooit moeten aanpassen maar zullen eerder andere waardes moeten aanpassen als ze tegen een limiet aanstoten. + Standaard waarde: 1.2\nDit is een vermenigvuldigingsfactor voor autosense (en binnenkort autotune) die zorgt dat de autosens ratio maximaal 20%% kan zijn. Dit maximum bepaalt in hoeverre je basaal kan worden verhoogd, en hoeveel je ISF en BG doel verlaagd kunnen worden. Standaard waarde: 0.7\nDit is de andere kan van de autosens veiligheid limiet. Dit zet een limiet op hoe laag het basaal kan aangepast worden, en hoe hoog het ISF en het streef BG. Autosens past de streefwaardes ook aan Standaardwaarde: waar Dit wordt gebruikt om autosens de bevoegdheid te geven BG doelen aan te passen alsook ISF en basalen @@ -453,6 +478,7 @@ Stel in en gebruik tijdelijk en standaard tijdelijke streefdoelen (bv. bij sport Telefoon nummer niet conform Foutief SMS telefoon nummer Kalibratie + Calibratie %1$.1f naar xDrip verzenden? xDrip+ niet geïnstalleerd Kalibratie naar xDrip verzonden Kalibratie verzonden. Het ontvangen van kalibraties moet actief zijn in xDrip. @@ -503,6 +529,7 @@ Stel in en gebruik tijdelijk en standaard tijdelijke streefdoelen (bv. bij sport insuline resistente volwassene Kies leeftijd van de patient voor veiligheids limieten Glimp + Voor een goede werking van %1$s moeten de accubesparing-opties zijn uitgeschakeld in de telefooninstellingen Loop pauzeren Gepauzeerd (%1$d m) Superbolus (%1$d m) @@ -538,6 +565,8 @@ Stel in en gebruik tijdelijk en standaard tijdelijke streefdoelen (bv. bij sport Statusindicatoren inschakelen voor cage, iage, sage, reservoir en batterijniveau op het beginscherm. Drempel waarschuwing reservoir niveau [E] Drempel alarm reservoir niveau [E] + Drempel waarschuwing batterij niveau [%%] + Drempel alarm voor batterij niveau [%%] IOB COB Firmware @@ -590,6 +619,7 @@ Stel in en gebruik tijdelijk en standaard tijdelijke streefdoelen (bv. bij sport Opname instellingen Max absorptietijd maaltijd [uur] Verwachte tijd in uren totdat alle koolhydraten opgenomen zijn + Geef vertraagde bolus weer in %% SAGE IAGE CAGE @@ -702,6 +732,8 @@ Stel in en gebruik tijdelijk en standaard tijdelijke streefdoelen (bv. bij sport Stuur BG data naar xDrip+ In xDrip+ kies 640g/Eversense data bron NSClient BG + Basale waarde vervangen door minimaal ondersteunde waarde; %1$s + Basale waarde vervangen door maximale ondersteunde waarde: %1$s BG berekening Bolus IOB berekening Basaal IOB berekening @@ -719,6 +751,8 @@ Stel in en gebruik tijdelijk en standaard tijdelijke streefdoelen (bv. bij sport Closed modus actief Maximum IOB juist ingesteld BG beschikbaar op gekozen bron + Basaalstanden niet ingesteld in hele uren: %1$s + Ongeldig profiel: %1$s Bolus in pomp programmeren Vernieuw Status @@ -731,6 +765,7 @@ Stel in en gebruik tijdelijk en standaard tijdelijke streefdoelen (bv. bij sport Actief Annuleren van TB Instellen TBR (%1$d%% / %2$d min) + Toedienen bolus (%1$.1f E) Vernieuwen Gevraagde is niet mogelijk met de pomp Opgelet: verlengde en multi wave bolussen zijn actief. Loop is overgeschakeld naar low-suspend modus gedurende 6 uur. Alleen gewone bolussen worden ondersteund in loop modus @@ -1053,11 +1088,13 @@ Stel in en gebruik tijdelijk en standaard tijdelijke streefdoelen (bv. bij sport Geluid dempen Pomp alarm Infuuswissel noteren + Reservoirwissel noteren Slangwissel noteren Batterijwissel noteren Werkingsmodus-wissel noteren - Alarm noteren + Alarmen noteren TBR-emulatie inschakelen + Gebruik vertraagde bolus ipv TBRs om de limiet van 250%% te omzeilen Verbindingsvertraging [s] Serienummer Release softwareversie @@ -1113,8 +1150,17 @@ Stel in en gebruik tijdelijk en standaard tijdelijke streefdoelen (bv. bij sport Tomato (MiaoMiao) Tomato Zomer/wintertijd omschakeling binnen 24 uur + Omschakeling zomer/wintertijd minder dan 3 uur geleden - Closed Loop modus gedeactiveerd interne opslag bijna vol Maak minstens %1$d MB vrij in interne opslag! Loop is uitgeschakeld! + Verkeerde invoer + Verkeerde code. Opdracht geannuleerd. + Niet ingesteld + Profiel wissel aangemaakt + Versie Checker + oude versie + zeer oude versie + Er is als sinds ten minste %1$d dagen een nieuwe versie beschikbaar! Na 60 dagen wordt de loop beperkt tot LGS, na 90 dagen zal de loop worden uitgeschakeld %1$d dag %1$d dag From 2d39d0be950d7a6db2ac5cc601df1e479189c3e3 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 16 May 2019 23:07:08 +0200 Subject: [PATCH 058/152] travis tools 28 --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5c6309f817..ec1632958e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,19 +2,19 @@ language: android jdk: oraclejdk8 env: matrix: - - ANDROID_TARGET=android-23 ANDROID_ABI=x86 org.gradle.jvmargs=-XX:-OmitStackTraceInFastThrow + - ANDROID_TARGET=android-28 ANDROID_ABI=x86 org.gradle.jvmargs=-XX:-OmitStackTraceInFastThrow android: components: - platform-tools - tools - - build-tools-27.0.2 - - android-23 + - build-tools-28.0.3 + - android-28 - extra-google-m2repository - extra-android-m2repository - extra-google-google_play_services before_install: -- yes | sdkmanager "platforms;android-27" +- yes | sdkmanager "platforms;android-28" script: # Unit Test From 0089c2ad9cfbd0d592ab38dc469dd29da4580167 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Fri, 17 May 2019 00:17:25 +0200 Subject: [PATCH 059/152] travis memory --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ec1632958e..31d571fde4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: android jdk: oraclejdk8 env: matrix: - - ANDROID_TARGET=android-28 ANDROID_ABI=x86 org.gradle.jvmargs=-XX:-OmitStackTraceInFastThrow + - ANDROID_TARGET=android-28 ANDROID_ABI=x86 org.gradle.jvmargs=-Xmx2g -XX:-OmitStackTraceInFastThrow android: components: - platform-tools From 767276ebbb3052fddc6ee07959e417c339538ac5 Mon Sep 17 00:00:00 2001 From: triplem <160079+triplem@users.noreply.github.com> Date: Sat, 18 May 2019 09:40:06 +0200 Subject: [PATCH 060/152] fork tests --- .travis.yml | 2 +- app/build.gradle | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 31d571fde4..ec1632958e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: android jdk: oraclejdk8 env: matrix: - - ANDROID_TARGET=android-28 ANDROID_ABI=x86 org.gradle.jvmargs=-Xmx2g -XX:-OmitStackTraceInFastThrow + - ANDROID_TARGET=android-28 ANDROID_ABI=x86 org.gradle.jvmargs=-XX:-OmitStackTraceInFastThrow android: components: - platform-tools diff --git a/app/build.gradle b/app/build.gradle index 4facf2fa78..d5487c62c2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -195,8 +195,15 @@ android { } testOptions { - unitTests.returnDefaultValues = true - unitTests.includeAndroidResources = true + unitTests { + returnDefaultValues = true + includeAndroidResources = true + + all { + maxParallelForks = 10 + forkEvery = 20 + } + } } useLibrary "org.apache.http.legacy" From 5f65ef9cac88258ec5c4e87854520662169e5e92 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Wed, 22 May 2019 20:38:03 +0200 Subject: [PATCH 061/152] New Crowdin translations (#1802) * New translations strings.xml (Dutch) * New translations strings.xml (Polish) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Swedish) * New translations strings.xml (Russian) * New translations strings.xml (Hebrew) * New translations strings.xml (French) * New translations strings.xml (German) * New translations strings.xml (Turkish) --- app/src/main/res/values-de/strings.xml | 8 ++++---- app/src/main/res/values-fr/strings.xml | 4 ++-- app/src/main/res/values-he/strings.xml | 4 ++-- app/src/main/res/values-nl/strings.xml | 4 ++-- app/src/main/res/values-pl/strings.xml | 4 ++-- app/src/main/res/values-pt/strings.xml | 28 +++++++++++++------------- app/src/main/res/values-ru/strings.xml | 4 ++-- app/src/main/res/values-sv/strings.xml | 4 ++-- app/src/main/res/values-tr/strings.xml | 7 ++++--- 9 files changed, 34 insertions(+), 33 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 2d0a15d6ac..e21f869a5b 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -329,8 +329,8 @@ Loope eine Woche erfolgreich am Tag mit regelmäßiger Kohlenhydrat-Eingabe. Aktiviere zusätzliche Funktionen wie z. B. den Mahlzeitenassistenten Aktiviere zusätzliche Funktionen wie z. B. SMB - Lies das Wiki und hebe maxIOB an, damit SMB funktioniert. Ein guter Startwert ist -maxIOB = durchschnittlicher Essensbolus + 3 x maximale Basalrate + "Lies das Wiki und hebe maxIOB an, damit SMB funktioniert. Ein guter Startwert ist +maxIOB = durchschnittlicher Essensbolus + 3 x maximale Basalrate" Limit erreicht Kein Profil ausgewählt Loop wurde deaktiviert. @@ -1066,8 +1066,8 @@ maxIOB = durchschnittlicher Essensbolus + 3 x maximale Basalrate Gesamt Berech. Handshaking - Sende die heutigen Logdateien unter Angabe dieser Uhrzeit an die Entwickler. -Unerwartetes Verhalten. + "Sende die heutigen Logdateien unter Angabe dieser Uhrzeit an die Entwickler. +Unerwartetes Verhalten." Max. Bolus überschritten Fehler bei Befehl Geschwindigkeits-Fehler diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 0ae7423e5d..8f9fb110db 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -239,8 +239,8 @@ Déconnectée Paramètres pompe DanaR Contrat de Licence pour Utilisateur Final - NE DOIT PAS ÊTRE UTILISÉ POUR PRENDRE DES DÉCISIONS MÉDICALES. IL N\'Y A AUCUNE GARANTIE POUR LE PROGRAMME, DANS LA LIMITE PERMISE PAR LA LOI APPLICABLE. SAUF S\'IL EST INDIQUÉ LE CONTRAIRE PAR ÉCRIT. LES TITULAIRES DU DROIT D\'AUTEUR ET / OU D\'AUTRES PARTIES FOURNISSENT LE PROGRAMME «EN L\'ÉTAT» SANS GARANTIE D\'AUCUNE SORTE, EXPLICITE OU IMPLICITE, Y COMPRIS MAIS NON SEULEMENT AUX GARANTIES IMPLICITES DE QUALITÉ MARCHANDE ET D\'ADÉQUATION À UN USAGE PARTICULIER -L\'ENSEMBLE DES RISQUES LIÉS À LA QUALITÉ ET À LA PERFORMANCE DU PROGRAMME SONT DE VOTRE RESPONSABILITÉ. SI LE PROGRAMME EST DÉFECTUEUX, VOUS ASSUMEZ LE COÛT DE TOUS LES SERVICES, RÉPARATIONS OU CORRECTIONS NÉCESSAIRES. + "NE DOIT PAS ÊTRE UTILISÉ POUR PRENDRE DES DÉCISIONS MÉDICALES. IL N'Y A AUCUNE GARANTIE POUR LE PROGRAMME, DANS LA LIMITE PERMISE PAR LA LOI APPLICABLE. SAUF S'IL EST INDIQUÉ LE CONTRAIRE PAR ÉCRIT. LES TITULAIRES DU DROIT D'AUTEUR ET / OU D'AUTRES PARTIES FOURNISSENT LE PROGRAMME «EN L'ÉTAT» SANS GARANTIE D'AUCUNE SORTE, EXPLICITE OU IMPLICITE, Y COMPRIS MAIS NON SEULEMENT AUX GARANTIES IMPLICITES DE QUALITÉ MARCHANDE ET D'ADÉQUATION À UN USAGE PARTICULIER +L'ENSEMBLE DES RISQUES LIÉS À LA QUALITÉ ET À LA PERFORMANCE DU PROGRAMME SONT DE VOTRE RESPONSABILITÉ. SI LE PROGRAMME EST DÉFECTUEUX, VOUS ASSUMEZ LE COÛT DE TOUS LES SERVICES, RÉPARATIONS OU CORRECTIONS NÉCESSAIRES." JE COMPRENDS ET J\'ACCEPTE LES CONDITIONS DU CONTRAT Sauvegarder L\'adaptateur bluetooth est introuvable diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 9b6d9033eb..fa54a4f375 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -2,8 +2,8 @@ רענן טיפול מ-Nightscout - בבקשה אתחל את הסמרטפון שלך או תרענן את אפלקצית AndroidAPS בהגדרות המערכת -אחרת AndroidAPS לא יוכל לבצע רישום (חשוב לעקוב אחר ולוודא כי האלגוריתמים פועלים כראוי)! + "בבקשה אתחל את הסמרטפון שלך או תרענן את אפלקצית AndroidAPS בהגדרות המערכת +אחרת AndroidAPS לא יוכל לבצע רישום (חשוב לעקוב אחר ולוודא כי האלגוריתמים פועלים כראוי)!" מציג את ההגדרות הקבועות מראש עבור מזון המוגדר ב-Nightscout להפעיל או לכבות את הלולאה. מסנכרן את הנתונים שלך עם Nightscout diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 9e37bfd9c6..907b2e7c08 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -318,8 +318,8 @@ Opzetten van visualisatie en monitoring en analyzeren van basaal en ratio\'s Controleren van beschikbaarheid BG en insuline pomp data op Nightscout Starten met de Open Loop modus - In Open Loop modus werken voor enkele dagen en handmatig meermaals tijdelijke basaal instellen. -Stel in en gebruik tijdelijk en standaard tijdelijke streefdoelen (bv. bij sporten of koolhydraten inname bij hypo) + "In Open Loop modus werken voor enkele dagen en handmatig meermaals tijdelijke basaal instellen. +Stel in en gebruik tijdelijk en standaard tijdelijke streefdoelen (bv. bij sporten of koolhydraten inname bij hypo)" De Open Loop begrijpen, inclusief de voorgestelde tijdelijke basaalstanden Gebaseerd op deze ervaringen beslissen wat het maximale basaal mag zijn en dit in de pomp instellen Starten met closed Loop met bescherming tegen lage BG diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index b06da87522..f032934ccf 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -580,8 +580,8 @@ Pokaż BGI Dodaj BGI do Linii Statusu Brak przesyłania do NS - Wszystkie dane wysyłane do NS są odrzucane. AAPS jest podłączony do NS, ale nie dokonuje - żadnych zmian w NS + "Wszystkie dane wysyłane do NS są odrzucane. AAPS jest podłączony do NS, ale nie dokonuje + żadnych zmian w NS" Krok bazy Krok bolusa BolusPrzedłużony diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 36ffb7c5e9..4ae1ab91b3 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1,28 +1,28 @@ - Segurança de tratamentos - Max bolus permitido [U] - Max hidratos permitidos [g] + Segurança do Tratamento + Máximo bolus permitido [U] + Máximo de carbs permitidos [g] Preferências - Atualizar tratamentos do NS - Reinicializar base de dados - Quer realmente reiniciar a base de dados? + Atualizar tratamentos de NS + Redefinir banco de dados + Você realmente quer redefinir os bancos de dados? Sair Usar bólus prolongado de >200%% Dispositivo Bluetooth DanaR - Usar sempre valores absolutos de basal - Por favor, reinicie o seu telefone ou reinicie o AndroidAPS a partir das Configurações do Sistema \ncaso contrário, o AndroidAPS não terá registro (importante para controlar e verificar se os algoritmos estão a funcionar corretamente)! - Alguns botões para aceder rapidamente a funções comuns - Inserir as entradas avançadas do livro de registo. + Sempre usar valores absolutos basais + Por favor, reinicie seu telefone ou reinicie o AndroidAPS nas Configurações do Sistema \nCaso contrário o Android APS não terá registro (importante para rastrear e verificar que os algoritmos estão funcionando corretamente)! + Alguns botões para acessar rapidamente a funções comuns + Digite as entradas avançadas do livro de log. Usado para configurar os plugins ativos Programa de aprendizagem Exibe as predefinições de comida definidas no Nightscout - Predefinição de Insulina Humalog e NovoRapid / NovoLog - Predefinição de Insulina Fiasp - Permite definir o pico de atividade da insulina e deve ser usado somente por usuários avançados + Predefinição de Insulina para Humalog e NovoRapid / NovoLog + Pré-ajuste de Insulina para Fiasp + Permite que você defina o pico da atividade de insulina, deve ser usado apenas por usuários avançados Ativar ou desativar a aplicação que desencadeia o loop. - Sincroniza os seus dados com o Nightscout + Sincroniza seus dados com o Nightscout Estado do algoritmo em 2016 Estado do algoritmo em 2017 Algoritmo mais recente para usuários avançados diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index aaa07dfec0..4d127557cf 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -991,9 +991,9 @@ Настроить плагин APS Настроить плагин чувствительности Sensitivity Плагин чувствительности Sensitivity применяется для определения чувствительности к инсулину и вычисления активных углеводов COB. Дополнительная информация: - https://github.com/MilosKozak/AndroidAPS/wiki/Sensitivity-detection-and-COB + "https://github.com/MilosKozak/AndroidAPS/wiki/Sensitivity-detection-and-COB -Context | Edit Context +Context | Edit Context" NSClient обрабатывает подключения к Nightscout. Вы можете пропустить этот этап сейчас, но вы не преодолеете все Цели пока не настроите его. Напоминание: новые профили инсулина требуют как минимум 5 часов длительности действия DIA. 5-6 часовая DIA эквивалентна трехчасовой на старых профилях. Настройте источник СК diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 409b7f21a6..080955de03 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -45,8 +45,8 @@ Känsligheten beräknas som ett viktat medelvärde av avvikelserna. Senare avvikelser får en högre vikt. Minimal upptagning av kolhydrater beräknas utifrån maximal upptagstid för kolhydrater som angetts i inställningarna. Den här algoritmen är den snabbaste att justera förändringar i känsligheten. Ta emot BG-data från den modifierade Dexcom G5-appen Ta emot BG-data från den modifierade Dexcom G6-appen. - Ta emot BG-data från den modifierade -Eversense-appen. + "Ta emot BG-data från den modifierade +Eversense-appen." Ta emot BG-data från Glimp Ta emot BG-data från 600SeriesAndroidUploader Ladda ner BG-data från Nightscout diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index d858630fff..c446e64172 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -441,8 +441,8 @@ Autosens hedefleri de ayarlar Varsayılan değer: true\nBu autosens in İnsülin Duyarlılık Faktörü ve bazallara ek olarak KŞ hedeflerinin ayarlanmasında kullanılır. Varsayılan değer: 2\nEğer sadece öğününüzü yerseniz, öğün bolusu alındıktan sonra bolus ertelemesi devreye girer, bu şekilde Döngü karşı bir tetbir olarak geçici bazal oranını azaltmasın diye. Buaradaki örnekte olduğu gibi varsayılan 2; 3 saat DIA olacak şekilde bolus ertelemesi kademeli olarak 1,5 saat üzeri (3DIA/2=1,5). - Varsayılan değer: 3.0 (AMA) Gelişmiş Yemek Asistanı veya 8.0 (SMB) Super Micro Bolus. Bu 5 dakika başına varsayılan karbonhidrat emilimi için bir ayardır. Standart değer AMA için 3mg/dl/5dk aynı şekilde SMB 8mg/dl/5dk dir. -Aktif Karbonhidratın ne kadar hızlı sindirildiğine ve KŞ\'nin beklenenden daha fazla düştüğü veya beklenildiği kadar yükselmediği zamanlarda gelecekte yapılacak tahminlerde KŞ\'nin hesaplanmasında ne kadar karbonhidrat emilimi gerçekleşeceğini öngörür. + "Varsayılan değer: 3.0 (AMA) Gelişmiş Yemek Asistanı veya 8.0 (SMB) Super Micro Bolus. Bu 5 dakika başına varsayılan karbonhidrat emilimi için bir ayardır. Standart değer AMA için 3mg/dl/5dk aynı şekilde SMB 8mg/dl/5dk dir. +Aktif Karbonhidratın ne kadar hızlı sindirildiğine ve KŞ'nin beklenenden daha fazla düştüğü veya beklenildiği kadar yükselmediği zamanlarda gelecekte yapılacak tahminlerde KŞ'nin hesaplanmasında ne kadar karbonhidrat emilimi gerçekleşeceğini öngörür." Dikkat!\nNormalde aşağıdaki bu değerleri değiştirmek zorunda değilsiniz. Lütfen burayı TIKLAYIN ve metni OKUYUN ve bu değerlerden herhangi birini değiştirmeden önce ANLADIĞINIZDAN emin olun. Yalnızca sayısal rakam girebilirsiniz. %1$s - %2$s aralığında yalnızca sayısal rakamlar kullanılabilir. @@ -701,7 +701,8 @@ Aktif Karbonhidratın ne kadar hızlı sindirildiğine ve KŞ\'nin beklenenden d Hayır Sadece pozitif Sadece negatif - COB (Aktif Karbonhidrat) hesaplaması + "COB (Aktif Karbonhidrat) hesaplaması +" Geçici hedef hesaplaması Döngü etkin Seçili APS From dd942d967352b2500f6056ab8d73adb250515382 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Fri, 31 May 2019 10:15:36 +0200 Subject: [PATCH 062/152] New translations strings.xml (Chinese Simplified) --- app/src/main/res/values-zh/strings.xml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 7e4b22eacd..997a859a16 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -9,6 +9,7 @@ 重置数据库 你真的想重置数据库吗? 退出 + 使用>200%%的扩展大剂量(extended boluses) DanaR 蓝牙设备 总是使用基础率的绝对值 请重启你的手机或者从系统设置里重启AndroidAPS软件 \n 否则 Android APS将不会记录日志 (这对于跟踪和检查算法是否正确很重要)! @@ -146,6 +147,8 @@ 违反约束条件 大剂量输注错误 临时基础输注错误 + 基础率值 [%%] + %% (100%% = 当前) 接受新的临时基础率 治疗 计算器 @@ -245,6 +248,7 @@ 泵 IOB 每日单位数 上次大剂量 + %1$.1f小时前 无效的输入数据 数值设定的不正确 重新加载配置文件 @@ -310,6 +314,7 @@ 停止 停止键按下了 正在等待泵 + 将要输注 %1$.2fU 设置可视化和监视, 并分析基础率和比率 验证 Nightscout 中的血糖是否可用, 并且正在上传泵的胰岛素数据 开始开环 @@ -332,6 +337,7 @@ 闭环被禁用 闭环被启用 %1$.2f 超过 %2$.2f的限制 + 值 %1$s 超过了硬限制 远程命令没有被允许 远程大剂量不可用。请稍后再试。 要开始基础率 %1$.2fU/h 持续时间%2$d 分钟,请回复如下代码 %3$s @@ -458,6 +464,7 @@ 配置文件 默认值: 3 这是一个关键的 OpenAPS 安全阀值。这是限制你的闭环可以最大扩张3倍的日最大基础率。你可能不需要改变这个值 但你应该这个值的含义。日最大基础率的3倍,当前基础率的4倍,这是建议的安全阀值 默认值: 4 这是另一个的关键 OpenAPS 安全阀值, 日最大基础率的3倍,当前基础率的4倍,这是建议的安全阀值。这意味着你的基础率, 无论你的泵上的最大基础率设置的多少,也 不能超过你的当前基础率的4倍。这是为了防止人们在了解该算法的工作原理之前进入危险的领域 。同样, 默认值为 4倍; 大多数人将永远不需要调整, 而是更有可能需要调整其他设置, + 默认值: 1.2 \n 这是 autosens 的安全阀值 对 autosens ( 很快autotune) 比率的高值设置最大增加20%% 最大限制, 这反过来决定了 autosens 可以调整的基础率的上限值, 可以调整的胰岛素敏感系数的下限值, 以及它可以设置血糖目标的下限值。 默认值: 0.7 \n  这是autosens 的另一个安全阈值, 。这个值决定了 autosens 可以调整的基础率的下限值, 可以调整的胰岛素敏感系数的上限值, 以及它可以设置血糖目标的上限值。 Autosens 也调整目标血糖 缺省值:是\n 这是用来允许 autosens调整血糖目标,还有 ISF胰岛素敏感系数和基础率这些参数。 @@ -470,6 +477,7 @@ 电话号码无效 无效的SMS手机号码 校准 + 确认将校准值 %1$.1f 发送到 xDrip 吗? 没有安装xDrip+ 校准发送到 xDrip 校准值发送了,必须在xDrip开启允许通过其他app使用它的校准功能 @@ -520,6 +528,7 @@ 胰岛素抵抗成人 请选择患者年龄以设置安全限制 Glimp + %1$s 需要加入电池优化白名单才能正常运行 闭环暂停了 暂停了 (%1$d m) 超级大剂量 (%1$d m) @@ -555,6 +564,8 @@ 在主屏幕上启用cage(碳水时间)、iage(胰岛素时间)、sage(探头使用时间)、储药器和电池电量的状态指示灯。 储药器药量低于阈值[U] 警告 储药器药量低于阈值[U] 严重警告 + 电池电量低于阈值[%%] 警告 + 电池电量低于阈值[%%] 严重警告 IOB活性胰岛素 COB活性碳水 固件 @@ -607,6 +618,7 @@ 食物吸收设置 膳食最大吸收时间 [h] 预计膳食中所有的碳水化合物被吸收的时间,以小时为单位 + 用百分比的形式显示扩展 %% 探头AGE 胰岛素AGE 管路AGE @@ -719,6 +731,8 @@ 将血糖数据发送到 xDrip + 在 xDrip + 选择640g/Eversense 数据源 NSClient 血糖 + 基础率值被泵支持的最小值: %1$s 替换了 + 基础率值被泵支持的最大值:%1$s 替换了 血糖计算 大剂量IOB (活性胰岛素) 计算 基础率IOB (活性胰岛素) 计算 @@ -736,6 +750,8 @@ 闭环模式启用了 合理地设置最大 IOB 所选的血糖值来源的血糖是可用的 + 基础率值与小时不一致:%1$s + 无效的配置文件:%1$s 正在调用泵上的大剂量输注程序 刷新 状态 @@ -748,6 +764,7 @@ 正在运行 正在取消临时基础率 正在设置临时基础率 (%1$d%% / %2$d 分钟) + 正在输注大剂量 (%1$.1f U) 正在刷新 泵不支持请求的操作 不安全的用法: 扩展大剂量(方波) 或者双波被启用了,闭环只支持常规大剂量, 闭环已经被设置为6个小时的低血糖暂停模式(low-suspend only 6 hours). @@ -1076,6 +1093,7 @@ 记录操作模式更改 记录报警 启用 TBR (临时基础率) 模拟 + 使用扩展大剂量而不是TBRs(临时基础率) 绕过250%%的限制 断开连接延迟 [s] 序列号 发行软件版本 @@ -1138,6 +1156,10 @@ 错误的代码。命令取消 未配置 配置文件切换已创建 + 版本检查器 + 旧版本 + 非常旧版本 + 至少可用 %1$d 天的新版本!60 天后返回 LGS,90 天后循环将被禁用 %1$d 天 From dd637021f7d60b34419897a4134b44e32c0ba0e7 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sat, 1 Jun 2019 00:48:37 +0200 Subject: [PATCH 063/152] tidepool xdrip port --- app/build.gradle | 10 +- .../eveningoutpost/dexdrip/Models/JoH.java | 126 +++++++ .../dexdrip/tidepool/BaseElement.java | 40 ++ .../dexdrip/tidepool/BaseMessage.java | 24 ++ .../dexdrip/tidepool/DateUtil.java | 46 +++ .../dexdrip/tidepool/EBasal.java | 43 +++ .../dexdrip/tidepool/EBloodGlucose.java | 45 +++ .../dexdrip/tidepool/EBolus.java | 26 ++ .../dexdrip/tidepool/ESensorGlucose.java | 37 ++ .../dexdrip/tidepool/EWizard.java | 34 ++ .../tidepool/GzipRequestInterceptor.java | 48 +++ .../dexdrip/tidepool/InfoInterceptor.java | 32 ++ .../dexdrip/tidepool/MAuthReply.java | 36 ++ .../dexdrip/tidepool/MAuthRequest.java | 24 ++ .../tidepool/MCloseDatasetRequest.java | 9 + .../dexdrip/tidepool/MDatasetReply.java | 47 +++ .../dexdrip/tidepool/MGetDatasetsRequest.java | 4 + .../dexdrip/tidepool/MOpenDatasetRequest.java | 63 ++++ .../dexdrip/tidepool/MUploadReply.java | 9 + .../dexdrip/tidepool/Session.java | 54 +++ .../dexdrip/tidepool/TidepoolCallback.java | 67 ++++ .../dexdrip/tidepool/TidepoolEntry.java | 28 ++ .../dexdrip/tidepool/TidepoolStatus.java | 31 ++ .../dexdrip/tidepool/TidepoolUploader.java | 342 ++++++++++++++++++ .../dexdrip/tidepool/UploadChunk.java | 182 ++++++++++ app/src/main/res/values/strings.xml | 14 + 26 files changed, 1420 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/Models/JoH.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/BaseElement.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/BaseMessage.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/DateUtil.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBasal.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBloodGlucose.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBolus.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/ESensorGlucose.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EWizard.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/GzipRequestInterceptor.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/InfoInterceptor.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MAuthReply.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MAuthRequest.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MCloseDatasetRequest.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MDatasetReply.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MGetDatasetsRequest.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MOpenDatasetRequest.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MUploadReply.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/Session.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolCallback.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolEntry.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolStatus.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolUploader.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/UploadChunk.java diff --git a/app/build.gradle b/app/build.gradle index cef63558b9..b374db47b3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -250,7 +250,7 @@ dependencies { // excluding org.json which is provided by Android exclude group: "org.json", module: "json" } - implementation "com.google.code.gson:gson:2.8.2" + implementation "com.google.code.gson:gson:2.4" implementation "com.google.guava:guava:24.1-jre" implementation "net.danlew:android.joda:2.9.9.1" @@ -277,6 +277,14 @@ dependencies { androidTestImplementation "com.google.dexmaker:dexmaker:${dexmakerVersion}" androidTestImplementation "com.google.dexmaker:dexmaker-mockito:${dexmakerVersion}" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + // xDrip-plus port + implementation 'com.activeandroid:thread-safe-active-android:3.1.1' + implementation 'com.squareup.retrofit2:retrofit:2.4.0' + implementation 'com.squareup.okhttp3:okhttp:3.10.0' + implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0' + implementation 'com.squareup.retrofit2:converter-gson:2.4.0' + } task unzip(type: Copy) { diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/Models/JoH.java b/app/src/main/java/com/eveningoutpost/dexdrip/Models/JoH.java new file mode 100644 index 0000000000..df801512fe --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/Models/JoH.java @@ -0,0 +1,126 @@ +package com.eveningoutpost.dexdrip.Models; + +import android.content.Context; +import android.os.PowerManager; +import android.util.Log; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.GregorianCalendar; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; + +public class JoH { + + private final static String TAG = "jamorham JoH"; + + public static String dateTimeText(long timestamp) { + return android.text.format.DateFormat.format("yyyy-MM-dd kk:mm:ss", timestamp).toString(); + } + + public static String niceTimeScalar(long t) { + String unit = MainApp.gs(R.string.unit_second); + t = t / 1000; + if (t != 1) unit = MainApp.gs(R.string.unit_seconds); + if (t > 59) { + unit = MainApp.gs(R.string.unit_minute); + t = t / 60; + if (t != 1) unit = MainApp.gs(R.string.unit_minutes); + if (t > 59) { + unit = MainApp.gs(R.string.unit_hour); + t = t / 60; + if (t != 1) unit = MainApp.gs(R.string.unit_hours); + if (t > 24) { + unit = MainApp.gs(R.string.unit_day); + t = t / 24; + if (t != 1) unit = MainApp.gs(R.string.unit_days); + if (t > 28) { + unit = MainApp.gs(R.string.unit_week); + t = t / 7; + if (t != 1) unit = MainApp.gs(R.string.unit_weeks); + } + } + } + } + //if (t != 1) unit = unit + "s"; //implemented plurality in every step, because in other languages plurality of time is not every time adding the same character + return qs((double) t, 0) + " " + unit; + } + + // singletons to avoid repeated allocation + private static DecimalFormatSymbols dfs; + private static DecimalFormat df; + public static String qs(double x, int digits) { + + if (digits == -1) { + digits = 0; + if (((int) x != x)) { + digits++; + if ((((int) x * 10) / 10 != x)) { + digits++; + if ((((int) x * 100) / 100 != x)) digits++; + } + } + } + + if (dfs == null) { + final DecimalFormatSymbols local_dfs = new DecimalFormatSymbols(); + local_dfs.setDecimalSeparator('.'); + dfs = local_dfs; // avoid race condition + } + + final DecimalFormat this_df; + // use singleton if on ui thread otherwise allocate new as DecimalFormat is not thread safe + if (Thread.currentThread().getId() == 1) { + if (df == null) { + final DecimalFormat local_df = new DecimalFormat("#", dfs); + local_df.setMinimumIntegerDigits(1); + df = local_df; // avoid race condition + } + this_df = df; + } else { + this_df = new DecimalFormat("#", dfs); + } + + this_df.setMaximumFractionDigits(digits); + return this_df.format(x); + } + + public static long getTimeZoneOffsetMs() { + return new GregorianCalendar().getTimeZone().getRawOffset(); + } + + public static boolean emptyString(final String str) { + return str == null || str.length() == 0; + } + + public static PowerManager.WakeLock getWakeLock(final String name, int millis) { + final PowerManager pm = (PowerManager) MainApp.instance().getSystemService(Context.POWER_SERVICE); + final PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name); + wl.acquire(millis); + Log.d(TAG, "getWakeLock: " + name + " " + wl.toString()); + return wl; + } + + public static void releaseWakeLock(PowerManager.WakeLock wl) { + Log.d(TAG, "releaseWakeLock: " + wl.toString()); + if (wl == null) return; + if (wl.isHeld()) { + try { + wl.release(); + } catch (Exception e) { + Log.e(TAG, "Error releasing wakelock: " + e); + } + } + } + + public static PowerManager.WakeLock fullWakeLock(final String name, long millis) { + final PowerManager pm = (PowerManager) MainApp.instance().getSystemService(Context.POWER_SERVICE); + PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, name); + wl.acquire(millis); + Log.d(TAG, "fullWakeLock: " + name + " " + wl.toString()); + return wl; + } + + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/BaseElement.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/BaseElement.java new file mode 100644 index 0000000000..0bf0bb6fa9 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/BaseElement.java @@ -0,0 +1,40 @@ +package com.eveningoutpost.dexdrip.tidepool; + +import com.google.gson.annotations.Expose; + +/** + * jamorham + *

+ * common element base + */ + +public abstract class BaseElement { + @Expose + public String deviceTime; + @Expose + public String time; + @Expose + public int timezoneOffset; + @Expose + public String type; + @Expose + public Origin origin; + + + BaseElement populate(final long timestamp, final String uuid) { + deviceTime = DateUtil.toFormatNoZone(timestamp); + time = DateUtil.toFormatAsUTC(timestamp); + timezoneOffset = DateUtil.getTimeZoneOffsetMinutes(timestamp); // TODO + origin = new Origin(uuid); + return this; + } + + public class Origin { + @Expose + String id; + + Origin(String id) { + this.id = id; + } + } +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/BaseMessage.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/BaseMessage.java new file mode 100644 index 0000000000..c96d3da1c4 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/BaseMessage.java @@ -0,0 +1,24 @@ +package com.eveningoutpost.dexdrip.tidepool; + +import com.eveningoutpost.dexdrip.Models.JoH; + +import okhttp3.MediaType; +import okhttp3.RequestBody; + +/** + * jamorham + * + * message base + */ + +public abstract class BaseMessage { + + public String toS() { + return JoH.defaultGsonInstance().toJson(this); + } + + public RequestBody getBody() { + return RequestBody.create(MediaType.parse("application/json"), this.toS()); + } + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/DateUtil.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/DateUtil.java new file mode 100644 index 0000000000..84f4076f75 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/DateUtil.java @@ -0,0 +1,46 @@ +package com.eveningoutpost.dexdrip.tidepool; + +/** + * jamorham + * + * Date utilities for preparing items for Tidepool upload + */ + +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.TimeZone; + +public class DateUtil { + + static String toFormatAsUTC(final long timestamp) { + final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'0000Z'", Locale.US); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + return format.format(timestamp); + } + + static String toFormatWithZone2(final long timestamp) { + // ISO 8601 not introduced till api 24 - so we have to do some gymnastics + final SimpleDateFormat formatIso8601 = new SimpleDateFormat("Z", Locale.US); + formatIso8601.setTimeZone(TimeZone.getDefault()); + String zone = formatIso8601.format(timestamp); + zone = zone.substring(0, zone.length() - 2) + ":" + zone.substring(zone.length() - 2); + if (zone.substring(0, 1).equals("+")) { + zone = zone.substring(1); + } + final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z" + zone + "'", Locale.US); + format.setTimeZone(TimeZone.getDefault()); + return format.format(timestamp); + } + + + static String toFormatNoZone(final long timestamp) { + final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US); + format.setTimeZone(TimeZone.getDefault()); + return format.format(timestamp); + } + + static int getTimeZoneOffsetMinutes(final long timestamp) { + return TimeZone.getDefault().getOffset(timestamp) / 60000; + } + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBasal.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBasal.java new file mode 100644 index 0000000000..96819ba31c --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBasal.java @@ -0,0 +1,43 @@ +package com.eveningoutpost.dexdrip.tidepool; + +import com.eveningoutpost.dexdrip.Models.JoH; +import com.google.gson.annotations.Expose; + +// jamorham + +public class EBasal extends BaseElement { + + long timestamp; // not exposed + + @Expose + String deliveryType = "automated"; + @Expose + long duration; + @Expose + double rate = -1; + @Expose + String scheduleName = "AAPS"; + @Expose + long clockDriftOffset = 0; + @Expose + long conversionOffset = 0; + + { + type = "basal"; + } + + EBasal(double rate, long timeStart, long duration, String uuid) { + this.timestamp = timeStart; + this.rate = rate; + this.duration = duration; + populate(timeStart, uuid); + } + + boolean isValid() { + return (rate > -1 && duration > 0); + } + + String toS() { + return rate + " Start: " + JoH.dateTimeText(timestamp) + " for: " + JoH.niceTimeScalar(duration); + } +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBloodGlucose.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBloodGlucose.java new file mode 100644 index 0000000000..493c16ec3b --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBloodGlucose.java @@ -0,0 +1,45 @@ +package com.eveningoutpost.dexdrip.tidepool; + +// jamorham + +import com.eveningoutpost.dexdrip.Models.BloodTest; +import com.google.gson.annotations.Expose; + +import java.util.LinkedList; +import java.util.List; + +class EBloodGlucose extends BaseElement { + + @Expose + String subType; + @Expose + String units; + @Expose + int value; + + EBloodGlucose() { + this.type = "smbg"; + this.units = "mg/dL"; + } + + + static EBloodGlucose fromBloodTest(final BloodTest bloodtest) { + final EBloodGlucose bg = new EBloodGlucose(); + bg.populate(bloodtest.timestamp, bloodtest.uuid); + + bg.subType = "manual"; // TODO + bg.value = (int) bloodtest.mgdl; + return bg; + } + + static List fromBloodTests(final List bloodTestList) { + if (bloodTestList == null) return null; + final List results = new LinkedList<>(); + for (BloodTest bt : bloodTestList) { + results.add(fromBloodTest(bt)); + } + return results; + } + +} + diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBolus.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBolus.java new file mode 100644 index 0000000000..285f0b013b --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBolus.java @@ -0,0 +1,26 @@ +package com.eveningoutpost.dexdrip.tidepool; + +import com.google.gson.annotations.Expose; + +// jamorham + +public class EBolus extends BaseElement { + + @Expose + public final String subType = "normal"; + @Expose + public final double normal; + @Expose + public final double expectedNormal; + + { + type = "bolus"; + } + + EBolus(double insulinDelivered, double insulinExpected, long timestamp, String uuid) { + this.normal = insulinDelivered; + this.expectedNormal = insulinExpected; + populate(timestamp, uuid); + } + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/ESensorGlucose.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/ESensorGlucose.java new file mode 100644 index 0000000000..857c7905ad --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/ESensorGlucose.java @@ -0,0 +1,37 @@ +package com.eveningoutpost.dexdrip.tidepool; + +// jamorham + +import com.google.gson.annotations.Expose; + +public class ESensorGlucose extends BaseElement { + + + @Expose + String units; + @Expose + int value; + + ESensorGlucose() { + this.type = "cbg"; + this.units = "mg/dL"; + } + +/* + static ESensorGlucose fromBgReading(final BgReading bgReading) { + final ESensorGlucose sensorGlucose = new ESensorGlucose(); + sensorGlucose.populate(bgReading.timestamp, bgReading.uuid); + sensorGlucose.value = (int) bgReading.calculated_value; // TODO best glucose? + return sensorGlucose; + } + + static List fromBgReadings(final List bgReadingList) { + if (bgReadingList == null) return null; + final List results = new LinkedList<>(); + for (BgReading bgReading : bgReadingList) { + results.add(fromBgReading(bgReading)); + } + return results; + } +*/ +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EWizard.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EWizard.java new file mode 100644 index 0000000000..08de1a390c --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EWizard.java @@ -0,0 +1,34 @@ +package com.eveningoutpost.dexdrip.tidepool; + +import com.google.gson.annotations.Expose; + +// jamorham + +public class EWizard extends BaseElement { + + @Expose + public String units = "mg/dL"; + @Expose + public double carbInput; + @Expose + public double insulinCarbRatio; + @Expose + public EBolus bolus; + + EWizard() { + type = "wizard"; + } +/* + public static EWizard fromTreatment(final Treatments treatment) { + final EWizard result = (EWizard)new EWizard().populate(treatment.timestamp, treatment.uuid); + result.carbInput = treatment.carbs; + result.insulinCarbRatio = Profile.getCarbRatio(treatment.timestamp); + if (treatment.insulin > 0) { + result.bolus = new EBolus(treatment.insulin, treatment.insulin, treatment.timestamp, treatment.uuid); + } else { + result.bolus = new EBolus(0.0001,0.0001, treatment.timestamp, treatment.uuid); // fake insulin record + } + return result; + } +*/ +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/GzipRequestInterceptor.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/GzipRequestInterceptor.java new file mode 100644 index 0000000000..7dc3c156e9 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/GzipRequestInterceptor.java @@ -0,0 +1,48 @@ +package com.eveningoutpost.dexdrip.tidepool; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; +import okio.BufferedSink; +import okio.GzipSink; +import okio.Okio; + +class GzipRequestInterceptor implements Interceptor { + @Override + public okhttp3.Response intercept(Chain chain) throws IOException { + final Request originalRequest = chain.request(); + if (originalRequest.body() == null + || originalRequest.header("Content-Encoding") != null) + { + return chain.proceed(originalRequest); + } + + final Request compressedRequest = originalRequest.newBuilder() + .header("Content-Encoding", "gzip") + .method(originalRequest.method(), gzip(originalRequest.body())) + .build(); + return chain.proceed(compressedRequest); + } + + private RequestBody gzip(final RequestBody body) { + return new RequestBody() { + @Override public MediaType contentType() { + return body.contentType(); + } + + @Override public long contentLength() { + return -1; // We don't know the compressed length in advance! + } + + @Override public void writeTo(BufferedSink sink) throws IOException { + BufferedSink gzipSink = Okio.buffer(new GzipSink(sink)); + body.writeTo(gzipSink); + gzipSink.close(); + } + }; + } + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/InfoInterceptor.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/InfoInterceptor.java new file mode 100644 index 0000000000..5feb804566 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/InfoInterceptor.java @@ -0,0 +1,32 @@ +package com.eveningoutpost.dexdrip.tidepool; + +import android.support.annotation.NonNull; +import android.util.Log; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +// jamorham + +public class InfoInterceptor implements Interceptor { + + private String tag = "interceptor"; + + public InfoInterceptor(String tag) { + this.tag = tag; + } + + @Override + public Response intercept(@NonNull final Chain chain) throws IOException { + final Request request = chain.request(); + if (request != null && request.body() != null) { + Log.d(tag, "Interceptor Body size: " + request.body().contentLength()); + //} else { + // UserError.Log.d(tag,"Null request body in InfoInterceptor"); + } + return chain.proceed(request); + } +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MAuthReply.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MAuthReply.java new file mode 100644 index 0000000000..77dc6e0739 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MAuthReply.java @@ -0,0 +1,36 @@ +package com.eveningoutpost.dexdrip.tidepool; + +// jamorham + +import com.eveningoutpost.dexdrip.Models.JoH; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class MAuthReply { + + @Expose + @SerializedName("emailVerified") + Boolean emailVerified; + @Expose + @SerializedName("emails") + List emailList; + @Expose + @SerializedName("termsAccepted") + String termsDate; + @Expose + @SerializedName("userid") + String userid; + @Expose + @SerializedName("username") + String username; + + public String toS() { + return JoH.defaultGsonInstance().toJson(this); + } + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MAuthRequest.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MAuthRequest.java new file mode 100644 index 0000000000..6c5ae762c9 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MAuthRequest.java @@ -0,0 +1,24 @@ +package com.eveningoutpost.dexdrip.tidepool; + +// jamorham + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.utils.SP; +import okhttp3.Credentials; + +import static com.eveningoutpost.dexdrip.Models.JoH.emptyString; + +public class MAuthRequest extends BaseMessage { + + public static String getAuthRequestHeader() { + + final String username = SP.getString(R.string.key_tidepool_username, null); + final String password = SP.getString(R.string.key_tidepool_password, null); + + if (emptyString(username) || emptyString(password)) return null; + return Credentials.basic(username.trim(), password); + } +} + + + diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MCloseDatasetRequest.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MCloseDatasetRequest.java new file mode 100644 index 0000000000..d12ec48060 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MCloseDatasetRequest.java @@ -0,0 +1,9 @@ +package com.eveningoutpost.dexdrip.tidepool; + +import com.google.gson.annotations.Expose; + +public class MCloseDatasetRequest extends BaseMessage { + @Expose + String dataState = "closed"; + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MDatasetReply.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MDatasetReply.java new file mode 100644 index 0000000000..9e4752ff1f --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MDatasetReply.java @@ -0,0 +1,47 @@ +package com.eveningoutpost.dexdrip.tidepool; + +import java.util.List; + +public class MDatasetReply { + + Data data; + + public class Data { + String createdTime; + String deviceId; + String id; + String time; + String timezone; + int timezoneOffset; + String type; + String uploadId; + Client client; + String computerTime; + String dataSetType; + List deviceManufacturers; + String deviceModel; + String deviceSerialNumber; + List deviceTags; + String timeProcessing; + String version; + // meta + } + + public class Client { + String name; + String version; + + } + + + // openDataSet and others return this in the root of the json reply it seems + String id; + String uploadId; + + public String getUploadId() { + return (data != null && data.uploadId != null) ? data.uploadId : uploadId; + } + + + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MGetDatasetsRequest.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MGetDatasetsRequest.java new file mode 100644 index 0000000000..8a72992e5d --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MGetDatasetsRequest.java @@ -0,0 +1,4 @@ +package com.eveningoutpost.dexdrip.tidepool; + +public class MGetDatasetsRequest extends BaseMessage { +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MOpenDatasetRequest.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MOpenDatasetRequest.java new file mode 100644 index 0000000000..caf62794f5 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MOpenDatasetRequest.java @@ -0,0 +1,63 @@ +package com.eveningoutpost.dexdrip.tidepool; + +import com.google.gson.annotations.Expose; + +import java.util.TimeZone; + +import info.nightscout.androidaps.BuildConfig; +import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.utils.T; + +import static com.eveningoutpost.dexdrip.Models.JoH.getTimeZoneOffsetMs; + +public class MOpenDatasetRequest extends BaseMessage { + + static final String UPLOAD_TYPE = "continuous"; + + @Expose + public String deviceId; + @Expose + public String time = DateUtil.toFormatAsUTC(info.nightscout.androidaps.utils.DateUtil.now()); + @Expose + public int timezoneOffset = (int) (getTimeZoneOffsetMs() / T.mins(1).msecs()); + @Expose + public String type = "upload"; + //public String byUser; + @Expose + public ClientInfo client = new ClientInfo(); + @Expose + public String computerTime = DateUtil.toFormatNoZone(info.nightscout.androidaps.utils.DateUtil.now()); + @Expose + public String dataSetType = UPLOAD_TYPE; // omit for "normal" + @Expose + public String[] deviceManufacturers = {((PluginBase) (ConfigBuilderPlugin.getPlugin().getActiveBgSource())).getName()}; + @Expose + public String deviceModel = ((PluginBase) (ConfigBuilderPlugin.getPlugin().getActiveBgSource())).getName(); + @Expose + public String[] deviceTags = {"bgm", "cgm", "insulin-pump"}; + @Expose + public Deduplicator deduplicator = new Deduplicator(); + @Expose + public String timeProcessing = "none"; + @Expose + public String timezone = TimeZone.getDefault().getID(); + @Expose + public String version = BuildConfig.VERSION_NAME; + + class ClientInfo { + @Expose + final String name = BuildConfig.APPLICATION_ID; + @Expose + final String version = "0.1.0"; // TODO: const it + } + + class Deduplicator { + @Expose + final String name = "org.tidepool.deduplicator.dataset.delete.origin"; + } + + static boolean isNormal() { + return UPLOAD_TYPE.equals("normal"); + } +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MUploadReply.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MUploadReply.java new file mode 100644 index 0000000000..bdcf164396 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MUploadReply.java @@ -0,0 +1,9 @@ +package com.eveningoutpost.dexdrip.tidepool; + +import java.util.List; + +public class MUploadReply { + + List data; + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/Session.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/Session.java new file mode 100644 index 0000000000..83bf20d036 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/Session.java @@ -0,0 +1,54 @@ +package com.eveningoutpost.dexdrip.tidepool; + +// jamorham + +// Manages the session data + +import java.util.List; + +import okhttp3.Headers; + +public class Session { + + private final String SESSION_TOKEN_HEADER; + final TidepoolUploader.Tidepool service = TidepoolUploader.getRetrofitInstance().create(TidepoolUploader.Tidepool.class); + final String authHeader; + + String token; + MAuthReply authReply; + MDatasetReply datasetReply; + long start; + long end; + volatile int iterations; + + + Session(String authHeader, String session_token_header) { + this.authHeader = authHeader; + this.SESSION_TOKEN_HEADER = session_token_header; + } + + void populateHeaders(final Headers headers) { + if (this.token == null) { + this.token = headers.get(SESSION_TOKEN_HEADER); + } + } + + void populateBody(final Object obj) { + if (obj == null) return; + if (obj instanceof MAuthReply) { + authReply = (MAuthReply) obj; + } else if (obj instanceof List) { + List list = (List)obj; + if (list.size() > 0 && list.get(0) instanceof MDatasetReply) { + datasetReply = (MDatasetReply) list.get(0); + } + } else if (obj instanceof MDatasetReply) { + datasetReply = (MDatasetReply) obj; + } + } + + boolean exceededIterations() { + return iterations > 50; + } + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolCallback.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolCallback.java new file mode 100644 index 0000000000..d99afdc1fa --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolCallback.java @@ -0,0 +1,67 @@ +package com.eveningoutpost.dexdrip.tidepool; + +import android.util.Log; + +import com.eveningoutpost.dexdrip.store.FastStore; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +// jamorham + +// Callback template to reduce boiler plate + +class TidepoolCallback implements Callback { + + final Session session; + final String name; + final Runnable onSuccess; + + Runnable onFailure; + + public TidepoolCallback(Session session, String name, Runnable onSuccess) { + this.session = session; + this.name = name; + this.onSuccess = onSuccess; + } + + TidepoolCallback setOnFailure(final Runnable runnable) { + this.onFailure = runnable; + return this; + } + + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + Log.d(TidepoolUploader.TAG, name + " success"); + session.populateBody(response.body()); + session.populateHeaders(response.headers()); + if (onSuccess != null) { + onSuccess.run(); + } + } else { + final String msg = name + " was not successful: " + response.code() + " " + response.message(); + Log.e(TidepoolUploader.TAG, msg); + status(msg); + if (onFailure != null) { + onFailure.run(); + } + } + } + + @Override + public void onFailure(Call call, Throwable t) { + final String msg = name + " Failed: " + t; + Log.e(TidepoolUploader.TAG, msg); + status(msg); + if (onFailure != null) { + onFailure.run(); + } + } + + + private static void status(final String status) { + FastStore.getInstance().putS(TidepoolUploader.STATUS_KEY, status); + } +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolEntry.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolEntry.java new file mode 100644 index 0000000000..1cd5ef5e25 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolEntry.java @@ -0,0 +1,28 @@ +package com.eveningoutpost.dexdrip.tidepool; + +// jamorham + +// lightweight class entry point + +import com.eveningoutpost.dexdrip.Models.JoH; +import com.eveningoutpost.dexdrip.UtilityModels.Pref; + +import static com.eveningoutpost.dexdrip.Models.JoH.isLANConnected; +import static com.eveningoutpost.dexdrip.utils.PowerStateReceiver.is_power_connected; + +public class TidepoolEntry { + + + public static boolean enabled() { + return Pref.getBooleanDefaultFalse("cloud_storage_tidepool_enable"); + } + + public static void newData() { + if (enabled() + && (!Pref.getBooleanDefaultFalse("tidepool_only_while_charging") || is_power_connected()) + && (!Pref.getBooleanDefaultFalse("tidepool_only_while_unmetered") || isLANConnected()) + && JoH.pratelimit("tidepool-new-data-upload", 1200)) { + TidepoolUploader.doLogin(false); + } + } +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolStatus.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolStatus.java new file mode 100644 index 0000000000..82e76c4d1f --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolStatus.java @@ -0,0 +1,31 @@ +package com.eveningoutpost.dexdrip.tidepool; + +// jamorham + +import com.eveningoutpost.dexdrip.Models.JoH; +import com.eveningoutpost.dexdrip.UtilityModels.StatusItem; +import com.eveningoutpost.dexdrip.store.FastStore; +import com.eveningoutpost.dexdrip.store.KeyStore; + +import java.util.ArrayList; +import java.util.List; + +import static com.eveningoutpost.dexdrip.Models.JoH.msSince; +import static com.eveningoutpost.dexdrip.Models.JoH.niceTimeScalar; + +public class TidepoolStatus { + + // data for MegaStatus + public static List megaStatus() { + + final KeyStore keyStore = FastStore.getInstance(); + final List l = new ArrayList<>(); + + l.add(new StatusItem("Tidepool Synced to", niceTimeScalar(msSince(UploadChunk.getLastEnd())) + " ago")); // TODO needs generic message format string + final String status = keyStore.getS(TidepoolUploader.STATUS_KEY); + if (!JoH.emptyString(status)) { + l.add(new StatusItem("Tidepool Status", status)); + } + return l; + } +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolUploader.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolUploader.java new file mode 100644 index 0000000000..52b4ef9200 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolUploader.java @@ -0,0 +1,342 @@ +package com.eveningoutpost.dexdrip.tidepool; + +import android.os.PowerManager; +import android.util.Log; + +import com.eveningoutpost.dexdrip.Models.JoH; +import com.eveningoutpost.dexdrip.UtilityModels.Inevitable; +import com.eveningoutpost.dexdrip.store.FastStore; + +import java.util.List; + +import info.nightscout.androidaps.BuildConfig; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.utils.SP; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.RequestBody; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Call; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.Header; +import retrofit2.http.Headers; +import retrofit2.http.POST; +import retrofit2.http.PUT; +import retrofit2.http.Path; +import retrofit2.http.Query; + +/** + * jamorham + *

+ * Tidepool Uploader + *

+ * huge thanks to bassettb for a working c# reference implementation upon which this is based + */ + +public class TidepoolUploader { + + protected static final String TAG = "TidepoolUploader"; + protected static final String STATUS_KEY = "Tidepool-Status"; + private static final boolean D = true; + private static final boolean REPEAT = false; + + private static Retrofit retrofit; + private static final String INTEGRATION_BASE_URL = "https://int-api.tidepool.org"; + private static final String PRODUCTION_BASE_URL = "https://api.tidepool.org"; + private static final String SESSION_TOKEN_HEADER = "x-tidepool-session-token"; + + private static PowerManager.WakeLock wl; + + public interface Tidepool { + @Headers({ + "User-Agent: AAPS- " + BuildConfig.VERSION_NAME, + "X-Tidepool-Client-Name: " + BuildConfig.APPLICATION_ID, + "X-Tidepool-Client-Version: 0.1.0", // TODO: const it + }) + + @POST("/auth/login") + Call getLogin(@Header("Authorization") String secret); + + @DELETE("/v1/users/{userId}/data") + Call deleteAllData(@Header(SESSION_TOKEN_HEADER) String token, @Path("userId") String id); + + @DELETE("/v1/datasets/{dataSetId}") + Call deleteDataSet(@Header(SESSION_TOKEN_HEADER) String token, @Path("dataSetId") String id); + + @GET("/v1/users/{userId}/data_sets") + Call> getOpenDataSets(@Header(SESSION_TOKEN_HEADER) String token, + @Path("userId") String id, + @Query("client.name") String clientName, + @Query("size") int size); + + @GET("/v1/datasets/{dataSetId}") + Call getDataSet(@Header(SESSION_TOKEN_HEADER) String token, @Path("dataSetId") String id); + + @POST("/v1/users/{userId}/data_sets") + Call openDataSet(@Header(SESSION_TOKEN_HEADER) String token, @Path("userId") String id, @Body RequestBody body); + + @POST("/v1/datasets/{sessionId}/data") + Call doUpload(@Header(SESSION_TOKEN_HEADER) String token, @Path("sessionId") String id, @Body RequestBody body); + + @PUT("/v1/datasets/{sessionId}") + Call closeDataSet(@Header(SESSION_TOKEN_HEADER) String token, @Path("sessionId") String id, @Body RequestBody body); + + } + + + public static Retrofit getRetrofitInstance() { + if (retrofit == null) { + + final HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(); + if (D) { + httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); + } + final OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(httpLoggingInterceptor) + .addInterceptor(new InfoInterceptor(TAG)) + // .addInterceptor(new GzipRequestInterceptor()) + .build(); + + retrofit = new Retrofit.Builder() + .baseUrl(SP.getBoolean(R.string.key_tidepool_dev_servers, false) ? INTEGRATION_BASE_URL : PRODUCTION_BASE_URL) + .client(client) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + } + return retrofit; + } + + public static void resetInstance() { + retrofit = null; + Log.d(TAG, "Instance reset"); + } + + public static void doLoginFromUi() { + doLogin(true); + } + + public static synchronized void doLogin(final boolean fromUi) { + if (!TidepoolEntry.enabled()) { + Log.d(TAG, "Cannot login as disabled by preference"); + if (fromUi) { +// JoH.static_toast_long("Cannot login as Tidepool feature not enabled"); + } + return; + } + // TODO failure backoff +// if (JoH.ratelimit("tidepool-login", 10)) { + extendWakeLock(30000); + final Session session = new Session(MAuthRequest.getAuthRequestHeader(), SESSION_TOKEN_HEADER); + if (session.authHeader != null) { + final Call call = session.service.getLogin(session.authHeader); + status("Connecting"); + if (fromUi) { +// JoH.static_toast_long("Connecting to Tidepool"); + } + + call.enqueue(new TidepoolCallback(session, "Login", () -> startSession(session, fromUi)) + .setOnFailure(() -> loginFailed(fromUi))); + } else { + Log.e(TAG, "Cannot do login as user credentials have not been set correctly"); + status("Invalid credentials"); + if (fromUi) { +// JoH.static_toast_long("Cannot login as Tidepool credentials have not been set correctly"); + } + releaseWakeLock(); + } + // } + } + + private static void loginFailed(boolean fromUi) { + if (fromUi) { +// JoH.static_toast_long("Login failed - see event log for details"); + } + releaseWakeLock(); + } + +/* public static void testLogin(Context rootContext) { + if (JoH.ratelimit("tidepool-login", 1)) { + + String message = "Failed to log into Tidepool.\n" + + "Check that your user name and password are correct."; + + final Session session = new Session(MAuthRequest.getAuthRequestHeader(), SESSION_TOKEN_HEADER); + if (session.authHeader != null) { + final Call call = session.service.getLogin(session.authHeader); + + try { + Response response = call.execute(); + UserError.Log.e(TAG, "Header: " + response.code()); + message = "Successfully logged into Tidepool."; + } catch (IOException e) { + e.printStackTrace(); + } + } else { + UserError.Log.e(TAG,"Cannot do login as user credentials have not been set correctly"); + } + + AlertDialog.Builder builder = new AlertDialog.Builder(rootContext); + + builder.setTitle("Tidepool Login"); + + builder.setMessage(message); + + builder.setPositiveButton("OK", (dialog, id) -> { + dialog.dismiss(); + }); + + final AlertDialog alert = builder.create(); + alert.show(); + } + }*/ + + + private static void startSession(final Session session, boolean fromUi) { +// if (JoH.ratelimit("tidepool-start-session", 60)) { + extendWakeLock(30000); + if (session.authReply.userid != null) { + // See if we already have an open data set to write to + Call> datasetCall = session.service.getOpenDataSets(session.token, + session.authReply.userid, BuildConfig.APPLICATION_ID, 1); + + datasetCall.enqueue(new TidepoolCallback>(session, "Get Open Datasets", () -> { + if (session.datasetReply == null) { + status("New data set"); + if (fromUi) { +// JoH.static_toast_long("Creating new data set"); + } + Call call = session.service.openDataSet(session.token, session.authReply.userid, new MOpenDatasetRequest().getBody()); + call.enqueue(new TidepoolCallback(session, "Open New Dataset", () -> doUpload(session)) + .setOnFailure(TidepoolUploader::releaseWakeLock)); + } else { + Log.d(TAG, "Existing Dataset: " + session.datasetReply.getUploadId()); + // TODO: Wouldn't need to do this if we could block on the above `call.enqueue`. + // ie, do the openDataSet conditionally, and then do `doUpload` either way. + status("Appending"); + if (fromUi) { +// JoH.static_toast_long("Found existing remote data set"); + } + doUpload(session); + } + }).setOnFailure(TidepoolUploader::releaseWakeLock)); + } else { + Log.wtf(TAG, "Got login response but cannot determine userid - cannot proceed"); + if (fromUi) { +// JoH.static_toast_long("Error: Cannot determine userid"); + } + status("Error userid"); + releaseWakeLock(); + } +// } else { +// status("Cool Down Wait"); +// if (fromUi) { +// JoH.static_toast_long("In cool down period, please wait 1 minute"); +// } +// } + } + + + private static void doUpload(final Session session) { + if (!TidepoolEntry.enabled()) { + Log.e(TAG, "Cannot upload - preference disabled"); + return; + } + extendWakeLock(60000); + session.iterations++; + final String chunk = UploadChunk.getNext(session); + if (chunk != null) { + if (chunk.length() == 2) { + Log.d(TAG, "Empty data set - marking as succeeded"); + doCompleted(session); + } else { + final RequestBody body = RequestBody.create(MediaType.parse("application/json"), chunk); + + final Call call = session.service.doUpload(session.token, session.datasetReply.getUploadId(), body); + status("Uploading"); + call.enqueue(new TidepoolCallback(session, "Data Upload", () -> { + UploadChunk.setLastEnd(session.end); + + if (REPEAT && !session.exceededIterations()) { + status("Queued Next"); + Log.d(TAG, "Scheduling next upload"); + Inevitable.task("Tidepool-next", 10000, () -> doUpload(session)); + } else { + + if (MOpenDatasetRequest.isNormal()) { + doClose(session); + } else { + doCompleted(session); + } + } + }).setOnFailure(TidepoolUploader::releaseWakeLock)); + } + } else { + Log.e(TAG, "Upload chunk is null, cannot proceed"); + releaseWakeLock(); + } + } + + + private static void doClose(final Session session) { + status("Closing"); + extendWakeLock(20000); + final Call call = session.service.closeDataSet(session.token, session.datasetReply.getUploadId(), new MCloseDatasetRequest().getBody()); + call.enqueue(new TidepoolCallback<>(session, "Session Stop", TidepoolUploader::closeSuccess)); + } + + private static void closeSuccess() { + status("Closed"); + Log.d(TAG, "Close success"); + releaseWakeLock(); + } + + private static void doCompleted(final Session session) { + status("Completed OK"); + Log.d(TAG, "ALL COMPLETED OK!"); + releaseWakeLock(); + } + + private static void status(final String status) { + FastStore.getInstance().putS(STATUS_KEY, status); + } + + private static synchronized void extendWakeLock(long ms) { + if (wl == null) { + wl = JoH.getWakeLock("tidepool-uploader", (int) ms); + } else { + JoH.releaseWakeLock(wl); // lets not get too messy + wl.acquire(ms); + } + } + + protected static synchronized void releaseWakeLock() { + Log.d(TAG, "Releasing wakelock"); + JoH.releaseWakeLock(wl); + } + + // experimental - not used + + private static void deleteData(final Session session) { + if (session.authReply.userid != null) { + Call call = session.service.deleteAllData(session.token, session.authReply.userid); + call.enqueue(new TidepoolCallback<>(session, "Delete Data", null)); + } else { + Log.wtf(TAG, "Got login response but cannot determine userid - cannot proceed"); + } + } + + private static void getDataSet(final Session session) { + Call call = session.service.getDataSet(session.token, "bogus"); + call.enqueue(new TidepoolCallback<>(session, "Get Data", null)); + } + + private static void deleteDataSet(final Session session) { + Call call = session.service.deleteDataSet(session.token, "bogus"); + call.enqueue(new TidepoolCallback<>(session, "Delete Data", null)); + } + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/UploadChunk.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/UploadChunk.java new file mode 100644 index 0000000000..9d8a580bbe --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/UploadChunk.java @@ -0,0 +1,182 @@ +package com.eveningoutpost.dexdrip.tidepool; + + +import com.eveningoutpost.dexdrip.Models.APStatus; +import com.eveningoutpost.dexdrip.Models.BgReading; +import com.eveningoutpost.dexdrip.Models.BloodTest; +import com.eveningoutpost.dexdrip.Models.JoH; +import com.eveningoutpost.dexdrip.Models.Profile; +import com.eveningoutpost.dexdrip.Models.Treatments; +import com.eveningoutpost.dexdrip.Models.UserError; +import com.eveningoutpost.dexdrip.UtilityModels.Constants; +import com.eveningoutpost.dexdrip.UtilityModels.PersistentStore; +import com.eveningoutpost.dexdrip.UtilityModels.Pref; +import com.eveningoutpost.dexdrip.utils.LogSlider; +import com.eveningoutpost.dexdrip.utils.NamedSliderProcessor; + +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import static com.eveningoutpost.dexdrip.Models.JoH.dateTimeText; + +/** + * jamorham + * + * This class gets the next time slice of all data to upload + */ + +public class UploadChunk implements NamedSliderProcessor { + + private static final String TAG = "TidepoolUploadChunk"; + private static final String LAST_UPLOAD_END_PREF = "tidepool-last-end"; + + private static final long MAX_UPLOAD_SIZE = Constants.DAY_IN_MS * 7; // don't change this + private static final long DEFAULT_WINDOW_OFFSET = Constants.MINUTE_IN_MS * 15; + private static final long MAX_LATENCY_THRESHOLD_MINUTES = 1440; // minutes per day + + + public static String getNext(final Session session) { + session.start = getLastEnd(); + session.end = maxWindow(session.start); + + final String result = get(session.start, session.end); + if (result != null && result.length() < 3) { + UserError.Log.d(TAG, "No records in this time period, setting start to best end time"); + setLastEnd(Math.max(session.end, getOldestRecordTimeStamp())); + } + return result; + } + + public static String get(final long start, final long end) { + + UserError.Log.uel(TAG, "Syncing data between: " + dateTimeText(start) + " -> " + dateTimeText(end)); + if (end <= start) { + UserError.Log.e(TAG, "End is <= start: " + dateTimeText(start) + " " + dateTimeText(end)); + return null; + } + if (end - start > MAX_UPLOAD_SIZE) { + UserError.Log.e(TAG, "More than max range - rejecting"); + return null; + } + + final List records = new LinkedList<>(); + + records.addAll(getTreatments(start, end)); + records.addAll(getBloodTests(start, end)); + records.addAll(getBasals(start, end)); + records.addAll(getBgReadings(start, end)); + + return JoH.defaultGsonInstance().toJson(records); + } + + private static long getWindowSizePreference() { + try { + long value = (long) getLatencySliderValue(Pref.getInt("tidepool_window_latency", 0)); + return Math.max(value * Constants.MINUTE_IN_MS, DEFAULT_WINDOW_OFFSET); + } catch (Exception e) { + UserError.Log.e(TAG, "Reverting to default of 15 minutes due to Window Size exception: " + e); + return DEFAULT_WINDOW_OFFSET; // default + } + } + + private static long maxWindow(final long last_end) { + //UserError.Log.d(TAG, "Max window is: " + getWindowSizePreference()); + return Math.min(last_end + MAX_UPLOAD_SIZE, JoH.tsl() - getWindowSizePreference()); + } + + public static long getLastEnd() { + long result = PersistentStore.getLong(LAST_UPLOAD_END_PREF); + return Math.max(result, JoH.tsl() - Constants.MONTH_IN_MS * 2); + } + + public static void setLastEnd(final long when) { + if (when > getLastEnd()) { + PersistentStore.setLong(LAST_UPLOAD_END_PREF, when); + UserError.Log.d(TAG, "Updating last end to: " + dateTimeText(when)); + } else { + UserError.Log.e(TAG, "Cannot set last end to: " + dateTimeText(when) + " vs " + dateTimeText(getLastEnd())); + } + } + + static List getTreatments(final long start, final long end) { + List result = new LinkedList<>(); + final List treatments = Treatments.latestForGraph(1800, start, end); + for (Treatments treatment : treatments) { + if (treatment.carbs > 0) { + result.add(EWizard.fromTreatment(treatment)); + } else if (treatment.insulin > 0) { + result.add(EBolus.fromTreatment(treatment)); + } else { + // note only TODO + } + } + return result; + } + + + // numeric limits must match max time windows + + static long getOldestRecordTimeStamp() { + // TODO we could make sure we include records older than the first bg record for completeness + + final long start = 0; + final long end = JoH.tsl(); + + final List bgReadingList = BgReading.latestForGraphAsc(1, start, end); + if (bgReadingList != null && bgReadingList.size() > 0) { + return bgReadingList.get(0).timestamp; + } + return -1; + } + + static List getBloodTests(final long start, final long end) { + return EBloodGlucose.fromBloodTests(BloodTest.latestForGraph(1800, start, end)); + } + + static List getBgReadings(final long start, final long end) { + return ESensorGlucose.fromBgReadings(BgReading.latestForGraphAsc(15000, start, end)); + } + + static List getBasals(final long start, final long end) { + final List basals = new LinkedList<>(); + final List aplist = APStatus.latestForGraph(15000, start, end); + EBasal current = null; + for (APStatus apStatus : aplist) { + final double this_rate = Profile.getBasalRate(apStatus.timestamp) * apStatus.basal_percent / 100d; + + if (current != null) { + if (this_rate != current.rate) { + current.duration = apStatus.timestamp - current.timestamp; + UserError.Log.d(TAG, "Adding current: " + current.toS()); + if (current.isValid()) { + basals.add(current); + } else { + UserError.Log.e(TAG, "Current basal is invalid: " + current.toS()); + } + current = null; + } else { + UserError.Log.d(TAG, "Same rate as previous basal record: " + current.rate + " " + apStatus.toS()); + } + } + if (current == null) { + current = new EBasal(this_rate, apStatus.timestamp, 0, UUID.nameUUIDFromBytes(("tidepool-basal" + apStatus.timestamp).getBytes()).toString()); // start duration is 0 + } + } + return basals; + + } + + @Override + public int interpolate(final String name, final int position) { + switch (name) { + case "latency": + return getLatencySliderValue(position); + } + throw new RuntimeException("name not matched in interpolate"); + } + + private static int getLatencySliderValue(final int position) { + return (int) LogSlider.calc(0, 300, 15, MAX_LATENCY_THRESHOLD_MINUTES, position); + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9620b64a50..67c746535f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1316,6 +1316,20 @@ Tomato (MiaoMiao) Tomato + second + minute + hour + day" + week" + seconds + minutes + hours + days" + weeks" + tidepool_username + tidepool_password + tidepool_dev_servers + smbmaxminutes Dayligh Saving time Dayligh Saving time change in 24h or less From 9b1acf6958356ceacadcb498fafbd793feb0f45d Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sat, 1 Jun 2019 18:40:33 +0200 Subject: [PATCH 064/152] add missing classes. convert to use AAPS data --- app/build.gradle | 5 + .../dexdrip/Models/BloodTest.java | 550 ++++++++++++++++++ .../eveningoutpost/dexdrip/Models/JoH.java | 57 ++ .../dexdrip/UtilityModels/Inevitable.java | 120 ++++ .../UtilityModels/PersistentStore.java | 138 +++++ .../dexdrip/UtilityModels/StatusItem.java | 98 ++++ .../dexdrip/store/FastStore.java | 53 ++ .../dexdrip/store/KeyStore.java | 28 + .../dexdrip/tidepool/EBolus.java | 6 + .../dexdrip/tidepool/ESensorGlucose.java | 13 +- .../dexdrip/tidepool/EWizard.java | 17 +- .../dexdrip/tidepool/TidepoolEntry.java | 15 +- .../dexdrip/tidepool/UploadChunk.java | 83 +-- .../dexdrip/utils/LogSlider.java | 13 + .../dexdrip/utils/NamedSliderProcessor.java | 11 + .../info/nightscout/androidaps/MainApp.java | 2 + .../androidaps/db/DatabaseHelper.java | 35 +- .../general/tidepool/TidepoolPlugin.java | 39 ++ .../plugins/treatments/TreatmentService.java | 17 + .../receivers/ChargingStateReceiver.java | 6 + .../info/nightscout/androidaps/utils/T.java | 6 + app/src/main/res/values/strings.xml | 18 + app/src/main/res/xml/pref_tidepool.xml | 49 ++ 23 files changed, 1322 insertions(+), 57 deletions(-) create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/Models/BloodTest.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/Inevitable.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/PersistentStore.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/StatusItem.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/store/FastStore.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/store/KeyStore.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/utils/LogSlider.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/utils/NamedSliderProcessor.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.java create mode 100644 app/src/main/res/xml/pref_tidepool.xml diff --git a/app/build.gradle b/app/build.gradle index b374db47b3..86f6e22678 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -283,8 +283,13 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.okhttp3:okhttp:3.10.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0' + implementation 'com.google.code.gson:gson:2.8.5' implementation 'com.squareup.retrofit2:converter-gson:2.4.0' + // you will want to install the android studio lombok plugin + compileOnly 'org.projectlombok:lombok:1.16.20' + // compileOnly 'javax.annotation:javax.annotation-api:1.3.2' + annotationProcessor "org.projectlombok:lombok:1.16.20" } task unzip(type: Copy) { diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/Models/BloodTest.java b/app/src/main/java/com/eveningoutpost/dexdrip/Models/BloodTest.java new file mode 100644 index 0000000000..a106e8e48a --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/Models/BloodTest.java @@ -0,0 +1,550 @@ +package com.eveningoutpost.dexdrip.Models; + +import android.provider.BaseColumns; + +import com.activeandroid.Model; +import com.activeandroid.annotation.Column; +import com.activeandroid.annotation.Table; +import com.google.gson.annotations.Expose; + +/** + * Created by jamorham on 11/12/2016. + */ + +@Table(name = "BloodTest", id = BaseColumns._ID) +public class BloodTest extends Model { + + public static final long STATE_VALID = 1 << 0; + public static final long STATE_CALIBRATION = 1 << 1; + public static final long STATE_NOTE = 1 << 2; + public static final long STATE_UNDONE = 1 << 3; + public static final long STATE_OVERWRITTEN = 1 << 4; + + private static long highest_timestamp = 0; + private static boolean patched = false; + private final static String TAG = "BloodTest"; + private final static String LAST_BT_AUTO_CALIB_UUID = "last-bt-auto-calib-uuid"; + private final static boolean d = false; + + @Expose + @Column(name = "timestamp", unique = true, onUniqueConflicts = Column.ConflictAction.IGNORE) + public long timestamp; + + @Expose + @Column(name = "mgdl") + public double mgdl; + + @Expose + @Column(name = "created_timestamp") + public long created_timestamp; + + @Expose + @Column(name = "state") + public long state; // bitfield + + @Expose + @Column(name = "source") + public String source; + + @Expose + @Column(name = "uuid", unique = true, onUniqueConflicts = Column.ConflictAction.IGNORE) + public String uuid; + +/* + public GlucoseReadingRx glucoseReadingRx; + + // patches and saves + public Long saveit() { + fixUpTable(); + return save(); + } + + public void addState(long flag) { + state |= flag; + save(); + } + + public void removeState(long flag) { + state &= ~flag; + save(); + } + + public String toS() { + final Gson gson = new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .create(); + return gson.toJson(this); + } + + private BloodTestMessage toMessageNative() { + return new BloodTestMessage.Builder() + .timestamp(timestamp) + .mgdl(mgdl) + .created_timestamp(created_timestamp) + .state(state) + .source(source) + .uuid(uuid) + .build(); + } + + public byte[] toMessage() { + final List btl = new ArrayList<>(); + btl.add(this); + return toMultiMessage(btl); + } + + + // static methods + private static final long CLOSEST_READING_MS = 30000; // 30 seconds + + public static BloodTest create(long timestamp_ms, double mgdl, String source) { + return create(timestamp_ms, mgdl, source, null); + } + + public static BloodTest create(long timestamp_ms, double mgdl, String source, String suggested_uuid) { + + if ((timestamp_ms == 0) || (mgdl == 0)) { + UserError.Log.e(TAG, "Either timestamp or mgdl is zero - cannot create reading"); + return null; + } + + if (timestamp_ms < 1487759433000L) { + UserError.Log.d(TAG, "Timestamp really too far in the past @ " + timestamp_ms); + return null; + } + + final long now = JoH.tsl(); + if (timestamp_ms > now) { + if ((timestamp_ms - now) > 600000) { + UserError.Log.wtf(TAG, "Timestamp is > 10 minutes in the future! Something is wrong: " + JoH.dateTimeText(timestamp_ms)); + return null; + } + timestamp_ms = now; // force to now if it showed up to 10 mins in the future + } + + final BloodTest match = getForPreciseTimestamp(timestamp_ms, CLOSEST_READING_MS); + if (match == null) { + final BloodTest bt = new BloodTest(); + bt.timestamp = timestamp_ms; + bt.mgdl = mgdl; + bt.uuid = suggested_uuid == null ? UUID.randomUUID().toString() : suggested_uuid; + bt.created_timestamp = JoH.tsl(); + bt.state = STATE_VALID; + bt.source = source; + bt.saveit(); + if (UploaderQueue.newEntry("insert", bt) != null) { + SyncService.startSyncService(3000); // sync in 3 seconds + } + + if (Pref.getBooleanDefaultFalse("bluetooth_meter_for_calibrations_auto")) { + if ((JoH.msSince(bt.timestamp) < Constants.MINUTE_IN_MS * 5) && (JoH.msSince(bt.timestamp) > 0)) { + UserError.Log.d(TAG, "Blood test value recent enough to send to G5"); + //Ob1G5StateMachine.addCalibration((int) bt.mgdl, timestamp_ms); + NativeCalibrationPipe.addCalibration((int) bt.mgdl, timestamp_ms); + } + } + + return bt; + } else { + UserError.Log.d(TAG, "Not creating new reading as timestamp is too close"); + } + return null; + } + + public static BloodTest createFromCal(double bg, double timeoffset, String source) { + return createFromCal(bg, timeoffset, source, null); + } + + public static BloodTest createFromCal(double bg, double timeoffset, String source, String suggested_uuid) { + final String unit = Pref.getString("units", "mgdl"); + + if (unit.compareTo("mgdl") != 0) { + bg = bg * Constants.MMOLL_TO_MGDL; + } + + if ((bg < 40) || (bg > 400)) { + Log.wtf(TAG, "Invalid out of range bloodtest glucose mg/dl value of: " + bg); + JoH.static_toast_long("Bloodtest out of range: " + bg + " mg/dl"); + return null; + } + + return create((long) (new Date().getTime() - timeoffset), bg, source, suggested_uuid); + } + + public static void pushBloodTestSyncToWatch(BloodTest bt, boolean is_new) { + Log.d(TAG, "pushTreatmentSyncToWatch Add treatment to UploaderQueue."); + if (Pref.getBooleanDefaultFalse("wear_sync")) { + if (UploaderQueue.newEntryForWatch(is_new ? "insert" : "update", bt) != null) { + SyncService.startSyncService(3000); // sync in 3 seconds + } + } + } + + public static BloodTest last() { + final List btl = last(1); + if ((btl != null) && (btl.size() > 0)) { + return btl.get(0); + } else { + return null; + } + } + + public static List last(int num) { + try { + return new Select() + .from(BloodTest.class) + .orderBy("timestamp desc") + .limit(num) + .execute(); + } catch (android.database.sqlite.SQLiteException e) { + fixUpTable(); + return null; + } + } + + public static List lastMatching(int num, String match) { + try { + return new Select() + .from(BloodTest.class) + .where("source like ?", match) + .orderBy("timestamp desc") + .limit(num) + .execute(); + } catch (android.database.sqlite.SQLiteException e) { + fixUpTable(); + return null; + } + } + + public static BloodTest lastValid() { + final List btl = lastValid(1); + if ((btl != null) && (btl.size() > 0)) { + return btl.get(0); + } else { + return null; + } + } + + public static List lastValid(int num) { + try { + return new Select() + .from(BloodTest.class) + .where("state & ? != 0", BloodTest.STATE_VALID) + .orderBy("timestamp desc") + .limit(num) + .execute(); + } catch (android.database.sqlite.SQLiteException e) { + fixUpTable(); + return null; + } + } + + + public static BloodTest byUUID(String uuid) { + if (uuid == null) return null; + try { + return new Select() + .from(BloodTest.class) + .where("uuid = ?", uuid) + .executeSingle(); + } catch (android.database.sqlite.SQLiteException e) { + fixUpTable(); + return null; + } + } + + public static BloodTest byid(long id) { + try { + return new Select() + .from(BloodTest.class) + .where("_ID = ?", id) + .executeSingle(); + } catch (android.database.sqlite.SQLiteException e) { + fixUpTable(); + return null; + } + } + + public static byte[] toMultiMessage(List btl) { + if (btl == null) return null; + final List BloodTestMessageList = new ArrayList<>(); + for (BloodTest bt : btl) { + BloodTestMessageList.add(bt.toMessageNative()); + } + return BloodTestMultiMessage.ADAPTER.encode(new BloodTestMultiMessage(BloodTestMessageList)); + } + + private static void processFromMessage(BloodTestMessage btm) { + if ((btm != null) && (btm.uuid != null) && (btm.uuid.length() == 36)) { + boolean is_new = false; + BloodTest bt = byUUID(btm.uuid); + if (bt == null) { + bt = getForPreciseTimestamp(Wire.get(btm.timestamp, BloodTestMessage.DEFAULT_TIMESTAMP), CLOSEST_READING_MS); + if (bt != null) { + UserError.Log.wtf(TAG, "Error matches a different uuid with the same timestamp: " + bt.uuid + " vs " + btm.uuid + " skipping!"); + return; + } + bt = new BloodTest(); + is_new = true; + } else { + if (bt.state != Wire.get(btm.state, BloodTestMessage.DEFAULT_STATE)) { + is_new = true; + } + } + bt.timestamp = Wire.get(btm.timestamp, BloodTestMessage.DEFAULT_TIMESTAMP); + bt.mgdl = Wire.get(btm.mgdl, BloodTestMessage.DEFAULT_MGDL); + bt.created_timestamp = Wire.get(btm.created_timestamp, BloodTestMessage.DEFAULT_CREATED_TIMESTAMP); + bt.state = Wire.get(btm.state, BloodTestMessage.DEFAULT_STATE); + bt.source = Wire.get(btm.source, BloodTestMessage.DEFAULT_SOURCE); + bt.uuid = btm.uuid; + bt.saveit(); // de-dupe by uuid + if (is_new) { // cannot handle updates yet + if (UploaderQueue.newEntry(is_new ? "insert" : "update", bt) != null) { + if (JoH.quietratelimit("start-sync-service", 5)) { + SyncService.startSyncService(3000); // sync in 3 seconds + } + } + } + } else { + UserError.Log.wtf(TAG, "processFromMessage uuid is null or invalid"); + } + } + + public static void processFromMultiMessage(byte[] payload) { + try { + final BloodTestMultiMessage btmm = BloodTestMultiMessage.ADAPTER.decode(payload); + if ((btmm != null) && (btmm.bloodtest_message != null)) { + for (BloodTestMessage btm : btmm.bloodtest_message) { + processFromMessage(btm); + } + Home.staticRefreshBGCharts(); + } + } catch (IOException | NullPointerException | IllegalStateException e) { + UserError.Log.e(TAG, "exception processFromMessage: " + e); + } + } + + public static BloodTest fromJSON(String json) { + if ((json == null) || (json.length() == 0)) { + UserError.Log.d(TAG, "Empty json received in bloodtest fromJson"); + return null; + } + try { + UserError.Log.d(TAG, "Processing incoming json: " + json); + return new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().fromJson(json, BloodTest.class); + } catch (Exception e) { + UserError.Log.d(TAG, "Got exception parsing bloodtest json: " + e.toString()); + Home.toaststaticnext("Error on Bloodtest sync, probably decryption key mismatch"); + return null; + } + } + + public static BloodTest getForPreciseTimestamp(long timestamp, long precision) { + BloodTest bloodTest = new Select() + .from(BloodTest.class) + .where("timestamp <= ?", (timestamp + precision)) + .where("timestamp >= ?", (timestamp - precision)) + .orderBy("abs(timestamp - " + timestamp + ") asc") + .executeSingle(); + if ((bloodTest != null) && (Math.abs(bloodTest.timestamp - timestamp) < precision)) { + return bloodTest; + } + return null; + } + + public static List latestForGraph(int number, double startTime) { + return latestForGraph(number, (long) startTime, Long.MAX_VALUE); + } + + public static List latestForGraph(int number, long startTime) { + return latestForGraph(number, startTime, Long.MAX_VALUE); + } + + public static List latestForGraph(int number, long startTime, long endTime) { + try { + return new Select() + .from(BloodTest.class) + .where("state & ? != 0", BloodTest.STATE_VALID) + .where("timestamp >= " + Math.max(startTime, 0)) + .where("timestamp <= " + endTime) + .orderBy("timestamp asc") // warn asc! + .limit(number) + .execute(); + } catch (android.database.sqlite.SQLiteException e) { + fixUpTable(); + return new ArrayList<>(); + } + } + + synchronized static void opportunisticCalibration() { + if (Pref.getBooleanDefaultFalse("bluetooth_meter_for_calibrations_auto")) { + final BloodTest bt = lastValid(); + if (bt == null) { + Log.d(TAG, "opportunistic: No blood tests"); + return; + } + if (JoH.msSince(bt.timestamp) > (Constants.HOUR_IN_MS * 8)) { + Log.d(TAG, "opportunistic: Blood test older than 8 hours ago"); + return; + } + + if ((bt.uuid == null) || (bt.uuid.length() < 8)) { + Log.d(TAG, "opportunisitic: invalid uuid"); + return; + } + + if ((bt.uuid != null) && (bt.uuid.length() > 1) && PersistentStore.getString(LAST_BT_AUTO_CALIB_UUID).equals(bt.uuid)) { + Log.d(TAG, "opportunistic: Already processed uuid: " + bt.uuid); + return; + } + + final Calibration calibration = Calibration.lastValid(); + if (calibration == null) { + Log.d(TAG, "opportunistic: No calibrations"); + // TODO do we try to initial calibrate using this? + return; + } + + if (JoH.msSince(calibration.timestamp) < Constants.HOUR_IN_MS) { + Log.d(TAG, "opportunistic: Last calibration less than 1 hour ago"); + return; + } + + if (bt.timestamp <= calibration.timestamp) { + Log.d(TAG, "opportunistic: Blood test isn't more recent than last calibration"); + return; + } + + // get closest bgreading - must be within dexcom period and locked to sensor + final BgReading bgReading = BgReading.getForPreciseTimestamp(bt.timestamp + (AddCalibration.estimatedInterstitialLagSeconds * 1000), BgGraphBuilder.DEXCOM_PERIOD); + if (bgReading == null) { + Log.d(TAG, "opportunistic: No matching bg reading"); + return; + } + + if (bt.timestamp > highest_timestamp) { + Accuracy.create(bt, bgReading, "xDrip Original"); + final CalibrationAbstract plugin = PluggableCalibration.getCalibrationPluginFromPreferences(); + final CalibrationAbstract.CalibrationData cd = (plugin != null) ? plugin.getCalibrationData(bgReading.timestamp) : null; + if (plugin != null) { + BgReading pluginBgReading = plugin.getBgReadingFromBgReading(bgReading, cd); + Accuracy.create(bt, pluginBgReading, plugin.getAlgorithmName()); + } + highest_timestamp = bt.timestamp; + } + + if (!CalibrationRequest.isSlopeFlatEnough(bgReading)) { + Log.d(TAG, "opportunistic: Slope is not flat enough at: " + JoH.dateTimeText(bgReading.timestamp)); + return; + } + + // TODO store evaluation failure for this record in cache for future optimization + + // TODO Check we have prior reading as well perhaps + JoH.clearCache(); + UserError.Log.ueh(TAG, "Opportunistic calibration for Blood Test at " + JoH.dateTimeText(bt.timestamp) + " of " + BgGraphBuilder.unitized_string_with_units_static(bt.mgdl) + " matching sensor slope at: " + JoH.dateTimeText(bgReading.timestamp) + " from source " + bt.source); + final long time_since = JoH.msSince(bt.timestamp); + + + Log.d(TAG, "opportunistic: attempting auto calibration"); + PersistentStore.setString(LAST_BT_AUTO_CALIB_UUID, bt.uuid); + Home.startHomeWithExtra(xdrip.getAppContext(), + Home.BLUETOOTH_METER_CALIBRATION, + BgGraphBuilder.unitized_string_static(bt.mgdl), + Long.toString(time_since), + "auto"); + } + } + + public static String evaluateAccuracy(long period) { + + // CACHE?? + + final List bloodTests = latestForGraph(1000, JoH.tsl() - period, JoH.tsl() - AddCalibration.estimatedInterstitialLagSeconds); + final List difference = new ArrayList<>(); + final List plugin_difference = new ArrayList<>(); + if ((bloodTests == null) || (bloodTests.size() == 0)) return null; + + final boolean show_plugin = true; + final CalibrationAbstract plugin = (show_plugin) ? PluggableCalibration.getCalibrationPluginFromPreferences() : null; + + + for (BloodTest bt : bloodTests) { + final BgReading bgReading = BgReading.getForPreciseTimestamp(bt.timestamp + (AddCalibration.estimatedInterstitialLagSeconds * 1000), BgGraphBuilder.DEXCOM_PERIOD); + + if (bgReading != null) { + final Calibration calibration = bgReading.calibration; + if (calibration == null) { + Log.d(TAG, "Calibration for bgReading is null! @ " + JoH.dateTimeText(bgReading.timestamp)); + continue; + } + final double diff = Math.abs(bgReading.calculated_value - bt.mgdl); + difference.add(diff); + if (d) { + Log.d(TAG, "Evaluate Accuracy: difference: " + JoH.qs(diff)); + } + final CalibrationAbstract.CalibrationData cd = (plugin != null) ? plugin.getCalibrationData(bgReading.timestamp) : null; + if ((plugin != null) && (cd != null)) { + final double plugin_diff = Math.abs(bt.mgdl - plugin.getGlucoseFromBgReading(bgReading, cd)); + plugin_difference.add(plugin_diff); + if (d) + Log.d(TAG, "Evaluate Plugin Accuracy: " + BgGraphBuilder.unitized_string_with_units_static(bt.mgdl) + " @ " + JoH.dateTimeText(bt.timestamp) + " difference: " + JoH.qs(plugin_diff) + "/" + JoH.qs(plugin_diff * Constants.MGDL_TO_MMOLL, 2) + " calibration: " + JoH.qs(cd.slope, 2) + " " + JoH.qs(cd.intercept, 2)); + } + } + } + + if (difference.size() == 0) return null; + double avg = DoubleMath.mean(difference); + Log.d(TAG, "Average accuracy: " + accuracyAsString(avg) + " (" + JoH.qs(avg, 5) + ")"); + + if (plugin_difference.size() > 0) { + double plugin_avg = DoubleMath.mean(plugin_difference); + Log.d(TAG, "Plugin Average accuracy: " + accuracyAsString(plugin_avg) + " (" + JoH.qs(plugin_avg, 5) + ")"); + return accuracyAsString(plugin_avg) + " / " + accuracyAsString(avg); + } + return accuracyAsString(avg); + } + + public static String accuracyAsString(double avg) { + final boolean domgdl = Pref.getString("units", "mgdl").equals("mgdl"); + // +- symbol + return "\u00B1" + (!domgdl ? JoH.qs(avg * Constants.MGDL_TO_MMOLL, 2) + " mmol" : JoH.qs(avg, 1) + " mgdl"); + } + + public static List cleanup(int retention_days) { + return new Delete() + .from(BloodTest.class) + .where("timestamp < ?", JoH.tsl() - (retention_days * Constants.DAY_IN_MS)) + .execute(); + } + + // create the table ourselves without worrying about model versioning and downgrading + private static void fixUpTable() { + if (patched) return; + final String[] patchup = { + "CREATE TABLE BloodTest (_id INTEGER PRIMARY KEY AUTOINCREMENT);", + "ALTER TABLE BloodTest ADD COLUMN timestamp INTEGER;", + "ALTER TABLE BloodTest ADD COLUMN created_timestamp INTEGER;", + "ALTER TABLE BloodTest ADD COLUMN state INTEGER;", + "ALTER TABLE BloodTest ADD COLUMN mgdl REAL;", + "ALTER TABLE BloodTest ADD COLUMN source TEXT;", + "ALTER TABLE BloodTest ADD COLUMN uuid TEXT;", + "CREATE UNIQUE INDEX index_Bloodtest_uuid on BloodTest(uuid);", + "CREATE UNIQUE INDEX index_Bloodtest_timestamp on BloodTest(timestamp);", + "CREATE INDEX index_Bloodtest_created_timestamp on BloodTest(created_timestamp);", + "CREATE INDEX index_Bloodtest_state on BloodTest(state);"}; + + for (String patch : patchup) { + try { + SQLiteUtils.execSql(patch); + // UserError.Log.e(TAG, "Processed patch should not have succeeded!!: " + patch); + } catch (Exception e) { + // UserError.Log.d(TAG, "Patch: " + patch + " generated exception as it should: " + e.toString()); + } + } + patched = true; + } + +*/ +} + diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/Models/JoH.java b/app/src/main/java/com/eveningoutpost/dexdrip/Models/JoH.java index df801512fe..3d8593df34 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/Models/JoH.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/Models/JoH.java @@ -1,15 +1,23 @@ package com.eveningoutpost.dexdrip.Models; import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.os.PowerManager; +import android.util.Base64; import android.util.Log; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.io.UnsupportedEncodingException; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.GregorianCalendar; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.utils.DateUtil; public class JoH { @@ -19,6 +27,14 @@ public class JoH { return android.text.format.DateFormat.format("yyyy-MM-dd kk:mm:ss", timestamp).toString(); } + public static long msSince(long when) { + return (DateUtil.now() - when); + } + + public static long msTill(long when) { + return (when - DateUtil.now()); + } + public static String niceTimeScalar(long t) { String unit = MainApp.gs(R.string.unit_second); t = t / 1000; @@ -122,5 +138,46 @@ public class JoH { return wl; } + public static boolean isLANConnected() { + final ConnectivityManager cm = + (ConnectivityManager) MainApp.instance().getSystemService(Context.CONNECTIVITY_SERVICE); + final NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + final boolean isConnected = activeNetwork != null && + activeNetwork.isConnected(); + return isConnected && ((activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) + || (activeNetwork.getType() == ConnectivityManager.TYPE_ETHERNET) + || (activeNetwork.getType() == ConnectivityManager.TYPE_BLUETOOTH)); + } + + private static Gson gson_instance; + public static Gson defaultGsonInstance() { + if (gson_instance == null) { + gson_instance = new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + //.registerTypeAdapter(Date.class, new DateTypeAdapter()) + // .serializeSpecialFloatingPointValues() + .create(); + } + return gson_instance; + } + + public static String base64encodeBytes(byte[] input) { + try { + return new String(Base64.encode(input, Base64.NO_WRAP), "UTF-8"); + } catch (UnsupportedEncodingException e) { + Log.e(TAG, "Got unsupported encoding: " + e); + return "encode-error"; + } + } + + public static byte[] base64decodeBytes(String input) { + try { + return Base64.decode(input.getBytes("UTF-8"), Base64.NO_WRAP); + } catch (UnsupportedEncodingException | IllegalArgumentException e) { + Log.e(TAG, "Got unsupported encoding: " + e); + return new byte[0]; + } + } + } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/Inevitable.java b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/Inevitable.java new file mode 100644 index 0000000000..c3efae80b4 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/Inevitable.java @@ -0,0 +1,120 @@ +package com.eveningoutpost.dexdrip.UtilityModels; + +import android.os.PowerManager; +import android.os.SystemClock; +import android.util.Log; + +import com.eveningoutpost.dexdrip.Models.JoH; + +import java.util.concurrent.ConcurrentHashMap; + +import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.T; + +/** + * Created by jamorham on 07/03/2018. + *

+ * Tasks which are fired from events can be scheduled here and only execute when they become idle + * and are not being rescheduled within their wait window. + */ + +public class Inevitable { + + private static final String TAG = Inevitable.class.getSimpleName(); + private static final int MAX_QUEUE_TIME = (int) T.mins(6).msecs(); + private static final boolean d = true; + + private static final ConcurrentHashMap tasks = new ConcurrentHashMap<>(); + + public static synchronized void task(final String id, long idle_for, Runnable runnable) { + if (idle_for > MAX_QUEUE_TIME) { + throw new RuntimeException(id + " Requested time: " + idle_for + " beyond max queue time"); + } + final Task task = tasks.get(id); + if (task != null) { + // if it already exists then extend the time + task.extendTime(idle_for); + + if (d) + Log.d(TAG, "Extending time for: " + id + " to " + JoH.dateTimeText(task.when)); + } else { + // otherwise create new task + if (runnable == null) return; // extension only if already exists + tasks.put(id, new Task(id, idle_for, runnable)); + + if (d) + Log.d(TAG, "Creating task: " + id + " due: " + JoH.dateTimeText(tasks.get(id).when)); + + // create a thread to wait and execute in background + final Thread t = new Thread(() -> { + final PowerManager.WakeLock wl = JoH.getWakeLock(id, MAX_QUEUE_TIME + 5000); + try { + boolean running = true; + // wait for task to be due or killed + while (running) { + SystemClock.sleep(500); + final Task thisTask = tasks.get(id); + running = thisTask != null && !thisTask.poll(); + } + } finally { + JoH.releaseWakeLock(wl); + } + }); + t.setPriority(Thread.MIN_PRIORITY); + //t.setDaemon(true); + t.start(); + } + } + + public static synchronized void stackableTask(String id, long idle_for, Runnable runnable) { + int stack = 0; + while (tasks.get(id = id + "-" + stack) != null) { + stack++; + } + if (stack > 0) { + Log.d(TAG, "Task stacked to: " + id); + } + task(id, idle_for, runnable); + } + + public static void kill(final String id) { + tasks.remove(id); + } + + public static boolean waiting(final String id) { + return tasks.containsKey(id); + } + + private static class Task { + private long when; + private final Runnable what; + private final String id; + + Task(String id, long offset, Runnable what) { + this.what = what; + this.id = id; + extendTime(offset); + } + + public void extendTime(long offset) { + this.when = DateUtil.now() + offset; + } + + public boolean poll() { + final long till = JoH.msTill(when); + if (till < 1) { + if (d) Log.d(TAG, "Executing task! " + this.id); + tasks.remove(this.id); // early remove to allow overlapping scheduling + what.run(); + return true; + } else if (till > MAX_QUEUE_TIME) { + Log.wtf(TAG, "Task: " + this.id + " In queue too long: " + till); + tasks.remove(this.id); + return true; + } + return false; + } + + } + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/PersistentStore.java b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/PersistentStore.java new file mode 100644 index 0000000000..5e2cfe6fed --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/PersistentStore.java @@ -0,0 +1,138 @@ +package com.eveningoutpost.dexdrip.UtilityModels; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; + +import com.eveningoutpost.dexdrip.Models.JoH; +import com.google.common.primitives.Bytes; + +import info.nightscout.androidaps.MainApp; + +/** + * Created by jamorham on 23/09/2016. + *

+ * This is for internal data which is never backed up, + * separate file means it doesn't clutter prefs + * we can afford to lose it, it is for internal states + * and is alternative to static variables which get + * flushed when classes are destroyed by garbage collection + *

+ * It is suitable for cache type variables where losing + * state will cause problems. Obviously it will be slower than + * pure in-memory state variables. + */ + + +public class PersistentStore { + + private static final String DATA_STORE_INTERNAL = "persist_internal_store"; + private static SharedPreferences prefs; + private static final boolean d = false; // debug flag + + public static String getString(final String name) { + return prefs.getString(name, ""); + } + + static { + try { + prefs = MainApp.instance() + .getSharedPreferences(DATA_STORE_INTERNAL, Context.MODE_PRIVATE); + } catch (NullPointerException e) { + android.util.Log.e("PersistentStore", "Failed to get context on init!!! nothing will work"); + } + } + + public static void setString(final String name, String value) { + prefs.edit().putString(name, value).apply(); + } + + // if string is different to what we have stored then update and return true + public static boolean updateStringIfDifferent(final String name, final String current) { + if (current == null) return false; // can't handle nulls + if (PersistentStore.getString(name).equals(current)) return false; + PersistentStore.setString(name, current); + return true; + } + + public static void appendString(String name, String value) { + setString(name, getString(name) + value); + } + + public static void appendString(String name, String value, String delimiter) { + String current = getString(name); + if (current.length() > 0) current += delimiter; + setString(name, current + value); + } + + public static void appendBytes(String name, byte[] value) { + setBytes(name, Bytes.concat(getBytes(name), value)); + } + + public static byte[] getBytes(String name) { + return JoH.base64decodeBytes(getString(name)); + } + + public static byte getByte(String name) { + return (byte) getLong(name); + } + + public static void setBytes(String name, byte[] value) { + setString(name, JoH.base64encodeBytes(value)); + } + + public static void setByte(String name, byte value) { + setLong(name, value); + } + + public static long getLong(String name) { + return prefs.getLong(name, 0); + } + + public static float getFloat(String name) { + return prefs.getFloat(name, 0); + } + + public static void setLong(String name, long value) { + prefs.edit().putLong(name, value).apply(); + } + + public static void setFloat(String name, float value) { + prefs.edit().putFloat(name, value).apply(); + } + + public static void setDouble(String name, double value) { + setLong(name, Double.doubleToRawLongBits(value)); + } + + public static double getDouble(String name) { + return Double.longBitsToDouble(getLong(name)); + } + + public static boolean getBoolean(String name) { + return prefs.getBoolean(name, false); + } + + public static boolean getBoolean(String name, boolean value) { + return prefs.getBoolean(name, value); + } + + public static void setBoolean(String name, boolean value) { + prefs.edit().putBoolean(name, value).apply(); + } + + public static long incrementLong(String name) { + final long val = getLong(name) + 1; + setLong(name, val); + return val; + } + + public static void setLongZeroIfSet(String name) { + if (getLong(name) > 0) setLong(name, 0); + } + + @SuppressLint("ApplySharedPref") + public static void commit() { + prefs.edit().commit(); + } +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/StatusItem.java b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/StatusItem.java new file mode 100644 index 0000000000..745b0ae6e4 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/StatusItem.java @@ -0,0 +1,98 @@ +package com.eveningoutpost.dexdrip.UtilityModels; + +import android.graphics.Color; +import android.support.annotation.ColorInt; + +import com.google.common.base.MoreObjects; + +import java.util.HashMap; + +import static com.eveningoutpost.dexdrip.UtilityModels.StatusItem.Highlight.BAD; +import static com.eveningoutpost.dexdrip.UtilityModels.StatusItem.Highlight.CRITICAL; +import static com.eveningoutpost.dexdrip.UtilityModels.StatusItem.Highlight.GOOD; +import static com.eveningoutpost.dexdrip.UtilityModels.StatusItem.Highlight.NORMAL; +import static com.eveningoutpost.dexdrip.UtilityModels.StatusItem.Highlight.NOTICE; + +/** + * Created by jamorham on 14/01/2017. + *

+ * For representing row items suitable for MegaStatus + */ + +public class StatusItem { + + private static final HashMap colorHints = new HashMap<>(); + + static { + colorHints.put(NORMAL, Color.TRANSPARENT); + colorHints.put(GOOD, Color.parseColor("#003000")); + colorHints.put(BAD, Color.parseColor("#480000")); + colorHints.put(NOTICE, Color.parseColor("#403000")); + colorHints.put(CRITICAL, Color.parseColor("#770000")); + } + + public enum Highlight { + NORMAL, + GOOD, + BAD, + NOTICE, + CRITICAL; + + @ColorInt + public int color() { + return colorHint(this); + } + + } + + public String name; + public String value; + public Highlight highlight; + public String button_name; + public Runnable runnable; + + + public StatusItem(String name, String value) { + this(name, value, NORMAL); + } + + public StatusItem() { + this("line-break", "", NORMAL); + } + + public StatusItem(String name, Highlight highlight) { + this("heading-break", name, highlight); + } + + public StatusItem(String name, Runnable runnable) { + this("button-break", "", NORMAL, name, runnable); + } + + public StatusItem(String name, String value, Highlight highlight) { + this(name, value, highlight, null, null); + } + + public StatusItem(String name, String value, Highlight highlight, String button_name, Runnable runnable) { + this.name = name; + this.value = value; + this.highlight = highlight; + this.button_name = button_name; + this.runnable = runnable; + } + + public StatusItem(String name, Integer value) { + this(name, value, NORMAL); + } + + public StatusItem(String name, Integer value, Highlight highlight) { + this.name = name; + this.value = Integer.toString(value); + this.highlight = highlight; + } + + @ColorInt + public static int colorHint(final Highlight highlight) { + return MoreObjects.firstNonNull(colorHints.get(highlight), Color.TRANSPARENT); + } + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/store/FastStore.java b/app/src/main/java/com/eveningoutpost/dexdrip/store/FastStore.java new file mode 100644 index 0000000000..3cb4e15e88 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/store/FastStore.java @@ -0,0 +1,53 @@ +package com.eveningoutpost.dexdrip.store; + +import java.util.HashMap; + +/** + * Created by jamorham on 08/11/2017. + *

+ * Fast implementation of KeyStore interface + * Uses an in-memory database for short lived data elements + * Content is expired as per normal garbage collection + * Neutral defaults favoured over null return values + * Static creation for fastest shared instance access + */ + +public class FastStore implements KeyStore { + + private static final FastStore mFastStore = new FastStore(); + private static final HashMap stringStore = new HashMap<>(); + private static final HashMap longStore = new HashMap<>(); + + // we trade substitution flexibility at the expense of some object creation + + private FastStore() { + // use getInstance! + } + + public static FastStore getInstance() { + return mFastStore; + } + + // interface methods + + public String getS(String key) { + if (stringStore.containsKey(key)) return stringStore.get(key); + return ""; + } + + public void putS(String key, String value) { + stringStore.put(key, value); + } + + public long getL(String key) { + if (longStore.containsKey(key)) return longStore.get(key); + return 0; + } + + public void putL(String key, long value) { + longStore.put(key, value); + } + +} + + diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/store/KeyStore.java b/app/src/main/java/com/eveningoutpost/dexdrip/store/KeyStore.java new file mode 100644 index 0000000000..3ea19214f6 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/store/KeyStore.java @@ -0,0 +1,28 @@ +package com.eveningoutpost.dexdrip.store; + +/** + * Created by jamorham on 08/11/2017. + * + * KeyStore is a persistence interface allowing storage and retrieval of primitive data types + * referenced by a String based key. + * + * Implementations may choose how long data is retained for. + * Typical usage might include caching expensive function results + * + */ + +public interface KeyStore { + + // java type erasure prevents us using generics and then implementing multiple generic interfaces + // so storage types we are interested in get their own interface methods + + void putS(String key, String value); + + String getS(String key); + + void putL(String key, long value); + + long getL(String key); + + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBolus.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBolus.java index 285f0b013b..384942e859 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBolus.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBolus.java @@ -2,6 +2,8 @@ package com.eveningoutpost.dexdrip.tidepool; import com.google.gson.annotations.Expose; +import info.nightscout.androidaps.plugins.treatments.Treatment; + // jamorham public class EBolus extends BaseElement { @@ -23,4 +25,8 @@ public class EBolus extends BaseElement { populate(timestamp, uuid); } + public static EBolus fromTreatment(Treatment treatment) { + return new EBolus(treatment.insulin, treatment.insulin, treatment.date, "uuid-AAPS"); + } + } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/ESensorGlucose.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/ESensorGlucose.java index 857c7905ad..45a4ba7732 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/ESensorGlucose.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/ESensorGlucose.java @@ -4,6 +4,11 @@ package com.eveningoutpost.dexdrip.tidepool; import com.google.gson.annotations.Expose; +import java.util.LinkedList; +import java.util.List; + +import info.nightscout.androidaps.db.BgReading; + public class ESensorGlucose extends BaseElement { @@ -17,11 +22,11 @@ public class ESensorGlucose extends BaseElement { this.units = "mg/dL"; } -/* + static ESensorGlucose fromBgReading(final BgReading bgReading) { final ESensorGlucose sensorGlucose = new ESensorGlucose(); - sensorGlucose.populate(bgReading.timestamp, bgReading.uuid); - sensorGlucose.value = (int) bgReading.calculated_value; // TODO best glucose? + sensorGlucose.populate(bgReading.date, "uuid-AAPS"); + sensorGlucose.value = (int) bgReading.value; // TODO best glucose? return sensorGlucose; } @@ -33,5 +38,5 @@ public class ESensorGlucose extends BaseElement { } return results; } -*/ + } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EWizard.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EWizard.java index 08de1a390c..64d5b87c34 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EWizard.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EWizard.java @@ -2,6 +2,9 @@ package com.eveningoutpost.dexdrip.tidepool; import com.google.gson.annotations.Expose; +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; +import info.nightscout.androidaps.plugins.treatments.Treatment; + // jamorham public class EWizard extends BaseElement { @@ -18,17 +21,17 @@ public class EWizard extends BaseElement { EWizard() { type = "wizard"; } -/* - public static EWizard fromTreatment(final Treatments treatment) { - final EWizard result = (EWizard)new EWizard().populate(treatment.timestamp, treatment.uuid); + + public static EWizard fromTreatment(final Treatment treatment) { + final EWizard result = (EWizard)new EWizard().populate(treatment.date, "uuid-AAPS"); result.carbInput = treatment.carbs; - result.insulinCarbRatio = Profile.getCarbRatio(treatment.timestamp); + result.insulinCarbRatio = ProfileFunctions.getInstance().getProfile(treatment.date).getIc(); if (treatment.insulin > 0) { - result.bolus = new EBolus(treatment.insulin, treatment.insulin, treatment.timestamp, treatment.uuid); + result.bolus = new EBolus(treatment.insulin, treatment.insulin, treatment.date, "uuid-AAPS"); } else { - result.bolus = new EBolus(0.0001,0.0001, treatment.timestamp, treatment.uuid); // fake insulin record + result.bolus = new EBolus(0.0001,0.0001, treatment.date, "uuid-AAPS"); // fake insulin record } return result; } -*/ + } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolEntry.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolEntry.java index 1cd5ef5e25..26f7901cda 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolEntry.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolEntry.java @@ -4,24 +4,25 @@ package com.eveningoutpost.dexdrip.tidepool; // lightweight class entry point -import com.eveningoutpost.dexdrip.Models.JoH; -import com.eveningoutpost.dexdrip.UtilityModels.Pref; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.receivers.ChargingStateReceiver; +import info.nightscout.androidaps.utils.SP; import static com.eveningoutpost.dexdrip.Models.JoH.isLANConnected; -import static com.eveningoutpost.dexdrip.utils.PowerStateReceiver.is_power_connected; public class TidepoolEntry { public static boolean enabled() { - return Pref.getBooleanDefaultFalse("cloud_storage_tidepool_enable"); + return SP.getBoolean(R.string.key_cloud_storage_tidepool_enable, false); } public static void newData() { if (enabled() - && (!Pref.getBooleanDefaultFalse("tidepool_only_while_charging") || is_power_connected()) - && (!Pref.getBooleanDefaultFalse("tidepool_only_while_unmetered") || isLANConnected()) - && JoH.pratelimit("tidepool-new-data-upload", 1200)) { + && (!SP.getBoolean(R.string.key_tidepool_only_while_charging, false) || ChargingStateReceiver.isCharging()) + && (!SP.getBoolean(R.string.key_tidepool_only_while_unmetered, false) || isLANConnected()) + // && JoH.pratelimit("tidepool-new-data-upload", 1200) + ) { TidepoolUploader.doLogin(false); } } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/UploadChunk.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/UploadChunk.java index 9d8a580bbe..61b734bc3b 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/UploadChunk.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/UploadChunk.java @@ -1,28 +1,34 @@ package com.eveningoutpost.dexdrip.tidepool; -import com.eveningoutpost.dexdrip.Models.APStatus; -import com.eveningoutpost.dexdrip.Models.BgReading; -import com.eveningoutpost.dexdrip.Models.BloodTest; +import android.util.Log; + import com.eveningoutpost.dexdrip.Models.JoH; -import com.eveningoutpost.dexdrip.Models.Profile; -import com.eveningoutpost.dexdrip.Models.Treatments; -import com.eveningoutpost.dexdrip.Models.UserError; -import com.eveningoutpost.dexdrip.UtilityModels.Constants; import com.eveningoutpost.dexdrip.UtilityModels.PersistentStore; -import com.eveningoutpost.dexdrip.UtilityModels.Pref; import com.eveningoutpost.dexdrip.utils.LogSlider; import com.eveningoutpost.dexdrip.utils.NamedSliderProcessor; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.UUID; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.db.BgReading; +import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; +import info.nightscout.androidaps.plugins.treatments.Treatment; +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; +import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.SP; +import info.nightscout.androidaps.utils.T; + import static com.eveningoutpost.dexdrip.Models.JoH.dateTimeText; /** * jamorham - * + *

* This class gets the next time slice of all data to upload */ @@ -31,8 +37,8 @@ public class UploadChunk implements NamedSliderProcessor { private static final String TAG = "TidepoolUploadChunk"; private static final String LAST_UPLOAD_END_PREF = "tidepool-last-end"; - private static final long MAX_UPLOAD_SIZE = Constants.DAY_IN_MS * 7; // don't change this - private static final long DEFAULT_WINDOW_OFFSET = Constants.MINUTE_IN_MS * 15; + private static final long MAX_UPLOAD_SIZE = T.days(7).msecs(); // don't change this + private static final long DEFAULT_WINDOW_OFFSET = T.mins(15).msecs(); private static final long MAX_LATENCY_THRESHOLD_MINUTES = 1440; // minutes per day @@ -42,7 +48,7 @@ public class UploadChunk implements NamedSliderProcessor { final String result = get(session.start, session.end); if (result != null && result.length() < 3) { - UserError.Log.d(TAG, "No records in this time period, setting start to best end time"); + Log.d(TAG, "No records in this time period, setting start to best end time"); setLastEnd(Math.max(session.end, getOldestRecordTimeStamp())); } return result; @@ -50,13 +56,13 @@ public class UploadChunk implements NamedSliderProcessor { public static String get(final long start, final long end) { - UserError.Log.uel(TAG, "Syncing data between: " + dateTimeText(start) + " -> " + dateTimeText(end)); + Log.e(TAG, "Syncing data between: " + dateTimeText(start) + " -> " + dateTimeText(end)); if (end <= start) { - UserError.Log.e(TAG, "End is <= start: " + dateTimeText(start) + " " + dateTimeText(end)); + Log.e(TAG, "End is <= start: " + dateTimeText(start) + " " + dateTimeText(end)); return null; } if (end - start > MAX_UPLOAD_SIZE) { - UserError.Log.e(TAG, "More than max range - rejecting"); + Log.e(TAG, "More than max range - rejecting"); return null; } @@ -72,37 +78,37 @@ public class UploadChunk implements NamedSliderProcessor { private static long getWindowSizePreference() { try { - long value = (long) getLatencySliderValue(Pref.getInt("tidepool_window_latency", 0)); - return Math.max(value * Constants.MINUTE_IN_MS, DEFAULT_WINDOW_OFFSET); + long value = (long) getLatencySliderValue(SP.getInt(R.string.key_tidepool_window_latency, 0)); + return Math.max(T.mins(value).msecs(), DEFAULT_WINDOW_OFFSET); } catch (Exception e) { - UserError.Log.e(TAG, "Reverting to default of 15 minutes due to Window Size exception: " + e); + Log.e(TAG, "Reverting to default of 15 minutes due to Window Size exception: " + e); return DEFAULT_WINDOW_OFFSET; // default } } private static long maxWindow(final long last_end) { - //UserError.Log.d(TAG, "Max window is: " + getWindowSizePreference()); - return Math.min(last_end + MAX_UPLOAD_SIZE, JoH.tsl() - getWindowSizePreference()); + //Log.d(TAG, "Max window is: " + getWindowSizePreference()); + return Math.min(last_end + MAX_UPLOAD_SIZE, DateUtil.now() - getWindowSizePreference()); } public static long getLastEnd() { long result = PersistentStore.getLong(LAST_UPLOAD_END_PREF); - return Math.max(result, JoH.tsl() - Constants.MONTH_IN_MS * 2); + return Math.max(result, DateUtil.now() - T.months(2).msecs()); } public static void setLastEnd(final long when) { if (when > getLastEnd()) { PersistentStore.setLong(LAST_UPLOAD_END_PREF, when); - UserError.Log.d(TAG, "Updating last end to: " + dateTimeText(when)); + Log.d(TAG, "Updating last end to: " + dateTimeText(when)); } else { - UserError.Log.e(TAG, "Cannot set last end to: " + dateTimeText(when) + " vs " + dateTimeText(getLastEnd())); + Log.e(TAG, "Cannot set last end to: " + dateTimeText(when) + " vs " + dateTimeText(getLastEnd())); } } static List getTreatments(final long start, final long end) { List result = new LinkedList<>(); - final List treatments = Treatments.latestForGraph(1800, start, end); - for (Treatments treatment : treatments) { + final List treatments = TreatmentsPlugin.getPlugin().getService().getTreatmentDataFromTime(start, end, true); + for (Treatment treatment : treatments) { if (treatment.carbs > 0) { result.add(EWizard.fromTreatment(treatment)); } else if (treatment.insulin > 0) { @@ -121,46 +127,47 @@ public class UploadChunk implements NamedSliderProcessor { // TODO we could make sure we include records older than the first bg record for completeness final long start = 0; - final long end = JoH.tsl(); + final long end = DateUtil.now(); - final List bgReadingList = BgReading.latestForGraphAsc(1, start, end); + final List bgReadingList = MainApp.getDbHelper().getBgreadingsDataFromTime(start, end, false); if (bgReadingList != null && bgReadingList.size() > 0) { - return bgReadingList.get(0).timestamp; + return bgReadingList.get(0).date; } return -1; } static List getBloodTests(final long start, final long end) { - return EBloodGlucose.fromBloodTests(BloodTest.latestForGraph(1800, start, end)); + return new ArrayList<>(); +// return EBloodGlucose.fromBloodTests(BloodTest.latestForGraph(1800, start, end)); } static List getBgReadings(final long start, final long end) { - return ESensorGlucose.fromBgReadings(BgReading.latestForGraphAsc(15000, start, end)); + return ESensorGlucose.fromBgReadings(MainApp.getDbHelper().getBgreadingsDataFromTime(start, end, true)); } static List getBasals(final long start, final long end) { final List basals = new LinkedList<>(); - final List aplist = APStatus.latestForGraph(15000, start, end); + final List aplist = MainApp.getDbHelper().getTemporaryBasalsDataFromTime(start, end, true); EBasal current = null; - for (APStatus apStatus : aplist) { - final double this_rate = Profile.getBasalRate(apStatus.timestamp) * apStatus.basal_percent / 100d; + for (TemporaryBasal temporaryBasal : aplist) { + final double this_rate = temporaryBasal.tempBasalConvertedToAbsolute(temporaryBasal.date, ProfileFunctions.getInstance().getProfile(temporaryBasal.date)); if (current != null) { if (this_rate != current.rate) { - current.duration = apStatus.timestamp - current.timestamp; - UserError.Log.d(TAG, "Adding current: " + current.toS()); + current.duration = temporaryBasal.date - current.timestamp; + Log.d(TAG, "Adding current: " + current.toS()); if (current.isValid()) { basals.add(current); } else { - UserError.Log.e(TAG, "Current basal is invalid: " + current.toS()); + Log.e(TAG, "Current basal is invalid: " + current.toS()); } current = null; } else { - UserError.Log.d(TAG, "Same rate as previous basal record: " + current.rate + " " + apStatus.toS()); + Log.d(TAG, "Same rate as previous basal record: " + current.rate + " " + temporaryBasal.toStringFull()); } } if (current == null) { - current = new EBasal(this_rate, apStatus.timestamp, 0, UUID.nameUUIDFromBytes(("tidepool-basal" + apStatus.timestamp).getBytes()).toString()); // start duration is 0 + current = new EBasal(this_rate, temporaryBasal.date, 0, UUID.nameUUIDFromBytes(("tidepool-basal" + temporaryBasal.date).getBytes()).toString()); // start duration is 0 } } return basals; diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utils/LogSlider.java b/app/src/main/java/com/eveningoutpost/dexdrip/utils/LogSlider.java new file mode 100644 index 0000000000..014d085ae4 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utils/LogSlider.java @@ -0,0 +1,13 @@ +package com.eveningoutpost.dexdrip.utils; + +// jamorham + +public class LogSlider { + + // logarithmic slider with positions start - end representing values start - end, calculate value at selected position + public static double calc(int sliderStart, int sliderEnd, double valueStart, double valueEnd, int position) { + valueStart = Math.log(Math.max(1, valueStart)); + valueEnd = Math.log(Math.max(1, valueEnd)); + return Math.exp(valueStart + (valueEnd - valueStart) / (sliderEnd - sliderStart) * (position - sliderStart)); + } +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utils/NamedSliderProcessor.java b/app/src/main/java/com/eveningoutpost/dexdrip/utils/NamedSliderProcessor.java new file mode 100644 index 0000000000..a13e4b05b1 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utils/NamedSliderProcessor.java @@ -0,0 +1,11 @@ +package com.eveningoutpost.dexdrip.utils; + +// jamorham + +// interpolate a slider by name from a supporting class + +public interface NamedSliderProcessor { + + int interpolate(String name, int position); + +} diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index 720e07dd15..7f1b527d00 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -50,6 +50,7 @@ import info.nightscout.androidaps.plugins.general.nsclient.receivers.DBAccessRec import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin; import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin; import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin; +import info.nightscout.androidaps.plugins.general.tidepool.TidepoolPlugin; import info.nightscout.androidaps.plugins.general.versionChecker.VersionCheckerPlugin; import info.nightscout.androidaps.plugins.general.wear.WearPlugin; import info.nightscout.androidaps.plugins.general.xdripStatusline.StatuslinePlugin; @@ -204,6 +205,7 @@ public class MainApp extends Application { pluginsList.add(StatuslinePlugin.initPlugin(this)); pluginsList.add(PersistentNotificationPlugin.getPlugin()); pluginsList.add(NSClientPlugin.getPlugin()); + pluginsList.add(TidepoolPlugin.getPlugin()); pluginsList.add(MaintenancePlugin.initPlugin(this)); pluginsList.add(ConfigBuilderPlugin.getPlugin()); diff --git a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java index eb51f5e2ba..4bddd714eb 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java @@ -596,7 +596,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } } - // -------------------- TREATMENT HANDLING ------------------- + // -------------------- TEMPTARGET HANDLING ------------------- public static void updateEarliestDataChange(long newDate) { if (earliestDataChange == null) { @@ -627,6 +627,23 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { return new ArrayList(); } + public List getTemptargetsDataFromTime(long from, long to, boolean ascending) { + try { + Dao daoTempTargets = getDaoTempTargets(); + List tempTargets; + QueryBuilder queryBuilder = daoTempTargets.queryBuilder(); + queryBuilder.orderBy("date", ascending); + Where where = queryBuilder.where(); + where.between("date", from, to); + PreparedQuery preparedQuery = queryBuilder.prepare(); + tempTargets = daoTempTargets.query(preparedQuery); + return tempTargets; + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return new ArrayList(); + } + public boolean createOrUpdate(TempTarget tempTarget) { try { TempTarget old; @@ -950,6 +967,22 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { return new ArrayList(); } + public List getTemporaryBasalsDataFromTime(long from, long to, boolean ascending) { + try { + List tempbasals; + QueryBuilder queryBuilder = getDaoTemporaryBasal().queryBuilder(); + queryBuilder.orderBy("date", ascending); + Where where = queryBuilder.where(); + where.between("date", from, to); + PreparedQuery preparedQuery = queryBuilder.prepare(); + tempbasals = getDaoTemporaryBasal().query(preparedQuery); + return tempbasals; + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return new ArrayList(); + } + private static void scheduleTemporaryBasalChange() { class PostRunnable implements Runnable { public void run() { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.java new file mode 100644 index 0000000000..2d00825820 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.java @@ -0,0 +1,39 @@ +package info.nightscout.androidaps.plugins.general.tidepool; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.source.BGSourceFragment; + +/** + * Created by mike on 28.11.2017. + */ + +public class TidepoolPlugin extends PluginBase { + private static Logger log = LoggerFactory.getLogger(L.DATABASE); + + private static TidepoolPlugin plugin = null; + + public static TidepoolPlugin getPlugin() { + if (plugin == null) + plugin = new TidepoolPlugin(); + return plugin; + } + + private TidepoolPlugin() { + super(new PluginDescription() + .mainType(PluginType.BGSOURCE) + .fragmentClass(BGSourceFragment.class.getName()) + .pluginName(R.string.tidepool) + .shortName(R.string.tidepool_shortname) + .preferencesId(R.xml.pref_tidepool) + .description(R.string.description_tidepool) + ); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java index 502b7f38b7..842c01d9ee 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java @@ -500,6 +500,23 @@ public class TreatmentService extends OrmLiteBaseService { return new ArrayList<>(); } + public List getTreatmentDataFromTime(long from, long to, boolean ascending) { + try { + Dao daoTreatments = getDao(); + List treatments; + QueryBuilder queryBuilder = daoTreatments.queryBuilder(); + queryBuilder.orderBy("date", ascending); + Where where = queryBuilder.where(); + where.between("date", from, to); + PreparedQuery preparedQuery = queryBuilder.prepare(); + treatments = daoTreatments.query(preparedQuery); + return treatments; + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return new ArrayList<>(); + } + @Nullable @Override public IBinder onBind(Intent intent) { diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java index b10c2e99e5..349e1771b9 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java +++ b/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java @@ -10,12 +10,15 @@ import info.nightscout.androidaps.events.EventChargingState; public class ChargingStateReceiver extends BroadcastReceiver { + private static EventChargingState lastEvent; + @Override public void onReceive(Context context, Intent intent) { EventChargingState event = grabChargingState(context); if (event != null) MainApp.bus().post(event); + lastEvent = event; } public EventChargingState grabChargingState(Context context) { @@ -32,4 +35,7 @@ public class ChargingStateReceiver extends BroadcastReceiver { return event; } + static public boolean isCharging() { + return lastEvent != null && lastEvent.isCharging; + } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/T.java b/app/src/main/java/info/nightscout/androidaps/utils/T.java index 2a9bcfc42c..5671f0725c 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/T.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/T.java @@ -43,6 +43,12 @@ public class T { return t; } + public static T months(long month) { + T t = new T(); + t.time = month * 31 * 24 * 60 * 60 * 1000L; + return t; + } + public long msecs() { return time; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 67c746535f..798c076d05 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1329,6 +1329,21 @@ tidepool_username tidepool_password tidepool_dev_servers + tidepool_window_latency + tidepool_test_login + tidepool_only_while_charging + tidepool_only_while_unmetered + cloud_storage_tidepool_enable + Upload data to the Tidepool service + Sync to Tidepool + Your Tidepool login user name, normally your email address + Login User Name + Your Tidepool login password + Login Password + Test Tidepool Login + Data Age Mins + If enabled, uploads will go to https://int-app.tidepool.org instead of the regular https://app.tidepool.org/ + Use Integration (test) servers smbmaxminutes Dayligh Saving time @@ -1348,6 +1363,9 @@ old version very old version New version for at least %1$d days available! Fallback to LGS after 60 days, loop will be disabled after 90 days + Tidepool + TDP + Uploads data to Tidepool %1$d day diff --git a/app/src/main/res/xml/pref_tidepool.xml b/app/src/main/res/xml/pref_tidepool.xml new file mode 100644 index 0000000000..a01b600df1 --- /dev/null +++ b/app/src/main/res/xml/pref_tidepool.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + From 6f4d79bca871f5bd7894e4ab3f34c9590b6a0fc6 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sat, 1 Jun 2019 18:51:41 +0200 Subject: [PATCH 065/152] fix copy paste errors --- app/build.gradle | 3 +-- .../androidaps/plugins/general/tidepool/TidepoolPlugin.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 86f6e22678..d2e5a3ff7f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -250,7 +250,7 @@ dependencies { // excluding org.json which is provided by Android exclude group: "org.json", module: "json" } - implementation "com.google.code.gson:gson:2.4" + implementation "com.google.code.gson:gson:2.8.5" implementation "com.google.guava:guava:24.1-jre" implementation "net.danlew:android.joda:2.9.9.1" @@ -283,7 +283,6 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.okhttp3:okhttp:3.10.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0' - implementation 'com.google.code.gson:gson:2.8.5' implementation 'com.squareup.retrofit2:converter-gson:2.4.0' // you will want to install the android studio lombok plugin diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.java index 2d00825820..580b602deb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.java @@ -27,8 +27,7 @@ public class TidepoolPlugin extends PluginBase { private TidepoolPlugin() { super(new PluginDescription() - .mainType(PluginType.BGSOURCE) - .fragmentClass(BGSourceFragment.class.getName()) + .mainType(PluginType.GENERAL) .pluginName(R.string.tidepool) .shortName(R.string.tidepool_shortname) .preferencesId(R.xml.pref_tidepool) From 42ceaac0f7ddde283e608dd7b0df1a1182f15762 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sat, 1 Jun 2019 22:29:57 +0200 Subject: [PATCH 066/152] remove activeandroid --- app/build.gradle | 1 - .../eveningoutpost/dexdrip/Models/BloodTest.java | 14 +------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d2e5a3ff7f..b9acadd2af 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -279,7 +279,6 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // xDrip-plus port - implementation 'com.activeandroid:thread-safe-active-android:3.1.1' implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.okhttp3:okhttp:3.10.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0' diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/Models/BloodTest.java b/app/src/main/java/com/eveningoutpost/dexdrip/Models/BloodTest.java index a106e8e48a..bd8d80dd65 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/Models/BloodTest.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/Models/BloodTest.java @@ -1,18 +1,12 @@ package com.eveningoutpost.dexdrip.Models; -import android.provider.BaseColumns; - -import com.activeandroid.Model; -import com.activeandroid.annotation.Column; -import com.activeandroid.annotation.Table; import com.google.gson.annotations.Expose; /** * Created by jamorham on 11/12/2016. */ -@Table(name = "BloodTest", id = BaseColumns._ID) -public class BloodTest extends Model { +public class BloodTest { public static final long STATE_VALID = 1 << 0; public static final long STATE_CALIBRATION = 1 << 1; @@ -27,27 +21,21 @@ public class BloodTest extends Model { private final static boolean d = false; @Expose - @Column(name = "timestamp", unique = true, onUniqueConflicts = Column.ConflictAction.IGNORE) public long timestamp; @Expose - @Column(name = "mgdl") public double mgdl; @Expose - @Column(name = "created_timestamp") public long created_timestamp; @Expose - @Column(name = "state") public long state; // bitfield @Expose - @Column(name = "source") public String source; @Expose - @Column(name = "uuid", unique = true, onUniqueConflicts = Column.ConflictAction.IGNORE) public String uuid; /* From e2364561ed2dac498612535aa1bd562012b6e090 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sun, 2 Jun 2019 16:24:51 +0200 Subject: [PATCH 067/152] transform to kotlin, cleanup, sort --- app/build.gradle | 10 +- .../dexdrip/Models/BloodTest.java | 538 ------------------ .../eveningoutpost/dexdrip/Models/JoH.java | 183 ------ .../dexdrip/UtilityModels/Inevitable.java | 120 ---- .../UtilityModels/PersistentStore.java | 138 ----- .../dexdrip/UtilityModels/StatusItem.java | 98 ---- .../dexdrip/store/FastStore.java | 53 -- .../dexdrip/store/KeyStore.java | 28 - .../dexdrip/tidepool/BaseElement.java | 40 -- .../dexdrip/tidepool/BaseMessage.java | 24 - .../dexdrip/tidepool/DateUtil.java | 46 -- .../dexdrip/tidepool/EBasal.java | 43 -- .../dexdrip/tidepool/EBloodGlucose.java | 45 -- .../dexdrip/tidepool/EBolus.java | 32 -- .../dexdrip/tidepool/ESensorGlucose.java | 42 -- .../dexdrip/tidepool/EWizard.java | 37 -- .../tidepool/GzipRequestInterceptor.java | 48 -- .../dexdrip/tidepool/InfoInterceptor.java | 32 -- .../dexdrip/tidepool/MAuthReply.java | 36 -- .../dexdrip/tidepool/MAuthRequest.java | 24 - .../tidepool/MCloseDatasetRequest.java | 9 - .../dexdrip/tidepool/MDatasetReply.java | 47 -- .../dexdrip/tidepool/MGetDatasetsRequest.java | 4 - .../dexdrip/tidepool/MOpenDatasetRequest.java | 63 -- .../dexdrip/tidepool/MUploadReply.java | 9 - .../dexdrip/tidepool/Session.java | 54 -- .../dexdrip/tidepool/TidepoolCallback.java | 67 --- .../dexdrip/tidepool/TidepoolEntry.java | 29 - .../dexdrip/tidepool/TidepoolStatus.java | 31 - .../dexdrip/tidepool/UploadChunk.java | 189 ------ .../dexdrip/utils/LogSlider.java | 13 - .../dexdrip/utils/NamedSliderProcessor.java | 11 - .../info/nightscout/androidaps/MainApp.java | 2 +- .../info/nightscout/androidaps/logging/L.java | 2 + .../general/tidepool/TidepoolPlugin.java | 38 -- .../general/tidepool/TidepoolPlugin.kt | 57 ++ .../tidepool/TidepoolUploaded.java.txt} | 112 ++-- .../general/tidepool/comm/InfoInterceptor.kt | 26 + .../plugins/general/tidepool/comm/Session.kt | 58 ++ .../tidepool/comm/TidepoolApiService.kt | 48 ++ .../general/tidepool/comm/TidepoolCallback.kt | 38 ++ .../general/tidepool/comm/TidepoolUploader.kt | 232 ++++++++ .../general/tidepool/comm/UploadChunk.kt | 169 ++++++ .../general/tidepool/elements/BasalElement.kt | 42 ++ .../general/tidepool/elements/BaseElement.kt | 30 + .../tidepool/elements/BloodGlucoseElement.kt | 33 ++ .../general/tidepool/elements/BolusElement.kt | 30 + .../tidepool/elements/SensorGlucoseElement.kt | 34 ++ .../tidepool/elements/WizardElement.kt | 37 ++ .../tidepool/events/EventTidepoolStatus.kt | 6 + .../tidepool/messages/AuthReplyMessage.kt | 29 + .../tidepool/messages/AuthRequestMessage.kt | 17 + .../general/tidepool/messages/BaseMessage.kt | 16 + .../messages/CloseDatasetRequestMessage.kt | 8 + .../tidepool/messages/DatasetReplyMessage.kt | 42 ++ .../messages/OpenDatasetRequestMessage.kt | 63 ++ .../tidepool/messages/UploadReplyMessage.kt | 6 + .../general/tidepool/utils/GsonInstance.kt | 17 + .../general/tidepool/utils/LogSlider.kt | 12 + .../general/tidepool/utils/RateLimit.kt | 29 + .../nightscout/androidaps/utils/DateUtil.java | 90 +++ .../androidaps/utils/StringUtils.java | 5 + app/src/main/res/values/strings.xml | 7 +- 63 files changed, 1247 insertions(+), 2231 deletions(-) delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/Models/BloodTest.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/Models/JoH.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/Inevitable.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/PersistentStore.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/StatusItem.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/store/FastStore.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/store/KeyStore.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/BaseElement.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/BaseMessage.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/DateUtil.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBasal.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBloodGlucose.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBolus.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/ESensorGlucose.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EWizard.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/GzipRequestInterceptor.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/InfoInterceptor.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MAuthReply.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MAuthRequest.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MCloseDatasetRequest.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MDatasetReply.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MGetDatasetsRequest.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MOpenDatasetRequest.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MUploadReply.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/Session.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolCallback.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolEntry.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolStatus.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/tidepool/UploadChunk.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/utils/LogSlider.java delete mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/utils/NamedSliderProcessor.java delete mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt rename app/src/main/java/{com/eveningoutpost/dexdrip/tidepool/TidepoolUploader.java => info/nightscout/androidaps/plugins/general/tidepool/TidepoolUploaded.java.txt} (77%) create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/InfoInterceptor.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/Session.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolApiService.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolCallback.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/UploadChunk.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BasalElement.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BaseElement.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BloodGlucoseElement.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BolusElement.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/SensorGlucoseElement.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/WizardElement.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolStatus.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/AuthReplyMessage.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/AuthRequestMessage.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/BaseMessage.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/CloseDatasetRequestMessage.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/DatasetReplyMessage.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/OpenDatasetRequestMessage.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/UploadReplyMessage.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/utils/GsonInstance.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/utils/LogSlider.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/utils/RateLimit.kt diff --git a/app/build.gradle b/app/build.gradle index b9acadd2af..e6b06601bf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -278,16 +278,22 @@ dependencies { androidTestImplementation "com.google.dexmaker:dexmaker-mockito:${dexmakerVersion}" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + // xDrip-plus port - implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.okhttp3:okhttp:3.10.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0' - implementation 'com.squareup.retrofit2:converter-gson:2.4.0' // you will want to install the android studio lombok plugin compileOnly 'org.projectlombok:lombok:1.16.20' // compileOnly 'javax.annotation:javax.annotation-api:1.3.2' annotationProcessor "org.projectlombok:lombok:1.16.20" + + // new for tidepool + implementation "com.squareup.retrofit2:retrofit:2.4.0" + implementation "com.squareup.retrofit2:adapter-rxjava2:2.3.0" + implementation "com.squareup.retrofit2:converter-gson:2.4.0" + + implementation "io.reactivex.rxjava2:rxandroid:2.0.1" } task unzip(type: Copy) { diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/Models/BloodTest.java b/app/src/main/java/com/eveningoutpost/dexdrip/Models/BloodTest.java deleted file mode 100644 index bd8d80dd65..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/Models/BloodTest.java +++ /dev/null @@ -1,538 +0,0 @@ -package com.eveningoutpost.dexdrip.Models; - -import com.google.gson.annotations.Expose; - -/** - * Created by jamorham on 11/12/2016. - */ - -public class BloodTest { - - public static final long STATE_VALID = 1 << 0; - public static final long STATE_CALIBRATION = 1 << 1; - public static final long STATE_NOTE = 1 << 2; - public static final long STATE_UNDONE = 1 << 3; - public static final long STATE_OVERWRITTEN = 1 << 4; - - private static long highest_timestamp = 0; - private static boolean patched = false; - private final static String TAG = "BloodTest"; - private final static String LAST_BT_AUTO_CALIB_UUID = "last-bt-auto-calib-uuid"; - private final static boolean d = false; - - @Expose - public long timestamp; - - @Expose - public double mgdl; - - @Expose - public long created_timestamp; - - @Expose - public long state; // bitfield - - @Expose - public String source; - - @Expose - public String uuid; - -/* - public GlucoseReadingRx glucoseReadingRx; - - // patches and saves - public Long saveit() { - fixUpTable(); - return save(); - } - - public void addState(long flag) { - state |= flag; - save(); - } - - public void removeState(long flag) { - state &= ~flag; - save(); - } - - public String toS() { - final Gson gson = new GsonBuilder() - .excludeFieldsWithoutExposeAnnotation() - .create(); - return gson.toJson(this); - } - - private BloodTestMessage toMessageNative() { - return new BloodTestMessage.Builder() - .timestamp(timestamp) - .mgdl(mgdl) - .created_timestamp(created_timestamp) - .state(state) - .source(source) - .uuid(uuid) - .build(); - } - - public byte[] toMessage() { - final List btl = new ArrayList<>(); - btl.add(this); - return toMultiMessage(btl); - } - - - // static methods - private static final long CLOSEST_READING_MS = 30000; // 30 seconds - - public static BloodTest create(long timestamp_ms, double mgdl, String source) { - return create(timestamp_ms, mgdl, source, null); - } - - public static BloodTest create(long timestamp_ms, double mgdl, String source, String suggested_uuid) { - - if ((timestamp_ms == 0) || (mgdl == 0)) { - UserError.Log.e(TAG, "Either timestamp or mgdl is zero - cannot create reading"); - return null; - } - - if (timestamp_ms < 1487759433000L) { - UserError.Log.d(TAG, "Timestamp really too far in the past @ " + timestamp_ms); - return null; - } - - final long now = JoH.tsl(); - if (timestamp_ms > now) { - if ((timestamp_ms - now) > 600000) { - UserError.Log.wtf(TAG, "Timestamp is > 10 minutes in the future! Something is wrong: " + JoH.dateTimeText(timestamp_ms)); - return null; - } - timestamp_ms = now; // force to now if it showed up to 10 mins in the future - } - - final BloodTest match = getForPreciseTimestamp(timestamp_ms, CLOSEST_READING_MS); - if (match == null) { - final BloodTest bt = new BloodTest(); - bt.timestamp = timestamp_ms; - bt.mgdl = mgdl; - bt.uuid = suggested_uuid == null ? UUID.randomUUID().toString() : suggested_uuid; - bt.created_timestamp = JoH.tsl(); - bt.state = STATE_VALID; - bt.source = source; - bt.saveit(); - if (UploaderQueue.newEntry("insert", bt) != null) { - SyncService.startSyncService(3000); // sync in 3 seconds - } - - if (Pref.getBooleanDefaultFalse("bluetooth_meter_for_calibrations_auto")) { - if ((JoH.msSince(bt.timestamp) < Constants.MINUTE_IN_MS * 5) && (JoH.msSince(bt.timestamp) > 0)) { - UserError.Log.d(TAG, "Blood test value recent enough to send to G5"); - //Ob1G5StateMachine.addCalibration((int) bt.mgdl, timestamp_ms); - NativeCalibrationPipe.addCalibration((int) bt.mgdl, timestamp_ms); - } - } - - return bt; - } else { - UserError.Log.d(TAG, "Not creating new reading as timestamp is too close"); - } - return null; - } - - public static BloodTest createFromCal(double bg, double timeoffset, String source) { - return createFromCal(bg, timeoffset, source, null); - } - - public static BloodTest createFromCal(double bg, double timeoffset, String source, String suggested_uuid) { - final String unit = Pref.getString("units", "mgdl"); - - if (unit.compareTo("mgdl") != 0) { - bg = bg * Constants.MMOLL_TO_MGDL; - } - - if ((bg < 40) || (bg > 400)) { - Log.wtf(TAG, "Invalid out of range bloodtest glucose mg/dl value of: " + bg); - JoH.static_toast_long("Bloodtest out of range: " + bg + " mg/dl"); - return null; - } - - return create((long) (new Date().getTime() - timeoffset), bg, source, suggested_uuid); - } - - public static void pushBloodTestSyncToWatch(BloodTest bt, boolean is_new) { - Log.d(TAG, "pushTreatmentSyncToWatch Add treatment to UploaderQueue."); - if (Pref.getBooleanDefaultFalse("wear_sync")) { - if (UploaderQueue.newEntryForWatch(is_new ? "insert" : "update", bt) != null) { - SyncService.startSyncService(3000); // sync in 3 seconds - } - } - } - - public static BloodTest last() { - final List btl = last(1); - if ((btl != null) && (btl.size() > 0)) { - return btl.get(0); - } else { - return null; - } - } - - public static List last(int num) { - try { - return new Select() - .from(BloodTest.class) - .orderBy("timestamp desc") - .limit(num) - .execute(); - } catch (android.database.sqlite.SQLiteException e) { - fixUpTable(); - return null; - } - } - - public static List lastMatching(int num, String match) { - try { - return new Select() - .from(BloodTest.class) - .where("source like ?", match) - .orderBy("timestamp desc") - .limit(num) - .execute(); - } catch (android.database.sqlite.SQLiteException e) { - fixUpTable(); - return null; - } - } - - public static BloodTest lastValid() { - final List btl = lastValid(1); - if ((btl != null) && (btl.size() > 0)) { - return btl.get(0); - } else { - return null; - } - } - - public static List lastValid(int num) { - try { - return new Select() - .from(BloodTest.class) - .where("state & ? != 0", BloodTest.STATE_VALID) - .orderBy("timestamp desc") - .limit(num) - .execute(); - } catch (android.database.sqlite.SQLiteException e) { - fixUpTable(); - return null; - } - } - - - public static BloodTest byUUID(String uuid) { - if (uuid == null) return null; - try { - return new Select() - .from(BloodTest.class) - .where("uuid = ?", uuid) - .executeSingle(); - } catch (android.database.sqlite.SQLiteException e) { - fixUpTable(); - return null; - } - } - - public static BloodTest byid(long id) { - try { - return new Select() - .from(BloodTest.class) - .where("_ID = ?", id) - .executeSingle(); - } catch (android.database.sqlite.SQLiteException e) { - fixUpTable(); - return null; - } - } - - public static byte[] toMultiMessage(List btl) { - if (btl == null) return null; - final List BloodTestMessageList = new ArrayList<>(); - for (BloodTest bt : btl) { - BloodTestMessageList.add(bt.toMessageNative()); - } - return BloodTestMultiMessage.ADAPTER.encode(new BloodTestMultiMessage(BloodTestMessageList)); - } - - private static void processFromMessage(BloodTestMessage btm) { - if ((btm != null) && (btm.uuid != null) && (btm.uuid.length() == 36)) { - boolean is_new = false; - BloodTest bt = byUUID(btm.uuid); - if (bt == null) { - bt = getForPreciseTimestamp(Wire.get(btm.timestamp, BloodTestMessage.DEFAULT_TIMESTAMP), CLOSEST_READING_MS); - if (bt != null) { - UserError.Log.wtf(TAG, "Error matches a different uuid with the same timestamp: " + bt.uuid + " vs " + btm.uuid + " skipping!"); - return; - } - bt = new BloodTest(); - is_new = true; - } else { - if (bt.state != Wire.get(btm.state, BloodTestMessage.DEFAULT_STATE)) { - is_new = true; - } - } - bt.timestamp = Wire.get(btm.timestamp, BloodTestMessage.DEFAULT_TIMESTAMP); - bt.mgdl = Wire.get(btm.mgdl, BloodTestMessage.DEFAULT_MGDL); - bt.created_timestamp = Wire.get(btm.created_timestamp, BloodTestMessage.DEFAULT_CREATED_TIMESTAMP); - bt.state = Wire.get(btm.state, BloodTestMessage.DEFAULT_STATE); - bt.source = Wire.get(btm.source, BloodTestMessage.DEFAULT_SOURCE); - bt.uuid = btm.uuid; - bt.saveit(); // de-dupe by uuid - if (is_new) { // cannot handle updates yet - if (UploaderQueue.newEntry(is_new ? "insert" : "update", bt) != null) { - if (JoH.quietratelimit("start-sync-service", 5)) { - SyncService.startSyncService(3000); // sync in 3 seconds - } - } - } - } else { - UserError.Log.wtf(TAG, "processFromMessage uuid is null or invalid"); - } - } - - public static void processFromMultiMessage(byte[] payload) { - try { - final BloodTestMultiMessage btmm = BloodTestMultiMessage.ADAPTER.decode(payload); - if ((btmm != null) && (btmm.bloodtest_message != null)) { - for (BloodTestMessage btm : btmm.bloodtest_message) { - processFromMessage(btm); - } - Home.staticRefreshBGCharts(); - } - } catch (IOException | NullPointerException | IllegalStateException e) { - UserError.Log.e(TAG, "exception processFromMessage: " + e); - } - } - - public static BloodTest fromJSON(String json) { - if ((json == null) || (json.length() == 0)) { - UserError.Log.d(TAG, "Empty json received in bloodtest fromJson"); - return null; - } - try { - UserError.Log.d(TAG, "Processing incoming json: " + json); - return new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().fromJson(json, BloodTest.class); - } catch (Exception e) { - UserError.Log.d(TAG, "Got exception parsing bloodtest json: " + e.toString()); - Home.toaststaticnext("Error on Bloodtest sync, probably decryption key mismatch"); - return null; - } - } - - public static BloodTest getForPreciseTimestamp(long timestamp, long precision) { - BloodTest bloodTest = new Select() - .from(BloodTest.class) - .where("timestamp <= ?", (timestamp + precision)) - .where("timestamp >= ?", (timestamp - precision)) - .orderBy("abs(timestamp - " + timestamp + ") asc") - .executeSingle(); - if ((bloodTest != null) && (Math.abs(bloodTest.timestamp - timestamp) < precision)) { - return bloodTest; - } - return null; - } - - public static List latestForGraph(int number, double startTime) { - return latestForGraph(number, (long) startTime, Long.MAX_VALUE); - } - - public static List latestForGraph(int number, long startTime) { - return latestForGraph(number, startTime, Long.MAX_VALUE); - } - - public static List latestForGraph(int number, long startTime, long endTime) { - try { - return new Select() - .from(BloodTest.class) - .where("state & ? != 0", BloodTest.STATE_VALID) - .where("timestamp >= " + Math.max(startTime, 0)) - .where("timestamp <= " + endTime) - .orderBy("timestamp asc") // warn asc! - .limit(number) - .execute(); - } catch (android.database.sqlite.SQLiteException e) { - fixUpTable(); - return new ArrayList<>(); - } - } - - synchronized static void opportunisticCalibration() { - if (Pref.getBooleanDefaultFalse("bluetooth_meter_for_calibrations_auto")) { - final BloodTest bt = lastValid(); - if (bt == null) { - Log.d(TAG, "opportunistic: No blood tests"); - return; - } - if (JoH.msSince(bt.timestamp) > (Constants.HOUR_IN_MS * 8)) { - Log.d(TAG, "opportunistic: Blood test older than 8 hours ago"); - return; - } - - if ((bt.uuid == null) || (bt.uuid.length() < 8)) { - Log.d(TAG, "opportunisitic: invalid uuid"); - return; - } - - if ((bt.uuid != null) && (bt.uuid.length() > 1) && PersistentStore.getString(LAST_BT_AUTO_CALIB_UUID).equals(bt.uuid)) { - Log.d(TAG, "opportunistic: Already processed uuid: " + bt.uuid); - return; - } - - final Calibration calibration = Calibration.lastValid(); - if (calibration == null) { - Log.d(TAG, "opportunistic: No calibrations"); - // TODO do we try to initial calibrate using this? - return; - } - - if (JoH.msSince(calibration.timestamp) < Constants.HOUR_IN_MS) { - Log.d(TAG, "opportunistic: Last calibration less than 1 hour ago"); - return; - } - - if (bt.timestamp <= calibration.timestamp) { - Log.d(TAG, "opportunistic: Blood test isn't more recent than last calibration"); - return; - } - - // get closest bgreading - must be within dexcom period and locked to sensor - final BgReading bgReading = BgReading.getForPreciseTimestamp(bt.timestamp + (AddCalibration.estimatedInterstitialLagSeconds * 1000), BgGraphBuilder.DEXCOM_PERIOD); - if (bgReading == null) { - Log.d(TAG, "opportunistic: No matching bg reading"); - return; - } - - if (bt.timestamp > highest_timestamp) { - Accuracy.create(bt, bgReading, "xDrip Original"); - final CalibrationAbstract plugin = PluggableCalibration.getCalibrationPluginFromPreferences(); - final CalibrationAbstract.CalibrationData cd = (plugin != null) ? plugin.getCalibrationData(bgReading.timestamp) : null; - if (plugin != null) { - BgReading pluginBgReading = plugin.getBgReadingFromBgReading(bgReading, cd); - Accuracy.create(bt, pluginBgReading, plugin.getAlgorithmName()); - } - highest_timestamp = bt.timestamp; - } - - if (!CalibrationRequest.isSlopeFlatEnough(bgReading)) { - Log.d(TAG, "opportunistic: Slope is not flat enough at: " + JoH.dateTimeText(bgReading.timestamp)); - return; - } - - // TODO store evaluation failure for this record in cache for future optimization - - // TODO Check we have prior reading as well perhaps - JoH.clearCache(); - UserError.Log.ueh(TAG, "Opportunistic calibration for Blood Test at " + JoH.dateTimeText(bt.timestamp) + " of " + BgGraphBuilder.unitized_string_with_units_static(bt.mgdl) + " matching sensor slope at: " + JoH.dateTimeText(bgReading.timestamp) + " from source " + bt.source); - final long time_since = JoH.msSince(bt.timestamp); - - - Log.d(TAG, "opportunistic: attempting auto calibration"); - PersistentStore.setString(LAST_BT_AUTO_CALIB_UUID, bt.uuid); - Home.startHomeWithExtra(xdrip.getAppContext(), - Home.BLUETOOTH_METER_CALIBRATION, - BgGraphBuilder.unitized_string_static(bt.mgdl), - Long.toString(time_since), - "auto"); - } - } - - public static String evaluateAccuracy(long period) { - - // CACHE?? - - final List bloodTests = latestForGraph(1000, JoH.tsl() - period, JoH.tsl() - AddCalibration.estimatedInterstitialLagSeconds); - final List difference = new ArrayList<>(); - final List plugin_difference = new ArrayList<>(); - if ((bloodTests == null) || (bloodTests.size() == 0)) return null; - - final boolean show_plugin = true; - final CalibrationAbstract plugin = (show_plugin) ? PluggableCalibration.getCalibrationPluginFromPreferences() : null; - - - for (BloodTest bt : bloodTests) { - final BgReading bgReading = BgReading.getForPreciseTimestamp(bt.timestamp + (AddCalibration.estimatedInterstitialLagSeconds * 1000), BgGraphBuilder.DEXCOM_PERIOD); - - if (bgReading != null) { - final Calibration calibration = bgReading.calibration; - if (calibration == null) { - Log.d(TAG, "Calibration for bgReading is null! @ " + JoH.dateTimeText(bgReading.timestamp)); - continue; - } - final double diff = Math.abs(bgReading.calculated_value - bt.mgdl); - difference.add(diff); - if (d) { - Log.d(TAG, "Evaluate Accuracy: difference: " + JoH.qs(diff)); - } - final CalibrationAbstract.CalibrationData cd = (plugin != null) ? plugin.getCalibrationData(bgReading.timestamp) : null; - if ((plugin != null) && (cd != null)) { - final double plugin_diff = Math.abs(bt.mgdl - plugin.getGlucoseFromBgReading(bgReading, cd)); - plugin_difference.add(plugin_diff); - if (d) - Log.d(TAG, "Evaluate Plugin Accuracy: " + BgGraphBuilder.unitized_string_with_units_static(bt.mgdl) + " @ " + JoH.dateTimeText(bt.timestamp) + " difference: " + JoH.qs(plugin_diff) + "/" + JoH.qs(plugin_diff * Constants.MGDL_TO_MMOLL, 2) + " calibration: " + JoH.qs(cd.slope, 2) + " " + JoH.qs(cd.intercept, 2)); - } - } - } - - if (difference.size() == 0) return null; - double avg = DoubleMath.mean(difference); - Log.d(TAG, "Average accuracy: " + accuracyAsString(avg) + " (" + JoH.qs(avg, 5) + ")"); - - if (plugin_difference.size() > 0) { - double plugin_avg = DoubleMath.mean(plugin_difference); - Log.d(TAG, "Plugin Average accuracy: " + accuracyAsString(plugin_avg) + " (" + JoH.qs(plugin_avg, 5) + ")"); - return accuracyAsString(plugin_avg) + " / " + accuracyAsString(avg); - } - return accuracyAsString(avg); - } - - public static String accuracyAsString(double avg) { - final boolean domgdl = Pref.getString("units", "mgdl").equals("mgdl"); - // +- symbol - return "\u00B1" + (!domgdl ? JoH.qs(avg * Constants.MGDL_TO_MMOLL, 2) + " mmol" : JoH.qs(avg, 1) + " mgdl"); - } - - public static List cleanup(int retention_days) { - return new Delete() - .from(BloodTest.class) - .where("timestamp < ?", JoH.tsl() - (retention_days * Constants.DAY_IN_MS)) - .execute(); - } - - // create the table ourselves without worrying about model versioning and downgrading - private static void fixUpTable() { - if (patched) return; - final String[] patchup = { - "CREATE TABLE BloodTest (_id INTEGER PRIMARY KEY AUTOINCREMENT);", - "ALTER TABLE BloodTest ADD COLUMN timestamp INTEGER;", - "ALTER TABLE BloodTest ADD COLUMN created_timestamp INTEGER;", - "ALTER TABLE BloodTest ADD COLUMN state INTEGER;", - "ALTER TABLE BloodTest ADD COLUMN mgdl REAL;", - "ALTER TABLE BloodTest ADD COLUMN source TEXT;", - "ALTER TABLE BloodTest ADD COLUMN uuid TEXT;", - "CREATE UNIQUE INDEX index_Bloodtest_uuid on BloodTest(uuid);", - "CREATE UNIQUE INDEX index_Bloodtest_timestamp on BloodTest(timestamp);", - "CREATE INDEX index_Bloodtest_created_timestamp on BloodTest(created_timestamp);", - "CREATE INDEX index_Bloodtest_state on BloodTest(state);"}; - - for (String patch : patchup) { - try { - SQLiteUtils.execSql(patch); - // UserError.Log.e(TAG, "Processed patch should not have succeeded!!: " + patch); - } catch (Exception e) { - // UserError.Log.d(TAG, "Patch: " + patch + " generated exception as it should: " + e.toString()); - } - } - patched = true; - } - -*/ -} - diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/Models/JoH.java b/app/src/main/java/com/eveningoutpost/dexdrip/Models/JoH.java deleted file mode 100644 index 3d8593df34..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/Models/JoH.java +++ /dev/null @@ -1,183 +0,0 @@ -package com.eveningoutpost.dexdrip.Models; - -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.os.PowerManager; -import android.util.Base64; -import android.util.Log; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -import java.io.UnsupportedEncodingException; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.util.GregorianCalendar; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.utils.DateUtil; - -public class JoH { - - private final static String TAG = "jamorham JoH"; - - public static String dateTimeText(long timestamp) { - return android.text.format.DateFormat.format("yyyy-MM-dd kk:mm:ss", timestamp).toString(); - } - - public static long msSince(long when) { - return (DateUtil.now() - when); - } - - public static long msTill(long when) { - return (when - DateUtil.now()); - } - - public static String niceTimeScalar(long t) { - String unit = MainApp.gs(R.string.unit_second); - t = t / 1000; - if (t != 1) unit = MainApp.gs(R.string.unit_seconds); - if (t > 59) { - unit = MainApp.gs(R.string.unit_minute); - t = t / 60; - if (t != 1) unit = MainApp.gs(R.string.unit_minutes); - if (t > 59) { - unit = MainApp.gs(R.string.unit_hour); - t = t / 60; - if (t != 1) unit = MainApp.gs(R.string.unit_hours); - if (t > 24) { - unit = MainApp.gs(R.string.unit_day); - t = t / 24; - if (t != 1) unit = MainApp.gs(R.string.unit_days); - if (t > 28) { - unit = MainApp.gs(R.string.unit_week); - t = t / 7; - if (t != 1) unit = MainApp.gs(R.string.unit_weeks); - } - } - } - } - //if (t != 1) unit = unit + "s"; //implemented plurality in every step, because in other languages plurality of time is not every time adding the same character - return qs((double) t, 0) + " " + unit; - } - - // singletons to avoid repeated allocation - private static DecimalFormatSymbols dfs; - private static DecimalFormat df; - public static String qs(double x, int digits) { - - if (digits == -1) { - digits = 0; - if (((int) x != x)) { - digits++; - if ((((int) x * 10) / 10 != x)) { - digits++; - if ((((int) x * 100) / 100 != x)) digits++; - } - } - } - - if (dfs == null) { - final DecimalFormatSymbols local_dfs = new DecimalFormatSymbols(); - local_dfs.setDecimalSeparator('.'); - dfs = local_dfs; // avoid race condition - } - - final DecimalFormat this_df; - // use singleton if on ui thread otherwise allocate new as DecimalFormat is not thread safe - if (Thread.currentThread().getId() == 1) { - if (df == null) { - final DecimalFormat local_df = new DecimalFormat("#", dfs); - local_df.setMinimumIntegerDigits(1); - df = local_df; // avoid race condition - } - this_df = df; - } else { - this_df = new DecimalFormat("#", dfs); - } - - this_df.setMaximumFractionDigits(digits); - return this_df.format(x); - } - - public static long getTimeZoneOffsetMs() { - return new GregorianCalendar().getTimeZone().getRawOffset(); - } - - public static boolean emptyString(final String str) { - return str == null || str.length() == 0; - } - - public static PowerManager.WakeLock getWakeLock(final String name, int millis) { - final PowerManager pm = (PowerManager) MainApp.instance().getSystemService(Context.POWER_SERVICE); - final PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name); - wl.acquire(millis); - Log.d(TAG, "getWakeLock: " + name + " " + wl.toString()); - return wl; - } - - public static void releaseWakeLock(PowerManager.WakeLock wl) { - Log.d(TAG, "releaseWakeLock: " + wl.toString()); - if (wl == null) return; - if (wl.isHeld()) { - try { - wl.release(); - } catch (Exception e) { - Log.e(TAG, "Error releasing wakelock: " + e); - } - } - } - - public static PowerManager.WakeLock fullWakeLock(final String name, long millis) { - final PowerManager pm = (PowerManager) MainApp.instance().getSystemService(Context.POWER_SERVICE); - PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, name); - wl.acquire(millis); - Log.d(TAG, "fullWakeLock: " + name + " " + wl.toString()); - return wl; - } - - public static boolean isLANConnected() { - final ConnectivityManager cm = - (ConnectivityManager) MainApp.instance().getSystemService(Context.CONNECTIVITY_SERVICE); - final NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); - final boolean isConnected = activeNetwork != null && - activeNetwork.isConnected(); - return isConnected && ((activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) - || (activeNetwork.getType() == ConnectivityManager.TYPE_ETHERNET) - || (activeNetwork.getType() == ConnectivityManager.TYPE_BLUETOOTH)); - } - - private static Gson gson_instance; - public static Gson defaultGsonInstance() { - if (gson_instance == null) { - gson_instance = new GsonBuilder() - .excludeFieldsWithoutExposeAnnotation() - //.registerTypeAdapter(Date.class, new DateTypeAdapter()) - // .serializeSpecialFloatingPointValues() - .create(); - } - return gson_instance; - } - - public static String base64encodeBytes(byte[] input) { - try { - return new String(Base64.encode(input, Base64.NO_WRAP), "UTF-8"); - } catch (UnsupportedEncodingException e) { - Log.e(TAG, "Got unsupported encoding: " + e); - return "encode-error"; - } - } - - public static byte[] base64decodeBytes(String input) { - try { - return Base64.decode(input.getBytes("UTF-8"), Base64.NO_WRAP); - } catch (UnsupportedEncodingException | IllegalArgumentException e) { - Log.e(TAG, "Got unsupported encoding: " + e); - return new byte[0]; - } - } - - -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/Inevitable.java b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/Inevitable.java deleted file mode 100644 index c3efae80b4..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/Inevitable.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.eveningoutpost.dexdrip.UtilityModels; - -import android.os.PowerManager; -import android.os.SystemClock; -import android.util.Log; - -import com.eveningoutpost.dexdrip.Models.JoH; - -import java.util.concurrent.ConcurrentHashMap; - -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.T; - -/** - * Created by jamorham on 07/03/2018. - *

- * Tasks which are fired from events can be scheduled here and only execute when they become idle - * and are not being rescheduled within their wait window. - */ - -public class Inevitable { - - private static final String TAG = Inevitable.class.getSimpleName(); - private static final int MAX_QUEUE_TIME = (int) T.mins(6).msecs(); - private static final boolean d = true; - - private static final ConcurrentHashMap tasks = new ConcurrentHashMap<>(); - - public static synchronized void task(final String id, long idle_for, Runnable runnable) { - if (idle_for > MAX_QUEUE_TIME) { - throw new RuntimeException(id + " Requested time: " + idle_for + " beyond max queue time"); - } - final Task task = tasks.get(id); - if (task != null) { - // if it already exists then extend the time - task.extendTime(idle_for); - - if (d) - Log.d(TAG, "Extending time for: " + id + " to " + JoH.dateTimeText(task.when)); - } else { - // otherwise create new task - if (runnable == null) return; // extension only if already exists - tasks.put(id, new Task(id, idle_for, runnable)); - - if (d) - Log.d(TAG, "Creating task: " + id + " due: " + JoH.dateTimeText(tasks.get(id).when)); - - // create a thread to wait and execute in background - final Thread t = new Thread(() -> { - final PowerManager.WakeLock wl = JoH.getWakeLock(id, MAX_QUEUE_TIME + 5000); - try { - boolean running = true; - // wait for task to be due or killed - while (running) { - SystemClock.sleep(500); - final Task thisTask = tasks.get(id); - running = thisTask != null && !thisTask.poll(); - } - } finally { - JoH.releaseWakeLock(wl); - } - }); - t.setPriority(Thread.MIN_PRIORITY); - //t.setDaemon(true); - t.start(); - } - } - - public static synchronized void stackableTask(String id, long idle_for, Runnable runnable) { - int stack = 0; - while (tasks.get(id = id + "-" + stack) != null) { - stack++; - } - if (stack > 0) { - Log.d(TAG, "Task stacked to: " + id); - } - task(id, idle_for, runnable); - } - - public static void kill(final String id) { - tasks.remove(id); - } - - public static boolean waiting(final String id) { - return tasks.containsKey(id); - } - - private static class Task { - private long when; - private final Runnable what; - private final String id; - - Task(String id, long offset, Runnable what) { - this.what = what; - this.id = id; - extendTime(offset); - } - - public void extendTime(long offset) { - this.when = DateUtil.now() + offset; - } - - public boolean poll() { - final long till = JoH.msTill(when); - if (till < 1) { - if (d) Log.d(TAG, "Executing task! " + this.id); - tasks.remove(this.id); // early remove to allow overlapping scheduling - what.run(); - return true; - } else if (till > MAX_QUEUE_TIME) { - Log.wtf(TAG, "Task: " + this.id + " In queue too long: " + till); - tasks.remove(this.id); - return true; - } - return false; - } - - } - -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/PersistentStore.java b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/PersistentStore.java deleted file mode 100644 index 5e2cfe6fed..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/PersistentStore.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.eveningoutpost.dexdrip.UtilityModels; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.SharedPreferences; - -import com.eveningoutpost.dexdrip.Models.JoH; -import com.google.common.primitives.Bytes; - -import info.nightscout.androidaps.MainApp; - -/** - * Created by jamorham on 23/09/2016. - *

- * This is for internal data which is never backed up, - * separate file means it doesn't clutter prefs - * we can afford to lose it, it is for internal states - * and is alternative to static variables which get - * flushed when classes are destroyed by garbage collection - *

- * It is suitable for cache type variables where losing - * state will cause problems. Obviously it will be slower than - * pure in-memory state variables. - */ - - -public class PersistentStore { - - private static final String DATA_STORE_INTERNAL = "persist_internal_store"; - private static SharedPreferences prefs; - private static final boolean d = false; // debug flag - - public static String getString(final String name) { - return prefs.getString(name, ""); - } - - static { - try { - prefs = MainApp.instance() - .getSharedPreferences(DATA_STORE_INTERNAL, Context.MODE_PRIVATE); - } catch (NullPointerException e) { - android.util.Log.e("PersistentStore", "Failed to get context on init!!! nothing will work"); - } - } - - public static void setString(final String name, String value) { - prefs.edit().putString(name, value).apply(); - } - - // if string is different to what we have stored then update and return true - public static boolean updateStringIfDifferent(final String name, final String current) { - if (current == null) return false; // can't handle nulls - if (PersistentStore.getString(name).equals(current)) return false; - PersistentStore.setString(name, current); - return true; - } - - public static void appendString(String name, String value) { - setString(name, getString(name) + value); - } - - public static void appendString(String name, String value, String delimiter) { - String current = getString(name); - if (current.length() > 0) current += delimiter; - setString(name, current + value); - } - - public static void appendBytes(String name, byte[] value) { - setBytes(name, Bytes.concat(getBytes(name), value)); - } - - public static byte[] getBytes(String name) { - return JoH.base64decodeBytes(getString(name)); - } - - public static byte getByte(String name) { - return (byte) getLong(name); - } - - public static void setBytes(String name, byte[] value) { - setString(name, JoH.base64encodeBytes(value)); - } - - public static void setByte(String name, byte value) { - setLong(name, value); - } - - public static long getLong(String name) { - return prefs.getLong(name, 0); - } - - public static float getFloat(String name) { - return prefs.getFloat(name, 0); - } - - public static void setLong(String name, long value) { - prefs.edit().putLong(name, value).apply(); - } - - public static void setFloat(String name, float value) { - prefs.edit().putFloat(name, value).apply(); - } - - public static void setDouble(String name, double value) { - setLong(name, Double.doubleToRawLongBits(value)); - } - - public static double getDouble(String name) { - return Double.longBitsToDouble(getLong(name)); - } - - public static boolean getBoolean(String name) { - return prefs.getBoolean(name, false); - } - - public static boolean getBoolean(String name, boolean value) { - return prefs.getBoolean(name, value); - } - - public static void setBoolean(String name, boolean value) { - prefs.edit().putBoolean(name, value).apply(); - } - - public static long incrementLong(String name) { - final long val = getLong(name) + 1; - setLong(name, val); - return val; - } - - public static void setLongZeroIfSet(String name) { - if (getLong(name) > 0) setLong(name, 0); - } - - @SuppressLint("ApplySharedPref") - public static void commit() { - prefs.edit().commit(); - } -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/StatusItem.java b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/StatusItem.java deleted file mode 100644 index 745b0ae6e4..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/StatusItem.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.eveningoutpost.dexdrip.UtilityModels; - -import android.graphics.Color; -import android.support.annotation.ColorInt; - -import com.google.common.base.MoreObjects; - -import java.util.HashMap; - -import static com.eveningoutpost.dexdrip.UtilityModels.StatusItem.Highlight.BAD; -import static com.eveningoutpost.dexdrip.UtilityModels.StatusItem.Highlight.CRITICAL; -import static com.eveningoutpost.dexdrip.UtilityModels.StatusItem.Highlight.GOOD; -import static com.eveningoutpost.dexdrip.UtilityModels.StatusItem.Highlight.NORMAL; -import static com.eveningoutpost.dexdrip.UtilityModels.StatusItem.Highlight.NOTICE; - -/** - * Created by jamorham on 14/01/2017. - *

- * For representing row items suitable for MegaStatus - */ - -public class StatusItem { - - private static final HashMap colorHints = new HashMap<>(); - - static { - colorHints.put(NORMAL, Color.TRANSPARENT); - colorHints.put(GOOD, Color.parseColor("#003000")); - colorHints.put(BAD, Color.parseColor("#480000")); - colorHints.put(NOTICE, Color.parseColor("#403000")); - colorHints.put(CRITICAL, Color.parseColor("#770000")); - } - - public enum Highlight { - NORMAL, - GOOD, - BAD, - NOTICE, - CRITICAL; - - @ColorInt - public int color() { - return colorHint(this); - } - - } - - public String name; - public String value; - public Highlight highlight; - public String button_name; - public Runnable runnable; - - - public StatusItem(String name, String value) { - this(name, value, NORMAL); - } - - public StatusItem() { - this("line-break", "", NORMAL); - } - - public StatusItem(String name, Highlight highlight) { - this("heading-break", name, highlight); - } - - public StatusItem(String name, Runnable runnable) { - this("button-break", "", NORMAL, name, runnable); - } - - public StatusItem(String name, String value, Highlight highlight) { - this(name, value, highlight, null, null); - } - - public StatusItem(String name, String value, Highlight highlight, String button_name, Runnable runnable) { - this.name = name; - this.value = value; - this.highlight = highlight; - this.button_name = button_name; - this.runnable = runnable; - } - - public StatusItem(String name, Integer value) { - this(name, value, NORMAL); - } - - public StatusItem(String name, Integer value, Highlight highlight) { - this.name = name; - this.value = Integer.toString(value); - this.highlight = highlight; - } - - @ColorInt - public static int colorHint(final Highlight highlight) { - return MoreObjects.firstNonNull(colorHints.get(highlight), Color.TRANSPARENT); - } - -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/store/FastStore.java b/app/src/main/java/com/eveningoutpost/dexdrip/store/FastStore.java deleted file mode 100644 index 3cb4e15e88..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/store/FastStore.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.eveningoutpost.dexdrip.store; - -import java.util.HashMap; - -/** - * Created by jamorham on 08/11/2017. - *

- * Fast implementation of KeyStore interface - * Uses an in-memory database for short lived data elements - * Content is expired as per normal garbage collection - * Neutral defaults favoured over null return values - * Static creation for fastest shared instance access - */ - -public class FastStore implements KeyStore { - - private static final FastStore mFastStore = new FastStore(); - private static final HashMap stringStore = new HashMap<>(); - private static final HashMap longStore = new HashMap<>(); - - // we trade substitution flexibility at the expense of some object creation - - private FastStore() { - // use getInstance! - } - - public static FastStore getInstance() { - return mFastStore; - } - - // interface methods - - public String getS(String key) { - if (stringStore.containsKey(key)) return stringStore.get(key); - return ""; - } - - public void putS(String key, String value) { - stringStore.put(key, value); - } - - public long getL(String key) { - if (longStore.containsKey(key)) return longStore.get(key); - return 0; - } - - public void putL(String key, long value) { - longStore.put(key, value); - } - -} - - diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/store/KeyStore.java b/app/src/main/java/com/eveningoutpost/dexdrip/store/KeyStore.java deleted file mode 100644 index 3ea19214f6..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/store/KeyStore.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.eveningoutpost.dexdrip.store; - -/** - * Created by jamorham on 08/11/2017. - * - * KeyStore is a persistence interface allowing storage and retrieval of primitive data types - * referenced by a String based key. - * - * Implementations may choose how long data is retained for. - * Typical usage might include caching expensive function results - * - */ - -public interface KeyStore { - - // java type erasure prevents us using generics and then implementing multiple generic interfaces - // so storage types we are interested in get their own interface methods - - void putS(String key, String value); - - String getS(String key); - - void putL(String key, long value); - - long getL(String key); - - -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/BaseElement.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/BaseElement.java deleted file mode 100644 index 0bf0bb6fa9..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/BaseElement.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -import com.google.gson.annotations.Expose; - -/** - * jamorham - *

- * common element base - */ - -public abstract class BaseElement { - @Expose - public String deviceTime; - @Expose - public String time; - @Expose - public int timezoneOffset; - @Expose - public String type; - @Expose - public Origin origin; - - - BaseElement populate(final long timestamp, final String uuid) { - deviceTime = DateUtil.toFormatNoZone(timestamp); - time = DateUtil.toFormatAsUTC(timestamp); - timezoneOffset = DateUtil.getTimeZoneOffsetMinutes(timestamp); // TODO - origin = new Origin(uuid); - return this; - } - - public class Origin { - @Expose - String id; - - Origin(String id) { - this.id = id; - } - } -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/BaseMessage.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/BaseMessage.java deleted file mode 100644 index c96d3da1c4..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/BaseMessage.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -import com.eveningoutpost.dexdrip.Models.JoH; - -import okhttp3.MediaType; -import okhttp3.RequestBody; - -/** - * jamorham - * - * message base - */ - -public abstract class BaseMessage { - - public String toS() { - return JoH.defaultGsonInstance().toJson(this); - } - - public RequestBody getBody() { - return RequestBody.create(MediaType.parse("application/json"), this.toS()); - } - -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/DateUtil.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/DateUtil.java deleted file mode 100644 index 84f4076f75..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/DateUtil.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -/** - * jamorham - * - * Date utilities for preparing items for Tidepool upload - */ - -import java.text.SimpleDateFormat; -import java.util.Locale; -import java.util.TimeZone; - -public class DateUtil { - - static String toFormatAsUTC(final long timestamp) { - final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'0000Z'", Locale.US); - format.setTimeZone(TimeZone.getTimeZone("UTC")); - return format.format(timestamp); - } - - static String toFormatWithZone2(final long timestamp) { - // ISO 8601 not introduced till api 24 - so we have to do some gymnastics - final SimpleDateFormat formatIso8601 = new SimpleDateFormat("Z", Locale.US); - formatIso8601.setTimeZone(TimeZone.getDefault()); - String zone = formatIso8601.format(timestamp); - zone = zone.substring(0, zone.length() - 2) + ":" + zone.substring(zone.length() - 2); - if (zone.substring(0, 1).equals("+")) { - zone = zone.substring(1); - } - final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z" + zone + "'", Locale.US); - format.setTimeZone(TimeZone.getDefault()); - return format.format(timestamp); - } - - - static String toFormatNoZone(final long timestamp) { - final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US); - format.setTimeZone(TimeZone.getDefault()); - return format.format(timestamp); - } - - static int getTimeZoneOffsetMinutes(final long timestamp) { - return TimeZone.getDefault().getOffset(timestamp) / 60000; - } - -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBasal.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBasal.java deleted file mode 100644 index 96819ba31c..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBasal.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -import com.eveningoutpost.dexdrip.Models.JoH; -import com.google.gson.annotations.Expose; - -// jamorham - -public class EBasal extends BaseElement { - - long timestamp; // not exposed - - @Expose - String deliveryType = "automated"; - @Expose - long duration; - @Expose - double rate = -1; - @Expose - String scheduleName = "AAPS"; - @Expose - long clockDriftOffset = 0; - @Expose - long conversionOffset = 0; - - { - type = "basal"; - } - - EBasal(double rate, long timeStart, long duration, String uuid) { - this.timestamp = timeStart; - this.rate = rate; - this.duration = duration; - populate(timeStart, uuid); - } - - boolean isValid() { - return (rate > -1 && duration > 0); - } - - String toS() { - return rate + " Start: " + JoH.dateTimeText(timestamp) + " for: " + JoH.niceTimeScalar(duration); - } -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBloodGlucose.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBloodGlucose.java deleted file mode 100644 index 493c16ec3b..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBloodGlucose.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -// jamorham - -import com.eveningoutpost.dexdrip.Models.BloodTest; -import com.google.gson.annotations.Expose; - -import java.util.LinkedList; -import java.util.List; - -class EBloodGlucose extends BaseElement { - - @Expose - String subType; - @Expose - String units; - @Expose - int value; - - EBloodGlucose() { - this.type = "smbg"; - this.units = "mg/dL"; - } - - - static EBloodGlucose fromBloodTest(final BloodTest bloodtest) { - final EBloodGlucose bg = new EBloodGlucose(); - bg.populate(bloodtest.timestamp, bloodtest.uuid); - - bg.subType = "manual"; // TODO - bg.value = (int) bloodtest.mgdl; - return bg; - } - - static List fromBloodTests(final List bloodTestList) { - if (bloodTestList == null) return null; - final List results = new LinkedList<>(); - for (BloodTest bt : bloodTestList) { - results.add(fromBloodTest(bt)); - } - return results; - } - -} - diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBolus.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBolus.java deleted file mode 100644 index 384942e859..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBolus.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -import com.google.gson.annotations.Expose; - -import info.nightscout.androidaps.plugins.treatments.Treatment; - -// jamorham - -public class EBolus extends BaseElement { - - @Expose - public final String subType = "normal"; - @Expose - public final double normal; - @Expose - public final double expectedNormal; - - { - type = "bolus"; - } - - EBolus(double insulinDelivered, double insulinExpected, long timestamp, String uuid) { - this.normal = insulinDelivered; - this.expectedNormal = insulinExpected; - populate(timestamp, uuid); - } - - public static EBolus fromTreatment(Treatment treatment) { - return new EBolus(treatment.insulin, treatment.insulin, treatment.date, "uuid-AAPS"); - } - -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/ESensorGlucose.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/ESensorGlucose.java deleted file mode 100644 index 45a4ba7732..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/ESensorGlucose.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -// jamorham - -import com.google.gson.annotations.Expose; - -import java.util.LinkedList; -import java.util.List; - -import info.nightscout.androidaps.db.BgReading; - -public class ESensorGlucose extends BaseElement { - - - @Expose - String units; - @Expose - int value; - - ESensorGlucose() { - this.type = "cbg"; - this.units = "mg/dL"; - } - - - static ESensorGlucose fromBgReading(final BgReading bgReading) { - final ESensorGlucose sensorGlucose = new ESensorGlucose(); - sensorGlucose.populate(bgReading.date, "uuid-AAPS"); - sensorGlucose.value = (int) bgReading.value; // TODO best glucose? - return sensorGlucose; - } - - static List fromBgReadings(final List bgReadingList) { - if (bgReadingList == null) return null; - final List results = new LinkedList<>(); - for (BgReading bgReading : bgReadingList) { - results.add(fromBgReading(bgReading)); - } - return results; - } - -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EWizard.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EWizard.java deleted file mode 100644 index 64d5b87c34..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EWizard.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -import com.google.gson.annotations.Expose; - -import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; -import info.nightscout.androidaps.plugins.treatments.Treatment; - -// jamorham - -public class EWizard extends BaseElement { - - @Expose - public String units = "mg/dL"; - @Expose - public double carbInput; - @Expose - public double insulinCarbRatio; - @Expose - public EBolus bolus; - - EWizard() { - type = "wizard"; - } - - public static EWizard fromTreatment(final Treatment treatment) { - final EWizard result = (EWizard)new EWizard().populate(treatment.date, "uuid-AAPS"); - result.carbInput = treatment.carbs; - result.insulinCarbRatio = ProfileFunctions.getInstance().getProfile(treatment.date).getIc(); - if (treatment.insulin > 0) { - result.bolus = new EBolus(treatment.insulin, treatment.insulin, treatment.date, "uuid-AAPS"); - } else { - result.bolus = new EBolus(0.0001,0.0001, treatment.date, "uuid-AAPS"); // fake insulin record - } - return result; - } - -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/GzipRequestInterceptor.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/GzipRequestInterceptor.java deleted file mode 100644 index 7dc3c156e9..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/GzipRequestInterceptor.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -import java.io.IOException; - -import okhttp3.Interceptor; -import okhttp3.MediaType; -import okhttp3.Request; -import okhttp3.RequestBody; -import okio.BufferedSink; -import okio.GzipSink; -import okio.Okio; - -class GzipRequestInterceptor implements Interceptor { - @Override - public okhttp3.Response intercept(Chain chain) throws IOException { - final Request originalRequest = chain.request(); - if (originalRequest.body() == null - || originalRequest.header("Content-Encoding") != null) - { - return chain.proceed(originalRequest); - } - - final Request compressedRequest = originalRequest.newBuilder() - .header("Content-Encoding", "gzip") - .method(originalRequest.method(), gzip(originalRequest.body())) - .build(); - return chain.proceed(compressedRequest); - } - - private RequestBody gzip(final RequestBody body) { - return new RequestBody() { - @Override public MediaType contentType() { - return body.contentType(); - } - - @Override public long contentLength() { - return -1; // We don't know the compressed length in advance! - } - - @Override public void writeTo(BufferedSink sink) throws IOException { - BufferedSink gzipSink = Okio.buffer(new GzipSink(sink)); - body.writeTo(gzipSink); - gzipSink.close(); - } - }; - } - -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/InfoInterceptor.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/InfoInterceptor.java deleted file mode 100644 index 5feb804566..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/InfoInterceptor.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -import android.support.annotation.NonNull; -import android.util.Log; - -import java.io.IOException; - -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - -// jamorham - -public class InfoInterceptor implements Interceptor { - - private String tag = "interceptor"; - - public InfoInterceptor(String tag) { - this.tag = tag; - } - - @Override - public Response intercept(@NonNull final Chain chain) throws IOException { - final Request request = chain.request(); - if (request != null && request.body() != null) { - Log.d(tag, "Interceptor Body size: " + request.body().contentLength()); - //} else { - // UserError.Log.d(tag,"Null request body in InfoInterceptor"); - } - return chain.proceed(request); - } -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MAuthReply.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MAuthReply.java deleted file mode 100644 index 77dc6e0739..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MAuthReply.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -// jamorham - -import com.eveningoutpost.dexdrip.Models.JoH; -import com.google.gson.annotations.Expose; -import com.google.gson.annotations.SerializedName; - -import java.util.List; - -import lombok.AllArgsConstructor; - -@AllArgsConstructor -public class MAuthReply { - - @Expose - @SerializedName("emailVerified") - Boolean emailVerified; - @Expose - @SerializedName("emails") - List emailList; - @Expose - @SerializedName("termsAccepted") - String termsDate; - @Expose - @SerializedName("userid") - String userid; - @Expose - @SerializedName("username") - String username; - - public String toS() { - return JoH.defaultGsonInstance().toJson(this); - } - -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MAuthRequest.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MAuthRequest.java deleted file mode 100644 index 6c5ae762c9..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MAuthRequest.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -// jamorham - -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.utils.SP; -import okhttp3.Credentials; - -import static com.eveningoutpost.dexdrip.Models.JoH.emptyString; - -public class MAuthRequest extends BaseMessage { - - public static String getAuthRequestHeader() { - - final String username = SP.getString(R.string.key_tidepool_username, null); - final String password = SP.getString(R.string.key_tidepool_password, null); - - if (emptyString(username) || emptyString(password)) return null; - return Credentials.basic(username.trim(), password); - } -} - - - diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MCloseDatasetRequest.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MCloseDatasetRequest.java deleted file mode 100644 index d12ec48060..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MCloseDatasetRequest.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -import com.google.gson.annotations.Expose; - -public class MCloseDatasetRequest extends BaseMessage { - @Expose - String dataState = "closed"; - -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MDatasetReply.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MDatasetReply.java deleted file mode 100644 index 9e4752ff1f..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MDatasetReply.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -import java.util.List; - -public class MDatasetReply { - - Data data; - - public class Data { - String createdTime; - String deviceId; - String id; - String time; - String timezone; - int timezoneOffset; - String type; - String uploadId; - Client client; - String computerTime; - String dataSetType; - List deviceManufacturers; - String deviceModel; - String deviceSerialNumber; - List deviceTags; - String timeProcessing; - String version; - // meta - } - - public class Client { - String name; - String version; - - } - - - // openDataSet and others return this in the root of the json reply it seems - String id; - String uploadId; - - public String getUploadId() { - return (data != null && data.uploadId != null) ? data.uploadId : uploadId; - } - - - -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MGetDatasetsRequest.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MGetDatasetsRequest.java deleted file mode 100644 index 8a72992e5d..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MGetDatasetsRequest.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -public class MGetDatasetsRequest extends BaseMessage { -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MOpenDatasetRequest.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MOpenDatasetRequest.java deleted file mode 100644 index caf62794f5..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MOpenDatasetRequest.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -import com.google.gson.annotations.Expose; - -import java.util.TimeZone; - -import info.nightscout.androidaps.BuildConfig; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.utils.T; - -import static com.eveningoutpost.dexdrip.Models.JoH.getTimeZoneOffsetMs; - -public class MOpenDatasetRequest extends BaseMessage { - - static final String UPLOAD_TYPE = "continuous"; - - @Expose - public String deviceId; - @Expose - public String time = DateUtil.toFormatAsUTC(info.nightscout.androidaps.utils.DateUtil.now()); - @Expose - public int timezoneOffset = (int) (getTimeZoneOffsetMs() / T.mins(1).msecs()); - @Expose - public String type = "upload"; - //public String byUser; - @Expose - public ClientInfo client = new ClientInfo(); - @Expose - public String computerTime = DateUtil.toFormatNoZone(info.nightscout.androidaps.utils.DateUtil.now()); - @Expose - public String dataSetType = UPLOAD_TYPE; // omit for "normal" - @Expose - public String[] deviceManufacturers = {((PluginBase) (ConfigBuilderPlugin.getPlugin().getActiveBgSource())).getName()}; - @Expose - public String deviceModel = ((PluginBase) (ConfigBuilderPlugin.getPlugin().getActiveBgSource())).getName(); - @Expose - public String[] deviceTags = {"bgm", "cgm", "insulin-pump"}; - @Expose - public Deduplicator deduplicator = new Deduplicator(); - @Expose - public String timeProcessing = "none"; - @Expose - public String timezone = TimeZone.getDefault().getID(); - @Expose - public String version = BuildConfig.VERSION_NAME; - - class ClientInfo { - @Expose - final String name = BuildConfig.APPLICATION_ID; - @Expose - final String version = "0.1.0"; // TODO: const it - } - - class Deduplicator { - @Expose - final String name = "org.tidepool.deduplicator.dataset.delete.origin"; - } - - static boolean isNormal() { - return UPLOAD_TYPE.equals("normal"); - } -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MUploadReply.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MUploadReply.java deleted file mode 100644 index bdcf164396..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/MUploadReply.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -import java.util.List; - -public class MUploadReply { - - List data; - -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/Session.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/Session.java deleted file mode 100644 index 83bf20d036..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/Session.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -// jamorham - -// Manages the session data - -import java.util.List; - -import okhttp3.Headers; - -public class Session { - - private final String SESSION_TOKEN_HEADER; - final TidepoolUploader.Tidepool service = TidepoolUploader.getRetrofitInstance().create(TidepoolUploader.Tidepool.class); - final String authHeader; - - String token; - MAuthReply authReply; - MDatasetReply datasetReply; - long start; - long end; - volatile int iterations; - - - Session(String authHeader, String session_token_header) { - this.authHeader = authHeader; - this.SESSION_TOKEN_HEADER = session_token_header; - } - - void populateHeaders(final Headers headers) { - if (this.token == null) { - this.token = headers.get(SESSION_TOKEN_HEADER); - } - } - - void populateBody(final Object obj) { - if (obj == null) return; - if (obj instanceof MAuthReply) { - authReply = (MAuthReply) obj; - } else if (obj instanceof List) { - List list = (List)obj; - if (list.size() > 0 && list.get(0) instanceof MDatasetReply) { - datasetReply = (MDatasetReply) list.get(0); - } - } else if (obj instanceof MDatasetReply) { - datasetReply = (MDatasetReply) obj; - } - } - - boolean exceededIterations() { - return iterations > 50; - } - -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolCallback.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolCallback.java deleted file mode 100644 index d99afdc1fa..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolCallback.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -import android.util.Log; - -import com.eveningoutpost.dexdrip.store.FastStore; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -// jamorham - -// Callback template to reduce boiler plate - -class TidepoolCallback implements Callback { - - final Session session; - final String name; - final Runnable onSuccess; - - Runnable onFailure; - - public TidepoolCallback(Session session, String name, Runnable onSuccess) { - this.session = session; - this.name = name; - this.onSuccess = onSuccess; - } - - TidepoolCallback setOnFailure(final Runnable runnable) { - this.onFailure = runnable; - return this; - } - - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful() && response.body() != null) { - Log.d(TidepoolUploader.TAG, name + " success"); - session.populateBody(response.body()); - session.populateHeaders(response.headers()); - if (onSuccess != null) { - onSuccess.run(); - } - } else { - final String msg = name + " was not successful: " + response.code() + " " + response.message(); - Log.e(TidepoolUploader.TAG, msg); - status(msg); - if (onFailure != null) { - onFailure.run(); - } - } - } - - @Override - public void onFailure(Call call, Throwable t) { - final String msg = name + " Failed: " + t; - Log.e(TidepoolUploader.TAG, msg); - status(msg); - if (onFailure != null) { - onFailure.run(); - } - } - - - private static void status(final String status) { - FastStore.getInstance().putS(TidepoolUploader.STATUS_KEY, status); - } -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolEntry.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolEntry.java deleted file mode 100644 index 26f7901cda..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolEntry.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -// jamorham - -// lightweight class entry point - -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.receivers.ChargingStateReceiver; -import info.nightscout.androidaps.utils.SP; - -import static com.eveningoutpost.dexdrip.Models.JoH.isLANConnected; - -public class TidepoolEntry { - - - public static boolean enabled() { - return SP.getBoolean(R.string.key_cloud_storage_tidepool_enable, false); - } - - public static void newData() { - if (enabled() - && (!SP.getBoolean(R.string.key_tidepool_only_while_charging, false) || ChargingStateReceiver.isCharging()) - && (!SP.getBoolean(R.string.key_tidepool_only_while_unmetered, false) || isLANConnected()) - // && JoH.pratelimit("tidepool-new-data-upload", 1200) - ) { - TidepoolUploader.doLogin(false); - } - } -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolStatus.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolStatus.java deleted file mode 100644 index 82e76c4d1f..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolStatus.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - -// jamorham - -import com.eveningoutpost.dexdrip.Models.JoH; -import com.eveningoutpost.dexdrip.UtilityModels.StatusItem; -import com.eveningoutpost.dexdrip.store.FastStore; -import com.eveningoutpost.dexdrip.store.KeyStore; - -import java.util.ArrayList; -import java.util.List; - -import static com.eveningoutpost.dexdrip.Models.JoH.msSince; -import static com.eveningoutpost.dexdrip.Models.JoH.niceTimeScalar; - -public class TidepoolStatus { - - // data for MegaStatus - public static List megaStatus() { - - final KeyStore keyStore = FastStore.getInstance(); - final List l = new ArrayList<>(); - - l.add(new StatusItem("Tidepool Synced to", niceTimeScalar(msSince(UploadChunk.getLastEnd())) + " ago")); // TODO needs generic message format string - final String status = keyStore.getS(TidepoolUploader.STATUS_KEY); - if (!JoH.emptyString(status)) { - l.add(new StatusItem("Tidepool Status", status)); - } - return l; - } -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/UploadChunk.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/UploadChunk.java deleted file mode 100644 index 61b734bc3b..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/UploadChunk.java +++ /dev/null @@ -1,189 +0,0 @@ -package com.eveningoutpost.dexdrip.tidepool; - - -import android.util.Log; - -import com.eveningoutpost.dexdrip.Models.JoH; -import com.eveningoutpost.dexdrip.UtilityModels.PersistentStore; -import com.eveningoutpost.dexdrip.utils.LogSlider; -import com.eveningoutpost.dexdrip.utils.NamedSliderProcessor; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.UUID; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.db.BgReading; -import info.nightscout.androidaps.db.TemporaryBasal; -import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; -import info.nightscout.androidaps.plugins.treatments.Treatment; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.SP; -import info.nightscout.androidaps.utils.T; - -import static com.eveningoutpost.dexdrip.Models.JoH.dateTimeText; - -/** - * jamorham - *

- * This class gets the next time slice of all data to upload - */ - -public class UploadChunk implements NamedSliderProcessor { - - private static final String TAG = "TidepoolUploadChunk"; - private static final String LAST_UPLOAD_END_PREF = "tidepool-last-end"; - - private static final long MAX_UPLOAD_SIZE = T.days(7).msecs(); // don't change this - private static final long DEFAULT_WINDOW_OFFSET = T.mins(15).msecs(); - private static final long MAX_LATENCY_THRESHOLD_MINUTES = 1440; // minutes per day - - - public static String getNext(final Session session) { - session.start = getLastEnd(); - session.end = maxWindow(session.start); - - final String result = get(session.start, session.end); - if (result != null && result.length() < 3) { - Log.d(TAG, "No records in this time period, setting start to best end time"); - setLastEnd(Math.max(session.end, getOldestRecordTimeStamp())); - } - return result; - } - - public static String get(final long start, final long end) { - - Log.e(TAG, "Syncing data between: " + dateTimeText(start) + " -> " + dateTimeText(end)); - if (end <= start) { - Log.e(TAG, "End is <= start: " + dateTimeText(start) + " " + dateTimeText(end)); - return null; - } - if (end - start > MAX_UPLOAD_SIZE) { - Log.e(TAG, "More than max range - rejecting"); - return null; - } - - final List records = new LinkedList<>(); - - records.addAll(getTreatments(start, end)); - records.addAll(getBloodTests(start, end)); - records.addAll(getBasals(start, end)); - records.addAll(getBgReadings(start, end)); - - return JoH.defaultGsonInstance().toJson(records); - } - - private static long getWindowSizePreference() { - try { - long value = (long) getLatencySliderValue(SP.getInt(R.string.key_tidepool_window_latency, 0)); - return Math.max(T.mins(value).msecs(), DEFAULT_WINDOW_OFFSET); - } catch (Exception e) { - Log.e(TAG, "Reverting to default of 15 minutes due to Window Size exception: " + e); - return DEFAULT_WINDOW_OFFSET; // default - } - } - - private static long maxWindow(final long last_end) { - //Log.d(TAG, "Max window is: " + getWindowSizePreference()); - return Math.min(last_end + MAX_UPLOAD_SIZE, DateUtil.now() - getWindowSizePreference()); - } - - public static long getLastEnd() { - long result = PersistentStore.getLong(LAST_UPLOAD_END_PREF); - return Math.max(result, DateUtil.now() - T.months(2).msecs()); - } - - public static void setLastEnd(final long when) { - if (when > getLastEnd()) { - PersistentStore.setLong(LAST_UPLOAD_END_PREF, when); - Log.d(TAG, "Updating last end to: " + dateTimeText(when)); - } else { - Log.e(TAG, "Cannot set last end to: " + dateTimeText(when) + " vs " + dateTimeText(getLastEnd())); - } - } - - static List getTreatments(final long start, final long end) { - List result = new LinkedList<>(); - final List treatments = TreatmentsPlugin.getPlugin().getService().getTreatmentDataFromTime(start, end, true); - for (Treatment treatment : treatments) { - if (treatment.carbs > 0) { - result.add(EWizard.fromTreatment(treatment)); - } else if (treatment.insulin > 0) { - result.add(EBolus.fromTreatment(treatment)); - } else { - // note only TODO - } - } - return result; - } - - - // numeric limits must match max time windows - - static long getOldestRecordTimeStamp() { - // TODO we could make sure we include records older than the first bg record for completeness - - final long start = 0; - final long end = DateUtil.now(); - - final List bgReadingList = MainApp.getDbHelper().getBgreadingsDataFromTime(start, end, false); - if (bgReadingList != null && bgReadingList.size() > 0) { - return bgReadingList.get(0).date; - } - return -1; - } - - static List getBloodTests(final long start, final long end) { - return new ArrayList<>(); -// return EBloodGlucose.fromBloodTests(BloodTest.latestForGraph(1800, start, end)); - } - - static List getBgReadings(final long start, final long end) { - return ESensorGlucose.fromBgReadings(MainApp.getDbHelper().getBgreadingsDataFromTime(start, end, true)); - } - - static List getBasals(final long start, final long end) { - final List basals = new LinkedList<>(); - final List aplist = MainApp.getDbHelper().getTemporaryBasalsDataFromTime(start, end, true); - EBasal current = null; - for (TemporaryBasal temporaryBasal : aplist) { - final double this_rate = temporaryBasal.tempBasalConvertedToAbsolute(temporaryBasal.date, ProfileFunctions.getInstance().getProfile(temporaryBasal.date)); - - if (current != null) { - if (this_rate != current.rate) { - current.duration = temporaryBasal.date - current.timestamp; - Log.d(TAG, "Adding current: " + current.toS()); - if (current.isValid()) { - basals.add(current); - } else { - Log.e(TAG, "Current basal is invalid: " + current.toS()); - } - current = null; - } else { - Log.d(TAG, "Same rate as previous basal record: " + current.rate + " " + temporaryBasal.toStringFull()); - } - } - if (current == null) { - current = new EBasal(this_rate, temporaryBasal.date, 0, UUID.nameUUIDFromBytes(("tidepool-basal" + temporaryBasal.date).getBytes()).toString()); // start duration is 0 - } - } - return basals; - - } - - @Override - public int interpolate(final String name, final int position) { - switch (name) { - case "latency": - return getLatencySliderValue(position); - } - throw new RuntimeException("name not matched in interpolate"); - } - - private static int getLatencySliderValue(final int position) { - return (int) LogSlider.calc(0, 300, 15, MAX_LATENCY_THRESHOLD_MINUTES, position); - } -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utils/LogSlider.java b/app/src/main/java/com/eveningoutpost/dexdrip/utils/LogSlider.java deleted file mode 100644 index 014d085ae4..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/utils/LogSlider.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.eveningoutpost.dexdrip.utils; - -// jamorham - -public class LogSlider { - - // logarithmic slider with positions start - end representing values start - end, calculate value at selected position - public static double calc(int sliderStart, int sliderEnd, double valueStart, double valueEnd, int position) { - valueStart = Math.log(Math.max(1, valueStart)); - valueEnd = Math.log(Math.max(1, valueEnd)); - return Math.exp(valueStart + (valueEnd - valueStart) / (sliderEnd - sliderStart) * (position - sliderStart)); - } -} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utils/NamedSliderProcessor.java b/app/src/main/java/com/eveningoutpost/dexdrip/utils/NamedSliderProcessor.java deleted file mode 100644 index a13e4b05b1..0000000000 --- a/app/src/main/java/com/eveningoutpost/dexdrip/utils/NamedSliderProcessor.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.eveningoutpost.dexdrip.utils; - -// jamorham - -// interpolate a slider by name from a supporting class - -public interface NamedSliderProcessor { - - int interpolate(String name, int position); - -} diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index 7f1b527d00..9acd222b55 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -205,7 +205,7 @@ public class MainApp extends Application { pluginsList.add(StatuslinePlugin.initPlugin(this)); pluginsList.add(PersistentNotificationPlugin.getPlugin()); pluginsList.add(NSClientPlugin.getPlugin()); - pluginsList.add(TidepoolPlugin.getPlugin()); + pluginsList.add(TidepoolPlugin.INSTANCE); pluginsList.add(MaintenancePlugin.initPlugin(this)); pluginsList.add(ConfigBuilderPlugin.getPlugin()); diff --git a/app/src/main/java/info/nightscout/androidaps/logging/L.java b/app/src/main/java/info/nightscout/androidaps/logging/L.java index f685bbd424..362e03a0c7 100644 --- a/app/src/main/java/info/nightscout/androidaps/logging/L.java +++ b/app/src/main/java/info/nightscout/androidaps/logging/L.java @@ -87,6 +87,7 @@ public class L { public static final String DATAFOOD = "DATAFOOD"; public static final String DATATREATMENTS = "DATATREATMENTS"; public static final String NSCLIENT = "NSCLIENT"; + public static final String TIDEPOOL = "TIDEPOOL"; public static final String CONSTRAINTS = "CONSTRAINTS"; public static final String PUMP = "PUMP"; public static final String PUMPQUEUE = "PUMPQUEUE"; @@ -114,6 +115,7 @@ public class L { logElements.add(new LogElement(EVENTS, false, true)); logElements.add(new LogElement(NOTIFICATION, true)); logElements.add(new LogElement(NSCLIENT, true)); + logElements.add(new LogElement(TIDEPOOL, true)); logElements.add(new LogElement(OVERVIEW, true)); logElements.add(new LogElement(PROFILE, true)); logElements.add(new LogElement(PUMP, true)); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.java deleted file mode 100644 index 580b602deb..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.java +++ /dev/null @@ -1,38 +0,0 @@ -package info.nightscout.androidaps.plugins.general.tidepool; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.PluginDescription; -import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.source.BGSourceFragment; - -/** - * Created by mike on 28.11.2017. - */ - -public class TidepoolPlugin extends PluginBase { - private static Logger log = LoggerFactory.getLogger(L.DATABASE); - - private static TidepoolPlugin plugin = null; - - public static TidepoolPlugin getPlugin() { - if (plugin == null) - plugin = new TidepoolPlugin(); - return plugin; - } - - private TidepoolPlugin() { - super(new PluginDescription() - .mainType(PluginType.GENERAL) - .pluginName(R.string.tidepool) - .shortName(R.string.tidepool_shortname) - .preferencesId(R.xml.pref_tidepool) - .description(R.string.description_tidepool) - ); - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt new file mode 100644 index 0000000000..724c35b606 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt @@ -0,0 +1,57 @@ +package info.nightscout.androidaps.plugins.general.tidepool + +import com.squareup.otto.Subscribe +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.events.EventNetworkChange +import info.nightscout.androidaps.events.EventNewBG +import info.nightscout.androidaps.interfaces.PluginBase +import info.nightscout.androidaps.interfaces.PluginDescription +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.general.tidepool.comm.TidepoolUploader +import info.nightscout.androidaps.plugins.general.tidepool.utils.RateLimit +import info.nightscout.androidaps.receivers.ChargingStateReceiver +import info.nightscout.androidaps.utils.SP +import org.slf4j.LoggerFactory + +object TidepoolPlugin : PluginBase(PluginDescription() + .mainType(PluginType.GENERAL) + .pluginName(R.string.tidepool) + .shortName(R.string.tidepool_shortname) + .preferencesId(R.xml.pref_tidepool) + .description(R.string.description_tidepool) +) { + private val log = LoggerFactory.getLogger(L.TIDEPOOL) + private var wifiConnected = false + + override fun onStart() { + MainApp.bus().register(this) + super.onStart() + } + + override fun onStop() { + MainApp.bus().unregister(this) + super.onStop() + } + + @Suppress("UNUSED_PARAMETER") + @Subscribe + fun onStatusEvent(ev: EventNewBG) { + if (enabled() + && (!SP.getBoolean(R.string.key_tidepool_only_while_charging, false) || ChargingStateReceiver.isCharging()) + && (!SP.getBoolean(R.string.key_tidepool_only_while_unmetered, false) || wifiConnected) + && RateLimit.ratelimit("tidepool-new-data-upload", 1200)) + TidepoolUploader.doLogin() + } + + @Subscribe + fun onEventNetworkChange(ev: EventNetworkChange) { + wifiConnected = ev.wifiConnected + } + + fun enabled(): Boolean { + return isEnabled(PluginType.GENERAL) && SP.getBoolean(R.string.key_cloud_storage_tidepool_enable, false) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolUploader.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolUploaded.java.txt similarity index 77% rename from app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolUploader.java rename to app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolUploaded.java.txt index 52b4ef9200..9cef0e726f 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolUploader.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolUploaded.java.txt @@ -3,14 +3,18 @@ package com.eveningoutpost.dexdrip.tidepool; import android.os.PowerManager; import android.util.Log; -import com.eveningoutpost.dexdrip.Models.JoH; -import com.eveningoutpost.dexdrip.UtilityModels.Inevitable; -import com.eveningoutpost.dexdrip.store.FastStore; - import java.util.List; import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.general.tidepool.comm.InfoInterceptor; +import info.nightscout.androidaps.plugins.general.tidepool.comm.Session; +import info.nightscout.androidaps.plugins.general.tidepool.comm.TidepoolCallback; +import info.nightscout.androidaps.plugins.general.tidepool.messages.AuthReplyMessage; +import info.nightscout.androidaps.plugins.general.tidepool.messages.AuthRequestMessage; +import info.nightscout.androidaps.plugins.general.tidepool.messages.DatasetReplyMessage; +import info.nightscout.androidaps.plugins.general.tidepool.messages.OpenDatasetRequestMessage; +import info.nightscout.androidaps.plugins.general.tidepool.messages.UploadReplyMessage; import info.nightscout.androidaps.utils.SP; import okhttp3.MediaType; import okhttp3.OkHttpClient; @@ -129,26 +133,26 @@ public class TidepoolUploader { } // TODO failure backoff // if (JoH.ratelimit("tidepool-login", 10)) { - extendWakeLock(30000); - final Session session = new Session(MAuthRequest.getAuthRequestHeader(), SESSION_TOKEN_HEADER); - if (session.authHeader != null) { - final Call call = session.service.getLogin(session.authHeader); - status("Connecting"); - if (fromUi) { + extendWakeLock(30000); + final Session session = new Session(MAuthRequest.getAuthRequestHeader(), SESSION_TOKEN_HEADER); + if (session.authHeader() != null) { + final Call call = session.service().getLogin(session.authHeader()); + status("Connecting"); + if (fromUi) { // JoH.static_toast_long("Connecting to Tidepool"); - } - - call.enqueue(new TidepoolCallback(session, "Login", () -> startSession(session, fromUi)) - .setOnFailure(() -> loginFailed(fromUi))); - } else { - Log.e(TAG, "Cannot do login as user credentials have not been set correctly"); - status("Invalid credentials"); - if (fromUi) { -// JoH.static_toast_long("Cannot login as Tidepool credentials have not been set correctly"); - } - releaseWakeLock(); } - // } + + call.enqueue(new TidepoolCallback(session, "Login", () -> startSession(session, fromUi)) + .setOnFailure(() -> loginFailed(fromUi))); + } else { + Log.e(TAG, "Cannot do login as user credentials have not been set correctly"); + status("Invalid credentials"); + if (fromUi) { +// JoH.static_toast_long("Cannot login as Tidepool credentials have not been set correctly"); + } + releaseWakeLock(); + } + // } } private static void loginFailed(boolean fromUi) { @@ -158,7 +162,7 @@ public class TidepoolUploader { releaseWakeLock(); } -/* public static void testLogin(Context rootContext) { + public static void testLogin(Context rootContext) { if (JoH.ratelimit("tidepool-login", 1)) { String message = "Failed to log into Tidepool.\n" + @@ -176,7 +180,7 @@ public class TidepoolUploader { e.printStackTrace(); } } else { - UserError.Log.e(TAG,"Cannot do login as user credentials have not been set correctly"); + UserError.Log.e(TAG, "Cannot do login as user credentials have not been set correctly"); } AlertDialog.Builder builder = new AlertDialog.Builder(rootContext); @@ -192,45 +196,45 @@ public class TidepoolUploader { final AlertDialog alert = builder.create(); alert.show(); } - }*/ + } private static void startSession(final Session session, boolean fromUi) { // if (JoH.ratelimit("tidepool-start-session", 60)) { - extendWakeLock(30000); - if (session.authReply.userid != null) { - // See if we already have an open data set to write to - Call> datasetCall = session.service.getOpenDataSets(session.token, - session.authReply.userid, BuildConfig.APPLICATION_ID, 1); + extendWakeLock(30000); + if (session.authReply.userid != null) { + // See if we already have an open data set to write to + Call> datasetCall = session.service.getOpenDataSets(session.token, + session.authReply.userid, BuildConfig.APPLICATION_ID, 1); - datasetCall.enqueue(new TidepoolCallback>(session, "Get Open Datasets", () -> { - if (session.datasetReply == null) { - status("New data set"); - if (fromUi) { + datasetCall.enqueue(new TidepoolCallback>(session, "Get Open Datasets", () -> { + if (session.datasetReply == null) { + status("New data set"); + if (fromUi) { // JoH.static_toast_long("Creating new data set"); - } - Call call = session.service.openDataSet(session.token, session.authReply.userid, new MOpenDatasetRequest().getBody()); - call.enqueue(new TidepoolCallback(session, "Open New Dataset", () -> doUpload(session)) - .setOnFailure(TidepoolUploader::releaseWakeLock)); - } else { - Log.d(TAG, "Existing Dataset: " + session.datasetReply.getUploadId()); - // TODO: Wouldn't need to do this if we could block on the above `call.enqueue`. - // ie, do the openDataSet conditionally, and then do `doUpload` either way. - status("Appending"); - if (fromUi) { -// JoH.static_toast_long("Found existing remote data set"); - } - doUpload(session); } - }).setOnFailure(TidepoolUploader::releaseWakeLock)); - } else { - Log.wtf(TAG, "Got login response but cannot determine userid - cannot proceed"); - if (fromUi) { -// JoH.static_toast_long("Error: Cannot determine userid"); + Call call = session.service.openDataSet(session.token, session.authReply.userid, new MOpenDatasetRequest().getBody()); + call.enqueue(new TidepoolCallback(session, "Open New Dataset", () -> doUpload(session)) + .setOnFailure(TidepoolUploader::releaseWakeLock)); + } else { + Log.d(TAG, "Existing Dataset: " + session.datasetReply.getUploadId()); + // TODO: Wouldn't need to do this if we could block on the above `call.enqueue`. + // ie, do the openDataSet conditionally, and then do `doUpload` either way. + status("Appending"); + if (fromUi) { +// JoH.static_toast_long("Found existing remote data set"); + } + doUpload(session); } - status("Error userid"); - releaseWakeLock(); + }).setOnFailure(TidepoolUploader::releaseWakeLock)); + } else { + Log.wtf(TAG, "Got login response but cannot determine userid - cannot proceed"); + if (fromUi) { +// JoH.static_toast_long("Error: Cannot determine userid"); } + status("Error userid"); + releaseWakeLock(); + } // } else { // status("Cool Down Wait"); // if (fromUi) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/InfoInterceptor.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/InfoInterceptor.kt new file mode 100644 index 0000000000..0cd7448153 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/InfoInterceptor.kt @@ -0,0 +1,26 @@ +package info.nightscout.androidaps.plugins.general.tidepool.comm + +import info.nightscout.androidaps.logging.L +import okhttp3.Interceptor +import okhttp3.Response +import org.slf4j.LoggerFactory +import java.io.IOException + +class InfoInterceptor(tag: String) : Interceptor { + + private val log = LoggerFactory.getLogger(L.TIDEPOOL) + private var tag = "interceptor" + + init { + this.tag = tag + } + + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + if (request != null && request.body() != null) { + if (L.isEnabled(L.TIDEPOOL)) log.debug("Interceptor Body size: " + request.body()!!.contentLength()) + } + return chain.proceed(request!!) + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/Session.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/Session.kt new file mode 100644 index 0000000000..4b888bf765 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/Session.kt @@ -0,0 +1,58 @@ +package info.nightscout.androidaps.plugins.general.tidepool.comm + +import info.nightscout.androidaps.plugins.general.tidepool.messages.AuthReplyMessage +import info.nightscout.androidaps.plugins.general.tidepool.messages.DatasetReplyMessage +import okhttp3.Headers + +class Session (authHeader: String?, session_token_header: String) { + var SESSION_TOKEN_HEADER: String + var authHeader: String? + + val service = TidepoolUploader.getRetrofitInstance()?.create(TidepoolApiService::class.java) + + internal var token: String? = null + internal var authReply: AuthReplyMessage? = null + internal var datasetReply: DatasetReplyMessage? = null + internal var start: Long = 0 + internal var end: Long = 0 + @Volatile + internal var iterations: Int = 0 + + + init { + this.authHeader = authHeader + this.SESSION_TOKEN_HEADER = session_token_header + } + + fun populateHeaders(headers: Headers) { + if (this.token == null) { + this.token = headers.get(SESSION_TOKEN_HEADER) + } + } + + fun populateBody(obj: Any?) { + if (obj == null) return + if (obj is AuthReplyMessage) { + authReply = obj + } else if (obj is List<*>) { + val list = obj as List<*>? + if (list!!.size > 0 && list[0] is DatasetReplyMessage) { + datasetReply = list[0] as DatasetReplyMessage + } + } else if (obj is DatasetReplyMessage) { + datasetReply = obj + } + } + + internal fun exceededIterations(): Boolean { + return iterations > 50 + } + + fun authHeader(): String? { + return authHeader; + } + + fun service(): TidepoolApiService? { + return service; + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolApiService.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolApiService.kt new file mode 100644 index 0000000000..45f4d92243 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolApiService.kt @@ -0,0 +1,48 @@ +package info.nightscout.androidaps.plugins.general.tidepool.comm + +import info.nightscout.androidaps.BuildConfig +import info.nightscout.androidaps.plugins.general.tidepool.messages.AuthReplyMessage +import info.nightscout.androidaps.plugins.general.tidepool.messages.DatasetReplyMessage +import info.nightscout.androidaps.plugins.general.tidepool.messages.UploadReplyMessage +import okhttp3.RequestBody +import retrofit2.Call +import retrofit2.http.* + +const val SESSION_TOKEN_HEADER: String = "x-tidepool-session-token" + +interface TidepoolApiService { + + @Headers( + "User-Agent: AAPS- " + BuildConfig.VERSION_NAME, + "X-Tidepool-Client-Name: info.nightscout.androidaps" + BuildConfig.APPLICATION_ID, + "X-Tidepool-Client-Version: 0.1.0" + ) + + @POST("/auth/login") + abstract fun getLogin(@Header("Authorization") secret: String): Call + + @DELETE("/v1/users/{userId}/data") + abstract fun deleteAllData(@Header(SESSION_TOKEN_HEADER) token: String, @Path("userId") id: String): Call + + @DELETE("/v1/datasets/{dataSetId}") + abstract fun deleteDataSet(@Header(SESSION_TOKEN_HEADER) token: String, @Path("dataSetId") id: String): Call + + @GET("/v1/users/{userId}/data_sets") + abstract fun getOpenDataSets(@Header(SESSION_TOKEN_HEADER) token: String, + @Path("userId") id: String, + @Query("client.name") clientName: String, + @Query("size") size: Int): Call> + + @GET("/v1/datasets/{dataSetId}") + abstract fun getDataSet(@Header(SESSION_TOKEN_HEADER) token: String, @Path("dataSetId") id: String): Call + + @POST("/v1/users/{userId}/data_sets") + abstract fun openDataSet(@Header(SESSION_TOKEN_HEADER) token: String, @Path("userId") id: String, @Body body: RequestBody): Call + + @POST("/v1/datasets/{sessionId}/data") + abstract fun doUpload(@Header(SESSION_TOKEN_HEADER) token: String, @Path("sessionId") id: String, @Body body: RequestBody): Call + + @PUT("/v1/datasets/{sessionId}") + abstract fun closeDataSet(@Header(SESSION_TOKEN_HEADER) token: String, @Path("sessionId") id: String, @Body body: RequestBody): Call + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolCallback.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolCallback.kt new file mode 100644 index 0000000000..89e9b49c71 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolCallback.kt @@ -0,0 +1,38 @@ +package info.nightscout.androidaps.plugins.general.tidepool.comm + +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolStatus +import org.slf4j.LoggerFactory +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +internal class TidepoolCallback(val session: Session, val name: String, val onSucc: () -> Unit, val onFail: () -> Unit) : Callback { + private val log = LoggerFactory.getLogger(L.TIDEPOOL) + + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful && response.body() != null) { + if (L.isEnabled(L.TIDEPOOL)) log.debug("$name success") + session.populateBody(response.body()) + session.populateHeaders(response.headers()) + onSucc() + } else { + val msg = name + " was not successful: " + response.code() + " " + response.message() + if (L.isEnabled(L.TIDEPOOL)) log.debug(msg) + status(msg) + onFail() + } + } + + override fun onFailure(call: Call, t: Throwable) { + val msg = "$name Failed: $t" + if (L.isEnabled(L.TIDEPOOL)) log.debug(msg) + status(msg) + onFail() + } + + private fun status(status: String) { + MainApp.bus().post(EventTidepoolStatus(status)) + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt new file mode 100644 index 0000000000..7c0939e53d --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt @@ -0,0 +1,232 @@ +package info.nightscout.androidaps.plugins.general.tidepool.comm + +import android.content.Context +import android.os.PowerManager +import info.nightscout.androidaps.BuildConfig +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.general.tidepool.TidepoolPlugin +import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolStatus +import info.nightscout.androidaps.plugins.general.tidepool.messages.* +import info.nightscout.androidaps.utils.OKDialog +import info.nightscout.androidaps.utils.SP +import okhttp3.MediaType +import okhttp3.OkHttpClient +import okhttp3.RequestBody +import okhttp3.logging.HttpLoggingInterceptor +import org.slf4j.LoggerFactory +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +object TidepoolUploader { + + private val log = LoggerFactory.getLogger(L.TIDEPOOL) + + private var wl: PowerManager.WakeLock? = null + + + private const val INTEGRATION_BASE_URL = "https://int-api.tidepool.org" + private const val PRODUCTION_BASE_URL = "https://api.tidepool.org" + + private var retrofit: Retrofit? = null + + fun getRetrofitInstance(): Retrofit? { + if (retrofit == null) { + + val httpLoggingInterceptor = HttpLoggingInterceptor() + httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY + + val client = OkHttpClient.Builder() + .addInterceptor(httpLoggingInterceptor) + .addInterceptor(InfoInterceptor(TidepoolUploader::class.java.name)) + .build() + + retrofit = Retrofit.Builder() + .baseUrl(if (SP.getBoolean(R.string.key_tidepool_dev_servers, false)) INTEGRATION_BASE_URL else PRODUCTION_BASE_URL) + .client(client) + .addConverterFactory(GsonConverterFactory.create()) + .build() + } + return retrofit + } + + // TODO: call on preference change + fun resetInstance() { + retrofit = null + if (L.isEnabled(L.TIDEPOOL)) + log.debug("Instance reset") + } + + @Synchronized + fun doLogin() { + if (!SP.getBoolean(R.string.key_cloud_storage_tidepool_enable, false)) { + log.debug("Cannot login as disabled by preference") + return + } + // TODO failure backoff + extendWakeLock(30000) + val session = Session(AuthRequestMessage.getAuthRequestHeader(), SESSION_TOKEN_HEADER) + if (session.authHeader != null) { + val call = session.service?.getLogin(session.authHeader!!) + status("Connecting") + + call?.enqueue(TidepoolCallback(session, "Login", { startSession(session) }, { loginFailed() })) + } else { + if (L.isEnabled(L.TIDEPOOL)) log.debug("Cannot do login as user credentials have not been set correctly") + status("Invalid credentials") + releaseWakeLock() + } + } + + fun testLogin(rootContext: Context) { + + var message = "Failed to log into Tidepool.\n" + "Check that your user name and password are correct." + + val session = Session(AuthRequestMessage.getAuthRequestHeader(), SESSION_TOKEN_HEADER) + if (session.authHeader != null) { + val call = session.service!!.getLogin(session.authHeader!!) + + val response = call.execute() + if (L.isEnabled(L.TIDEPOOL)) log.debug("Header: " + response.code()) + message = "Successfully logged into Tidepool." + + } else { + if (L.isEnabled(L.TIDEPOOL)) log.debug("Cannot do login as user credentials have not been set correctly") + } + + OKDialog.show(rootContext, MainApp.gs(R.string.tidepool), message, null); + } + + + private fun loginFailed() { + releaseWakeLock() + } + + private fun startSession(session: Session) { + extendWakeLock(30000) + if (session.authReply?.userid != null) { + // See if we already have an open data set to write to + val datasetCall = session.service!!.getOpenDataSets(session.token!!, + session.authReply!!.userid!!, BuildConfig.APPLICATION_ID, 1) + + datasetCall.enqueue(TidepoolCallback>(session, "Get Open Datasets", { + if (session.datasetReply == null) { + status("New data set") + val call = session.service.openDataSet(session.token!!, session.authReply!!.userid!!, OpenDatasetRequestMessage().getBody()) + call.enqueue(TidepoolCallback(session, "Open New Dataset", { doUpload(session) }, { releaseWakeLock() })) + } else { + if (L.isEnabled(L.TIDEPOOL)) log.debug("Existing Dataset: " + session.datasetReply!!.getUploadId()) + // TODO: Wouldn't need to do this if we could block on the above `call.enqueue`. + // ie, do the openDataSet conditionally, and then do `doUpload` either way. + status("Appending") + doUpload(session) + } + }, { releaseWakeLock() })) + } else { + log.error("Got login response but cannot determine userid - cannot proceed") + status("Error userid") + releaseWakeLock() + } + } + + private fun doUpload(session: Session) { + if (!TidepoolPlugin.enabled()) { + if (L.isEnabled(L.TIDEPOOL)) + log.debug("Cannot upload - preference disabled") + return + } + extendWakeLock(60000) + session.iterations++ + val chunk = UploadChunk.getNext(session) + if (chunk != null) { + if (chunk.length == 2) { + if (L.isEnabled(L.TIDEPOOL)) log.debug("Empty data set - marking as succeeded") + doCompleted() + } else { + val body = RequestBody.create(MediaType.parse("application/json"), chunk) + + val call = session.service!!.doUpload(session.token!!, session.datasetReply!!.getUploadId()!!, body) + status("Uploading") + call.enqueue(TidepoolCallback(session, "Data Upload", { + UploadChunk.setLastEnd(session.end) + if (OpenDatasetRequestMessage.isNormal()) { + doClose(session) + } else { + doCompleted() + } + }, { releaseWakeLock() })) + } + } else { + log.error("Upload chunk is null, cannot proceed") + releaseWakeLock() + } + } + + private fun status(status: String) { + MainApp.bus().post(EventTidepoolStatus(status)) + } + + private fun doCompleted() { + status("Completed OK") + if (L.isEnabled(L.TIDEPOOL)) log.debug("ALL COMPLETED OK!") + releaseWakeLock() + } + + private fun doClose(session: Session) { + status("Closing") + extendWakeLock(20000) + val call = session.service!!.closeDataSet(session.token!!, session.datasetReply!!.getUploadId()!!, CloseDatasetRequestMessage().getBody()) + call.enqueue(TidepoolCallback(session, "Session Stop", { closeSuccess() }, {})) + } + + private fun closeSuccess() { + status("Closed") + if (L.isEnabled(L.TIDEPOOL)) log.debug("Close success") + releaseWakeLock() + } + + private fun deleteData(session: Session) { + if (session.authReply!!.userid != null) { + val call = session.service!!.deleteAllData(session.token!!, session.authReply!!.userid!!) + call.enqueue(TidepoolCallback(session, "Delete Data", {}, {})) + } else { + log.error("Got login response but cannot determine userid - cannot proceed") + } + } + + private fun getDataSet(session: Session) { + val call = session.service!!.getDataSet(session.token!!, "bogus") + call.enqueue(TidepoolCallback(session, "Get Data", {}, {})) + } + + private fun deleteDataSet(session: Session) { + val call = session.service!!.deleteDataSet(session.token!!, "bogus") + call.enqueue(TidepoolCallback(session, "Delete Data", {}, {})) + } + + @Synchronized + private fun extendWakeLock(ms: Long) { + if (wl == null) { + val pm = MainApp.instance().getSystemService(Context.POWER_SERVICE) as PowerManager + wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AndroidAPS:TidepoolUploader") + wl?.acquire(ms) + } else { + releaseWakeLock() // lets not get too messy + wl?.acquire(ms) + } + } + + @Synchronized + private fun releaseWakeLock() { + if (wl == null) return + if (wl!!.isHeld()) { + try { + wl!!.release() + } catch (e: Exception) { + log.error("Error releasing wakelock: $e") + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/UploadChunk.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/UploadChunk.kt new file mode 100644 index 0000000000..0fa1da3ecc --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/UploadChunk.kt @@ -0,0 +1,169 @@ +package info.nightscout.androidaps.plugins.general.tidepool.comm + +import android.util.Log +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.plugins.general.tidepool.elements.* +import info.nightscout.androidaps.plugins.general.tidepool.utils.GsonInstance +import info.nightscout.androidaps.plugins.general.tidepool.utils.LogSlider +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.SP +import info.nightscout.androidaps.utils.T +import java.util.* + +object UploadChunk { + + private val TAG = "TidepoolUploadChunk" + + private val MAX_UPLOAD_SIZE = T.days(7).msecs() // don't change this + private val DEFAULT_WINDOW_OFFSET = T.mins(15).msecs() + private val MAX_LATENCY_THRESHOLD_MINUTES: Long = 1440 // minutes per day + + + fun getNext(session: Session): String? { + session.start = getLastEnd() + session.end = maxWindow(session.start) + + val result = get(session.start, session.end) + if (result != null && result.length < 3) { + Log.d(TAG, "No records in this time period, setting start to best end time") + setLastEnd(Math.max(session.end, getOldestRecordTimeStamp())) + } + return result + } + + operator fun get(start: Long, end: Long): String? { + + Log.e(TAG, "Syncing data between: " + DateUtil.dateAndTimeFullString(start) + " -> " + DateUtil.dateAndTimeFullString(end)) + if (end <= start) { + Log.e(TAG, "End is <= start: " + DateUtil.dateAndTimeFullString(start) + " " + DateUtil.dateAndTimeFullString(end)) + return null + } + if (end - start > MAX_UPLOAD_SIZE) { + Log.e(TAG, "More than max range - rejecting") + return null + } + + val records = LinkedList() + + records.addAll(getTreatments(start, end)) + records.addAll(getBloodTests(start, end)) + records.addAll(getBasals(start, end)) + records.addAll(getBgReadings(start, end)) + + return GsonInstance.defaultGsonInstance().toJson(records) + } + + private fun getWindowSizePreference(): Long { + try { + val value = getLatencySliderValue(SP.getInt(R.string.key_tidepool_window_latency, 0)).toLong() + return Math.max(T.mins(value).msecs(), DEFAULT_WINDOW_OFFSET) + } catch (e: Exception) { + Log.e(TAG, "Reverting to default of 15 minutes due to Window Size exception: $e") + return DEFAULT_WINDOW_OFFSET // default + } + + } + + private fun maxWindow(last_end: Long): Long { + //Log.d(TAG, "Max window is: " + getWindowSizePreference()); + return Math.min(last_end + MAX_UPLOAD_SIZE, DateUtil.now() - getWindowSizePreference()) + } + + fun getLastEnd(): Long { + val result = SP.getLong(R.string.key_tidepool_last_end, 0) + return Math.max(result, DateUtil.now() - T.months(2).msecs()) + } + + fun setLastEnd(time: Long) { + if (time > getLastEnd()) { + SP.putLong(R.string.key_tidepool_last_end, time) + Log.d(TAG, "Updating last end to: " + DateUtil.dateAndTimeFullString(time)) + } else { + Log.e(TAG, "Cannot set last end to: " + DateUtil.dateAndTimeFullString(time) + " vs " + DateUtil.dateAndTimeFullString(getLastEnd())) + } + } + + internal fun getTreatments(start: Long, end: Long): List { + val result = LinkedList() + val treatments = TreatmentsPlugin.getPlugin().service.getTreatmentDataFromTime(start, end, true) + for (treatment in treatments) { + if (treatment.carbs > 0) { + result.add(WizardElement.fromTreatment(treatment)) + } else if (treatment.insulin > 0) { + result.add(BolusElement.fromTreatment(treatment)) + } else { + // note only TODO + } + } + return result + } + + + // numeric limits must match max time windows + + internal fun getOldestRecordTimeStamp(): Long { + // TODO we could make sure we include records older than the first bg record for completeness + + val start: Long = 0 + val end = DateUtil.now() + + val bgReadingList = MainApp.getDbHelper().getBgreadingsDataFromTime(start, end, false) + return if (bgReadingList != null && bgReadingList.size > 0) { + bgReadingList[0].date + } else -1 + } + + @Suppress("UNUSED_PARAMETER") + internal fun getBloodTests(start: Long, end: Long): List { + return ArrayList() + // return BloodGlucoseElement.fromBloodTests(BloodTest.latestForGraph(1800, start, end)); + } + + internal fun getBgReadings(start: Long, end: Long): List { + return SensorGlucoseElement.fromBgReadings(MainApp.getDbHelper().getBgreadingsDataFromTime(start, end, true)) + } + + internal fun getBasals(start: Long, end: Long): List { + val basals = LinkedList() + val aplist = MainApp.getDbHelper().getTemporaryBasalsDataFromTime(start, end, true) + var current: BasalElement? = null + for (temporaryBasal in aplist) { + val this_rate = temporaryBasal.tempBasalConvertedToAbsolute(temporaryBasal.date, ProfileFunctions.getInstance().getProfile(temporaryBasal.date)) + + if (current != null) { + if (this_rate != current.rate) { + current.duration = temporaryBasal.date - current.timestamp + Log.d(TAG, "Adding current: " + current.toS()) + if (current.isValid()) { + basals.add(current) + } else { + Log.e(TAG, "Current basal is invalid: " + current.toS()) + } + current = null + } else { + Log.d(TAG, "Same rate as previous basal record: " + current.rate + " " + temporaryBasal.toStringFull()) + } + } + if (current == null) { + current = BasalElement().create(this_rate, temporaryBasal.date, 0, UUID.nameUUIDFromBytes(("tidepool-basal" + temporaryBasal.date).toByteArray()).toString()) // start duration is 0 + } + } + return basals + + } + + fun interpolate(name: String, position: Int): Int { + when (name) { + "latency" -> return getLatencySliderValue(position) + } + throw RuntimeException("name not matched in interpolate") + } + + private fun getLatencySliderValue(position: Int): Int { + return LogSlider.calc(0, 300, 15.0, MAX_LATENCY_THRESHOLD_MINUTES.toDouble(), position).toInt() + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BasalElement.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BasalElement.kt new file mode 100644 index 0000000000..65f28e6c51 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BasalElement.kt @@ -0,0 +1,42 @@ +package info.nightscout.androidaps.plugins.general.tidepool.elements + +import com.google.gson.annotations.Expose +import info.nightscout.androidaps.utils.DateUtil + +class BasalElement : BaseElement() { + + internal var timestamp: Long = 0 // not exposed + + @Expose + internal var deliveryType = "automated" + @Expose + internal var duration: Long = 0 + @Expose + internal var rate = -1.0 + @Expose + internal var scheduleName = "AAPS" + @Expose + internal var clockDriftOffset: Long = 0 + @Expose + internal var conversionOffset: Long = 0 + + init { + type = "basal"; + } + + fun create(rate: Double, timeStart: Long, duration: Long, uuid: String) : BasalElement { + this.timestamp = timeStart + this.rate = rate + this.duration = duration + populate(timeStart, uuid) + return this + } + + internal fun isValid(): Boolean { + return rate > -1 && duration > 0 + } + + internal fun toS(): String { + return rate.toString() + " Start: " + DateUtil.dateAndTimeFullString(timestamp) + " for: " + DateUtil.niceTimeScalar(duration) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BaseElement.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BaseElement.kt new file mode 100644 index 0000000000..6357c7a367 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BaseElement.kt @@ -0,0 +1,30 @@ +package info.nightscout.androidaps.plugins.general.tidepool.elements + +import com.google.gson.annotations.Expose +import info.nightscout.androidaps.utils.DateUtil + +open class BaseElement { + @Expose + var deviceTime: String = "" + @Expose + var time: String = "" + @Expose + var timezoneOffset: Int = 0 + @Expose + var type: String? = null + @Expose + var origin: Origin? = null + + + internal fun populate(timestamp: Long, uuid: String): BaseElement { + deviceTime = DateUtil.toISONoZone(timestamp) + time = DateUtil.toISOAsUTC(timestamp) + timezoneOffset = DateUtil.getTimeZoneOffsetMinutes(timestamp) // TODO + origin = Origin(uuid) + return this + } + + inner class Origin internal constructor(@field:Expose + internal var id: String) + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BloodGlucoseElement.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BloodGlucoseElement.kt new file mode 100644 index 0000000000..6057c5cd68 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BloodGlucoseElement.kt @@ -0,0 +1,33 @@ +package info.nightscout.androidaps.plugins.general.tidepool.elements + +import com.google.gson.annotations.Expose + +class BloodGlucoseElement : BaseElement() { + + @Expose + var subType: String = "manual" + @Expose + var units: String = "mg/dL" + @Expose + var value: Int = 0 + +/* TODO: from careportal ???? + fun fromBloodTest(bloodtest: BloodTest): BloodGlucoseElement { + val bg = BloodGlucoseElement() + bg.populate(bloodtest.timestamp, bloodtest.uuid) + + bg.subType = "manual" // TODO + bg.value = bloodtest.mgdl.toInt() + return bg + } + + fun fromBloodTests(bloodTestList: List?): List? { + if (bloodTestList == null) return null + val results = LinkedList() + for (bt in bloodTestList) { + results.add(fromBloodTest(bt)) + } + return results + } + */ +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BolusElement.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BolusElement.kt new file mode 100644 index 0000000000..eaa58320ee --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BolusElement.kt @@ -0,0 +1,30 @@ +package info.nightscout.androidaps.plugins.general.tidepool.elements + +import com.google.gson.annotations.Expose +import info.nightscout.androidaps.plugins.treatments.Treatment + +class BolusElement : BaseElement() { + @Expose + var subType = "normal" + @Expose + var normal: Double = 0.0 + @Expose + var expectedNormal: Double = 0.0 + + init { + type = "bolus"; + } + + fun create(insulinDelivered: Double, timestamp: Long, uuid: String): BolusElement { + this.normal = insulinDelivered + this.expectedNormal = insulinDelivered + populate(timestamp, uuid) + return this + } + + companion object { + fun fromTreatment(treatment: Treatment): BolusElement { + return BolusElement().create(treatment.insulin, treatment.date, "uuid-AAPS") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/SensorGlucoseElement.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/SensorGlucoseElement.kt new file mode 100644 index 0000000000..da51ee0e0e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/SensorGlucoseElement.kt @@ -0,0 +1,34 @@ +package info.nightscout.androidaps.plugins.general.tidepool.elements + +import com.google.gson.annotations.Expose +import info.nightscout.androidaps.db.BgReading +import java.util.* + +class SensorGlucoseElement : BaseElement() { + + @Expose + internal var units: String = "mg/dL" + @Expose + internal var value: Int = 0 + + init { + this.type = "cbg" + } + + companion object { + internal fun fromBgReading(bgReading: BgReading): SensorGlucoseElement { + val sensorGlucose = SensorGlucoseElement() + sensorGlucose.populate(bgReading.date, "uuid-AAPS") + sensorGlucose.value = bgReading.value.toInt() + return sensorGlucose + } + + internal fun fromBgReadings(bgReadingList: List): List { + val results = LinkedList() + for (bgReading in bgReadingList) { + results.add(fromBgReading(bgReading)) + } + return results + } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/WizardElement.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/WizardElement.kt new file mode 100644 index 0000000000..7c22bec8d6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/WizardElement.kt @@ -0,0 +1,37 @@ +package info.nightscout.androidaps.plugins.general.tidepool.elements + +import com.google.gson.annotations.Expose +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.plugins.treatments.Treatment + +class WizardElement internal constructor() : BaseElement() { + + @Expose + var units = "mg/dL" + @Expose + var carbInput: Double = 0.toDouble() + @Expose + var insulinCarbRatio: Double = 0.toDouble() + @Expose + var bolus: BolusElement? = null + + init { + type = "wizard" + } + + companion object { + + fun fromTreatment(treatment: Treatment): WizardElement { + val result = WizardElement().populate(treatment.date, "uuid-AAPS") as WizardElement + result.carbInput = treatment.carbs + result.insulinCarbRatio = ProfileFunctions.getInstance().getProfile(treatment.date)!!.ic + if (treatment.insulin > 0) { + result.bolus = BolusElement().create(treatment.insulin, treatment.date, "uuid-AAPS") + } else { + result.bolus = BolusElement().create(0.0001, treatment.date, "uuid-AAPS") // fake insulin record + } + return result + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolStatus.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolStatus.kt new file mode 100644 index 0000000000..44feea7406 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolStatus.kt @@ -0,0 +1,6 @@ +package info.nightscout.androidaps.plugins.general.tidepool.events + +import info.nightscout.androidaps.events.Event + +class EventTidepoolStatus (val status: String) : Event() { +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/AuthReplyMessage.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/AuthReplyMessage.kt new file mode 100644 index 0000000000..c72b1916d6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/AuthReplyMessage.kt @@ -0,0 +1,29 @@ +package info.nightscout.androidaps.plugins.general.tidepool.messages + +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName +import info.nightscout.androidaps.plugins.general.tidepool.utils.GsonInstance + +class AuthReplyMessage { + + @Expose + @SerializedName("emailVerified") + internal var emailVerified: Boolean? = null + @Expose + @SerializedName("emails") + internal var emailList: List? = null + @Expose + @SerializedName("termsAccepted") + internal var termsDate: String? = null + @Expose + @SerializedName("userid") + internal var userid: String? = null + @Expose + @SerializedName("username") + internal var username: String? = null + + fun toS(): String { + return GsonInstance.defaultGsonInstance().toJson(this) + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/AuthRequestMessage.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/AuthRequestMessage.kt new file mode 100644 index 0000000000..33c230ea75 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/AuthRequestMessage.kt @@ -0,0 +1,17 @@ +package info.nightscout.androidaps.plugins.general.tidepool.messages + +import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.SP +import info.nightscout.androidaps.utils.StringUtils +import okhttp3.Credentials + +class AuthRequestMessage : BaseMessage() { + companion object { + fun getAuthRequestHeader(): String? { + val username = SP.getString(R.string.key_tidepool_username, null) + val password = SP.getString(R.string.key_tidepool_password, null) + + return if (StringUtils.emptyString(username) || StringUtils.emptyString(password)) null else Credentials.basic(username.trim { it <= ' ' }, password) + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/BaseMessage.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/BaseMessage.kt new file mode 100644 index 0000000000..501e7f94a7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/BaseMessage.kt @@ -0,0 +1,16 @@ +package info.nightscout.androidaps.plugins.general.tidepool.messages + +import info.nightscout.androidaps.plugins.general.tidepool.utils.GsonInstance +import okhttp3.MediaType +import okhttp3.RequestBody + +open class BaseMessage { + fun toS(): String { + return GsonInstance.defaultGsonInstance().toJson(this) ?: "null" + } + + fun getBody(): RequestBody { + return RequestBody.create(MediaType.parse("application/json"), this.toS()) + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/CloseDatasetRequestMessage.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/CloseDatasetRequestMessage.kt new file mode 100644 index 0000000000..f8f6780971 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/CloseDatasetRequestMessage.kt @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.plugins.general.tidepool.messages + +import com.google.gson.annotations.Expose + +class CloseDatasetRequestMessage : BaseMessage() { + @Expose + internal var dataState = "closed" +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/DatasetReplyMessage.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/DatasetReplyMessage.kt new file mode 100644 index 0000000000..7809ddbce0 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/DatasetReplyMessage.kt @@ -0,0 +1,42 @@ +package info.nightscout.androidaps.plugins.general.tidepool.messages + + +class DatasetReplyMessage { + + internal var data: Data? = null + + // openDataSet and others return this in the root of the json reply it seems + internal var id: String? = null + internal var uploadId: String? = null + + inner class Data { + internal var createdTime: String? = null + internal var deviceId: String? = null + internal var id: String? = null + internal var time: String? = null + internal var timezone: String? = null + internal var timezoneOffset: Int = 0 + internal var type: String? = null + internal var uploadId: String? = null + internal var client: Client? = null + internal var computerTime: String? = null + internal var dataSetType: String? = null + internal var deviceManufacturers: List? = null + internal var deviceModel: String? = null + internal var deviceSerialNumber: String? = null + internal var deviceTags: List? = null + internal var timeProcessing: String? = null + internal var version: String? = null + // meta + } + + inner class Client { + internal var name: String? = null + internal var version: String? = null + + } + + fun getUploadId(): String? { + return if (data != null && data!!.uploadId != null) data!!.uploadId else uploadId + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/OpenDatasetRequestMessage.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/OpenDatasetRequestMessage.kt new file mode 100644 index 0000000000..cca39ff9c2 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/OpenDatasetRequestMessage.kt @@ -0,0 +1,63 @@ +package info.nightscout.androidaps.plugins.general.tidepool.messages + +import com.google.gson.annotations.Expose +import info.nightscout.androidaps.BuildConfig +import info.nightscout.androidaps.interfaces.PluginBase +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.T +import java.util.* + +class OpenDatasetRequestMessage : BaseMessage() { + + @Expose + var deviceId: String? = null + @Expose + var time = DateUtil.toISOAsUTC(DateUtil.now()) + @Expose + var timezoneOffset = (DateUtil.getTimeZoneOffsetMs() / T.mins(1).msecs()).toInt() + @Expose + var type = "upload" + //public String byUser; + @Expose + var client = ClientInfo() + @Expose + var computerTime = DateUtil.toISONoZone(DateUtil.now()) + @Expose + var dataSetType = UPLOAD_TYPE // omit for "normal" + @Expose + var deviceManufacturers = arrayOf((ConfigBuilderPlugin.getPlugin().activeBgSource as PluginBase).name) + @Expose + var deviceModel = (ConfigBuilderPlugin.getPlugin().activeBgSource as PluginBase).name + @Expose + var deviceTags = arrayOf("bgm", "cgm", "insulin-pump") + @Expose + var deduplicator = Deduplicator() + @Expose + var timeProcessing = "none" + @Expose + var timezone = TimeZone.getDefault().id + @Expose + var version = BuildConfig.VERSION_NAME + + inner class ClientInfo { + @Expose + val name = BuildConfig.APPLICATION_ID + @Expose + val version = "0.1.0" // TODO: const it + } + + inner class Deduplicator { + @Expose + val name = "org.tidepool.deduplicator.dataset.delete.origin" + } + + companion object { + internal val UPLOAD_TYPE = "continuous" + + fun isNormal(): Boolean { + return UPLOAD_TYPE == "normal" + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/UploadReplyMessage.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/UploadReplyMessage.kt new file mode 100644 index 0000000000..2054eb237a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/UploadReplyMessage.kt @@ -0,0 +1,6 @@ +package info.nightscout.androidaps.plugins.general.tidepool.messages + +class UploadReplyMessage { + + internal var data: List? = null +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/utils/GsonInstance.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/utils/GsonInstance.kt new file mode 100644 index 0000000000..2c7ceb81d0 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/utils/GsonInstance.kt @@ -0,0 +1,17 @@ +package info.nightscout.androidaps.plugins.general.tidepool.utils + +import com.google.gson.Gson +import com.google.gson.GsonBuilder + +object GsonInstance { + private var gson_instance: Gson? = null + + fun defaultGsonInstance(): Gson { + if (gson_instance == null) { + gson_instance = GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .create() + } + return gson_instance as Gson + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/utils/LogSlider.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/utils/LogSlider.kt new file mode 100644 index 0000000000..f4515b328b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/utils/LogSlider.kt @@ -0,0 +1,12 @@ +package info.nightscout.androidaps.plugins.general.tidepool.utils + +object LogSlider { + // logarithmic slider with positions start - end representing values start - end, calculate value at selected position + fun calc(sliderStart: Int, sliderEnd: Int, start: Double, end: Double, position: Int): Double { + var valueStart = start + var valueEnd = end + valueStart = Math.log(Math.max(1.0, valueStart)) + valueEnd = Math.log(Math.max(1.0, valueEnd)) + return Math.exp(valueStart + (valueEnd - valueStart) / (sliderEnd - sliderStart) * (position - sliderStart)) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/utils/RateLimit.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/utils/RateLimit.kt new file mode 100644 index 0000000000..416cf2f9f2 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/utils/RateLimit.kt @@ -0,0 +1,29 @@ +package info.nightscout.androidaps.plugins.general.tidepool.utils + +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.T +import org.slf4j.LoggerFactory +import java.util.* + +object RateLimit { + + private val rateLimits = HashMap() + + private val log = LoggerFactory.getLogger(L.TIDEPOOL) + + // return true if below rate limit + @Synchronized + fun ratelimit(name: String, seconds: Int): Boolean { + // check if over limit + if (rateLimits.containsKey(name) && DateUtil.now() - rateLimits.get(name)!! < T.secs(seconds.toLong()).msecs()) { + if (L.isEnabled(L.TIDEPOOL)) + log.debug("$name rate limited: $seconds seconds") + return false + } + // not over limit + rateLimits.put(name, DateUtil.now()) + return true + } +} + diff --git a/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java b/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java index 7cc4424244..8e875e6dca 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java @@ -8,6 +8,8 @@ import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.ISODateTimeFormat; import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; @@ -71,6 +73,18 @@ public class DateUtil { return toISOString(new Date(date), FORMAT_DATE_ISO_OUT, TimeZone.getTimeZone("UTC")); } + public static String toISOAsUTC(final long timestamp) { + final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'0000Z'", Locale.US); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + return format.format(timestamp); + } + + public static String toISONoZone(final long timestamp) { + final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US); + format.setTimeZone(TimeZone.getDefault()); + return format.format(timestamp); + } + public static Date toDate(Integer seconds) { Calendar calendar = new GregorianCalendar(); calendar.set(Calendar.MONTH, 0); // Set january to be sure we miss DST changing @@ -187,4 +201,80 @@ public class DateUtil { long diff = Math.abs(date - now()); return diff < T.mins(2).msecs(); } + + public static long getTimeZoneOffsetMs() { + return new GregorianCalendar().getTimeZone().getRawOffset(); + } + + public static int getTimeZoneOffsetMinutes(final long timestamp) { + return TimeZone.getDefault().getOffset(timestamp) / 60000; + } + + public static String niceTimeScalar(long t) { + String unit = MainApp.gs(R.string.unit_second); + t = t / 1000; + if (t != 1) unit = MainApp.gs(R.string.unit_seconds); + if (t > 59) { + unit = MainApp.gs(R.string.unit_minute); + t = t / 60; + if (t != 1) unit = MainApp.gs(R.string.unit_minutes); + if (t > 59) { + unit = MainApp.gs(R.string.unit_hour); + t = t / 60; + if (t != 1) unit = MainApp.gs(R.string.unit_hours); + if (t > 24) { + unit = MainApp.gs(R.string.unit_day); + t = t / 24; + if (t != 1) unit = MainApp.gs(R.string.unit_days); + if (t > 28) { + unit = MainApp.gs(R.string.unit_week); + t = t / 7; + if (t != 1) unit = MainApp.gs(R.string.unit_weeks); + } + } + } + } + //if (t != 1) unit = unit + "s"; //implemented plurality in every step, because in other languages plurality of time is not every time adding the same character + return qs((double) t, 0) + " " + unit; + } + + // singletons to avoid repeated allocation + private static DecimalFormatSymbols dfs; + private static DecimalFormat df; + public static String qs(double x, int digits) { + + if (digits == -1) { + digits = 0; + if (((int) x != x)) { + digits++; + if ((((int) x * 10) / 10 != x)) { + digits++; + if ((((int) x * 100) / 100 != x)) digits++; + } + } + } + + if (dfs == null) { + final DecimalFormatSymbols local_dfs = new DecimalFormatSymbols(); + local_dfs.setDecimalSeparator('.'); + dfs = local_dfs; // avoid race condition + } + + final DecimalFormat this_df; + // use singleton if on ui thread otherwise allocate new as DecimalFormat is not thread safe + if (Thread.currentThread().getId() == 1) { + if (df == null) { + final DecimalFormat local_df = new DecimalFormat("#", dfs); + local_df.setMinimumIntegerDigits(1); + df = local_df; // avoid race condition + } + this_df = df; + } else { + this_df = new DecimalFormat("#", dfs); + } + + this_df.setMaximumFractionDigits(digits); + return this_df.format(x); + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/StringUtils.java b/app/src/main/java/info/nightscout/androidaps/utils/StringUtils.java index cad2b76291..37a3b7b7bf 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/StringUtils.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/StringUtils.java @@ -17,4 +17,9 @@ public class StringUtils { return string; } + + public static boolean emptyString(final String str) { + return str == null || str.length() == 0; + } + } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 798c076d05..89a32876be 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1344,6 +1344,10 @@ Data Age Mins If enabled, uploads will go to https://int-app.tidepool.org instead of the regular https://app.tidepool.org/ Use Integration (test) servers + Tidepool + TDP + Uploads data to Tidepool + tidepool_last_end smbmaxminutes Dayligh Saving time @@ -1363,9 +1367,6 @@ old version very old version New version for at least %1$d days available! Fallback to LGS after 60 days, loop will be disabled after 90 days - Tidepool - TDP - Uploads data to Tidepool %1$d day From 048ea4d489566b4e88bb5312e2d5276d7ce4745f Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Mon, 3 Jun 2019 00:03:06 +0200 Subject: [PATCH 068/152] Tidepool test UI --- app/build.gradle | 2 +- .../general/tidepool/TidepoolFragment.kt | 41 +++++++++++++++ .../tidepool/TidepoolJavaFragment.java | 36 +++++++++++++ .../general/tidepool/TidepoolPlugin.kt | 31 ++++++++++- .../general/tidepool/comm/TidepoolUploader.kt | 30 +++++------ .../general/tidepool/comm/UploadChunk.kt | 32 +++++++----- .../tidepool/events/EventTidepoolDoUpload.kt | 4 ++ .../tidepool/events/EventTidepoolResetData.kt | 6 +++ app/src/main/res/layout/tidepool_fragment.xml | 51 +++++++++++++++++++ 9 files changed, 202 insertions(+), 31 deletions(-) create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolFragment.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolJavaFragment.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolDoUpload.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolResetData.kt create mode 100644 app/src/main/res/layout/tidepool_fragment.xml diff --git a/app/build.gradle b/app/build.gradle index e6b06601bf..08f92dc239 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -104,7 +104,7 @@ android { targetSdkVersion 25 multiDexEnabled true versionCode 1500 - version "2.3.1-dev" + version "2.3.1-tidepool" buildConfigField "String", "VERSION", '"' + version + '"' buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"' buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"' diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolFragment.kt new file mode 100644 index 0000000000..de6cda31ff --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolFragment.kt @@ -0,0 +1,41 @@ +package info.nightscout.androidaps.plugins.general.tidepool + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.squareup.otto.Subscribe +import info.nightscout.androidaps.R +import info.nightscout.androidaps.plugins.common.SubscriberFragment +import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientUpdateGUI +import info.nightscout.androidaps.plugins.general.tidepool.comm.TidepoolUploader +import kotlinx.android.synthetic.main.tidepool_fragment.* + +class TidepoolFragment : SubscriberFragment() { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.tidepool_fragment, container, false) + + tidepool_login.setOnClickListener { + TidepoolUploader.doLogin() + } + tidepool_removeall.setOnClickListener { } + tidepool_uploadnow.setOnClickListener { } + return view + } + + @Subscribe + fun onStatusEvent(ev: EventNSClientUpdateGUI) { + updateGUI() + } + + override fun updateGUI() { + val activity = activity + activity?.runOnUiThread { +// TidepoolPlugin.updateLog() +// tidepool_log.text = TidepoolPlugin.textLog + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolJavaFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolJavaFragment.java new file mode 100644 index 0000000000..24b465bda6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolJavaFragment.java @@ -0,0 +1,36 @@ +package info.nightscout.androidaps.plugins.general.tidepool; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.common.SubscriberFragment; +import info.nightscout.androidaps.plugins.general.tidepool.comm.TidepoolUploader; +import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolResetData; + +public class TidepoolJavaFragment extends SubscriberFragment { + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.tidepool_fragment, container, false); + + Button login = view.findViewById(R.id.tidepool_login); + login.setOnClickListener(v -> { + TidepoolUploader.INSTANCE.doLogin(); + }); + Button removeall = view.findViewById(R.id.tidepool_removeall); + removeall.setOnClickListener(v -> { + MainApp.bus().post(new EventTidepoolResetData()); + }); + return view; + } + + @Override + protected void updateGUI() { + + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt index 724c35b606..51698112e5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt @@ -9,7 +9,10 @@ import info.nightscout.androidaps.interfaces.PluginBase import info.nightscout.androidaps.interfaces.PluginDescription import info.nightscout.androidaps.interfaces.PluginType import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.general.tidepool.comm.Session import info.nightscout.androidaps.plugins.general.tidepool.comm.TidepoolUploader +import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolDoUpload +import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolResetData import info.nightscout.androidaps.plugins.general.tidepool.utils.RateLimit import info.nightscout.androidaps.receivers.ChargingStateReceiver import info.nightscout.androidaps.utils.SP @@ -19,12 +22,15 @@ object TidepoolPlugin : PluginBase(PluginDescription() .mainType(PluginType.GENERAL) .pluginName(R.string.tidepool) .shortName(R.string.tidepool_shortname) + .fragmentClass(TidepoolJavaFragment::class.java.name) .preferencesId(R.xml.pref_tidepool) .description(R.string.description_tidepool) ) { private val log = LoggerFactory.getLogger(L.TIDEPOOL) private var wifiConnected = false + var session: Session? = null + override fun onStart() { MainApp.bus().register(this) super.onStart() @@ -35,6 +41,12 @@ object TidepoolPlugin : PluginBase(PluginDescription() super.onStop() } + fun doUpload() { + if (session == null) + session = TidepoolUploader.doLogin() + else TidepoolUploader.doUpload(session!!) + } + @Suppress("UNUSED_PARAMETER") @Subscribe fun onStatusEvent(ev: EventNewBG) { @@ -42,7 +54,24 @@ object TidepoolPlugin : PluginBase(PluginDescription() && (!SP.getBoolean(R.string.key_tidepool_only_while_charging, false) || ChargingStateReceiver.isCharging()) && (!SP.getBoolean(R.string.key_tidepool_only_while_unmetered, false) || wifiConnected) && RateLimit.ratelimit("tidepool-new-data-upload", 1200)) - TidepoolUploader.doLogin() + doUpload() + } + + @Suppress("UNUSED_PARAMETER") + @Subscribe + fun onEventTidepoolDoUpload(ev: EventTidepoolDoUpload) { + doUpload() + } + + @Suppress("UNUSED_PARAMETER") + @Subscribe + fun onEventTidepoolResetData(ev: EventTidepoolResetData) { + if (session == null) + session = TidepoolUploader.doLogin() + if (session != null) { + TidepoolUploader.deleteDataSet(session!!) + TidepoolUploader.startSession(session!!) + } } @Subscribe diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt index 7c0939e53d..3bfe9a7aab 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt @@ -59,10 +59,10 @@ object TidepoolUploader { } @Synchronized - fun doLogin() { + fun doLogin(): Session? { if (!SP.getBoolean(R.string.key_cloud_storage_tidepool_enable, false)) { log.debug("Cannot login as disabled by preference") - return + return null } // TODO failure backoff extendWakeLock(30000) @@ -72,10 +72,12 @@ object TidepoolUploader { status("Connecting") call?.enqueue(TidepoolCallback(session, "Login", { startSession(session) }, { loginFailed() })) + return session } else { if (L.isEnabled(L.TIDEPOOL)) log.debug("Cannot do login as user credentials have not been set correctly") status("Invalid credentials") releaseWakeLock() + return null } } @@ -103,7 +105,7 @@ object TidepoolUploader { releaseWakeLock() } - private fun startSession(session: Session) { + fun startSession(session: Session) { extendWakeLock(30000) if (session.authReply?.userid != null) { // See if we already have an open data set to write to @@ -130,7 +132,7 @@ object TidepoolUploader { } } - private fun doUpload(session: Session) { + fun doUpload(session: Session) { if (!TidepoolPlugin.enabled()) { if (L.isEnabled(L.TIDEPOOL)) log.debug("Cannot upload - preference disabled") @@ -164,6 +166,7 @@ object TidepoolUploader { } private fun status(status: String) { + log.debug("New status: $status") MainApp.bus().post(EventTidepoolStatus(status)) } @@ -186,23 +189,18 @@ object TidepoolUploader { releaseWakeLock() } - private fun deleteData(session: Session) { - if (session.authReply!!.userid != null) { - val call = session.service!!.deleteAllData(session.token!!, session.authReply!!.userid!!) - call.enqueue(TidepoolCallback(session, "Delete Data", {}, {})) - } else { - log.error("Got login response but cannot determine userid - cannot proceed") - } - } - private fun getDataSet(session: Session) { val call = session.service!!.getDataSet(session.token!!, "bogus") call.enqueue(TidepoolCallback(session, "Get Data", {}, {})) } - private fun deleteDataSet(session: Session) { - val call = session.service!!.deleteDataSet(session.token!!, "bogus") - call.enqueue(TidepoolCallback(session, "Delete Data", {}, {})) + fun deleteDataSet(session: Session) { + if (session.datasetReply?.id != null) { + val call = session.service?.deleteDataSet(session.token!!, session.datasetReply!!.id!!) + call?.enqueue(TidepoolCallback(session, "Delete Dataset", {}, {})) + } else { + log.error("Got login response but cannot determine dataseId - cannot proceed") + } } @Synchronized diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/UploadChunk.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/UploadChunk.kt index 0fa1da3ecc..bcedcf246d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/UploadChunk.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/UploadChunk.kt @@ -1,8 +1,8 @@ package info.nightscout.androidaps.plugins.general.tidepool.comm -import android.util.Log import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.R +import info.nightscout.androidaps.logging.L import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions import info.nightscout.androidaps.plugins.general.tidepool.elements.* import info.nightscout.androidaps.plugins.general.tidepool.utils.GsonInstance @@ -11,6 +11,7 @@ import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.SP import info.nightscout.androidaps.utils.T +import org.slf4j.LoggerFactory import java.util.* object UploadChunk { @@ -21,6 +22,7 @@ object UploadChunk { private val DEFAULT_WINDOW_OFFSET = T.mins(15).msecs() private val MAX_LATENCY_THRESHOLD_MINUTES: Long = 1440 // minutes per day + private val log = LoggerFactory.getLogger(L.TIDEPOOL) fun getNext(session: Session): String? { session.start = getLastEnd() @@ -28,7 +30,7 @@ object UploadChunk { val result = get(session.start, session.end) if (result != null && result.length < 3) { - Log.d(TAG, "No records in this time period, setting start to best end time") + if (L.isEnabled(L.TIDEPOOL)) log.debug("No records in this time period, setting start to best end time") setLastEnd(Math.max(session.end, getOldestRecordTimeStamp())) } return result @@ -36,13 +38,13 @@ object UploadChunk { operator fun get(start: Long, end: Long): String? { - Log.e(TAG, "Syncing data between: " + DateUtil.dateAndTimeFullString(start) + " -> " + DateUtil.dateAndTimeFullString(end)) + if (L.isEnabled(L.TIDEPOOL)) log.debug("Syncing data between: " + DateUtil.dateAndTimeFullString(start) + " -> " + DateUtil.dateAndTimeFullString(end)) if (end <= start) { - Log.e(TAG, "End is <= start: " + DateUtil.dateAndTimeFullString(start) + " " + DateUtil.dateAndTimeFullString(end)) + if (L.isEnabled(L.TIDEPOOL)) log.debug("End is <= start: " + DateUtil.dateAndTimeFullString(start) + " " + DateUtil.dateAndTimeFullString(end)) return null } if (end - start > MAX_UPLOAD_SIZE) { - Log.e(TAG, "More than max range - rejecting") + if (L.isEnabled(L.TIDEPOOL)) log.debug("More than max range - rejecting") return null } @@ -61,7 +63,7 @@ object UploadChunk { val value = getLatencySliderValue(SP.getInt(R.string.key_tidepool_window_latency, 0)).toLong() return Math.max(T.mins(value).msecs(), DEFAULT_WINDOW_OFFSET) } catch (e: Exception) { - Log.e(TAG, "Reverting to default of 15 minutes due to Window Size exception: $e") + if (L.isEnabled(L.TIDEPOOL)) log.debug("Reverting to default of 15 minutes due to Window Size exception: $e") return DEFAULT_WINDOW_OFFSET // default } @@ -79,10 +81,11 @@ object UploadChunk { fun setLastEnd(time: Long) { if (time > getLastEnd()) { - SP.putLong(R.string.key_tidepool_last_end, time) - Log.d(TAG, "Updating last end to: " + DateUtil.dateAndTimeFullString(time)) + //TODO SP.putLong(R.string.key_tidepool_last_end, time) + SP.putLong(R.string.key_tidepool_last_end, 0) + if (L.isEnabled(L.TIDEPOOL)) log.debug("Updating last end to: " + DateUtil.dateAndTimeFullString(time)) } else { - Log.e(TAG, "Cannot set last end to: " + DateUtil.dateAndTimeFullString(time) + " vs " + DateUtil.dateAndTimeFullString(getLastEnd())) + if (L.isEnabled(L.TIDEPOOL)) log.debug("Cannot set last end to: " + DateUtil.dateAndTimeFullString(time) + " vs " + DateUtil.dateAndTimeFullString(getLastEnd())) } } @@ -123,7 +126,10 @@ object UploadChunk { } internal fun getBgReadings(start: Long, end: Long): List { - return SensorGlucoseElement.fromBgReadings(MainApp.getDbHelper().getBgreadingsDataFromTime(start, end, true)) + val readings = MainApp.getDbHelper().getBgreadingsDataFromTime(start, end, true) + if (L.isEnabled(L.TIDEPOOL)) + log.debug("${readings.size} selected for upload") + return SensorGlucoseElement.fromBgReadings(readings) } internal fun getBasals(start: Long, end: Long): List { @@ -136,15 +142,15 @@ object UploadChunk { if (current != null) { if (this_rate != current.rate) { current.duration = temporaryBasal.date - current.timestamp - Log.d(TAG, "Adding current: " + current.toS()) + if (L.isEnabled(L.TIDEPOOL)) log.debug("Adding current: " + current.toS()) if (current.isValid()) { basals.add(current) } else { - Log.e(TAG, "Current basal is invalid: " + current.toS()) + if (L.isEnabled(L.TIDEPOOL)) log.debug("Current basal is invalid: " + current.toS()) } current = null } else { - Log.d(TAG, "Same rate as previous basal record: " + current.rate + " " + temporaryBasal.toStringFull()) + if (L.isEnabled(L.TIDEPOOL)) log.debug("Same rate as previous basal record: " + current.rate + " " + temporaryBasal.toStringFull()) } } if (current == null) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolDoUpload.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolDoUpload.kt new file mode 100644 index 0000000000..a77d3232d6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolDoUpload.kt @@ -0,0 +1,4 @@ +package info.nightscout.androidaps.plugins.general.tidepool.events + +class EventTidepoolDoUpload { +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolResetData.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolResetData.kt new file mode 100644 index 0000000000..73dac259c3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolResetData.kt @@ -0,0 +1,6 @@ +package info.nightscout.androidaps.plugins.general.tidepool.events + +import info.nightscout.androidaps.events.Event + +class EventTidepoolResetData :Event() { +} \ No newline at end of file diff --git a/app/src/main/res/layout/tidepool_fragment.xml b/app/src/main/res/layout/tidepool_fragment.xml new file mode 100644 index 0000000000..975dad589e --- /dev/null +++ b/app/src/main/res/layout/tidepool_fragment.xml @@ -0,0 +1,51 @@ + + + + + +