diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index 56c712f629..bad0f88d6a 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -229,6 +229,10 @@ public class MainApp extends Application { return sBus; } + public static String gs(int id) { + return sResources.getString(id); + } + public static MainApp instance() { return sInstance; } 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 b2e5d0d9dc..db34f9d692 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/Profile.java +++ b/app/src/main/java/info/nightscout/androidaps/data/Profile.java @@ -120,7 +120,7 @@ public class Profile { } } catch (JSONException e) { log.error("Unhandled exception", e); - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.invalidprofile)); + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.invalidprofile)); } } @@ -386,6 +386,8 @@ public class Profile { } public BasalValue[] getBasalValues() { + if (basal_v == null) + basal_v = convertToSparseArray(basal); BasalValue[] ret = new BasalValue[basal_v.size()]; for (Integer index = 0; index < basal_v.size(); index++) { 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 62848b7e5e..9cbb0bef4c 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 @@ -421,23 +421,6 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr } -/* - @Override - public PumpDescription getPumpDescription() { - if (activePump != null) - return activePump.getPumpDescription(); - else { - PumpDescription emptyDescription = new PumpDescription(); - emptyDescription.isBolusCapable = false; - emptyDescription.isExtendedBolusCapable = false; - emptyDescription.isSetBasalProfileCapable = false; - emptyDescription.isTempBasalCapable = true; // needs to be true before real driver is selected - emptyDescription.isRefillingCapable = false; - return emptyDescription; - } - } -*/ - /** * Constraints interface **/ diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/DanaRSService.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/DanaRSService.java index bd6f5ca690..7790257640 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/DanaRSService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/DanaRSService.java @@ -258,6 +258,7 @@ public class DanaRSService extends Service { MainApp.bus().post(bolusingEvent); SystemClock.sleep(1000); } + // do not call loadEvents() directly, reconnection may be needed ConfigBuilderPlugin.getCommandQueue().loadEvents(new Callback() { @Override public void run() { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/services/DanaRv2ExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/services/DanaRv2ExecutionService.java index 6d6ba34838..71a2f9ac79 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/services/DanaRv2ExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/services/DanaRv2ExecutionService.java @@ -424,6 +424,7 @@ public class DanaRv2ExecutionService extends Service { MainApp.bus().post(bolusingEvent); SystemClock.sleep(1000); } + // do not call loadEvents() directly, reconnection may be needed ConfigBuilderPlugin.getCommandQueue().loadEvents(new Callback() { @Override public void run() { 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 0e7e18ec7e..3e696e9857 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java @@ -1,5 +1,6 @@ package info.nightscout.androidaps.queue; +import android.content.Context; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.text.Html; @@ -37,42 +38,41 @@ import info.nightscout.androidaps.queue.commands.CommandTempBasalPercent; /** * Created by mike on 08.11.2017. - * + *

* DATA FLOW: * --------- - * + *

* (request) - > ConfigBuilder.getCommandQueue().bolus(...) - * + *

* app no longer waits for result but passes Callback - * + *

* request is added to queue, if another request of the same type already exists in queue, it's removed prior adding * but if request of the same type is currently executed (probably important only for bolus which is running long time), new request is declined * new QueueThread is created and started if current if finished * CommandReadStatus is added automatically before command if queue is empty - * + *

* biggest change is we don't need exec pump commands in Handler because it's finished immediately * command queueing if not realized by stacking in different Handlers and threads anymore but by internal queue with better control - * + *

* QueueThread calls ConfigBuilder#connect which is passed to getActivePump().connect * connect should be executed on background and return immediately. afterwards isConnecting() is expected to be true - * + *

* while isConnecting() == true GUI is updated by posting connection progress - * + *

* if connect is successful: isConnected() becomes true, isConnecting() becomes false - * CommandQueue starts calling execute() of commands. execute() is expected to be blocking (return after finish). - * callback with result is called after finish automatically + * CommandQueue starts calling execute() of commands. execute() is expected to be blocking (return after finish). + * callback with result is called after finish automatically * if connect failed: isConnected() becomes false, isConnecting() becomes false - * connect() is called again - * + * connect() is called again + *

* when queue is empty, disconnect is called - * */ public class CommandQueue { private static Logger log = LoggerFactory.getLogger(CommandQueue.class); private LinkedList queue = new LinkedList<>(); - private Command performing; + protected Command performing; private QueueThread thread = null; @@ -94,10 +94,19 @@ public class CommandQueue { } } + private synchronized boolean isLastScheduled(Command.CommandType type) { + if (queue.size() > 0 && queue.get(queue.size() - 1).commandType == type) { + return true; + } + return false; + } + + private synchronized void inject(Command command) { + // inject as a first command + queue.addFirst(command); + } + private synchronized void add(Command command) { - // inject reading of status when adding first command to the queue - if (queue.size() == 0 && command.commandType != Command.CommandType.READSTATUS) - queue.add(new CommandReadStatus("Queue", null)); queue.add(command); } @@ -128,7 +137,7 @@ public class CommandQueue { // After new command added to the queue // start thread again if not already running - private synchronized void notifyAboutNewCommand() { + protected synchronized void notifyAboutNewCommand() { if (thread == null || thread.getState() == Thread.State.TERMINATED) { thread = new QueueThread(this); thread.start(); @@ -166,17 +175,7 @@ public class CommandQueue { MainApp.bus().post(new EventBolusRequested(detailedBolusInfo.insulin)); // Bring up bolus progress dialog - if (detailedBolusInfo.context != null) { - BolusProgressDialog bolusProgressDialog = new BolusProgressDialog(); - bolusProgressDialog.setInsulin(detailedBolusInfo.insulin); - bolusProgressDialog.show(((AppCompatActivity) detailedBolusInfo.context).getSupportFragmentManager(), "BolusProgress"); - } else { - Intent i = new Intent(); - i.putExtra("insulin", detailedBolusInfo.insulin); - i.setClass(MainApp.instance(), BolusProgressHelperActivity.class); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - MainApp.instance().startActivity(i); - } + showBolusProgressDialog(detailedBolusInfo.insulin, detailedBolusInfo.context); return true; } @@ -231,7 +230,7 @@ public class CommandQueue { return false; } - Double rateAfterConstraints = MainApp.getConfigBuilder().applyBolusConstraints(insulin); + Double rateAfterConstraints = MainApp.getConfigBuilder().applyBolusConstraints(insulin); // remove all unfinished removeAll(Command.CommandType.EXTENDEDBOLUS); @@ -326,11 +325,12 @@ public class CommandQueue { // returns true if command is queued public boolean readStatus(String reason, Callback callback) { - //if (isRunning(Command.CommandType.READSTATUS)) { - // if (callback != null) - // callback.result(executingNowError()).run(); - // return false; - //} + if (isLastScheduled(Command.CommandType.READSTATUS)) { + log.debug("QUEUE: READSTATUS " + reason + " ignored as duplicated"); + if (callback != null) + callback.result(executingNowError()).run(); + return false; + } // remove all unfinished //removeAll(Command.CommandType.READSTATUS); @@ -409,4 +409,18 @@ public class CommandQueue { } else return true; } + protected void showBolusProgressDialog(Double insulin, Context context) { + if (context != null) { + BolusProgressDialog bolusProgressDialog = new BolusProgressDialog(); + bolusProgressDialog.setInsulin(insulin); + bolusProgressDialog.show(((AppCompatActivity) context).getSupportFragmentManager(), "BolusProgress"); + } else { + Intent i = new Intent(); + i.putExtra("insulin", insulin); + i.setClass(MainApp.instance(), BolusProgressHelperActivity.class); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + MainApp.instance().startActivity(i); + } + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/Command.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/Command.java index 4624e2385f..5129c7983f 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/commands/Command.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/Command.java @@ -29,7 +29,7 @@ public abstract class Command { public void cancel() { PumpEnactResult result = new PumpEnactResult(); result.success = false; - result.comment = MainApp.sResources.getString(R.string.connectiontimedout); + result.comment = MainApp.gs(R.string.connectiontimedout); if (callback != null) callback.result(result).run(); } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandReadStatus.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandReadStatus.java index 46c524fa29..44c778c5f7 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandReadStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandReadStatus.java @@ -27,6 +27,6 @@ public class CommandReadStatus extends Command { @Override public String status() { - return "READSTATUS"; + return "READSTATUS " + reason; } } diff --git a/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.java b/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.java new file mode 100644 index 0000000000..31d8636de2 --- /dev/null +++ b/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.java @@ -0,0 +1,143 @@ +package info.nightscout.androidaps.queue; + +import android.content.Context; +import android.text.Html; + +import com.squareup.otto.Bus; +import com.squareup.otto.ThreadEnforcer; + +import junit.framework.Assert; + +import org.json.JSONObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import info.nightscout.androidaps.Constants; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.PumpMDI.MDIPlugin; +import info.nightscout.androidaps.queue.commands.Command; +import info.nightscout.utils.ToastUtils; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Created by mike on 14.01.2018. + */ + +@RunWith(PowerMockRunner.class) +@PrepareForTest({MainApp.class, ConfigBuilderPlugin.class, ConfigBuilderPlugin.class, ToastUtils.class, Context.class}) +public class CommandQueueTest extends CommandQueue { + + String profileJson = "{\"dia\":\"3\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"100\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"0.1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"5\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}"; + + @Test + public void doTests() throws Exception { + prepareMock(0d, 0); + + // start with empty queue + Assert.assertEquals(0, size()); + + // add bolus command + bolus(new DetailedBolusInfo(), null); + Assert.assertEquals(1, size()); + + // add READSTATUS + readStatus("anyString", null); + Assert.assertEquals(2, size()); + + // adding another bolus should remove the first one (size still == 2) + bolus(new DetailedBolusInfo(), null); + Assert.assertEquals(2, size()); + + // clear the queue should reset size + clear(); + Assert.assertEquals(0, size()); + + // add tempbasal + tempBasalAbsolute(0, 30, true, null); + Assert.assertEquals(1, size()); + + // add tempbasal percent. it should replace previous TEMPBASAL + tempBasalPercent(0, 30, true, null); + Assert.assertEquals(1, size()); + + // add extended bolus + extendedBolus(1, 30, null); + Assert.assertEquals(2, size()); + + // add cancel temp basal should remove previous 2 temp basal setting + extendedBolus(1, 30, null); + Assert.assertEquals(2, size()); + + // cancel extended bolus should replace previous extended + extendedBolus(1, 30, null); + Assert.assertEquals(2, size()); + + // add setProfile + setProfile(new Profile(new JSONObject(profileJson), Constants.MGDL), null); + Assert.assertEquals(3, size()); + + // add loadHistory + loadHistory((byte) 0, null); + Assert.assertEquals(4, size()); + + // add loadEvents + loadEvents(null); + Assert.assertEquals(5, size()); + + clear(); + tempBasalAbsolute(0, 30, true, null); + pickup(); + Assert.assertEquals(0, size()); + Assert.assertNotNull(performing); + Assert.assertEquals(Command.CommandType.TEMPBASAL, performing.commandType); + resetPerforming(); + Assert.assertNull(performing); + } + + private void prepareMock(Double insulin, Integer carbs) throws Exception { + ConfigBuilderPlugin configBuilderPlugin = mock(ConfigBuilderPlugin.class); + when(configBuilderPlugin.applyBolusConstraints(insulin)).thenReturn(insulin); + when(configBuilderPlugin.applyCarbsConstraints(carbs)).thenReturn(carbs); + + PowerMockito.mockStatic(ConfigBuilderPlugin.class); + PumpInterface pump = MDIPlugin.getPlugin(); + when(ConfigBuilderPlugin.getActivePump()).thenReturn(pump); + + PowerMockito.mockStatic(MainApp.class); + MainApp mainApp = mock(MainApp.class); + when(MainApp.getConfigBuilder()).thenReturn(configBuilderPlugin); + when(MainApp.instance()).thenReturn(mainApp); + + PowerMockito.mockStatic(ToastUtils.class); + Context context = mock(Context.class); + String message = null; + PowerMockito.doNothing().when(ToastUtils.class, "showToastInUiThread", context, message); + + Bus bus = new Bus(ThreadEnforcer.ANY); + + when(MainApp.bus()).thenReturn(bus); + when(MainApp.gs(0)).thenReturn(""); + } + + @Override + protected synchronized void notifyAboutNewCommand() { + } + + @Override + protected void showBolusProgressDialog(Double insulin, Context context) { + } + + @Override + public boolean isThisProfileSet(Profile profile) { + return false; + } +}