diff --git a/app/build.gradle b/app/build.gradle index 0c5fe18a24..6d781a8962 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -266,7 +266,10 @@ dependencies { exclude group: "org.json", module: "json" } implementation "com.google.code.gson:gson:2.8.6" - implementation "com.google.guava:guava:24.1-jre" + implementation ("com.google.guava:guava:24.1-jre") { + exclude group: "com.google.code.findbugs", module: "jsr305" + } + implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation "net.danlew:android.joda:2.10.3" @@ -284,6 +287,7 @@ dependencies { testImplementation "joda-time:joda-time:2.10.5" testImplementation("com.google.truth:truth:0.39") { exclude group: "com.google.guava", module: "guava" + exclude group: "com.google.code.findbugs", module: "jsr305" } testImplementation "org.skyscreamer:jsonassert:1.5.0" testImplementation "org.hamcrest:hamcrest-all:1.3" @@ -293,9 +297,6 @@ 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" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" @@ -307,6 +308,10 @@ dependencies { implementation "com.squareup.retrofit2:adapter-rxjava2:2.6.2" implementation "com.squareup.retrofit2:converter-gson:2.6.2" + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0-alpha03' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test:rules:1.3.0-alpha03' + androidTestImplementation 'com.google.code.findbugs:jsr305:3.0.2' } diff --git a/app/src/androidTest/java/info/nightscout/androidaps/ApplicationTest.java b/app/src/androidTest/java/info/nightscout/androidaps/ApplicationTest.java deleted file mode 100644 index a047e606a9..0000000000 --- a/app/src/androidTest/java/info/nightscout/androidaps/ApplicationTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package info.nightscout.androidaps; - -import android.app.Application; -import android.test.ApplicationTestCase; - -/** - * Testing Fundamentals - */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); - } -} \ No newline at end of file diff --git a/app/src/androidTest/java/info/nightscout/androidaps/EspressoHelper.kt b/app/src/androidTest/java/info/nightscout/androidaps/EspressoHelper.kt new file mode 100644 index 0000000000..b50bb746fb --- /dev/null +++ b/app/src/androidTest/java/info/nightscout/androidaps/EspressoHelper.kt @@ -0,0 +1,27 @@ +package info.nightscout.androidaps + +import androidx.test.espresso.ViewAction +import androidx.test.espresso.ViewInteraction +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers + +fun ViewInteraction.isDisplayed(): Boolean { + try { + check(matches(ViewMatchers.isDisplayed())) + return true + } catch (e: Throwable) { + return false + } +} + +fun ViewInteraction.waitAndPerform(viewActions: ViewAction): ViewInteraction? { + val startTime = System.currentTimeMillis() + while (!isDisplayed()) { + Thread.sleep(100) + if (System.currentTimeMillis() - startTime >= 5000) { + throw AssertionError("View not visible after 5000 milliseconds") + } + } + return perform(viewActions) +} + diff --git a/app/src/androidTest/java/info/nightscout/androidaps/SetupWizardActivityTest.kt b/app/src/androidTest/java/info/nightscout/androidaps/SetupWizardActivityTest.kt new file mode 100644 index 0000000000..dd1c0b12bc --- /dev/null +++ b/app/src/androidTest/java/info/nightscout/androidaps/SetupWizardActivityTest.kt @@ -0,0 +1,222 @@ +package info.nightscout.androidaps + + +import android.os.SystemClock +import android.view.View +import android.view.ViewGroup +import androidx.test.espresso.Espresso.onData +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.scrollTo +import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import androidx.test.rule.ActivityTestRule +import androidx.test.rule.GrantPermissionRule +import info.nightscout.androidaps.data.Profile +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin +import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin +import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin +import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin +import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin +import info.nightscout.androidaps.plugins.source.RandomBgPlugin +import info.nightscout.androidaps.setupwizard.SetupWizardActivity +import info.nightscout.androidaps.utils.HardLimits +import info.nightscout.androidaps.utils.SP +import info.nightscout.androidaps.utils.isRunningTest +import org.hamcrest.CoreMatchers.allOf +import org.hamcrest.Description +import org.hamcrest.Matcher +import org.hamcrest.Matchers +import org.hamcrest.TypeSafeMatcher +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + + +@LargeTest +@RunWith(AndroidJUnit4::class) +class SetupWizardActivityTest { + + @Rule + @JvmField + var mActivityTestRule = ActivityTestRule(SetupWizardActivity::class.java) + + @Rule + @JvmField + var mGrantPermissionRule = + GrantPermissionRule.grant( + android.Manifest.permission.ACCESS_FINE_LOCATION, + android.Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, + android.Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + + @Before + fun clear() { + SP.clear() + } +/* + +To run from command line +gradlew connectedFullDebugAndroidTest + +do not run when your production phone is connected !!! + +do this before for running in emulator +adb shell settings put global window_animation_scale 0 & +adb shell settings put global transition_animation_scale 0 & +adb shell settings put global animator_duration_scale 0 & + */ + + + @Test + fun setupWizardActivityTest() { + Assert.assertTrue(isRunningTest()) + // Welcome page + onView(withId(R.id.next_button)).perform(click()) + // Language selection + onView(withText("English")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Agreement page + onView(withText("I UNDERSTAND AND AGREE")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Loction permission + var askButton = onView(withText("Ask for permission")) + if (askButton.isDisplayed()) { + askButton.perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + } + // Store permission + askButton = onView(withText("Ask for permission")) + if (askButton.isDisplayed()) { + askButton.perform(scrollTo(), click()) + onView(withText("OK")).perform(click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + } + // Units selection + onView(withText("mmol/L")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).perform(click()) + // Display target selection + onView(withText("4.2")).perform(scrollTo(), ViewActions.replaceText("5")) + onView(withText("10.0")).perform(scrollTo(), ViewActions.replaceText("11")) + onView(withId(R.id.next_button)).perform(click()) + // NSClient + onView(withId(R.id.next_button)).perform(click()) + // Age selection + onView(withText("Adult")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Insulin selection + onView(withText("Ultra-Rapid Oref")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // BG source selection + onView(withText("Random BG")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Profile selection + onView(withText("Local Profile")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Local profile - DIA + onView(withTagValue(Matchers.`is`("LP_DIA"))).perform(scrollTo(), ViewActions.replaceText("6.0")) + // Local profile - IC + onView(withId(R.id.ic_tab)).perform(scrollTo(), click()) + onView(Matchers.allOf(withTagValue(Matchers.`is`("IC-1-0")), isDisplayed())) + .perform(ViewActions.replaceText("2"), ViewActions.closeSoftKeyboard()) + // Local profile - ISF + onView(withId(R.id.isf_tab)).perform(scrollTo(), click()) + onView(Matchers.allOf(withTagValue(Matchers.`is`("ISF-1-0")), isDisplayed())) + .perform(ViewActions.replaceText("3"), ViewActions.closeSoftKeyboard()) + // Local profile - BAS + onView(withId(R.id.basal_tab)).perform(scrollTo(), click()) + onView(childAtPosition(Matchers.allOf(withId(R.id.localprofile_basal), childAtPosition(withClassName(Matchers.`is`("android.widget.LinearLayout")), 6)), 2)) + .perform(scrollTo(), click()) + onView(Matchers.allOf(withTagValue(Matchers.`is`("BASAL-1-0")), isDisplayed())) + .perform(ViewActions.replaceText("1.1"), ViewActions.closeSoftKeyboard()) + onView(Matchers.allOf(withTagValue(Matchers.`is`("BASAL-1-1")), isDisplayed())) + .perform(ViewActions.replaceText("1.2"), ViewActions.closeSoftKeyboard()) + onView(Matchers.allOf(withId(R.id.timelistedit_time), childAtPosition(childAtPosition(withId(R.id.localprofile_basal), 2), 0))) + .perform(scrollTo(), click()) + onData(Matchers.anything()).inAdapterView(childAtPosition(withClassName(Matchers.`is`("android.widget.PopupWindow\$PopupBackgroundView")), 0)).atPosition(13) + .perform(click()) + // Local profile - TARGET + onView(withId(R.id.target_tab)).perform(scrollTo(), click()) + onView(Matchers.allOf(withTagValue(Matchers.`is`("TARGET-1-0")), isDisplayed())) + .perform(ViewActions.replaceText("6"), ViewActions.closeSoftKeyboard()) + onView(Matchers.allOf(withTagValue(Matchers.`is`("TARGET-2-0")), isDisplayed())) + .perform(ViewActions.replaceText("6.5"), ViewActions.closeSoftKeyboard()) + onView(withText("Save")).perform(scrollTo(), click()) + onView(Matchers.allOf(withId(R.id.localprofile_profileswitch), isDisplayed())) + .perform(scrollTo(), click()) + onView(allOf(withId(R.id.ok), isDisplayed())).perform(click()) + onView(Matchers.allOf(withText("OK"), isDisplayed())).perform(click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Profile switch + askButton = onView(withText("Do Profile Switch")) + if (askButton.isDisplayed()) { + askButton.perform(scrollTo(), click()) + onView(allOf(withId(R.id.ok), isDisplayed())).perform(click()) + onView(Matchers.allOf(withText("OK"), isDisplayed())).perform(click()) + while (ProfileFunctions.getInstance().profile == null) SystemClock.sleep(100) + onView(withId(R.id.next_button)).waitAndPerform(click()) + } + // Pump + onView(withText("Virtual Pump")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // APS + onView(withText("OpenAPS SMB")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Open Closed Loop + onView(withText("Closed Loop")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Loop + askButton = onView(withText("Enable loop")) + if (askButton.isDisplayed()) { + askButton.perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + } + // Sensitivity + onView(withText("Sensitivity Oref1")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Objectives + onView(allOf(withText("Start"), isDisplayed())).perform(scrollTo(), click()) + onView(withId(R.id.finish_button)).waitAndPerform(click()) + + // Verify settings + Assert.assertEquals(Constants.MMOL, ProfileFunctions.getSystemUnits()) + Assert.assertEquals(17.0, HardLimits.maxBolus(), 0.0001) // Adult + Assert.assertTrue(RandomBgPlugin.isEnabled(PluginType.BGSOURCE)) + Assert.assertTrue(LocalProfilePlugin.isEnabled(PluginType.PROFILE)) + val p = ProfileFunctions.getInstance().profile + Assert.assertNotNull(p) + Assert.assertEquals(2.0, p!!.ic, 0.0001) + Assert.assertEquals(3.0 * Constants.MMOLL_TO_MGDL, p.isfMgdl, 0.0001) + Assert.assertEquals(1.1, p.getBasalTimeFromMidnight(0), 0.0001) + Assert.assertEquals(6.0 * Constants.MMOLL_TO_MGDL, p.targetLowMgdl, 0.0001) + Assert.assertTrue(VirtualPumpPlugin.getPlugin().isEnabled(PluginType.PUMP)) + Assert.assertTrue(OpenAPSSMBPlugin.getPlugin().isEnabled(PluginType.APS)) + Assert.assertTrue(LoopPlugin.getPlugin().isEnabled(PluginType.LOOP)) + Assert.assertTrue(SensitivityOref1Plugin.getPlugin().isEnabled(PluginType.SENSITIVITY)) + Assert.assertTrue(ObjectivesPlugin.objectives[0].isStarted) + } + + private fun childAtPosition( + parentMatcher: Matcher, position: Int): Matcher { + + return object : TypeSafeMatcher() { + override fun describeTo(description: Description) { + description.appendText("Child at position $position in parent ") + parentMatcher.describeTo(description) + } + + public override fun matchesSafely(view: View): Boolean { + val parent = view.parent + return parent is ViewGroup && parentMatcher.matches(parent) + && view == parent.getChildAt(position) + } + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/Constants.java b/app/src/main/java/info/nightscout/androidaps/Constants.java index 21a89d8920..92833ac283 100644 --- a/app/src/main/java/info/nightscout/androidaps/Constants.java +++ b/app/src/main/java/info/nightscout/androidaps/Constants.java @@ -78,4 +78,8 @@ public class Constants { //Storage [MB] public static final long MINIMUM_FREE_SPACE = 200; + // Overview + public static final double LOWMARK = 76.0; + public static final double HIGHMARK = 180.0; + } diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index 64d260c99d..4802655d3a 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -51,6 +51,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.source.RandomBgPlugin; import info.nightscout.androidaps.utils.ActivityMonitor; import info.nightscout.androidaps.plugins.general.wear.WearPlugin; import info.nightscout.androidaps.plugins.general.xdripStatusline.StatuslinePlugin; @@ -212,6 +213,7 @@ public class MainApp extends Application { pluginsList.add(SourcePoctechPlugin.getPlugin()); pluginsList.add(SourceTomatoPlugin.getPlugin()); pluginsList.add(SourceEversensePlugin.getPlugin()); + pluginsList.add(RandomBgPlugin.INSTANCE); if (!Config.NSCLIENT) pluginsList.add(SmsCommunicatorPlugin.INSTANCE); pluginsList.add(FoodPlugin.getPlugin()); diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/PluginDescription.java b/app/src/main/java/info/nightscout/androidaps/interfaces/PluginDescription.java index 5882c5870c..2929d3f21b 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/PluginDescription.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/PluginDescription.java @@ -29,8 +29,8 @@ public class PluginDescription { return this; } - public PluginDescription alwaysVisible(boolean alwayVisible) { - this.alwaysVisible = alwayVisible; + public PluginDescription alwaysVisible(boolean alwaysVisible) { + this.alwaysVisible = alwaysVisible; return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt index 34aadf84c6..9864f875e4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt @@ -28,6 +28,7 @@ import info.nightscout.androidaps.plugins.constraints.objectives.events.EventNtp import info.nightscout.androidaps.plugins.constraints.objectives.events.EventObjectivesUpdateGui import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective.ExamTask import info.nightscout.androidaps.receivers.NetworkChangeReceiver +import info.nightscout.androidaps.setupwizard.events.EventSWUpdate import info.nightscout.androidaps.utils.* import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable @@ -217,6 +218,7 @@ class ObjectivesFragment : Fragment() { scrollToCurrentObjective() startUpdateTimer() RxBus.send(EventObjectivesUpdateGui()) + RxBus.send(EventSWUpdate(false)) } else { // move out of UI thread Thread { @@ -234,6 +236,7 @@ class ObjectivesFragment : Fragment() { RxBus.send(EventNtpStatus(MainApp.gs(R.string.success), 100)) SystemClock.sleep(1000) RxBus.send(EventObjectivesUpdateGui()) + RxBus.send(EventSWUpdate(false)) SystemClock.sleep(100) scrollToCurrentObjective() } else { @@ -254,6 +257,7 @@ class ObjectivesFragment : Fragment() { scrollToCurrentObjective() startUpdateTimer() RxBus.send(EventObjectivesUpdateGui()) + RxBus.send(EventSWUpdate(false)) } else // move out of UI thread Thread { @@ -270,6 +274,7 @@ class ObjectivesFragment : Fragment() { RxBus.send(EventNtpStatus(MainApp.gs(R.string.success), 100)) SystemClock.sleep(1000) RxBus.send(EventObjectivesUpdateGui()) + RxBus.send(EventSWUpdate(false)) SystemClock.sleep(100) scrollToCurrentObjective() } else { @@ -284,12 +289,14 @@ class ObjectivesFragment : Fragment() { objective.startedOn = 0 scrollToCurrentObjective() RxBus.send(EventObjectivesUpdateGui()) + RxBus.send(EventSWUpdate(false)) } } holder.unFinish.setOnClickListener { objective.accomplishedOn = 0 scrollToCurrentObjective() RxBus.send(EventObjectivesUpdateGui()) + RxBus.send(EventSWUpdate(false)) } if (objective.hasSpecialInput && !objective.isAccomplished && objective.isStarted && objective.specialActionEnabled()) { // generate random request code if none exists diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt index 96d6396f87..8e4a9581b8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt @@ -1,5 +1,6 @@ package info.nightscout.androidaps.plugins.general.overview +import info.nightscout.androidaps.Constants import info.nightscout.androidaps.R import info.nightscout.androidaps.data.Profile import info.nightscout.androidaps.events.EventRefreshOverview @@ -65,14 +66,14 @@ object OverviewPlugin : PluginBase(PluginDescription() fun determineHighLine(): Double { var highLineSetting = SP.getDouble(R.string.key_high_mark, bgTargetHigh) - if (highLineSetting < 1) highLineSetting = 180.0 + if (highLineSetting < 1) highLineSetting = Constants.HIGHMARK highLineSetting = Profile.toCurrentUnits(highLineSetting) return highLineSetting } fun determineLowLine(): Double { var lowLineSetting = SP.getDouble(R.string.key_low_mark, bgTargetLow) - if (lowLineSetting < 1) lowLineSetting = 76.0 + if (lowLineSetting < 1) lowLineSetting = Constants.LOWMARK lowLineSetting = Profile.toCurrentUnits(lowLineSetting) return lowLineSetting } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.kt index 3b6dbe7315..777afc7331 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.kt @@ -94,14 +94,15 @@ class LocalProfileFragment : Fragment() { localprofile_name.setText(LocalProfilePlugin.currentProfile().name) localprofile_name.addTextChangedListener(textWatch) localprofile_dia.setParams(LocalProfilePlugin.currentProfile().dia, HardLimits.MINDIA, HardLimits.MAXDIA, 0.1, DecimalFormat("0.0"), false, localprofile_save, textWatch) - TimeListEdit(context, view, R.id.localprofile_ic, MainApp.gs(R.string.nsprofileview_ic_label), LocalProfilePlugin.currentProfile().ic, null, HardLimits.MINIC, HardLimits.MAXIC, 0.1, DecimalFormat("0.0"), save) - basalView = TimeListEdit(context, view, R.id.localprofile_basal, MainApp.gs(R.string.nsprofileview_basal_label) + ": " + sumLabel(), LocalProfilePlugin.currentProfile().basal, null, pumpDescription.basalMinimumRate, 10.0, 0.01, DecimalFormat("0.00"), save) + localprofile_dia.setTag("LP_DIA") + TimeListEdit(context, view, R.id.localprofile_ic, "IC", MainApp.gs(R.string.nsprofileview_ic_label), LocalProfilePlugin.currentProfile().ic, null, HardLimits.MINIC, HardLimits.MAXIC, 0.1, DecimalFormat("0.0"), save) + basalView = TimeListEdit(context, view, R.id.localprofile_basal, "BASAL", MainApp.gs(R.string.nsprofileview_basal_label) + ": " + sumLabel(), LocalProfilePlugin.currentProfile().basal, null, pumpDescription.basalMinimumRate, 10.0, 0.01, DecimalFormat("0.00"), save) if (units == Constants.MGDL) { - TimeListEdit(context, view, R.id.localprofile_isf, MainApp.gs(R.string.nsprofileview_isf_label), LocalProfilePlugin.currentProfile().isf, null, HardLimits.MINISF, HardLimits.MAXISF, 1.0, DecimalFormat("0"), save) - TimeListEdit(context, view, R.id.localprofile_target, MainApp.gs(R.string.nsprofileview_target_label), LocalProfilePlugin.currentProfile().targetLow, LocalProfilePlugin.currentProfile().targetHigh, HardLimits.VERY_HARD_LIMIT_TARGET_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TARGET_BG[1].toDouble(), 1.0, DecimalFormat("0"), save) + TimeListEdit(context, view, R.id.localprofile_isf, "ISF", MainApp.gs(R.string.nsprofileview_isf_label), LocalProfilePlugin.currentProfile().isf, null, HardLimits.MINISF, HardLimits.MAXISF, 1.0, DecimalFormat("0"), save) + TimeListEdit(context, view, R.id.localprofile_target, "TARGET", MainApp.gs(R.string.nsprofileview_target_label), LocalProfilePlugin.currentProfile().targetLow, LocalProfilePlugin.currentProfile().targetHigh, HardLimits.VERY_HARD_LIMIT_TARGET_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TARGET_BG[1].toDouble(), 1.0, DecimalFormat("0"), save) } else { - TimeListEdit(context, view, R.id.localprofile_isf, MainApp.gs(R.string.nsprofileview_isf_label), LocalProfilePlugin.currentProfile().isf, null, Profile.fromMgdlToUnits(HardLimits.MINISF, Constants.MMOL), Profile.fromMgdlToUnits(HardLimits.MAXISF, Constants.MMOL), 0.1, DecimalFormat("0.0"), save) - TimeListEdit(context, view, R.id.localprofile_target, MainApp.gs(R.string.nsprofileview_target_label), LocalProfilePlugin.currentProfile().targetLow, LocalProfilePlugin.currentProfile().targetHigh, Profile.fromMgdlToUnits(HardLimits.VERY_HARD_LIMIT_TARGET_BG[0].toDouble(), Constants.MMOL), Profile.fromMgdlToUnits(HardLimits.VERY_HARD_LIMIT_TARGET_BG[1].toDouble(), Constants.MMOL), 0.1, DecimalFormat("0.0"), save) + TimeListEdit(context, view, R.id.localprofile_isf, "ISF", MainApp.gs(R.string.nsprofileview_isf_label), LocalProfilePlugin.currentProfile().isf, null, Profile.fromMgdlToUnits(HardLimits.MINISF, Constants.MMOL), Profile.fromMgdlToUnits(HardLimits.MAXISF, Constants.MMOL), 0.1, DecimalFormat("0.0"), save) + TimeListEdit(context, view, R.id.localprofile_target, "TARGET", MainApp.gs(R.string.nsprofileview_target_label), LocalProfilePlugin.currentProfile().targetLow, LocalProfilePlugin.currentProfile().targetHigh, Profile.fromMgdlToUnits(HardLimits.VERY_HARD_LIMIT_TARGET_BG[0].toDouble(), Constants.MMOL), Profile.fromMgdlToUnits(HardLimits.VERY_HARD_LIMIT_TARGET_BG[1].toDouble(), Constants.MMOL), 0.1, DecimalFormat("0.0"), save) } // Spinner @@ -181,10 +182,11 @@ class LocalProfileFragment : Fragment() { @Suppress("SETTEXTL18N") localprofile_units.text = MainApp.gs(R.string.units_colon) + " " + (if (LocalProfilePlugin.currentProfile().mgdl) MainApp.gs(R.string.mgdl) else MainApp.gs(R.string.mmol)) localprofile_dia.setParams(LocalProfilePlugin.currentProfile().dia, MIN_DIA, 12.0, 0.1, DecimalFormat("0.0"), false, localprofile_save, textWatch) - TimeListEdit(context, view, R.id.localprofile_ic, MainApp.gs(R.string.nsprofileview_ic_label) + ":", LocalProfilePlugin.currentProfile().ic, null, 0.5, 50.0, 0.1, DecimalFormat("0.0"), save) - TimeListEdit(context, view, R.id.localprofile_isf, MainApp.gs(R.string.nsprofileview_isf_label) + ":", LocalProfilePlugin.currentProfile().isf, null, 0.5, 500.0, 0.1, DecimalFormat("0.0"), save) - basalView = TimeListEdit(context, view, R.id.localprofile_basal, MainApp.gs(R.string.nsprofileview_basal_label) + ": " + sumLabel(), LocalProfilePlugin.currentProfile().basal, null, pumpDescription.basalMinimumRate, 10.0, 0.01, DecimalFormat("0.00"), save) - TimeListEdit(context, view, R.id.localprofile_target, MainApp.gs(R.string.nsprofileview_target_label) + ":", LocalProfilePlugin.currentProfile().targetLow, LocalProfilePlugin.currentProfile().targetHigh, 3.0, 200.0, 0.1, DecimalFormat("0.0"), save) + localprofile_dia.setTag("LP_DIA") + TimeListEdit(context, view, R.id.localprofile_ic, "IC", MainApp.gs(R.string.nsprofileview_ic_label) + ":", LocalProfilePlugin.currentProfile().ic, null, 0.5, 50.0, 0.1, DecimalFormat("0.0"), save) + TimeListEdit(context, view, R.id.localprofile_isf, "ISF", MainApp.gs(R.string.nsprofileview_isf_label) + ":", LocalProfilePlugin.currentProfile().isf, null, 0.5, 500.0, 0.1, DecimalFormat("0.0"), save) + basalView = TimeListEdit(context, view, R.id.localprofile_basal, "BASAL", MainApp.gs(R.string.nsprofileview_basal_label) + ": " + sumLabel(), LocalProfilePlugin.currentProfile().basal, null, pumpDescription.basalMinimumRate, 10.0, 0.01, DecimalFormat("0.00"), save) + TimeListEdit(context, view, R.id.localprofile_target, "TARGET", MainApp.gs(R.string.nsprofileview_target_label) + ":", LocalProfilePlugin.currentProfile().targetLow, LocalProfilePlugin.currentProfile().targetHigh, 3.0, 200.0, 0.1, DecimalFormat("0.0"), save) updateGUI() } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/RandomBgPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/RandomBgPlugin.kt new file mode 100644 index 0000000000..ea6731ca60 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/RandomBgPlugin.kt @@ -0,0 +1,77 @@ +package info.nightscout.androidaps.plugins.source + +import android.content.Intent +import android.os.Handler +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.db.BgReading +import info.nightscout.androidaps.interfaces.BgSourceInterface +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.pump.virtual.VirtualPumpPlugin +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.isRunningTest +import org.slf4j.LoggerFactory +import java.util.* +import kotlin.math.PI +import kotlin.math.sin + +object RandomBgPlugin : PluginBase(PluginDescription() + .mainType(PluginType.BGSOURCE) + .fragmentClass(BGSourceFragment::class.java.name) + .pluginName(R.string.randombg) + .shortName(R.string.randombg_short) + .description(R.string.description_source_randombg)), BgSourceInterface { + + private val log = LoggerFactory.getLogger(L.BGSOURCE) + + private val loopHandler = Handler() + private lateinit var refreshLoop: Runnable + + const val interval = 5L // minutes + + init { + refreshLoop = Runnable { + handleNewData(Intent()) + loopHandler.postDelayed(refreshLoop, T.mins(interval).msecs()) + } + } + + override fun advancedFilteringSupported(): Boolean { + return false + } + + override fun onStart() { + super.onStart() + loopHandler.postDelayed(refreshLoop, T.mins(interval).msecs()) + } + + override fun onStop() { + super.onStop() + loopHandler.removeCallbacks(refreshLoop) + } + + override fun specialEnableCondition(): Boolean { + return VirtualPumpPlugin.getPlugin().isEnabled(PluginType.PUMP) && (MainApp.engineeringMode || isRunningTest()) + } + + override fun handleNewData(intent: Intent) { + if (!isEnabled(PluginType.BGSOURCE)) return + val min = 70 + val max = 190 + + val cal = GregorianCalendar() + val currentMinute = cal.get(Calendar.MINUTE) + (cal.get(Calendar.HOUR_OF_DAY) % 2) * 60 + val bgMgdl = min + (max - min) * sin(currentMinute / 120.0 * 2 * PI) + + val bgReading = BgReading() + bgReading.value = bgMgdl + bgReading.date = DateUtil.now() + bgReading.raw = bgMgdl + MainApp.getDbHelper().createIfNotExists(bgReading, "RandomBG") + log.debug("Generated BG: $bgReading") + } +} 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 37cfe695b4..45ab985fce 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.java +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.java @@ -9,6 +9,7 @@ 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.activities.PreferencesActivity; @@ -49,6 +50,8 @@ import info.nightscout.androidaps.utils.LocaleHelper; import info.nightscout.androidaps.utils.PasswordProtection; import info.nightscout.androidaps.utils.SP; +import static info.nightscout.androidaps.utils.EspressoTestHelperKt.isRunningTest; + public class SWDefinition { private AppCompatActivity activity; private List screens = new ArrayList<>(); @@ -66,7 +69,7 @@ public class SWDefinition { } private SWDefinition add(SWScreen newScreen) { - screens.add(newScreen); + if (newScreen != null) screens.add(newScreen); return this; } @@ -119,18 +122,17 @@ public class SWDefinition { private SWScreen displaySettings = new SWScreen(R.string.wear_display_settings) .skippable(false) - .add(new SWEditNumberWithUnits(4d, 3d, 8d) + .add(new SWEditNumberWithUnits(Constants.LOWMARK * Constants.MGDL_TO_MMOLL, 3d, 8d) .preferenceId(R.string.key_low_mark) .updateDelay(5) .label(R.string.low_mark) .comment(R.string.low_mark_comment)) .add(new SWBreak()) - .add(new SWEditNumberWithUnits(10d, 5d, 20d) + .add(new SWEditNumberWithUnits(Constants.HIGHMARK * Constants.MGDL_TO_MMOLL, 5d, 20d) .preferenceId(R.string.key_high_mark) .updateDelay(5) .label(R.string.high_mark) - .comment(R.string.high_mark_comment)) - .validator(() -> SP.contains(R.string.key_low_mark) && SP.contains(R.string.key_high_mark)); + .comment(R.string.high_mark_comment)); private SWScreen screenPermissionBattery = new SWScreen(R.string.permission) .skippable(false) @@ -300,7 +302,7 @@ public class SWDefinition { .add(new SWInfotext() .label(R.string.profileswitch_ismissing)) .add(new SWButton() - .text(R.string.profileswitch) + .text(R.string.doprofileswitch) .action(() -> { NewNSTreatmentDialog newDialog = new NewNSTreatmentDialog(); final OptionsToShow profileSwitch = CareportalFragment.PROFILESWITCHDIRECT; @@ -432,7 +434,7 @@ public class SWDefinition { add(screenSetupWizard) .add(screenLanguage) .add(screenEula) - .add(screenPermissionBattery) + .add(isRunningTest() ? null : screenPermissionBattery) // cannot mock ask battery optimalization .add(screenPermissionBt) .add(screenPermissionStore) .add(screenImport) @@ -460,7 +462,7 @@ public class SWDefinition { add(screenSetupWizard) .add(screenLanguage) .add(screenEula) - .add(screenPermissionBattery) + .add(isRunningTest() ? null : screenPermissionBattery) // cannot mock ask battery optimalization .add(screenPermissionBt) .add(screenPermissionStore) .add(screenImport) @@ -484,7 +486,7 @@ public class SWDefinition { add(screenSetupWizard) .add(screenLanguage) .add(screenEula) - .add(screenPermissionBattery) + .add(isRunningTest() ? null : screenPermissionBattery) // cannot mock ask battery optimalization .add(screenPermissionStore) .add(screenImport) .add(screenUnits) @@ -496,5 +498,4 @@ public class SWDefinition { .add(screenSensitivity) ; } - } \ No newline at end of file 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 603292d3cc..8d0d37e003 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.java @@ -187,7 +187,7 @@ public class SetupWizardActivity extends NoSplashAppCompatActivity { return page; page++; } - return currentWizardPage; + return Math.min(currentWizardPage, screens.size() -1); } private int previousPage() { @@ -197,7 +197,7 @@ public class SetupWizardActivity extends NoSplashAppCompatActivity { return page; page--; } - return currentWizardPage; + return Math.max(currentWizardPage, 0); } @Override diff --git a/app/src/main/java/info/nightscout/androidaps/utils/EspressoTestHelper.kt b/app/src/main/java/info/nightscout/androidaps/utils/EspressoTestHelper.kt new file mode 100644 index 0000000000..7288e6679f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/EspressoTestHelper.kt @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.utils + +@Synchronized +fun isRunningTest(): Boolean { + return try { + Class.forName("androidx.test.espresso.Espresso") + true + } catch (e: ClassNotFoundException) { + false + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/utils/NumberPicker.java b/app/src/main/java/info/nightscout/androidaps/utils/NumberPicker.java index 54e676a52d..b91236ba25 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/NumberPicker.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/NumberPicker.java @@ -154,6 +154,11 @@ public class NumberPicker extends LinearLayout implements View.OnKeyListener, }); } + @Override + public void setTag(Object tag) { + editText.setTag(tag); + } + public void setOnValueChangedListener(OnValueChangedListener onValueChangedListener) { mOnValueChangedListener = onValueChangedListener; } 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 d9ed263c98..0143377bb2 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/TimeListEdit.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/TimeListEdit.java @@ -50,6 +50,7 @@ public class TimeListEdit { private Context context; private View view; private int resLayoutId; + private String tagPrefix; private String label; private JSONArray data1; private JSONArray data2; @@ -63,10 +64,11 @@ public class TimeListEdit { private int inflatedUntil = -1; - public TimeListEdit(Context context, View view, int resLayoutId, String label, JSONArray data1, JSONArray data2, double min, double max, double step, NumberFormat formatter, Runnable save) { + public TimeListEdit(Context context, View view, int resLayoutId, String tagPrefix, String label, JSONArray data1, JSONArray data2, double min, double max, double step, NumberFormat formatter, Runnable save) { this.context = context; this.view = view; this.resLayoutId = resLayoutId; + this.tagPrefix = tagPrefix; this.label = label; this.data1 = data1; this.data2 = data2; @@ -185,7 +187,7 @@ public class TimeListEdit { int before, int count) { } }); - + numberPickers1[position].setTag(tagPrefix +"-1-" + position); numberPickers2[position].setTextWatcher(new TextWatcher() { @Override @@ -205,6 +207,7 @@ public class TimeListEdit { int before, int count) { } }); + numberPickers2[position].setTag(tagPrefix +"-2-" + position); layout.addView(childView); } diff --git a/app/src/main/res/layout/activity_setupwizard.xml b/app/src/main/res/layout/activity_setupwizard.xml index 2c2930b827..1a9a7aad94 100644 --- a/app/src/main/res/layout/activity_setupwizard.xml +++ b/app/src/main/res/layout/activity_setupwizard.xml @@ -13,6 +13,7 @@ android:orientation="horizontal"> Next unfinished Request code: %1$s (check all correct answers) - https://androidaps.readthedocs.io/en/latest/EN/Getting-Started/FAQ.html#what-to-do-when-taking-a-shower-or-bath - https://androidaps.readthedocs.io/en/latest/EN/Getting-Started/Screenshots.html#the-homescreen - https://androidaps.readthedocs.io/en/latest/EN/Getting-Started/Screenshots.html#config-builder - https://androidaps.readthedocs.io/en/latest/EN/Getting-Started/Screenshots.html#the-homescreen + https://androidaps.readthedocs.io/en/latest/EN/Getting-Started/FAQ.html#what-to-do-when-taking-a-shower-or-bath + https://androidaps.readthedocs.io/en/latest/EN/Getting-Started/Screenshots.html#the-homescreen + https://androidaps.readthedocs.io/en/latest/EN/Getting-Started/Screenshots.html#config-builder + https://androidaps.readthedocs.io/en/latest/EN/Getting-Started/Screenshots.html#the-homescreen Not connected to the internet Failed retrieve time Objective requirements not met diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0ae7ebf708..29a40248e8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -661,6 +661,7 @@ Basal type Invalid profile !!! ProfileSwitch + Do Profile Switch Pump battery age Pump Battery Change Alarm options @@ -1679,5 +1680,8 @@ Activity monitor Do you want to reset activity stats? Statistics + Random BG + Generate random BG data (Demo mode only) + BG