diff --git a/app/build.gradle b/app/build.gradle index 46a2ee19dd..f3a1139a38 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 = { -> @@ -92,6 +95,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 @@ -249,6 +255,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/app/src/main/java/info/nightscout/androidaps/MainActivity.java b/app/src/main/java/info/nightscout/androidaps/MainActivity.java index f441a8b243..31d462a007 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/MainActivity.java @@ -48,9 +48,12 @@ 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; import info.nightscout.androidaps.setupwizard.SetupWizardActivity; import info.nightscout.androidaps.tabs.TabPageAdapter; import info.nightscout.androidaps.utils.AndroidPermission; @@ -59,7 +62,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 +117,11 @@ public class MainActivity extends AppCompatActivity { public void onPageScrollStateChanged(int state) { } }); - VersionChecker.check(); + + //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/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index a00f23fc10..b224e93612 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; @@ -119,7 +120,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 { @@ -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/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/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 new file mode 100644 index 0000000000..7be99e2369 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerPlugin.kt @@ -0,0 +1,79 @@ +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. + * 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 { + checkWarning() + checkUpdate() + return if (isOldVersion(GRACE_PERIOD_VERY_OLD)) + value.set(false, MainApp.gs(R.string.very_old_version), this) + else + value + } + + 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_warning, 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 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 + + 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 + } + + 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) + val GRACE_PERIOD_VERY_OLD = TimeUnit.DAYS.toMillis(90) + +} 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..b8cfa7fffc --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtils.kt @@ -0,0 +1,84 @@ +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 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 + +// 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? { + 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) + +@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() + 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") + +fun compareWithCurrentVersion(newVersion: String?, currentVersion: String) { + val comparison: Int? = newVersion?.versionStrip()?.compareTo(currentVersion.versionStrip()) + when { + comparison == null -> onVersionNotDetectable() + comparison == 0 -> onSameVersionDetected() + comparison > 0 -> onNewVersionDetected(currentVersion = currentVersion, newVersion = newVersion) + else -> log.debug("Version newer than master. Are you developer?") + } +} + +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/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(); - } - -} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 79d626f9be..9aa326361b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1326,6 +1326,15 @@ Wrong code. Command cancelled. Not configured Profile switch created + Version Checker + new_version_available_since + 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 + %1$d day %1$d days 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(); 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..29725e4d9e --- /dev/null +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/versionChecker/VersionCheckerUtilsKtTest.kt @@ -0,0 +1,216 @@ +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 +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() { + val buildGradle = """blabla + | android { + | aosenuthoae + | } + | version = "2.2.2" + | appName = "Aaoeu" + """.trimMargin() + val detectedVersion: String? = buildGradle.byteInputStream().findVersion() + assertEquals("2.2.2", detectedVersion) + } + + + // 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 + | } + | version = "2.2.2-nefarious-underground-mod" + | appName = "Aaoeu" + """.trimMargin() + val detectedVersion: String? = buildGradle.byteInputStream().findVersion() + assertEquals(null, detectedVersion) + } + + @Test + fun findVersionMatchesDoesNotMatchErrorResponse() { + val buildGradle = """Balls! No build.gradle here. Move along""" + 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, 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 + @PrepareForTest(MainApp::class, L::class, SP::class) + fun `should find update4`() { + val bus = prepareBus() + + 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 + @PrepareForTest(MainApp::class, L::class, SP::class) + 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() + 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 + @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()) + + PowerMockito.verifyStatic(SP::class.java, times(1)) + SP.remove(eq(R.string.key_new_version_available_since)) + PowerMockito.verifyNoMoreInteractions(SP::class.java) + } + + @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()) + + 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(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) + `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 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" } }