diff --git a/app/build.gradle b/app/build.gradle
index 8b50ad30ed..3b55fd2235 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -156,6 +156,7 @@ allprojects {
flatDir {
dirs 'libs'
}
+ maven { url 'https://jitpack.io' }
}
}
@@ -218,6 +219,8 @@ dependencies {
implementation "com.jakewharton:butterknife:${butterknifeVersion}"
annotationProcessor "com.jakewharton:butterknife-compiler:${butterknifeVersion}"
+ implementation 'com.github.DavidProdinger:weekdays-selector:1.0.4'
+
testImplementation "junit:junit:4.12"
testImplementation "org.json:json:20140107"
testImplementation "org.mockito:mockito-core:2.7.22"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index dc9201d86c..bc12ee8a94 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -18,6 +18,7 @@
+
@@ -150,7 +151,10 @@
android:name=".services.DataService"
android:exported="false" />
+
();
@@ -109,6 +110,7 @@ public class L {
logElements.add(new LogElement(DATASERVICE, true));
logElements.add(new LogElement(DATATREATMENTS, true));
logElements.add(new LogElement(EVENTS, false, true));
+ logElements.add(new LogElement(LOCATION, true));
logElements.add(new LogElement(NOTIFICATION, true));
logElements.add(new LogElement(NSCLIENT, true));
logElements.add(new LogElement(OVERVIEW, true));
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationEvent.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationEvent.java
new file mode 100644
index 0000000000..c6d104f61c
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationEvent.java
@@ -0,0 +1,73 @@
+package info.nightscout.androidaps.plugins.general.automation;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import info.nightscout.androidaps.plugins.general.automation.actions.Action;
+import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger;
+
+public class AutomationEvent {
+
+ private Trigger trigger;
+ private List actions = new ArrayList<>();
+ private String title;
+
+ public void setTitle(String title) { this.title = title; }
+
+ public void setTrigger(Trigger trigger) { this.trigger = trigger; }
+
+ public Trigger getTrigger() {
+ return trigger;
+ }
+
+ public List getActions() {
+ return actions;
+ }
+
+ public void addAction(Action action) { actions.add(action); }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String toJSON() {
+ JSONObject o = new JSONObject();
+ try {
+ // title
+ o.put("title", title);
+ // trigger
+ o.put("trigger", trigger.toJSON());
+ // actions
+ JSONArray array = new JSONArray();
+ for (Action a : actions) {
+ array.put(a.toJSON());
+ }
+ o.put("actions", array);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return o.toString();
+ }
+
+ public AutomationEvent fromJSON(String data) {
+ try {
+ JSONObject d = new JSONObject(data);
+ // title
+ title = d.getString("title");
+ // trigger
+ trigger = Trigger.instantiate(d.getString("trigger"));
+ // actions
+ JSONArray array = d.getJSONArray("actions");
+ for (int i = 0; i < array.length(); i++) {
+ actions.add(Action.instantiate(array.getJSONObject(i)));
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return this;
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationFragment.java
new file mode 100644
index 0000000000..359ddfd813
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationFragment.java
@@ -0,0 +1,373 @@
+package info.nightscout.androidaps.plugins.general.automation;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+import android.support.v4.app.FragmentManager;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import java.util.HashSet;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import info.nightscout.androidaps.MainApp;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.plugins.common.SubscriberFragment;
+import info.nightscout.androidaps.plugins.general.automation.actions.Action;
+import info.nightscout.androidaps.plugins.general.automation.dialogs.ChooseTriggerDialog;
+import info.nightscout.androidaps.plugins.general.automation.dialogs.EditActionDialog;
+import info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog;
+import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger;
+import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector;
+
+public class AutomationFragment extends SubscriberFragment {
+
+ @BindView(R.id.eventListView)
+ RecyclerView mEventListView;
+
+ private EventListAdapter mEventListAdapter;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ View view = inflater.inflate(R.layout.automation_fragment, container, false);
+ unbinder = ButterKnife.bind(this, view);
+
+ final AutomationPlugin plugin = AutomationPlugin.getPlugin();
+ mEventListAdapter = new EventListAdapter(plugin.getAutomationEvents());
+ mEventListView.setLayoutManager(new LinearLayoutManager(getContext()));
+ mEventListView.setAdapter(mEventListAdapter);
+
+ EditEventDialog.setOnClickListener(event -> {
+ plugin.getAutomationEvents().add(event);
+ mEventListAdapter.notifyDataSetChanged();
+ });
+
+ updateGUI();
+
+ return view;
+ }
+
+ @Override
+ public void updateGUI() {
+ Activity activity = getActivity();
+ if (activity != null)
+ activity.runOnUiThread(() -> mEventListAdapter.notifyDataSetChanged());
+ }
+
+ @OnClick(R.id.fabAddEvent)
+ void onClickAddEvent(View v) {
+ EditEventDialog dialog = EditEventDialog.newInstance(new AutomationEvent());
+ dialog.show(getFragmentManager(), "EditEventDialog");
+ }
+
+ /**
+ * RecyclerViewAdapter to display event lists.
+ */
+ public static class EventListAdapter extends RecyclerView.Adapter {
+ private final List mEventList;
+
+ public EventListAdapter(List events) {
+ this.mEventList = events;
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.automation_event_item, parent, false);
+ return new ViewHolder(v, parent.getContext());
+ }
+
+ private void addImage(@DrawableRes int res, Context context, LinearLayout layout) {
+ ImageView iv = new ImageView(context);
+ iv.setImageResource(res);
+ iv.setLayoutParams(new LinearLayout.LayoutParams(MainApp.dpToPx(24),MainApp.dpToPx(24)));
+ layout.addView(iv);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ final AutomationEvent event = mEventList.get(position);
+ holder.eventTitle.setText(event.getTitle());
+ holder.iconLayout.removeAllViews();
+
+ // trigger icons
+ HashSet triggerIcons = new HashSet<>();
+ TriggerConnector.fillIconSet((TriggerConnector)event.getTrigger(), triggerIcons);
+ for(int res : triggerIcons) {
+ addImage(res, holder.context, holder.iconLayout);
+ }
+
+ // arrow icon
+ ImageView iv = new ImageView(holder.context);
+ iv.setImageResource(R.drawable.ic_arrow_forward_white_24dp);
+ iv.setLayoutParams(new LinearLayout.LayoutParams(MainApp.dpToPx(24),MainApp.dpToPx(24)));
+ iv.setPadding(MainApp.dpToPx(4), 0, MainApp.dpToPx(4), 0);
+ holder.iconLayout.addView(iv);
+
+ // action icons
+ HashSet actionIcons = new HashSet<>();
+ for(Action action : event.getActions()) {
+ if (action.icon().isPresent())
+ actionIcons.add(action.icon().get());
+ }
+ for(int res : actionIcons) {
+ addImage(res, holder.context, holder.iconLayout);
+ }
+
+ // TODO: check null
+ //holder.eventDescription.setText(event.getTrigger().friendlyDescription());
+ }
+
+ @Override
+ public int getItemCount() {
+ return mEventList.size();
+ }
+
+ static class ViewHolder extends RecyclerView.ViewHolder {
+ final RelativeLayout rootLayout;
+ final LinearLayout iconLayout;
+ final TextView eventTitle;
+ final Context context;
+
+ public ViewHolder(View view, Context context) {
+ super(view);
+ this.context = context;
+ eventTitle = view.findViewById(R.id.viewEventTitle);
+ rootLayout = view.findViewById(R.id.rootLayout);
+ iconLayout = view.findViewById(R.id.iconLayout);
+ }
+ }
+ }
+
+ /**
+ * RecyclerViewAdapter to display event lists.
+ */
+ public static class ActionListAdapter extends RecyclerView.Adapter {
+ private final List mActionList;
+ private final FragmentManager mFragmentManager;
+
+ public ActionListAdapter(FragmentManager manager, List events) {
+ this.mActionList = events;
+ this.mFragmentManager = manager;
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.automation_action_item, parent, false);
+ return new ViewHolder(v);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ final Action action = mActionList.get(position);
+ holder.actionTitle.setText(action.friendlyName());
+ holder.itemRoot.setOnClickListener(v -> {
+ if (action.hasDialog()) {
+ EditActionDialog dialog = EditActionDialog.newInstance(action);
+ dialog.show(mFragmentManager, "EditActionDialog");
+ }
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return mActionList.size();
+ }
+
+ static class ViewHolder extends RecyclerView.ViewHolder {
+ TextView actionTitle;
+ TextView actionDescription;
+ LinearLayout itemRoot;
+
+ public ViewHolder(View view) {
+ super(view);
+ itemRoot = view.findViewById(R.id.itemRoot);
+ actionTitle = view.findViewById(R.id.viewActionTitle);
+ actionDescription = view.findViewById(R.id.viewActionDescription);
+ }
+ }
+ }
+
+ /**
+ * Custom Adapter to display triggers dynamically with nested linear layouts.
+ */
+ public static class TriggerListAdapter {
+ private final LinearLayout mRootLayout;
+ private final Context mContext;
+ private final TriggerConnector mRootConnector;
+ private final FragmentManager mFragmentManager;
+
+ public TriggerListAdapter(Context context, FragmentManager fragmentManager, LinearLayout rootLayout, TriggerConnector rootTrigger) {
+ mRootLayout = rootLayout;
+ mContext = context;
+ mFragmentManager = fragmentManager;
+ mRootConnector = rootTrigger;
+ build();
+ }
+
+
+ public void destroy() {
+ mRootLayout.removeAllViews();
+ }
+
+ private void build() {
+ for(int i = 0; i < mRootConnector.size(); ++i) {
+ final Trigger trigger = mRootConnector.get(i);
+
+ // spinner
+ if (i > 0) {
+ createSpinner(trigger);
+ }
+
+ // trigger layout
+ mRootLayout.addView(trigger.createView(mContext, mFragmentManager));
+
+ // buttons
+ createButtons(trigger);
+ }
+
+ if (mRootConnector.size() == 0) {
+ Button buttonAdd = new Button(mContext);
+ buttonAdd.setText("Add New");
+ buttonAdd.setOnClickListener(v -> {
+ ChooseTriggerDialog dialog = ChooseTriggerDialog.newInstance();
+ dialog.setOnClickListener(newTriggerObject -> {
+ mRootConnector.add(newTriggerObject);
+ rebuild();
+ });
+ dialog.show(mFragmentManager, "ChooseTriggerDialog");
+ });
+ mRootLayout.addView(buttonAdd);
+ }
+ }
+
+ private Spinner createSpinner() {
+ Spinner spinner = new Spinner(mContext);
+ ArrayAdapter spinnerArrayAdapter = new ArrayAdapter<>(mContext, android.R.layout.simple_spinner_item, TriggerConnector.Type.labels());
+ spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinner.setAdapter(spinnerArrayAdapter);
+ return spinner;
+ }
+
+ private void createSpinner(Trigger trigger) {
+ final TriggerConnector connector = trigger.getConnector();
+ final int initialPosition = connector.getConnectorType().ordinal();
+ Spinner spinner = createSpinner();
+ spinner.setSelection(initialPosition);
+ spinner.setBackgroundColor(MainApp.gc(R.color.black_overlay));
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ );
+ params.setMargins(0, MainApp.dpToPx(8), 0, MainApp.dpToPx(8));
+ spinner.setLayoutParams(params);
+ spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ if (position != initialPosition) {
+ // conector type changed
+ changeConnector(trigger, connector, TriggerConnector.Type.values()[position]);
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) { }
+ });
+ mRootLayout.addView(spinner);
+ }
+
+ private void createButtons(Trigger trigger) {
+ // do not create buttons for TriggerConnector
+ if (trigger instanceof TriggerConnector) {
+ return;
+ }
+
+ // Button Layout
+ LinearLayout buttonLayout = new LinearLayout(mContext);
+ buttonLayout.setOrientation(LinearLayout.HORIZONTAL);
+ buttonLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ mRootLayout.addView(buttonLayout);
+
+ // Button [-]
+ Button buttonRemove = new Button(mContext);
+ buttonRemove.setText("del");
+ buttonRemove.setOnClickListener(v -> {
+ final TriggerConnector connector = trigger.getConnector();
+ connector.remove(trigger);
+ connector.simplify().rebuildView();
+ });
+ buttonLayout.addView(buttonRemove);
+
+ // Button [+]
+ Button buttonAdd = new Button(mContext);
+ buttonAdd.setText("add");
+ buttonAdd.setOnClickListener(v -> {
+ ChooseTriggerDialog dialog = ChooseTriggerDialog.newInstance();
+ dialog.show(mFragmentManager, "ChooseTriggerDialog");
+ dialog.setOnClickListener(newTriggerObject -> {
+ TriggerConnector connector = trigger.getConnector();
+ connector.add(connector.pos(trigger)+1, newTriggerObject);
+ connector.simplify().rebuildView();
+ });
+ });
+ buttonLayout.addView(buttonAdd);
+
+ // Button [*]
+ Button buttonCopy = new Button(mContext);
+ buttonCopy.setText("copy");
+ buttonCopy.setOnClickListener(v -> {
+ TriggerConnector connector = trigger.getConnector();
+ connector.add(connector.pos(trigger)+1, trigger.duplicate());
+ connector.simplify().rebuildView();
+ });
+ buttonLayout.addView(buttonCopy);
+ }
+
+ public static void changeConnector(final Trigger trigger, final TriggerConnector connector, final TriggerConnector.Type newConnectorType) {
+ if (connector.size() > 2) {
+ // split connector
+ int pos = connector.pos(trigger) - 1;
+
+ TriggerConnector newConnector = new TriggerConnector(newConnectorType);
+
+ // move trigger from pos and pos+1 into new connector
+ for(int i = 0; i < 2; ++i) {
+ Trigger t = connector.get(pos);
+ newConnector.add(t);
+ connector.remove(t);
+ }
+
+ connector.add(pos, newConnector);
+ } else {
+ connector.changeConnectorType(newConnectorType);
+ }
+
+ connector.simplify().rebuildView();
+ }
+
+ public void rebuild() {
+ destroy();
+ build();
+ }
+ }
+
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.java
new file mode 100644
index 0000000000..a65c4fee4b
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.java
@@ -0,0 +1,120 @@
+package info.nightscout.androidaps.plugins.general.automation;
+
+import android.content.Context;
+import android.content.Intent;
+import android.location.Location;
+
+import com.squareup.otto.Subscribe;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import info.nightscout.androidaps.MainApp;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.events.EventChargingState;
+import info.nightscout.androidaps.events.EventLocationChange;
+import info.nightscout.androidaps.events.EventNetworkChange;
+import info.nightscout.androidaps.events.EventPreferenceChange;
+import info.nightscout.androidaps.interfaces.PluginBase;
+import info.nightscout.androidaps.interfaces.PluginDescription;
+import info.nightscout.androidaps.interfaces.PluginType;
+import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished;
+import info.nightscout.androidaps.services.LocationService;
+
+public class AutomationPlugin extends PluginBase {
+
+ static AutomationPlugin plugin = null;
+
+ public static AutomationPlugin getPlugin() {
+ if (plugin == null)
+ plugin = new AutomationPlugin();
+ return plugin;
+ }
+
+ private final List automationEvents = new ArrayList<>();
+ private EventLocationChange eventLocationChange;
+ private EventChargingState eventChargingState;
+ private EventNetworkChange eventNetworkChange;
+
+ private AutomationPlugin() {
+ super(new PluginDescription()
+ .mainType(PluginType.GENERAL)
+ .fragmentClass(AutomationFragment.class.getName())
+ .pluginName(R.string.automation)
+ .shortName(R.string.automation_short)
+ .preferencesId(R.xml.pref_automation)
+ .description(R.string.automation_description)
+ );
+ }
+
+ @Override
+ protected void onStart() {
+ Context context = MainApp.instance().getApplicationContext();
+ context.startService(new Intent(context, LocationService.class));
+
+ MainApp.bus().register(this);
+ super.onStart();
+ }
+
+ @Override
+ protected void onStop() {
+ Context context = MainApp.instance().getApplicationContext();
+ context.stopService(new Intent(context, LocationService.class));
+
+ MainApp.bus().unregister(this);
+ }
+
+ public List getAutomationEvents() {
+ return automationEvents;
+ }
+
+ public EventLocationChange getEventLocationChange() {
+ return eventLocationChange;
+ }
+
+ public EventChargingState getEventChargingState() {
+ return eventChargingState;
+ }
+
+ public EventNetworkChange getEventNetworkChange() {
+ return eventNetworkChange;
+ }
+
+ @Subscribe
+ public void onEventPreferenceChange(EventPreferenceChange e) {
+ if (e.isChanged(R.string.key_location)) {
+ Context context = MainApp.instance().getApplicationContext();
+ context.stopService(new Intent(context, LocationService.class));
+ context.startService(new Intent(context, LocationService.class));
+ }
+ }
+
+ @Subscribe
+ public void onEventLocationChange(EventLocationChange e) {
+ eventLocationChange = e;
+ processActions();
+ }
+
+ @Subscribe
+ public void onEventChargingState(EventChargingState e) {
+ eventChargingState = e;
+ processActions();
+ }
+
+ @Subscribe
+ public void onEventNetworkChange(EventNetworkChange e) {
+ eventNetworkChange = e;
+ processActions();
+ }
+
+ @Subscribe
+ public void onEventAutosensCalculationFinished(EventAutosensCalculationFinished e) {
+ processActions();
+ }
+
+ // TODO keepalive
+
+ void processActions() {
+
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Action.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Action.java
new file mode 100644
index 0000000000..d1522640f3
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Action.java
@@ -0,0 +1,51 @@
+package info.nightscout.androidaps.plugins.general.automation.actions;
+
+import android.widget.LinearLayout;
+
+import com.google.common.base.Optional;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import info.nightscout.androidaps.queue.Callback;
+
+public abstract class Action {
+
+ public abstract int friendlyName();
+
+ abstract void doAction(Callback callback);
+
+ public void generateDialog(LinearLayout root) { }
+
+ public boolean hasDialog() { return false; }
+
+ public String toJSON() {
+ JSONObject o = new JSONObject();
+ try {
+ o.put("type", this.getClass().getName());
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return o.toString();
+ }
+
+ public abstract Optional icon();
+
+ public void copy(Action action) { }
+
+ /*package*/ Action fromJSON(String data) {
+ return this;
+ }
+
+ public static Action instantiate(JSONObject object) {
+ try {
+ String type = object.getString("type");
+ JSONObject data = object.getJSONObject("data");
+ Class clazz = Class.forName(type);
+ return ((Action) clazz.newInstance()).fromJSON(data.toString());
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | JSONException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionLoopDisable.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionLoopDisable.java
new file mode 100644
index 0000000000..794d4a31ce
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionLoopDisable.java
@@ -0,0 +1,43 @@
+package info.nightscout.androidaps.plugins.general.automation.actions;
+
+import com.google.common.base.Optional;
+
+import info.nightscout.androidaps.MainApp;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.data.PumpEnactResult;
+import info.nightscout.androidaps.events.EventRefreshOverview;
+import info.nightscout.androidaps.interfaces.PluginType;
+import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin;
+import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin;
+import info.nightscout.androidaps.queue.Callback;
+
+public class ActionLoopDisable extends Action {
+ @Override
+ public int friendlyName() {
+ return R.string.disableloop;
+ }
+
+ @Override
+ void doAction(Callback callback) {
+ if (LoopPlugin.getPlugin().isEnabled(PluginType.LOOP)) {
+ LoopPlugin.getPlugin().setPluginEnabled(PluginType.LOOP, false);
+ ConfigBuilderPlugin.getPlugin().storeSettings("ActionLoopDisable");
+ ConfigBuilderPlugin.getPlugin().getCommandQueue().cancelTempBasal(true, new Callback() {
+ @Override
+ public void run() {
+ MainApp.bus().post(new EventRefreshOverview("ActionLoopDisable"));
+ if (callback != null)
+ callback.result(result).run();
+ }
+ });
+ } else {
+ if (callback != null)
+ callback.result(new PumpEnactResult().success(true).comment(R.string.alreadydisabled)).run();
+ }
+ }
+
+ @Override
+ public Optional icon() {
+ return Optional.of(R.drawable.ic_stop_24dp);
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionLoopEnable.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionLoopEnable.java
new file mode 100644
index 0000000000..6e8839f353
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionLoopEnable.java
@@ -0,0 +1,38 @@
+package info.nightscout.androidaps.plugins.general.automation.actions;
+
+import com.google.common.base.Optional;
+
+import info.nightscout.androidaps.MainApp;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.data.PumpEnactResult;
+import info.nightscout.androidaps.events.EventRefreshOverview;
+import info.nightscout.androidaps.interfaces.PluginType;
+import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin;
+import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin;
+import info.nightscout.androidaps.queue.Callback;
+
+public class ActionLoopEnable extends Action {
+ @Override
+ public int friendlyName() {
+ return R.string.enableloop;
+ }
+
+ @Override
+ void doAction(Callback callback) {
+ if (!LoopPlugin.getPlugin().isEnabled(PluginType.LOOP)) {
+ LoopPlugin.getPlugin().setPluginEnabled(PluginType.LOOP, true);
+ ConfigBuilderPlugin.getPlugin().storeSettings("ActionLoopEnable");
+ MainApp.bus().post(new EventRefreshOverview("ActionLoopEnable"));
+ if (callback != null)
+ callback.result(new PumpEnactResult().success(true).comment(R.string.ok)).run();
+ } else {
+ if (callback != null)
+ callback.result(new PumpEnactResult().success(true).comment(R.string.alreadyenabled)).run();
+ }
+ }
+
+ @Override
+ public Optional icon() {
+ return Optional.of(R.drawable.ic_play_circle_outline_24dp);
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionLoopResume.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionLoopResume.java
new file mode 100644
index 0000000000..c734acd541
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionLoopResume.java
@@ -0,0 +1,39 @@
+package info.nightscout.androidaps.plugins.general.automation.actions;
+
+import com.google.common.base.Optional;
+
+import info.nightscout.androidaps.MainApp;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.data.PumpEnactResult;
+import info.nightscout.androidaps.events.EventRefreshOverview;
+import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin;
+import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin;
+import info.nightscout.androidaps.plugins.general.nsclient.NSUpload;
+import info.nightscout.androidaps.queue.Callback;
+
+public class ActionLoopResume extends Action {
+ @Override
+ public int friendlyName() {
+ return R.string.resumeloop;
+ }
+
+ @Override
+ void doAction(Callback callback) {
+ if (LoopPlugin.getPlugin().isSuspended()) {
+ LoopPlugin.getPlugin().suspendTo(0);
+ ConfigBuilderPlugin.getPlugin().storeSettings("ActionLoopResume");
+ NSUpload.uploadOpenAPSOffline(0);
+ MainApp.bus().post(new EventRefreshOverview("ActionLoopResume"));
+ if (callback != null)
+ callback.result(new PumpEnactResult().success(true).comment(R.string.ok)).run();
+ } else {
+ if (callback != null)
+ callback.result(new PumpEnactResult().success(true).comment(R.string.notsuspended)).run();
+ }
+ }
+
+ @Override
+ public Optional icon() {
+ return Optional.of(R.drawable.ic_replay_24dp);
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionLoopSuspend.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionLoopSuspend.java
new file mode 100644
index 0000000000..4938a6939d
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionLoopSuspend.java
@@ -0,0 +1,37 @@
+package info.nightscout.androidaps.plugins.general.automation.actions;
+
+import com.google.common.base.Optional;
+
+import info.nightscout.androidaps.MainApp;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.data.PumpEnactResult;
+import info.nightscout.androidaps.events.EventRefreshOverview;
+import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin;
+import info.nightscout.androidaps.queue.Callback;
+
+public class ActionLoopSuspend extends Action {
+ private int minutes;
+
+ @Override
+ public int friendlyName() {
+ return R.string.suspendloop;
+ }
+
+ @Override
+ void doAction(Callback callback) {
+ if (!LoopPlugin.getPlugin().isSuspended()) {
+ LoopPlugin.getPlugin().suspendLoop(minutes);
+ MainApp.bus().post(new EventRefreshOverview("ActionLoopSuspend"));
+ if (callback != null)
+ callback.result(new PumpEnactResult().success(true).comment(R.string.ok)).run();
+ } else {
+ if (callback != null)
+ callback.result(new PumpEnactResult().success(true).comment(R.string.alreadysuspended)).run();
+ }
+ }
+
+ @Override
+ public Optional icon() {
+ return Optional.of(R.drawable.ic_pause_circle_outline_24dp);
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionStartTempTarget.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionStartTempTarget.java
new file mode 100644
index 0000000000..f7afe1d06c
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionStartTempTarget.java
@@ -0,0 +1,110 @@
+package info.nightscout.androidaps.plugins.general.automation.actions;
+
+import android.widget.LinearLayout;
+
+import com.google.common.base.Optional;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import info.nightscout.androidaps.Constants;
+import info.nightscout.androidaps.MainApp;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.data.PumpEnactResult;
+import info.nightscout.androidaps.db.Source;
+import info.nightscout.androidaps.db.TempTarget;
+import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
+import info.nightscout.androidaps.plugins.general.automation.elements.InputBg;
+import info.nightscout.androidaps.plugins.general.automation.elements.InputDuration;
+import info.nightscout.androidaps.plugins.general.automation.elements.Label;
+import info.nightscout.androidaps.queue.Callback;
+import info.nightscout.androidaps.utils.DateUtil;
+import info.nightscout.androidaps.utils.JsonHelper;
+
+public class ActionStartTempTarget extends Action {
+ private String reason;
+
+ private InputBg value;
+ private InputDuration duration = new InputDuration(0, InputDuration.TimeUnit.MINUTES);
+
+ public ActionStartTempTarget() {
+ value = new InputBg(Constants.MGDL);
+ }
+
+ public ActionStartTempTarget(String units) {
+ value = new InputBg(units);
+ }
+
+ @Override
+ public int friendlyName() {
+ return R.string.starttemptarget;
+ }
+
+ @Override
+ void doAction(Callback callback) {
+ TempTarget tempTarget = new TempTarget().date(DateUtil.now()).duration((int)duration.getMinutes()).reason(reason).source(Source.USER).low(value.getMgdl()).high(value.getMgdl());
+ TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget);
+ if (callback != null)
+ callback.result(new PumpEnactResult().success(true).comment(R.string.ok)).run();
+ }
+
+ @Override
+ public void generateDialog(LinearLayout root) {
+ int unitResId = value.getUnits().equals(Constants.MGDL) ? R.string.mgdl : R.string.mmol;
+ Label labelBg = new Label(MainApp.gs(R.string.careportal_newnstreatment_percentage_label), MainApp.gs(unitResId), value);
+ labelBg.generateDialog(root);
+ Label labelDuration = new Label(MainApp.gs(R.string.careportal_newnstreatment_duration_min_label), "min", duration);
+ labelDuration.generateDialog(root);
+ }
+
+ @Override
+ public boolean hasDialog() {
+ return true;
+ }
+
+ @Override
+ public String toJSON() {
+ JSONObject o = new JSONObject();
+ try {
+ o.put("type", ActionStartTempTarget.class.getName());
+ JSONObject data = new JSONObject();
+ data.put("reason", reason);
+ data.put("valueInMg", value.getMgdl());
+ data.put("units", value.getUnits());
+ data.put("durationInMinutes", duration.getMinutes());
+ o.put("data", data);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return o.toString();
+ }
+
+ @Override
+ Action fromJSON(String data) {
+ try {
+ JSONObject d = new JSONObject(data);
+ reason = JsonHelper.safeGetString(d, "reason");
+ value.setUnits(JsonHelper.safeGetString(d, "units"));
+ value.setMgdl(JsonHelper.safeGetInt(d, "valueInMg"));
+ duration.setMinutes(JsonHelper.safeGetDouble(d, "durationInMinutes"));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return this;
+ }
+
+ @Override
+ public void copy(Action action) {
+ if (action instanceof ActionStartTempTarget) {
+ ActionStartTempTarget src = (ActionStartTempTarget)action;
+ this.duration = src.duration;
+ this.value = src.value;
+ this.reason = src.reason;
+ }
+ }
+
+ @Override
+ public Optional icon() {
+ return Optional.of(R.drawable.icon_cp_cgm_target);
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/ChooseActionDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/ChooseActionDialog.java
new file mode 100644
index 0000000000..12956dd108
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/ChooseActionDialog.java
@@ -0,0 +1,139 @@
+package info.nightscout.androidaps.plugins.general.automation.dialogs;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import butterknife.Unbinder;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.plugins.general.automation.actions.Action;
+import info.nightscout.androidaps.plugins.general.automation.actions.ActionLoopDisable;
+import info.nightscout.androidaps.plugins.general.automation.actions.ActionLoopEnable;
+import info.nightscout.androidaps.plugins.general.automation.actions.ActionLoopResume;
+import info.nightscout.androidaps.plugins.general.automation.actions.ActionLoopSuspend;
+import info.nightscout.androidaps.plugins.general.automation.actions.ActionStartTempTarget;
+
+public class ChooseActionDialog extends DialogFragment {
+
+ public interface OnClickListener {
+ void onClick(Action newActionObject);
+ }
+
+ private static OnClickListener mClickListener = null;
+
+ private static final List actionDummyObjects = new ArrayList() {{
+ add(new ActionLoopDisable());
+ add(new ActionLoopEnable());
+ add(new ActionLoopResume());
+ add(new ActionLoopSuspend());
+ add(new ActionStartTempTarget());
+ }};
+
+ private Unbinder mUnbinder;
+
+ @BindView(R.id.radioGroup)
+ RadioGroup mRadioGroup;
+
+ public static ChooseActionDialog newInstance() {
+ Bundle args = new Bundle();
+
+ ChooseActionDialog fragment = new ChooseActionDialog();
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.automation_dialog_choose_action, container, false);
+ mUnbinder = ButterKnife.bind(this, view);
+
+ for(Action a : actionDummyObjects) {
+ RadioButton radioButton = new RadioButton(getContext());
+ radioButton.setText(a.friendlyName());
+ radioButton.setTag(a);
+ mRadioGroup.addView(radioButton);
+ }
+
+ // restore checked radio button
+ int checkedIndex = 0;
+ if (savedInstanceState != null) {
+ checkedIndex = savedInstanceState.getInt("checkedIndex");
+ }
+
+ ((RadioButton)mRadioGroup.getChildAt(checkedIndex)).setChecked(true);
+
+ return view;
+ }
+
+ private int getCheckedIndex() {
+ for(int i = 0; i < mRadioGroup.getChildCount(); ++i) {
+ if (((RadioButton)mRadioGroup.getChildAt(i)).isChecked())
+ return i;
+ }
+ return -1;
+ }
+
+ private Class getActionClass() {
+ int radioButtonID = mRadioGroup.getCheckedRadioButtonId();
+ RadioButton radioButton = mRadioGroup.findViewById(radioButtonID);
+ if (radioButton != null) {
+ Object tag = radioButton.getTag();
+ if (tag instanceof Action)
+ return tag.getClass();
+ }
+ return null;
+ }
+
+ private Action instantiateAction() {
+ Class actionClass = getActionClass();
+ if (actionClass != null) {
+ try {
+ return (Action) actionClass.newInstance();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+
+ public static void setOnClickListener(OnClickListener clickListener) {
+ mClickListener = clickListener;
+ }
+
+ @Override
+ public void onDestroyView() {
+ mUnbinder.unbind();
+ super.onDestroyView();
+ }
+
+ @OnClick(R.id.ok)
+ public void onButtonOk(View view) {
+ if (mClickListener != null)
+ mClickListener.onClick(instantiateAction());
+
+ dismiss();
+ }
+
+ @OnClick(R.id.cancel)
+ public void onButtonCancel(View view) {
+ dismiss();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle bundle) {
+ bundle.putInt("checkedIndex", getCheckedIndex());
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/ChooseTriggerDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/ChooseTriggerDialog.java
new file mode 100644
index 0000000000..cdb6b0739f
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/ChooseTriggerDialog.java
@@ -0,0 +1,132 @@
+package info.nightscout.androidaps.plugins.general.automation.dialogs;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import butterknife.Unbinder;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger;
+import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerBg;
+import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerTime;
+
+public class ChooseTriggerDialog extends DialogFragment {
+
+ public interface OnClickListener {
+ void onClick(Trigger newTriggerObject);
+ }
+
+ private static final List triggerDummyObjects = new ArrayList() {{
+ add(new TriggerBg());
+ add(new TriggerTime());
+ }};
+
+ private Unbinder mUnbinder;
+ private OnClickListener mClickListener = null;
+
+ @BindView(R.id.radioGroup)
+ RadioGroup mRadioGroup;
+
+ public static ChooseTriggerDialog newInstance() {
+ Bundle args = new Bundle();
+
+ ChooseTriggerDialog fragment = new ChooseTriggerDialog();
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.automation_dialog_choose_trigger, container, false);
+ mUnbinder = ButterKnife.bind(this, view);
+
+ for(Trigger t : triggerDummyObjects) {
+ RadioButton radioButton = new RadioButton(getContext());
+ radioButton.setText(t.friendlyName());
+ radioButton.setTag(t);
+ mRadioGroup.addView(radioButton);
+ }
+
+ // restore checked radio button
+ int checkedIndex = 0;
+ if (savedInstanceState != null) {
+ checkedIndex = savedInstanceState.getInt("checkedIndex");
+ }
+
+ ((RadioButton)mRadioGroup.getChildAt(checkedIndex)).setChecked(true);
+
+ return view;
+ }
+
+ private int getCheckedIndex() {
+ for(int i = 0; i < mRadioGroup.getChildCount(); ++i) {
+ if (((RadioButton)mRadioGroup.getChildAt(i)).isChecked())
+ return i;
+ }
+ return -1;
+ }
+
+ private Class getTriggerClass() {
+ int radioButtonID = mRadioGroup.getCheckedRadioButtonId();
+ RadioButton radioButton = mRadioGroup.findViewById(radioButtonID);
+ if (radioButton != null) {
+ Object tag = radioButton.getTag();
+ if (tag instanceof Trigger)
+ return tag.getClass();
+ }
+ return null;
+ }
+
+ private Trigger instantiateTrigger() {
+ Class triggerClass = getTriggerClass();
+ if (triggerClass != null) {
+ try {
+ return (Trigger) triggerClass.newInstance();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+
+ public void setOnClickListener(OnClickListener clickListener) {
+ mClickListener = clickListener;
+ }
+
+ @Override
+ public void onDestroyView() {
+ mUnbinder.unbind();
+ super.onDestroyView();
+ }
+
+ @OnClick(R.id.ok)
+ public void onButtonOk(View view) {
+ if (mClickListener != null)
+ mClickListener.onClick(instantiateTrigger());
+
+ dismiss();
+ }
+
+ @OnClick(R.id.cancel)
+ public void onButtonCancel(View view) {
+ dismiss();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle bundle) {
+ bundle.putInt("checkedIndex", getCheckedIndex());
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditActionDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditActionDialog.java
new file mode 100644
index 0000000000..696ccd6f8e
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditActionDialog.java
@@ -0,0 +1,90 @@
+package info.nightscout.androidaps.plugins.general.automation.dialogs;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import butterknife.Unbinder;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.plugins.general.automation.actions.Action;
+import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector;
+
+public class EditActionDialog extends DialogFragment {
+ private static Action resultAction;
+
+ private Unbinder mUnbinder;
+ private Action mAction;
+
+ @BindView(R.id.layout_root)
+ LinearLayout mRootLayout;
+
+ @BindView(R.id.viewActionTitle)
+ TextView mViewActionTitle;
+
+ public static EditActionDialog newInstance(Action action) {
+ Bundle args = new Bundle();
+ EditActionDialog fragment = new EditActionDialog();
+ fragment.setArguments(args);
+ resultAction = action;
+
+ return fragment;
+ }
+
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.automation_dialog_action, container, false);
+ mUnbinder = ButterKnife.bind(this, view);
+
+ if (savedInstanceState != null) {
+ String actionData = savedInstanceState.getString("action");
+ if (actionData != null) {
+ try {
+ mAction = Action.instantiate(new JSONObject(actionData));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ if (mAction == null)
+ mAction = resultAction;
+
+ mViewActionTitle.setText(mAction.friendlyName());
+ mRootLayout.removeAllViews();
+ mAction.generateDialog(mRootLayout);
+
+ return view;
+ }
+
+ @Override
+ public void onDestroyView() {
+ mUnbinder.unbind();
+ super.onDestroyView();
+ }
+
+ @OnClick(R.id.ok)
+ public void onButtonOk(View view) {
+ resultAction.copy(mAction);
+ dismiss();
+ }
+
+ @OnClick(R.id.cancel)
+ public void onButtonCancel(View view) {
+ dismiss();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle bundle) {
+ bundle.putString("action", mAction.toJSON());
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditEventDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditEventDialog.java
new file mode 100644
index 0000000000..2289b0dd18
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditEventDialog.java
@@ -0,0 +1,153 @@
+package info.nightscout.androidaps.plugins.general.automation.dialogs;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.design.widget.TextInputEditText;
+import android.support.v4.app.DialogFragment;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import butterknife.Unbinder;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.plugins.general.automation.AutomationEvent;
+import info.nightscout.androidaps.plugins.general.automation.AutomationFragment;
+import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin;
+import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector;
+
+public class EditEventDialog extends DialogFragment {
+ public interface OnClickListener {
+ void onClick(AutomationEvent event);
+ }
+
+ private static OnClickListener mClickListener = null;
+ private static AutomationEvent mEvent;
+
+ public static void setOnClickListener(OnClickListener clickListener) {
+ mClickListener = clickListener;
+ }
+
+ @BindView(R.id.inputEventTitle)
+ TextInputEditText mEditEventTitle;
+
+ @BindView(R.id.editTrigger)
+ TextView mEditTrigger;
+
+ @BindView(R.id.editAction)
+ TextView mEditAction;
+
+ @BindView(R.id.triggerDescription)
+ TextView mTriggerDescription;
+
+ @BindView(R.id.actionListView)
+ RecyclerView mActionListView;
+
+ private Unbinder mUnbinder;
+ private AutomationFragment.ActionListAdapter mActionListAdapter;
+
+ public static EditEventDialog newInstance(AutomationEvent event) {
+ mEvent = event; // FIXME
+
+ Bundle args = new Bundle();
+ EditEventDialog fragment = new EditEventDialog();
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.automation_dialog_event, container, false);
+ mUnbinder = ButterKnife.bind(this, view);
+
+ // load data from bundle
+ if (savedInstanceState != null) {
+ String eventData = savedInstanceState.getString("event");
+ if (eventData != null) mEvent.fromJSON(eventData);
+ } else {
+ mEvent.setTrigger(new TriggerConnector(TriggerConnector.Type.OR));
+ }
+
+ // display root trigger
+ mTriggerDescription.setText(mEvent.getTrigger().friendlyDescription());
+
+ // setup trigger click event listener
+ EditTriggerDialog.setOnClickListener(trigger -> {
+ mEvent.setTrigger(trigger);
+ mTriggerDescription.setText(mEvent.getTrigger().friendlyDescription());
+ });
+ mEditTrigger.setOnClickListener(v -> {
+ EditTriggerDialog dialog = EditTriggerDialog.newInstance(mEvent.getTrigger());
+ dialog.show(getFragmentManager(), "EditTriggerDialog");
+ });
+
+ // setup action list view
+ mActionListAdapter = new AutomationFragment.ActionListAdapter(getFragmentManager(), mEvent.getActions());
+ mActionListView.setLayoutManager(new LinearLayoutManager(getContext()));
+ mActionListView.setAdapter(mActionListAdapter);
+
+ // setup action click event listener
+ ChooseActionDialog.setOnClickListener(newActionObject -> {
+ mEvent.addAction(newActionObject);
+ mActionListAdapter.notifyDataSetChanged();
+ });
+ mEditAction.setOnClickListener(v -> {
+ ChooseActionDialog dialog = ChooseActionDialog.newInstance();
+ dialog.show(getFragmentManager(), "ChooseActionDialog");
+ });
+
+
+ return view;
+ }
+
+ @Override
+ public void onDestroyView() {
+ mUnbinder.unbind();
+ super.onDestroyView();
+ }
+
+ @OnClick(R.id.ok)
+ public void onButtonOk(View view) {
+ // check for title
+ String title = mEditEventTitle.getText().toString();
+ if (title.isEmpty()) {
+ Toast.makeText(getContext(), R.string.automation_missing_task_name, Toast.LENGTH_LONG).show();
+ return;
+ }
+ mEvent.setTitle(title);
+
+ // check for at least one trigger
+ TriggerConnector con = (TriggerConnector) mEvent.getTrigger();
+ if (con.size() == 0) {
+ Toast.makeText(getContext(), R.string.automation_missing_trigger, Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ // check for at least one action
+ if (mEvent.getActions().isEmpty()) {
+ Toast.makeText(getContext(), R.string.automation_missing_action, Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ if (mClickListener != null) mClickListener.onClick(mEvent);
+ dismiss();
+ }
+
+ @OnClick(R.id.cancel)
+ public void onButtonCancel(View view) {
+ dismiss();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle bundle) {
+ bundle.putString("event", mEvent.toJSON());
+ }
+
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditTriggerDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditTriggerDialog.java
new file mode 100644
index 0000000000..a22ed1380c
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditTriggerDialog.java
@@ -0,0 +1,85 @@
+package info.nightscout.androidaps.plugins.general.automation.dialogs;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import butterknife.Unbinder;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger;
+
+public class EditTriggerDialog extends DialogFragment {
+
+ public interface OnClickListener {
+ void onClick(Trigger newTriggerObject);
+ }
+
+ private static OnClickListener mClickListener = null;
+
+ @BindView(R.id.layoutTrigger)
+ LinearLayout mLayoutTrigger;
+
+ private Trigger mTrigger;
+ private Unbinder mUnbinder;
+
+ public static EditTriggerDialog newInstance(Trigger trigger) {
+ Bundle args = new Bundle();
+ args.putString("trigger", trigger.toJSON());
+ EditTriggerDialog fragment = new EditTriggerDialog();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.automation_dialog_edit_trigger, container, false);
+ mUnbinder = ButterKnife.bind(this, view);
+
+ // load data from bundle
+ Bundle bundle = savedInstanceState != null ? savedInstanceState : getArguments();
+ if (bundle != null) {
+ String triggerData = bundle.getString("trigger");
+ if (triggerData != null) mTrigger = Trigger.instantiate(triggerData);
+ }
+
+ // display root trigger
+ mLayoutTrigger.addView(mTrigger.createView(getContext(), getFragmentManager()));
+
+ return view;
+ }
+
+ public static void setOnClickListener(OnClickListener clickListener) {
+ mClickListener = clickListener;
+ }
+
+ @Override
+ public void onDestroyView() {
+ mUnbinder.unbind();
+ super.onDestroyView();
+ }
+
+ @OnClick(R.id.ok)
+ public void onButtonOk(View view) {
+ if (mClickListener != null)
+ mClickListener.onClick(mTrigger);
+
+ dismiss();
+ }
+
+ @OnClick(R.id.cancel)
+ public void onButtonCancel(View view) {
+ dismiss();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle bundle) {
+ bundle.putString("trigger", mTrigger.toJSON());
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/Element.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/Element.java
new file mode 100644
index 0000000000..de7b0e4dfd
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/Element.java
@@ -0,0 +1,7 @@
+package info.nightscout.androidaps.plugins.general.automation.elements;
+
+import android.widget.LinearLayout;
+
+public class Element {
+ public void generateDialog(LinearLayout root) { }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputBg.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputBg.java
new file mode 100644
index 0000000000..b5613345b9
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputBg.java
@@ -0,0 +1,87 @@
+package info.nightscout.androidaps.plugins.general.automation.elements;
+
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.widget.LinearLayout;
+
+import java.text.DecimalFormat;
+
+import info.nightscout.androidaps.Constants;
+import info.nightscout.androidaps.data.Profile;
+import info.nightscout.androidaps.utils.NumberPicker;
+
+public class InputBg extends Element {
+ final private TextWatcher textWatcher = new TextWatcher() {
+ @Override
+ public void afterTextChanged(Editable s) {
+ // TODO: validate inputs
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+ };
+
+ private String units;
+ private double value;
+ private final double minValue, maxValue, step;
+ private final DecimalFormat decimalFormat;
+
+ public InputBg(String units) {
+ this.units = units;
+
+ // set default initial value
+ if (units.equals(Constants.MMOL)) {
+ // mmol
+ value = 5.5;
+ minValue = 2;
+ maxValue = 30;
+ step = 0.1;
+ decimalFormat = new DecimalFormat("0.0");
+ } else {
+ // mg/dL
+ value = 100;
+ minValue = 36;
+ maxValue = 540;
+ step = 1;
+ decimalFormat = new DecimalFormat("0");
+ }
+ }
+
+ @Override
+ public void generateDialog(LinearLayout root) {
+ NumberPicker numberPicker = new NumberPicker(root.getContext(), null);
+ numberPicker.setParams(0d, minValue, maxValue, step, decimalFormat, false, textWatcher);
+ numberPicker.setValue(value);
+ numberPicker.setOnValueChangedListener(value -> this.value = value);
+ root.addView(numberPicker);
+ }
+
+ public String getUnits() {
+ return units;
+ }
+
+ public void setUnits(String units) {
+ if (!this.units.equals(units)) {
+ String previousUnits = this.units;
+ this.units = units;
+ value = Profile.toUnits(Profile.toMgdl(value, previousUnits), Profile.toMmol(value, previousUnits), units);
+ }
+ }
+
+ public double getValue() {
+ return value;
+ }
+
+ public int getMgdl() {
+ return (int)Profile.toMgdl(value, units);
+ }
+
+ public void setMgdl(int value) {
+ this.value = Profile.fromMgdlToUnits(value, units);
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputDuration.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputDuration.java
new file mode 100644
index 0000000000..abb699da72
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputDuration.java
@@ -0,0 +1,61 @@
+package info.nightscout.androidaps.plugins.general.automation.elements;
+
+import android.widget.LinearLayout;
+
+import java.text.DecimalFormat;
+
+import info.nightscout.androidaps.utils.NumberPicker;
+
+public class InputDuration extends Element {
+ public enum TimeUnit {
+ MINUTES,
+ HOURS
+ }
+
+ private TimeUnit unit;
+ private double value;
+
+ public InputDuration(double value, TimeUnit unit) {
+ this.unit = unit;
+ this.value = value;
+ }
+
+ @Override
+ public void generateDialog(LinearLayout root) {
+ NumberPicker numberPicker = new NumberPicker(root.getContext(), null);
+ if (unit.equals(TimeUnit.MINUTES)) {
+ // Minutes
+ numberPicker.setParams(0d, 0d, 24 * 60d, 10d, new DecimalFormat("0"), false);
+ } else {
+ // Hours
+ numberPicker.setParams(0d, 0d, 24d, 1d, new DecimalFormat("0"), false);
+ }
+ numberPicker.setValue(value);
+ numberPicker.setOnValueChangedListener(value -> this.value = value);
+ root.addView(numberPicker);
+ }
+
+ public TimeUnit getUnit() {
+ return unit;
+ }
+
+ public double getValue() {
+ return value;
+ }
+
+ public void setMinutes(double value) {
+ if (unit.equals(TimeUnit.MINUTES)) {
+ this.value = value;
+ } else {
+ this.value = value / 60d;
+ }
+ }
+
+ public double getMinutes() {
+ if (unit.equals(TimeUnit.MINUTES)) {
+ return value;
+ } else {
+ return value * 60d;
+ }
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/Label.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/Label.java
new file mode 100644
index 0000000000..939b3aa2ac
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/Label.java
@@ -0,0 +1,59 @@
+package info.nightscout.androidaps.plugins.general.automation.elements;
+
+import android.graphics.Typeface;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import info.nightscout.androidaps.MainApp;
+
+public class Label extends Element {
+ private final Element element;
+ private final String textPre;
+ private final String textPost;
+
+ public Label(String textPre, String textPost, Element element) {
+ this.element = element;
+ this.textPre = textPre;
+ this.textPost = textPost;
+ }
+
+ @Override
+ public void generateDialog(LinearLayout root) {
+ // container layout
+ LinearLayout layout = new LinearLayout(root.getContext());
+ layout.setOrientation(LinearLayout.HORIZONTAL);
+ layout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ // text view pre element
+ int px = MainApp.dpToPx(10);
+ TextView textViewPre = new TextView(root.getContext());
+ textViewPre.setText(textPre);
+ textViewPre.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ textViewPre.setWidth(MainApp.dpToPx(120));
+ textViewPre.setPadding(px, px, px, px);
+ textViewPre.setTypeface(textViewPre.getTypeface(), Typeface.BOLD);
+ layout.addView(textViewPre);
+
+ // add element to layout
+ element.generateDialog(layout);
+
+ // text view post element
+ if (textPost != null) {
+ px = MainApp.dpToPx(5);
+ TextView textViewPost = new TextView(root.getContext());
+ textViewPost.setText(textPost);
+ textViewPost.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ textViewPost.setWidth(MainApp.dpToPx(45));
+ textViewPost.setPadding(px, px, px, px);
+ textViewPost.setTypeface(textViewPost.getTypeface(), Typeface.BOLD);
+ layout.addView(textViewPost);
+ }
+
+ // add layout to root layout
+ root.addView(layout);
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/Trigger.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/Trigger.java
new file mode 100644
index 0000000000..2f766a14c8
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/Trigger.java
@@ -0,0 +1,142 @@
+package info.nightscout.androidaps.plugins.general.automation.triggers;
+
+import android.support.v4.app.FragmentManager;
+import android.content.Context;
+import android.support.annotation.StringRes;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.google.common.base.Optional;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import info.nightscout.androidaps.MainApp;
+import info.nightscout.androidaps.R;
+
+public abstract class Trigger {
+
+ public enum Comparator {
+ IS_LOWER,
+ IS_EQUAL_OR_LOWER,
+ IS_EQUAL,
+ IS_EQUAL_OR_GREATER,
+ IS_GREATER,
+ IS_NOT_AVAILABLE;
+
+ public @StringRes int getStringRes() {
+ switch (this) {
+ case IS_LOWER:
+ return R.string.islower;
+ case IS_EQUAL_OR_LOWER:
+ return R.string.isequalorlower;
+ case IS_EQUAL:
+ return R.string.isequal;
+ case IS_EQUAL_OR_GREATER:
+ return R.string.isequalorgreater;
+ case IS_GREATER:
+ return R.string.isgreater;
+ case IS_NOT_AVAILABLE:
+ return R.string.isnotavailable;
+ default:
+ return R.string.unknown;
+ }
+ }
+
+ public boolean check(T obj1, T obj2) {
+ if (obj1 == null || obj2 == null)
+ return this.equals(Comparator.IS_NOT_AVAILABLE);
+
+ int comparison = obj1.compareTo(obj2);
+ switch (this) {
+ case IS_LOWER:
+ return comparison < 0;
+ case IS_EQUAL_OR_LOWER:
+ return comparison <= 0;
+ case IS_EQUAL:
+ return comparison == 0;
+ case IS_EQUAL_OR_GREATER:
+ return comparison >= 0;
+ case IS_GREATER:
+ return comparison > 0;
+ default:
+ return false;
+ }
+ }
+
+ public static List labels() {
+ List list = new ArrayList<>();
+ for(Comparator c : values()) {
+ list.add(MainApp.gs(c.getStringRes()));
+ }
+ return list;
+ }
+ }
+
+ protected TriggerConnector connector = null;
+
+ Trigger() {
+ }
+
+ public TriggerConnector getConnector() {
+ return connector;
+ }
+
+ public abstract boolean shouldRun();
+
+ public abstract String toJSON();
+
+ /*package*/ abstract Trigger fromJSON(String data);
+
+ public abstract int friendlyName();
+
+ public abstract String friendlyDescription();
+
+ public abstract Optional icon();
+
+ void notifyAboutRun(long time) {
+ }
+
+ public abstract Trigger duplicate();
+
+ public static Trigger instantiate(String json) {
+ try {
+ return instantiate(new JSONObject(json));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public static Trigger instantiate(JSONObject object) {
+ try {
+ String type = object.getString("type");
+ JSONObject data = object.getJSONObject("data");
+ Class clazz = Class.forName(type);
+ return ((Trigger) clazz.newInstance()).fromJSON(data.toString());
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | JSONException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public View createView(Context context, FragmentManager fragmentManager) {
+ final int padding = MainApp.dpToPx(4);
+
+ LinearLayout root = new LinearLayout(context);
+ root.setPadding(padding, padding, padding, padding);
+ root.setOrientation(LinearLayout.VERTICAL);
+ root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+
+ TextView title = new TextView(context);
+ title.setText(friendlyName());
+ root.addView(title);
+
+ return root;
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBg.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBg.java
new file mode 100644
index 0000000000..cd3217b414
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBg.java
@@ -0,0 +1,208 @@
+package info.nightscout.androidaps.plugins.general.automation.triggers;
+
+import android.support.v4.app.FragmentManager;
+import android.content.Context;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.google.common.base.Optional;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.text.DecimalFormat;
+
+import info.nightscout.androidaps.MainApp;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.data.GlucoseStatus;
+import info.nightscout.androidaps.data.Profile;
+import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions;
+import info.nightscout.androidaps.utils.JsonHelper;
+import info.nightscout.androidaps.utils.NumberPicker;
+
+public class TriggerBg extends Trigger {
+
+ private double threshold = 100.0; // FIXME
+ private Comparator comparator = Comparator.IS_EQUAL;
+ private String units = ProfileFunctions.getInstance().getProfileUnits();
+
+ final private TextWatcher textWatcher = new TextWatcher() {
+ @Override
+ public void afterTextChanged(Editable s) {
+ // TODO: validate inputs
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+ };
+
+ public TriggerBg() {
+ super();
+ }
+
+ private TriggerBg(TriggerBg triggerBg) {
+ super();
+ comparator = triggerBg.comparator;
+ units = triggerBg.units;
+ threshold = triggerBg.threshold;
+ }
+
+ public double getThreshold() {
+ return threshold;
+ }
+
+ public Comparator getComparator() {
+ return comparator;
+ }
+
+ public String getUnits() {
+ return units;
+ }
+
+ @Override
+ public synchronized boolean shouldRun() {
+ GlucoseStatus glucoseStatus = GlucoseStatus.getGlucoseStatusData();
+
+ if (glucoseStatus == null && comparator.equals(Comparator.IS_NOT_AVAILABLE))
+ return true;
+ if (glucoseStatus == null)
+ return false;
+
+ return comparator.check(glucoseStatus.glucose, Profile.toMgdl(threshold, units));
+ }
+
+ @Override
+ public synchronized String toJSON() {
+ JSONObject o = new JSONObject();
+ try {
+ o.put("type", TriggerBg.class.getName());
+ JSONObject data = new JSONObject();
+ data.put("threshold", threshold);
+ data.put("comparator", comparator.toString());
+ data.put("units", units);
+ o.put("data", data);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return o.toString();
+ }
+
+ @Override
+ Trigger fromJSON(String data) {
+ try {
+ JSONObject d = new JSONObject(data);
+ threshold = JsonHelper.safeGetDouble(d, "threshold");
+ comparator = Comparator.valueOf(JsonHelper.safeGetString(d, "comparator"));
+ units = JsonHelper.safeGetString(d, "units");
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return this;
+ }
+
+ @Override
+ public int friendlyName() {
+ return R.string.glucose;
+ }
+
+ @Override
+ public String friendlyDescription() {
+ if (comparator.equals(Comparator.IS_NOT_AVAILABLE))
+ return MainApp.gs(R.string.glucoseisnotavailable);
+ else
+ return MainApp.gs(R.string.glucosecompared, MainApp.gs(comparator.getStringRes()), threshold, units);
+ }
+
+ @Override
+ public Optional icon() {
+ return Optional.of(R.drawable.icon_cp_bgcheck);
+ }
+
+ @Override
+ public Trigger duplicate() {
+ return new TriggerBg(this);
+ }
+
+ TriggerBg threshold(double threshold) {
+ this.threshold = threshold;
+ return this;
+ }
+
+ TriggerBg comparator(Comparator comparator) {
+ this.comparator = comparator;
+ return this;
+ }
+
+ TriggerBg units(String units) {
+ this.units = units;
+ return this;
+ }
+
+ @Override
+ public View createView(Context context, FragmentManager fragmentManager) {
+ LinearLayout root = (LinearLayout) super.createView(context, fragmentManager);
+
+ // spinner for comparator
+ Spinner spinner = new Spinner(context);
+ ArrayAdapter spinnerArrayAdapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item, Comparator.labels());
+ spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinner.setAdapter(spinnerArrayAdapter);
+ LinearLayout.LayoutParams spinnerParams = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ );
+ spinnerParams.setMargins(0, MainApp.dpToPx(4), 0, MainApp.dpToPx(4));
+ spinner.setLayoutParams(spinnerParams);
+ spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ comparator = Comparator.values()[position];
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) { }
+ });
+ spinner.setSelection(comparator.ordinal());
+ root.addView(spinner);
+
+ // horizontal layout
+ LinearLayout layout = new LinearLayout(context);
+ layout.setOrientation(LinearLayout.HORIZONTAL);
+ layout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ root.addView(layout);
+
+ // input field for threshold
+ NumberPicker numberPicker = new NumberPicker(context, null);
+ numberPicker.setParams(0d, 0d, (double) 500, 1d, new DecimalFormat("0"), false, textWatcher);
+ numberPicker.setValue(threshold);
+ numberPicker.setOnValueChangedListener(value -> threshold = value);
+ layout.addView(numberPicker);
+
+ // text view for unit
+ TextView tvUnits = new TextView(context);
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.MATCH_PARENT
+ );
+ params.setMargins(MainApp.dpToPx(6), 0, 0, 0);
+ tvUnits.setLayoutParams(params);
+ tvUnits.setText(units);
+ tvUnits.setGravity(Gravity.CENTER_VERTICAL);
+ layout.addView(tvUnits);
+
+ return root;
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerConnector.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerConnector.java
new file mode 100644
index 0000000000..db4541f373
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerConnector.java
@@ -0,0 +1,262 @@
+package info.nightscout.androidaps.plugins.general.automation.triggers;
+
+import android.support.v4.app.FragmentManager;
+import android.content.Context;
+import android.support.annotation.StringRes;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.google.common.base.Optional;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import info.nightscout.androidaps.MainApp;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.plugins.general.automation.AutomationFragment;
+import info.nightscout.androidaps.utils.JsonHelper;
+
+public class TriggerConnector extends Trigger {
+ public enum Type {
+ AND,
+ OR,
+ XOR;
+
+ public boolean apply(boolean a, boolean b) {
+ switch (this) {
+ case AND:
+ return a && b;
+ case OR:
+ return a || b;
+ case XOR:
+ return a ^ b;
+ }
+ return false;
+ }
+
+ public @StringRes int getStringRes() {
+ switch (this) {
+ case OR:
+ return R.string.or;
+ case XOR:
+ return R.string.xor;
+
+ default:
+ case AND:
+ return R.string.and;
+ }
+ }
+
+ public static List labels() {
+ List list = new ArrayList<>();
+ for(Type t : values()) {
+ list.add(MainApp.gs(t.getStringRes()));
+ }
+ return list;
+ }
+ }
+
+ public static void fillIconSet(TriggerConnector connector, HashSet set) {
+ for(Trigger t : connector.list) {
+ if (t instanceof TriggerConnector) {
+ fillIconSet((TriggerConnector) t, set);
+ } else {
+ Optional icon = t.icon();
+ if (icon.isPresent()) {
+ set.add(icon.get());
+ }
+ }
+ }
+ }
+
+ protected List list = new ArrayList<>();
+ private Type connectorType;
+
+ public TriggerConnector() {
+ connectorType = Type.AND;
+ }
+
+ public TriggerConnector(Type connectorType) {
+ this.connectorType = connectorType;
+ }
+
+ public void changeConnectorType(Type type) { this.connectorType = type; }
+
+ public Type getConnectorType() { return connectorType; }
+
+ public synchronized void add(Trigger t) {
+ list.add(t);
+ t.connector = this;
+ }
+
+ public synchronized void add(int pos, Trigger t) {
+ list.add(pos, t);
+ t.connector = this;
+ }
+
+ public synchronized boolean remove(Trigger t) {
+ return list.remove(t);
+ }
+
+ public int size() {
+ return list.size();
+ }
+
+ public Trigger get(int i) {
+ return list.get(i);
+ }
+
+ public int pos(Trigger trigger) {
+ for(int i = 0; i < list.size(); ++i) {
+ if (list.get(i) == trigger) return i;
+ }
+ return -1;
+ }
+
+ @Override
+ public synchronized boolean shouldRun() {
+ boolean result = true;
+
+ // check first trigger
+ if (list.size() > 0)
+ result = list.get(0).shouldRun();
+
+ // check all others
+ for (int i = 1; i < list.size(); ++i) {
+ result = connectorType.apply(result, list.get(i).shouldRun());
+ }
+
+ return result;
+ }
+
+ @Override
+ public synchronized String toJSON() {
+ JSONObject o = new JSONObject();
+ try {
+ o.put("type", TriggerConnector.class.getName());
+ JSONObject data = new JSONObject();
+ data.put("connectorType", connectorType.toString());
+ JSONArray array = new JSONArray();
+ for (Trigger t : list) {
+ array.put(t.toJSON());
+ }
+ data.put("triggerList", array);
+ o.put("data", data);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return o.toString();
+ }
+
+ @Override
+ Trigger fromJSON(String data) {
+ try {
+ JSONObject d = new JSONObject(data);
+ connectorType = Type.valueOf(JsonHelper.safeGetString(d, "connectorType"));
+ JSONArray array = d.getJSONArray("triggerList");
+ for (int i = 0; i < array.length(); i++) {
+ Trigger newItem = instantiate(new JSONObject(array.getString(i)));
+ add(newItem);
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return this;
+ }
+
+ @Override
+ public int friendlyName() {
+ return connectorType.getStringRes();
+ }
+
+ @Override
+ public String friendlyDescription() {
+ int counter = 0;
+ StringBuilder result = new StringBuilder();
+ for (Trigger t : list) {
+ if (counter++ > 0) result.append(" " + MainApp.gs(friendlyName()) + " ");
+ result.append(t.friendlyDescription());
+ }
+ return result.toString();
+ }
+
+ @Override
+ public Optional icon() {
+ return Optional.absent();
+ }
+
+ @Override
+ public Trigger duplicate() {
+ return null;
+ }
+
+ private AutomationFragment.TriggerListAdapter adapter;
+
+ public void rebuildView() {
+ if (adapter != null)
+ adapter.rebuild();
+ }
+
+ @Override
+ public View createView(Context context, FragmentManager fragmentManager) {
+ final int padding = MainApp.dpToPx(5);
+
+ LinearLayout root = new LinearLayout(context);
+ root.setOrientation(LinearLayout.VERTICAL);
+ root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ root.setPadding(padding,padding,padding,padding);
+ root.setBackgroundResource(R.drawable.border_automation_unit);
+
+ LinearLayout triggerListLayout = new LinearLayout(context);
+ triggerListLayout.setOrientation(LinearLayout.VERTICAL);
+ triggerListLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ root.addView(triggerListLayout);
+
+ adapter = new AutomationFragment.TriggerListAdapter(context, fragmentManager, triggerListLayout, this);
+
+ return root;
+ }
+
+ public TriggerConnector simplify() {
+ // simplify children
+ for(int i = 0; i < size(); ++i) {
+ if (get(i) instanceof TriggerConnector) {
+ TriggerConnector t = (TriggerConnector) get(i);
+ t.simplify();
+ }
+ }
+
+ // drop connector with only 1 element
+ if (size() == 1 && get(0) instanceof TriggerConnector) {
+ TriggerConnector c = (TriggerConnector) get(0);
+ remove(c);
+ changeConnectorType(c.getConnectorType());
+ for (Trigger t : c.list) {
+ add(t);
+ }
+ c.list.clear();
+ return simplify();
+ }
+
+ // merge connectors
+ if (connector != null && (connector.getConnectorType().equals(connectorType) || size() == 1)) {
+ final int pos = connector.pos(this);
+ connector.remove(this);
+ // move triggers of child connector into parent connector
+ for (int i = size()-1; i >= 0; --i) {
+ connector.add(pos, get(i));
+ }
+ list.clear();
+ return connector.simplify();
+ }
+
+ return this;
+ }
+
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTime.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTime.java
new file mode 100644
index 0000000000..faeeafcb1d
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTime.java
@@ -0,0 +1,303 @@
+package info.nightscout.androidaps.plugins.general.automation.triggers;
+
+import android.support.v4.app.FragmentManager;
+import android.content.Context;
+import android.support.annotation.StringRes;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.dpro.widgets.WeekdaysPicker;
+import com.google.common.base.Optional;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import info.nightscout.androidaps.MainApp;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.utils.DateUtil;
+import info.nightscout.androidaps.utils.JsonHelper;
+import info.nightscout.androidaps.utils.T;
+
+public class TriggerTime extends Trigger {
+
+ public enum DayOfWeek {
+ MONDAY,
+ TUESDAY,
+ WEDNESDAY,
+ THURSDAY,
+ FRIDAY,
+ SATURDAY,
+ SUNDAY;
+
+ private static final int[] calendarInts = new int[] {
+ Calendar.MONDAY,
+ Calendar.TUESDAY,
+ Calendar.WEDNESDAY,
+ Calendar.THURSDAY,
+ Calendar.FRIDAY,
+ Calendar.SATURDAY,
+ Calendar.SUNDAY
+ };
+
+ private static final int[] fullNames = new int[] {
+ R.string.weekday_monday,
+ R.string.weekday_tuesday,
+ R.string.weekday_wednesday,
+ R.string.weekday_thursday,
+ R.string.weekday_friday,
+ R.string.weekday_saturday,
+ R.string.weekday_sunday
+ };
+
+ private static final int[] shortNames = new int[] {
+ R.string.weekday_monday_short,
+ R.string.weekday_tuesday_short,
+ R.string.weekday_wednesday_short,
+ R.string.weekday_thursday_short,
+ R.string.weekday_friday_short,
+ R.string.weekday_saturday_short,
+ R.string.weekday_sunday_short
+ };
+
+ public int toCalendarInt() {
+ return calendarInts[ordinal()];
+ }
+
+ public static DayOfWeek fromCalendarInt(int day) {
+ for(int i = 0; i < calendarInts.length; ++i) {
+ if (calendarInts[i] == day)
+ return values()[i];
+ }
+ return null;
+ }
+
+ public @StringRes int getFullName() {
+ return fullNames[ordinal()];
+ }
+
+ public @StringRes int getShortName() {
+ return shortNames[ordinal()];
+ }
+ }
+
+ private final boolean[] weekdays = new boolean[DayOfWeek.values().length];
+
+ private long lastRun;
+
+ // Single execution
+ private long runAt;
+
+ // Recurring
+ private boolean recurring;
+ private int hour;
+ private int minute;
+
+ private long validTo;
+
+ public TriggerTime() {
+ super();
+ setAll(false);
+ }
+
+ private TriggerTime(TriggerTime triggerTime) {
+ super();
+ lastRun = triggerTime.lastRun;
+ runAt = triggerTime.runAt;
+ recurring = triggerTime.recurring;
+ hour = triggerTime.hour;
+ minute = triggerTime.minute;
+ validTo = triggerTime.validTo;
+
+ for(int i = 0; i < weekdays.length; ++i) {
+ weekdays[i] = triggerTime.weekdays[i];
+ }
+ }
+
+ public void setAll(boolean value) {
+ for(DayOfWeek day : DayOfWeek.values()) {
+ set(day, value);
+ }
+ }
+
+ public TriggerTime set(DayOfWeek day, boolean value) {
+ weekdays[day.ordinal()] = value;
+ return this;
+ }
+
+ public boolean isSet(DayOfWeek day) {
+ return weekdays[day.ordinal()];
+ }
+
+ public long getLastRun() {
+ return lastRun;
+ }
+
+ public long getRunAt() {
+ return runAt;
+ }
+
+ public boolean isRecurring() {
+ return recurring;
+ }
+
+ @Override
+ public boolean shouldRun() {
+ if (recurring) {
+ if (validTo != 0 && DateUtil.now() > validTo)
+ return false;
+ Calendar c = Calendar.getInstance();
+ int scheduledDayOfWeek = c.get(Calendar.DAY_OF_WEEK);
+
+ Calendar scheduledCal = DateUtil.gregorianCalendar();
+ scheduledCal.set(Calendar.HOUR_OF_DAY, hour);
+ scheduledCal.set(Calendar.MINUTE, minute);
+ scheduledCal.set(Calendar.SECOND, 0);
+ long scheduled = scheduledCal.getTimeInMillis();
+
+ if (isSet(DayOfWeek.fromCalendarInt(scheduledDayOfWeek))) {
+ if (DateUtil.now() >= scheduled && DateUtil.now() - scheduled < T.mins(5).msecs()) {
+ if (lastRun < scheduled)
+ return true;
+ }
+ }
+ return false;
+ } else {
+ long now = DateUtil.now();
+ if (now >= runAt && now - runAt < T.mins(5).msecs())
+ return true;
+ return false;
+ }
+ }
+
+ @Override
+ public String toJSON() {
+ JSONObject object = new JSONObject();
+ JSONObject data = new JSONObject();
+ try {
+ data.put("lastRun", lastRun);
+ data.put("runAt", runAt);
+ data.put("recurring", recurring);
+ for(int i = 0; i < weekdays.length; ++i) {
+ data.put(DayOfWeek.values()[i].name(), weekdays[i]);
+ }
+ data.put("hour", hour);
+ data.put("minute", minute);
+ data.put("validTo", validTo);
+ object.put("type", TriggerTime.class.getName());
+ object.put("data", data);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return object.toString();
+ }
+
+ @Override
+ Trigger fromJSON(String data) {
+ JSONObject o;
+ try {
+ o = new JSONObject(data);
+ lastRun = JsonHelper.safeGetLong(o, "lastRun");
+ runAt = JsonHelper.safeGetLong(o, "runAt");
+ recurring = JsonHelper.safeGetBoolean(o, "recurring");
+ for(int i = 0; i < weekdays.length; ++i) {
+ weekdays[i] = JsonHelper.safeGetBoolean(o, DayOfWeek.values()[i].name());
+ }
+ hour = JsonHelper.safeGetInt(o, "hour");
+ minute = JsonHelper.safeGetInt(o, "minute");
+ validTo = JsonHelper.safeGetLong(o, "validTo");
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return this;
+ }
+
+ @Override
+ public int friendlyName() {
+ return R.string.time;
+ }
+
+ @Override
+ public String friendlyDescription() {
+ if (recurring) {
+ // TODO
+ return "Every ";
+ } else {
+ return MainApp.gs(R.string.atspecifiedtime, DateUtil.dateAndTimeString(runAt));
+ }
+ }
+
+ @Override
+ public Optional icon() {
+ return Optional.of(R.drawable.ic_access_alarm_24dp);
+ }
+
+ @Override
+ void notifyAboutRun(long time) {
+ lastRun = time;
+ }
+
+ @Override
+ public Trigger duplicate() {
+ return new TriggerTime(this);
+ }
+
+ TriggerTime lastRun(long lastRun) {
+ this.lastRun = lastRun;
+ return this;
+ }
+
+ TriggerTime runAt(long runAt) {
+ this.runAt = runAt;
+ return this;
+ }
+
+ TriggerTime recurring(boolean recurring) {
+ this.recurring = recurring;
+ return this;
+ }
+
+ TriggerTime validTo(long validTo) {
+ this.validTo = validTo;
+ return this;
+ }
+
+ TriggerTime hour(int hour) {
+ this.hour = hour;
+ return this;
+ }
+
+ TriggerTime minute(int minute) {
+ this.minute = minute;
+ return this;
+ }
+
+ private List getSelectedDays() {
+ List selectedDays = new ArrayList<>();
+ for(int i = 0; i < weekdays.length; ++i) {
+ DayOfWeek day = DayOfWeek.values()[i];
+ boolean selected = weekdays[i];
+ if (selected) selectedDays.add(day.toCalendarInt());
+ }
+ return selectedDays;
+ }
+
+ @Override
+ public View createView(Context context, FragmentManager fragmentManager) {
+ LinearLayout root = (LinearLayout) super.createView(context, fragmentManager);
+
+ // TODO: Replace external tool WeekdaysPicker with a self-made GUI element
+ WeekdaysPicker weekdaysPicker = new WeekdaysPicker(context);
+ weekdaysPicker.setEditable(true);
+ weekdaysPicker.setSelectedDays(getSelectedDays());
+ weekdaysPicker.setOnWeekdaysChangeListener((view, i, list) -> set(DayOfWeek.fromCalendarInt(i), list.contains(i)));
+ weekdaysPicker.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ root.addView(weekdaysPicker);
+ return root;
+ }
+}
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..cd97b21f06 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
@@ -14,6 +14,14 @@ import android.support.v4.app.NotificationCompat;
import android.support.v4.app.RemoteInput;
import android.support.v4.app.TaskStackBuilder;
+// Android Auto
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationManagerCompat;
+import android.support.v4.app.RemoteInput;
+
+
+
+
import com.squareup.otto.Subscribe;
import info.nightscout.androidaps.Constants;
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 95ceccab3d..9720e2d253 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
@@ -242,6 +242,7 @@ public class SmsCommunicatorPlugin extends PluginBase {
LoopPlugin loopPlugin = MainApp.getSpecificPlugin(LoopPlugin.class);
if (loopPlugin != null && loopPlugin.isEnabled(PluginType.LOOP)) {
loopPlugin.setPluginEnabled(PluginType.LOOP, false);
+ ConfigBuilderPlugin.getPlugin().storeSettings("SMS_LOOP_STOP");
ConfigBuilderPlugin.getPlugin().getCommandQueue().cancelTempBasal(true, new Callback() {
@Override
public void run() {
@@ -260,6 +261,7 @@ public class SmsCommunicatorPlugin extends PluginBase {
loopPlugin = MainApp.getSpecificPlugin(LoopPlugin.class);
if (loopPlugin != null && !loopPlugin.isEnabled(PluginType.LOOP)) {
loopPlugin.setPluginEnabled(PluginType.LOOP, true);
+ ConfigBuilderPlugin.getPlugin().storeSettings("SMS_LOOP_START");
reply = MainApp.gs(R.string.smscommunicator_loophasbeenenabled);
sendSMS(new Sms(receivedSms.phoneNumber, reply, System.currentTimeMillis()));
MainApp.bus().post(new EventRefreshOverview("SMS_LOOP_START"));
diff --git a/app/src/main/java/info/nightscout/androidaps/services/LocationService.java b/app/src/main/java/info/nightscout/androidaps/services/LocationService.java
new file mode 100644
index 0000000000..b2d40b02bc
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/services/LocationService.java
@@ -0,0 +1,146 @@
+package info.nightscout.androidaps.services;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.location.Location;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.v4.app.ActivityCompat;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import info.nightscout.androidaps.MainApp;
+import info.nightscout.androidaps.R;
+import info.nightscout.androidaps.events.EventLocationChange;
+import info.nightscout.androidaps.logging.L;
+import info.nightscout.androidaps.utils.SP;
+
+public class LocationService extends Service {
+ private static Logger log = LoggerFactory.getLogger(L.LOCATION);
+
+ private LocationManager mLocationManager = null;
+ private static final int LOCATION_INTERVAL = 1000;
+ private static final float LOCATION_DISTANCE = 10f;
+
+ public LocationService() {
+ MainApp.bus().register(this);
+ }
+
+ private class LocationListener implements android.location.LocationListener {
+ Location mLastLocation;
+
+ public LocationListener(String provider) {
+ if (L.isEnabled(L.LOCATION))
+ log.debug("LocationListener " + provider);
+ mLastLocation = new Location(provider);
+ }
+
+ @Override
+ public void onLocationChanged(Location location) {
+ if (L.isEnabled(L.LOCATION))
+ log.debug("onLocationChanged: " + location);
+ mLastLocation.set(location);
+ MainApp.bus().post(new EventLocationChange(location));
+ }
+
+ @Override
+ public void onProviderDisabled(String provider) {
+ if (L.isEnabled(L.LOCATION))
+ log.debug("onProviderDisabled: " + provider);
+ }
+
+ @Override
+ public void onProviderEnabled(String provider) {
+ if (L.isEnabled(L.LOCATION))
+ log.debug("onProviderEnabled: " + provider);
+ }
+
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ if (L.isEnabled(L.LOCATION))
+ log.debug("onStatusChanged: " + provider);
+ }
+ }
+
+ LocationListener mLocationListener = new LocationListener(LocationManager.PASSIVE_PROVIDER);
+
+ @Override
+ public IBinder onBind(Intent arg0) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (L.isEnabled(L.LOCATION))
+ log.debug("onStartCommand");
+ super.onStartCommand(intent, flags, startId);
+ return START_STICKY;
+ }
+
+ @Override
+ public void onCreate() {
+
+ if (L.isEnabled(L.LOCATION))
+ log.debug("onCreate");
+
+ initializeLocationManager();
+
+ try {
+ if (SP.getString(R.string.key_location, "NONE").equals("NETWORK"))
+ mLocationManager.requestLocationUpdates(
+ LocationManager.NETWORK_PROVIDER,
+ LOCATION_INTERVAL,
+ LOCATION_DISTANCE,
+ mLocationListener
+ );
+ if (SP.getString(R.string.key_location, "NONE").equals("GPS"))
+ mLocationManager.requestLocationUpdates(
+ LocationManager.GPS_PROVIDER,
+ LOCATION_INTERVAL,
+ LOCATION_DISTANCE,
+ mLocationListener
+ );
+ if (SP.getString(R.string.key_location, "NONE").equals("PASSIVE"))
+ mLocationManager.requestLocationUpdates(
+ LocationManager.PASSIVE_PROVIDER,
+ LOCATION_INTERVAL,
+ LOCATION_DISTANCE,
+ mLocationListener
+ );
+ } catch (java.lang.SecurityException ex) {
+ log.error("fail to request location update, ignore", ex);
+ } catch (IllegalArgumentException ex) {
+ log.error("network provider does not exist, " + ex.getMessage());
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (L.isEnabled(L.LOCATION))
+ log.debug("onDestroy");
+ super.onDestroy();
+ MainApp.bus().unregister(this);
+ if (mLocationManager != null) {
+ try {
+ if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ mLocationManager.removeUpdates(mLocationListener);
+ } catch (Exception ex) {
+ log.error("fail to remove location listener, ignore", ex);
+ }
+ }
+ }
+
+ private void initializeLocationManager() {
+ if (L.isEnabled(L.LOCATION))
+ log.debug("initializeLocationManager - LOCATION_INTERVAL: " + LOCATION_INTERVAL + " LOCATION_DISTANCE: " + LOCATION_DISTANCE);
+ if (mLocationManager == null) {
+ mLocationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
+ }
+ }
+}
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..9ce121962b 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java
+++ b/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java
@@ -187,4 +187,8 @@ public class DateUtil {
long diff = Math.abs(date - now());
return diff < T.mins(2).msecs();
}
+
+ public static GregorianCalendar gregorianCalendar() {
+ return new GregorianCalendar();
+ }
}
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 71c16442dc..f32b79c439 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/NumberPicker.java
+++ b/app/src/main/java/info/nightscout/androidaps/utils/NumberPicker.java
@@ -34,6 +34,10 @@ public class NumberPicker extends LinearLayout implements View.OnKeyListener,
View.OnTouchListener, View.OnClickListener {
private static Logger log = LoggerFactory.getLogger(NumberPicker.class);
+ public interface OnValueChangedListener {
+ void onValueChanged(double value);
+ }
+
TextView editText;
Button minusButton;
Button plusButton;
@@ -48,6 +52,7 @@ public class NumberPicker extends LinearLayout implements View.OnKeyListener,
private Handler mHandler;
private ScheduledExecutorService mUpdater;
+ private OnValueChangedListener mOnValueChangedListener;
private class UpdateCounterTask implements Runnable {
private boolean mInc;
@@ -139,10 +144,15 @@ public class NumberPicker extends LinearLayout implements View.OnKeyListener,
@Override
public void afterTextChanged(Editable s) {
value = SafeParse.stringToDouble(editText.getText().toString());
+ callValueChangedListener();
}
});
}
+ public void setOnValueChangedListener(OnValueChangedListener onValueChangedListener) {
+ mOnValueChangedListener = onValueChangedListener;
+ }
+
public void setTextWatcher(TextWatcher textWatcher) {
this.textWatcher = textWatcher;
editText.addTextChangedListener(textWatcher);
@@ -164,6 +174,7 @@ public class NumberPicker extends LinearLayout implements View.OnKeyListener,
this.step = step;
this.formater = formater;
this.allowZero = allowZero;
+ callValueChangedListener();
editText.setKeyListener(DigitsKeyListener.getInstance(minValue < 0, step != Math.rint(step)));
@@ -178,6 +189,7 @@ public class NumberPicker extends LinearLayout implements View.OnKeyListener,
if (textWatcher != null)
editText.removeTextChangedListener(textWatcher);
this.value = value;
+ callValueChangedListener();
updateEditText();
if (textWatcher != null)
editText.addTextChangedListener(textWatcher);
@@ -199,6 +211,7 @@ public class NumberPicker extends LinearLayout implements View.OnKeyListener,
value += step * multiplier;
if (value > maxValue) {
value = maxValue;
+ callValueChangedListener();
ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.youareonallowedlimit));
stopUpdating();
}
@@ -209,6 +222,7 @@ public class NumberPicker extends LinearLayout implements View.OnKeyListener,
value -= step * multiplier;
if (value < minValue) {
value = minValue;
+ callValueChangedListener();
ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.youareonallowedlimit));
stopUpdating();
}
@@ -222,6 +236,11 @@ public class NumberPicker extends LinearLayout implements View.OnKeyListener,
editText.setText(formater.format(value));
}
+ private void callValueChangedListener() {
+ if (mOnValueChangedListener != null)
+ mOnValueChangedListener.onValueChanged(value);
+ }
+
private void startUpdating(boolean inc) {
if (mUpdater != null) {
log.debug("Another executor is still active");
diff --git a/app/src/main/java/info/nightscout/utils/MidnightTime.java b/app/src/main/java/info/nightscout/utils/MidnightTime.java
new file mode 100644
index 0000000000..523fe13826
--- /dev/null
+++ b/app/src/main/java/info/nightscout/utils/MidnightTime.java
@@ -0,0 +1,43 @@
+package info.nightscout.utils;
+
+import android.util.LongSparseArray;
+
+import java.util.Calendar;
+
+public class MidnightTime {
+ private static LongSparseArray times = new LongSparseArray();
+
+ private static long hits = 0;
+ private static long misses = 0;
+
+ public static long calc() {
+ Calendar c = Calendar.getInstance();
+ c.set(Calendar.HOUR_OF_DAY, 0);
+ c.set(Calendar.MINUTE, 0);
+ c.set(Calendar.SECOND, 0);
+ c.set(Calendar.MILLISECOND, 0);
+ return c.getTimeInMillis();
+ }
+
+ public static long calc(long time) {
+ Long m = (Long) times.get(time);
+ if (m != null) {
+ ++hits;
+ return m;
+ }
+ Calendar c = Calendar.getInstance();
+ c.setTimeInMillis(time);
+ c.set(Calendar.HOUR_OF_DAY, 0);
+ c.set(Calendar.MINUTE, 0);
+ c.set(Calendar.SECOND, 0);
+ c.set(Calendar.MILLISECOND, 0);
+ m = c.getTimeInMillis();
+ times.append(time, m);
+ ++misses;
+ return m;
+ }
+
+ public static String log() {
+ return "Hits: " + hits + " misses: " + misses + " stored: " + times.size();
+ }
+}
diff --git a/app/src/main/res/drawable/border_automation_unit.xml b/app/src/main/res/drawable/border_automation_unit.xml
new file mode 100644
index 0000000000..eb85656295
--- /dev/null
+++ b/app/src/main/res/drawable/border_automation_unit.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_access_alarm_24dp.xml b/app/src/main/res/drawable/ic_access_alarm_24dp.xml
new file mode 100644
index 0000000000..3e1d84e037
--- /dev/null
+++ b/app/src/main/res/drawable/ic_access_alarm_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_add_black_24dp.xml b/app/src/main/res/drawable/ic_add_black_24dp.xml
new file mode 100644
index 0000000000..0258249cc4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_arrow_forward_white_24dp.xml b/app/src/main/res/drawable/ic_arrow_forward_white_24dp.xml
new file mode 100644
index 0000000000..5304b93e18
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_forward_white_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_pause_circle_outline_24dp.xml b/app/src/main/res/drawable/ic_pause_circle_outline_24dp.xml
new file mode 100644
index 0000000000..4a469646bd
--- /dev/null
+++ b/app/src/main/res/drawable/ic_pause_circle_outline_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_play_circle_outline_24dp.xml b/app/src/main/res/drawable/ic_play_circle_outline_24dp.xml
new file mode 100644
index 0000000000..9997bf2034
--- /dev/null
+++ b/app/src/main/res/drawable/ic_play_circle_outline_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_replay_24dp.xml b/app/src/main/res/drawable/ic_replay_24dp.xml
new file mode 100644
index 0000000000..32942990ac
--- /dev/null
+++ b/app/src/main/res/drawable/ic_replay_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_stop_24dp.xml b/app/src/main/res/drawable/ic_stop_24dp.xml
new file mode 100644
index 0000000000..88299804a8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_stop_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/automation_action_item.xml b/app/src/main/res/layout/automation_action_item.xml
new file mode 100644
index 0000000000..b6ed902642
--- /dev/null
+++ b/app/src/main/res/layout/automation_action_item.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/automation_dialog_action.xml b/app/src/main/res/layout/automation_dialog_action.xml
new file mode 100644
index 0000000000..223f459453
--- /dev/null
+++ b/app/src/main/res/layout/automation_dialog_action.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/automation_dialog_choose_action.xml b/app/src/main/res/layout/automation_dialog_choose_action.xml
new file mode 100644
index 0000000000..291f175cd4
--- /dev/null
+++ b/app/src/main/res/layout/automation_dialog_choose_action.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/automation_dialog_choose_trigger.xml b/app/src/main/res/layout/automation_dialog_choose_trigger.xml
new file mode 100644
index 0000000000..37e456702e
--- /dev/null
+++ b/app/src/main/res/layout/automation_dialog_choose_trigger.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/automation_dialog_edit_action.xml b/app/src/main/res/layout/automation_dialog_edit_action.xml
new file mode 100644
index 0000000000..d354ebe2a4
--- /dev/null
+++ b/app/src/main/res/layout/automation_dialog_edit_action.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/automation_dialog_edit_trigger.xml b/app/src/main/res/layout/automation_dialog_edit_trigger.xml
new file mode 100644
index 0000000000..e627d0bb5f
--- /dev/null
+++ b/app/src/main/res/layout/automation_dialog_edit_trigger.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/automation_dialog_event.xml b/app/src/main/res/layout/automation_dialog_event.xml
new file mode 100644
index 0000000000..f4d5352888
--- /dev/null
+++ b/app/src/main/res/layout/automation_dialog_event.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/automation_event_item.xml b/app/src/main/res/layout/automation_event_item.xml
new file mode 100644
index 0000000000..a481c39755
--- /dev/null
+++ b/app/src/main/res/layout/automation_event_item.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/automation_fragment.xml b/app/src/main/res/layout/automation_fragment.xml
new file mode 100644
index 0000000000..a43225cb44
--- /dev/null
+++ b/app/src/main/res/layout/automation_fragment.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-ja/insight_alerts.xml b/app/src/main/res/values-ja/insight_alerts.xml
new file mode 100644
index 0000000000..70489fbc5e
--- /dev/null
+++ b/app/src/main/res/values-ja/insight_alerts.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 9ba3919b9f..c87ab94e09 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -118,6 +118,18 @@
- @string/yes
+
+ - @string/use_passive_location
+ - @string/use_network_location
+ - @string/use_gps_location
+
+
+
+ - PASSIVE
+ - NETWORK
+ - GPS
+
+
- Generic AAPS
- Accu-Chek Spirit
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8d1b16a7ad..2d9ccea4c3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,4 +1,4 @@
-
+
Treatments safety
Max allowed bolus [U]
Max allowed carbs [g]
@@ -579,6 +579,7 @@
Loop suspended
Suspended (%1$d m)
Superbolus (%1$d m)
+ Suspend loop
Suspend loop for 1h
Suspend loop for 2h
Suspend loop for 3h
@@ -1298,6 +1299,50 @@
Min. recovery duration [s]
Recovery duration
Timeout during handshake - reset bluetooth
+ Sun
+ Sat
+ Fri
+ Thu
+ Wed
+ Tue
+ Mon
+ Sunday
+ Saturday
+ Friday
+ Thursday
+ Wednesday
+ Tuesday
+ Monday
+ User defined automation tasks
+ Please enter a task name.
+ Please specify at least one trigger.
+ Please specify at least one action.
+ Already enabled
+ Already disabled
+ Already suspended
+ Resume loop
+ Not suspended
+ Start temp target
+ is lower than
+ is equal or lower than
+ is equal to
+ is equal or greater than
+ is greater than
+ is not available
+ unknown
+ Glucose is not available
+ Glucose %1$s %2$.2f %3$s
+ And
+ Or
+ Exclusive or
+ At %1$s
+ Use network location
+ Use GPS location
+ Use passive location
+ Location service
+ location
+ Auto
+ Automation
== ∑ %1$s U
U/h
diff --git a/app/src/main/res/xml/pref_automation.xml b/app/src/main/res/xml/pref_automation.xml
new file mode 100644
index 0000000000..2a590bbaa1
--- /dev/null
+++ b/app/src/main/res/xml/pref_automation.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/ComposeTriggerTest.java b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/ComposeTriggerTest.java
new file mode 100644
index 0000000000..8d676cd229
--- /dev/null
+++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/ComposeTriggerTest.java
@@ -0,0 +1,137 @@
+package info.nightscout.androidaps.plugins.general.automation;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import info.nightscout.androidaps.plugins.general.automation.triggers.DummyTrigger;
+import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger;
+import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({})
+public class ComposeTriggerTest {
+ @Test
+ public void testTriggerList() {
+ TriggerConnector root = new TriggerConnector();
+
+ // add some triggers
+ Trigger t0 = new DummyTrigger();
+ root.add(t0);
+ Trigger t1 = new DummyTrigger();
+ root.add(t1);
+ Trigger t2 = new DummyTrigger();
+ root.add(t2);
+
+ Assert.assertEquals(3, root.size());
+ Assert.assertEquals(t0, root.get(0));
+ Assert.assertEquals(t1, root.get(1));
+ Assert.assertEquals(t2, root.get(2));
+
+ // remove a trigger
+ root.remove(t1);
+
+ Assert.assertEquals(2, root.size());
+ Assert.assertEquals(t0, root.get(0));
+ Assert.assertEquals(t2, root.get(1));
+ }
+
+ @Test
+ public void testChangeConnector() {
+ // initialize scenario
+ TriggerConnector root = new TriggerConnector(TriggerConnector.Type.AND);
+ Trigger t[] = new Trigger[4];
+ for(int i = 0; i < t.length; ++i) {
+ t[i] = new DummyTrigger();
+ root.add(t[i]);
+ }
+ Assert.assertEquals(4, root.size());
+
+ // change connector of t1,t2 from "and" to "or"
+ Assert.assertEquals(root, t[2].getConnector());
+ AutomationFragment.TriggerListAdapter.changeConnector(t[2], t[2].getConnector(), TriggerConnector.Type.OR);
+
+ Assert.assertEquals(3, root.size());
+ Assert.assertEquals(t[0], root.get(0));
+ Assert.assertEquals(t[3], root.get(2));
+ Assert.assertTrue(root.get(1) instanceof TriggerConnector);
+
+ TriggerConnector newConnector = (TriggerConnector) root.get(1);
+ Assert.assertEquals(2, newConnector.size());
+ Assert.assertEquals(t[1], newConnector.get(0));
+ Assert.assertEquals(t[2], newConnector.get(1));
+
+ // undo
+ Assert.assertEquals(newConnector, t[2].getConnector());
+ AutomationFragment.TriggerListAdapter.changeConnector(t[2], t[2].getConnector(), TriggerConnector.Type.AND);
+ Assert.assertEquals(4, root.size());
+ for(int i = 0; i < 4; ++i) {
+ Assert.assertEquals(t[i], root.get(i));
+ }
+ }
+
+ @Test
+ public void testConnectorSimplify() {
+ // initialize scenario
+ /*
+ * parent
+ * -> child
+ * -> t0
+ * -> t1
+ */
+ TriggerConnector parent = new TriggerConnector(TriggerConnector.Type.AND);
+ TriggerConnector child = new TriggerConnector(TriggerConnector.Type.AND);
+ Trigger t0 = new DummyTrigger();
+ Trigger t1 = new DummyTrigger();
+ child.add(t0);
+ child.add(t1);
+ parent.add(child);
+ Assert.assertEquals(1, parent.size());
+ Assert.assertEquals(child, parent.get(0));
+ Assert.assertEquals(2, child.size());
+ Assert.assertEquals(t0, child.get(0));
+ Assert.assertEquals(t1, child.get(1));
+
+ // simplify
+ parent.simplify();
+
+ /*
+ * parent
+ * -> t0
+ * -> t1
+ */
+ Assert.assertEquals(2, parent.size());
+ Assert.assertEquals(t0, parent.get(0));
+ Assert.assertEquals(t1, parent.get(1));
+
+ // add a new child at position 1
+ /*
+ * parent
+ * -> t0
+ * -> newChild
+ * -> t2
+ * -> t1
+ */
+ TriggerConnector newChild = new TriggerConnector(TriggerConnector.Type.AND);
+ Trigger t2 = new DummyTrigger();
+ newChild.add(t2);
+ parent.add(1, newChild);
+
+ // simplify
+ parent.simplify();
+
+ /*
+ * parent
+ * -> t0
+ * -> t2
+ * -> t1
+ */
+ Assert.assertEquals(3, parent.size());
+ Assert.assertEquals(t0, parent.get(0));
+ Assert.assertEquals(t2, parent.get(1));
+ Assert.assertEquals(t1, parent.get(2));
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/DummyTrigger.java b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/DummyTrigger.java
new file mode 100644
index 0000000000..087df4326e
--- /dev/null
+++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/DummyTrigger.java
@@ -0,0 +1,39 @@
+package info.nightscout.androidaps.plugins.general.automation.triggers;
+
+public class DummyTrigger extends Trigger {
+ private boolean result;
+
+ public DummyTrigger() {
+ this.result = false;
+ }
+
+ public DummyTrigger(boolean result) {
+ this.result = result;
+ }
+
+ @Override
+ public boolean shouldRun() {
+ return result;
+ }
+
+ @Override
+ public String toJSON() { return null; }
+
+ @Override
+ Trigger fromJSON(String data) {
+ return null;
+ }
+
+ @Override
+ public int friendlyName() {
+ return 0;
+ }
+
+ @Override
+ public String friendlyDescription() {
+ return null;
+ }
+
+ @Override
+ public Trigger duplicate() { return new DummyTrigger(result); }
+}
diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBgTest.java b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBgTest.java
new file mode 100644
index 0000000000..00373c0668
--- /dev/null
+++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBgTest.java
@@ -0,0 +1,105 @@
+package info.nightscout.androidaps.plugins.general.automation.triggers;
+
+import com.squareup.otto.Bus;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Assert;
+import org.junit.Before;
+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 java.util.ArrayList;
+import java.util.List;
+
+import info.AAPSMocker;
+import info.nightscout.androidaps.Constants;
+import info.nightscout.androidaps.MainApp;
+import info.nightscout.androidaps.db.BgReading;
+import info.nightscout.androidaps.plugins.ConfigBuilder.ProfileFunctions;
+import info.nightscout.androidaps.plugins.NSClientInternal.data.NSSgv;
+import info.nightscout.utils.DateUtil;
+import info.nightscout.utils.T;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.powermock.api.mockito.PowerMockito.when;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({MainApp.class, Bus.class, ProfileFunctions.class, DateUtil.class})
+public class TriggerBgTest {
+
+ @Test
+ public void shouldRunTest() {
+ when(MainApp.getDbHelper().getBgreadingsDataFromTime(anyLong(), anyBoolean())).thenReturn(generateOneCurrentRecordBgData());
+
+ TriggerBg t = new TriggerBg().units(Constants.MMOL).threshold(4.1d).comparator(Trigger.Comparator.IS_EQUAL);
+ Assert.assertFalse(t.shouldRun());
+ t = new TriggerBg().units(Constants.MGDL).threshold(214).comparator(Trigger.Comparator.IS_EQUAL);
+ Assert.assertTrue(t.shouldRun());
+ t = new TriggerBg().units(Constants.MGDL).threshold(214).comparator(Trigger.Comparator.IS_EQUAL_OR_GREATER);
+ Assert.assertTrue(t.shouldRun());
+ t = new TriggerBg().units(Constants.MGDL).threshold(214).comparator(Trigger.Comparator.IS_EQUAL_OR_LOWER);
+ Assert.assertTrue(t.shouldRun());
+ t = new TriggerBg().units(Constants.MGDL).threshold(215).comparator(Trigger.Comparator.IS_EQUAL);
+ Assert.assertFalse(t.shouldRun());
+ t = new TriggerBg().units(Constants.MGDL).threshold(215).comparator(Trigger.Comparator.IS_EQUAL_OR_LOWER);
+ Assert.assertTrue(t.shouldRun());
+ t = new TriggerBg().units(Constants.MGDL).threshold(215).comparator(Trigger.Comparator.IS_EQUAL_OR_GREATER);
+ Assert.assertFalse(t.shouldRun());
+ t = new TriggerBg().units(Constants.MGDL).threshold(213).comparator(Trigger.Comparator.IS_EQUAL_OR_GREATER);
+ Assert.assertTrue(t.shouldRun());
+ t = new TriggerBg().units(Constants.MGDL).threshold(213).comparator(Trigger.Comparator.IS_EQUAL_OR_LOWER);
+ Assert.assertFalse(t.shouldRun());
+
+ when(MainApp.getDbHelper().getBgreadingsDataFromTime(anyLong(), anyBoolean())).thenReturn(new ArrayList<>());
+ t = new TriggerBg().units(Constants.MGDL).threshold(213).comparator(Trigger.Comparator.IS_EQUAL_OR_LOWER);
+ Assert.assertFalse(t.shouldRun());
+ t = new TriggerBg().comparator(Trigger.Comparator.IS_NOT_AVAILABLE);
+ Assert.assertTrue(t.shouldRun());
+ }
+
+ String bgJson = "{\"data\":{\"comparator\":\"IS_EQUAL\",\"threshold\":4.1,\"units\":\"mmol\"},\"type\":\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerBg\"}";
+
+ @Test
+ public void toJSONTest() {
+ TriggerBg t = new TriggerBg().units(Constants.MMOL).threshold(4.1d).comparator(Trigger.Comparator.IS_EQUAL);
+ Assert.assertEquals(bgJson, t.toJSON());
+ }
+
+ @Test
+ public void fromJSONTest() throws JSONException {
+ TriggerBg t = new TriggerBg().units(Constants.MMOL).threshold(4.1d).comparator(Trigger.Comparator.IS_EQUAL);
+
+ TriggerBg t2 = (TriggerBg) Trigger.instantiate(new JSONObject(t.toJSON()));
+ Assert.assertEquals(Trigger.Comparator.IS_EQUAL, t2.getComparator());
+ Assert.assertEquals(4.1d, t2.getThreshold(), 0.01d);
+ Assert.assertEquals(Constants.MMOL, t2.getUnits());
+ }
+
+ @Before
+ public void mock() {
+ AAPSMocker.mockMainApp();
+ AAPSMocker.mockBus();
+ AAPSMocker.mockDatabaseHelper();
+ AAPSMocker.mockProfileFunctions();
+
+ PowerMockito.mockStatic(DateUtil.class);
+ when(DateUtil.now()).thenReturn(1514766900000L + T.mins(1).msecs());
+
+ }
+
+ List generateOneCurrentRecordBgData() {
+ List list = new ArrayList<>();
+ try {
+ list.add(new BgReading(new NSSgv(new JSONObject("{\"mgdl\":214,\"mills\":1514766900000,\"direction\":\"Flat\"}"))));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return list;
+ }
+
+}
diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerConnectorTest.java b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerConnectorTest.java
new file mode 100644
index 0000000000..a5ba819b18
--- /dev/null
+++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerConnectorTest.java
@@ -0,0 +1,98 @@
+package info.nightscout.androidaps.plugins.general.automation.triggers;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({})
+public class TriggerConnectorTest {
+
+ @Test
+ public void testTriggerList() {
+ TriggerConnector t = new TriggerConnector();
+ TriggerConnector t2 = new TriggerConnector();
+ TriggerConnector t3 = new TriggerConnector();
+
+ Assert.assertTrue(t.size() == 0);
+
+ t.add(t2);
+ Assert.assertTrue(t.size() == 1);
+ Assert.assertEquals(t2, t.get(0));
+
+ t.add(t3);
+ Assert.assertTrue(t.size() == 2);
+ Assert.assertEquals(t2, t.get(0));
+ Assert.assertEquals(t3, t.get(1));
+
+ Assert.assertTrue(t.remove(t2));
+ Assert.assertTrue(t.size() == 1);
+ Assert.assertEquals(t3, t.get(0));
+
+ Assert.assertTrue(t.shouldRun());
+ }
+
+ @Test
+ public void testListTriggerOR() {
+ TriggerConnector t = new TriggerConnector(TriggerConnector.Type.OR);
+ t.add(new DummyTrigger(false));
+ t.add(new DummyTrigger(false));
+ Assert.assertFalse(t.shouldRun());
+
+ t.add(new DummyTrigger(true));
+ t.add(new DummyTrigger(false));
+ Assert.assertTrue(t.shouldRun());
+ }
+
+ @Test
+ public void testListTriggerXOR() {
+ TriggerConnector t = new TriggerConnector(TriggerConnector.Type.XOR);
+ t.add(new DummyTrigger(false));
+ t.add(new DummyTrigger(false));
+ Assert.assertFalse(t.shouldRun());
+
+ t.add(new DummyTrigger(true));
+ t.add(new DummyTrigger(false));
+ Assert.assertTrue(t.shouldRun());
+
+ t.add(new DummyTrigger(true));
+ t.add(new DummyTrigger(false));
+ Assert.assertFalse(t.shouldRun());
+ }
+
+ @Test
+ public void testListTriggerAND() {
+ TriggerConnector t = new TriggerConnector(TriggerConnector.Type.AND);
+ t.add(new DummyTrigger(true));
+ t.add(new DummyTrigger(true));
+ Assert.assertTrue(t.shouldRun());
+
+ t.add(new DummyTrigger(true));
+ t.add(new DummyTrigger(false));
+ Assert.assertFalse(t.shouldRun());
+ }
+
+ String empty = "{\"data\":{\"connectorType\":\"AND\",\"triggerList\":[]},\"type\":\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector\"}";
+ String oneItem = "{\"data\":{\"connectorType\":\"AND\",\"triggerList\":[\"{\\\"data\\\":{\\\"connectorType\\\":\\\"AND\\\",\\\"triggerList\\\":[]},\\\"type\\\":\\\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector\\\"}\"]},\"type\":\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector\"}";
+
+ @Test
+ public void toJSONTest() {
+ TriggerConnector t = new TriggerConnector();
+ Assert.assertEquals(empty, t.toJSON());
+ t.add(new TriggerConnector());
+ Assert.assertEquals(oneItem, t.toJSON());
+ }
+ @Test
+ public void fromJSONTest() throws JSONException {
+ TriggerConnector t = new TriggerConnector();
+ t.add(new TriggerConnector());
+
+ TriggerConnector t2 = (TriggerConnector) Trigger.instantiate(new JSONObject(t.toJSON()));
+ Assert.assertEquals(1, t2.size());
+ Assert.assertTrue(t2.get(0) instanceof TriggerConnector);
+ }
+}
diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTimeTest.java b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTimeTest.java
new file mode 100644
index 0000000000..375253e7bf
--- /dev/null
+++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTimeTest.java
@@ -0,0 +1,89 @@
+package info.nightscout.androidaps.plugins.general.automation.triggers;
+
+import com.squareup.otto.Bus;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Assert;
+import org.junit.Before;
+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 java.util.GregorianCalendar;
+
+import info.AAPSMocker;
+import info.nightscout.androidaps.MainApp;
+import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger;
+import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerTime;
+import info.nightscout.utils.DateUtil;
+import info.nightscout.utils.T;
+
+import static org.powermock.api.mockito.PowerMockito.when;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({MainApp.class, Bus.class, DateUtil.class, GregorianCalendar.class})
+public class TriggerTimeTest {
+
+ long now = 1514766900000L;
+
+ @Test
+ public void shouldRunTest() {
+
+ // scheduled 1 min before
+ TriggerTime t = new TriggerTime().runAt(now - T.mins(1).msecs());
+ Assert.assertTrue(t.shouldRun());
+
+ // scheduled 1 min in the future
+ t = new TriggerTime().runAt(now + T.mins(1).msecs());
+ Assert.assertFalse(t.shouldRun());
+
+ // limit by validTo
+ t = new TriggerTime().recurring(true).hour(1).minute(34).validTo(1);
+ t.setAll(true);
+ Assert.assertFalse(t.shouldRun());
+
+ // scheduled 1 min before
+ t = new TriggerTime().recurring(true).hour(1).minute(34);
+ t.setAll(true);
+ Assert.assertTrue(t.shouldRun());
+
+ // already run
+ t = new TriggerTime().recurring(true).hour(1).minute(34).lastRun(now - 1);
+ t.setAll(true);
+ Assert.assertFalse(t.shouldRun());
+
+ }
+
+ String timeJson = "{\"data\":{\"runAt\":1514766840000,\"THURSDAY\":false,\"lastRun\":0,\"SUNDAY\":false,\"recurring\":false,\"TUESDAY\":false,\"FRIDAY\":false,\"minute\":0,\"WEDNESDAY\":false,\"MONDAY\":false,\"hour\":0,\"SATURDAY\":false,\"validTo\":0},\"type\":\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerTime\"}";
+
+ @Test
+ public void toJSONTest() {
+ TriggerTime t = new TriggerTime().runAt(now - T.mins(1).msecs());
+ Assert.assertEquals(timeJson, t.toJSON());
+ }
+
+ @Test
+ public void fromJSONTest() throws JSONException {
+ TriggerTime t = new TriggerTime().runAt(now - T.mins(1).msecs());
+
+ TriggerTime t2 = (TriggerTime) Trigger.instantiate(new JSONObject(t.toJSON()));
+ Assert.assertEquals(now - T.mins(1).msecs(), t2.getRunAt());
+ Assert.assertEquals(false, t2.isRecurring());
+ }
+
+ @Before
+ public void mock() {
+ AAPSMocker.mockMainApp();
+ AAPSMocker.mockBus();
+
+ PowerMockito.mockStatic(DateUtil.class);
+ when(DateUtil.now()).thenReturn(now);
+
+ GregorianCalendar calendar = new GregorianCalendar();
+ calendar.setTimeInMillis(now);
+ when(DateUtil.gregorianCalendar()).thenReturn(calendar);
+ }
+}
diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/DanaRv2PluginTest.java b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/DanaRv2PluginTest.java
index 288df852f9..140b0165bd 100644
--- a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/DanaRv2PluginTest.java
+++ b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/DanaRv2PluginTest.java
@@ -1,4 +1,4 @@
-package info.nightscout.androidaps.plugins.pump.danaRv2;
+package info.nightscout.androidaps.plugins.PumpdanaRv2;
import android.content.Context;
@@ -13,14 +13,17 @@ import org.powermock.modules.junit4.PowerMockRunner;
import info.AAPSMocker;
import info.nightscout.androidaps.Constants;
import info.nightscout.androidaps.MainApp;
+import info.nightscout.androidaps.R;
import info.nightscout.androidaps.interfaces.Constraint;
import info.nightscout.androidaps.interfaces.PluginType;
-import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin;
-import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump;
-import info.nightscout.androidaps.utils.SP;
-import info.nightscout.androidaps.utils.ToastUtils;
+import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin;
+import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump;
+import info.nightscout.androidaps.plugins.PumpDanaRv2.DanaRv2Plugin;
+import info.nightscout.utils.SP;
+import info.nightscout.utils.ToastUtils;
import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
/**
* Created by Rumen on 01.08.2018
diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MessageHashTable_v2Test.java b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MessageHashTable_v2Test.java
index 9fa40854a4..63e3af0fa4 100644
--- a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MessageHashTable_v2Test.java
+++ b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MessageHashTable_v2Test.java
@@ -1,4 +1,4 @@
-package info.nightscout.androidaps.plugins.pump.danaRv2.comm;
+package info.nightscout.androidaps.plugins.PumpdanaRv2.comm;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -8,8 +8,10 @@ import org.powermock.modules.junit4.PowerMockRunner;
import info.AAPSMocker;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.logging.L;
-import info.nightscout.androidaps.plugins.pump.danaR.comm.MessageBase;
-import info.nightscout.androidaps.utils.SP;
+import info.nightscout.androidaps.plugins.PumpDanaR.comm.MessageBase;
+import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MessageHashTable_v2;
+import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MsgStatusAPS_v2;
+import info.nightscout.utils.SP;
import static org.junit.Assert.*;
/**
diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgCheckValue_v2Test.java b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgCheckValue_v2Test.java
index acc2675c92..924672f3e0 100644
--- a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgCheckValue_v2Test.java
+++ b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgCheckValue_v2Test.java
@@ -1,4 +1,4 @@
-package info.nightscout.androidaps.plugins.pump.danaRv2.comm;
+package info.nightscout.androidaps.plugins.PumpdanaRv2.comm;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -8,13 +8,15 @@ import org.powermock.modules.junit4.PowerMockRunner;
import info.AAPSMocker;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.logging.L;
-import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin;
-import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin;
-import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump;
-import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin;
-import info.nightscout.androidaps.plugins.treatments.Treatment;
+import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin;
+import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPlugin;
+import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump;
+import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgCheckValue;
+import info.nightscout.androidaps.plugins.PumpDanaRv2.DanaRv2Plugin;
+import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MsgCheckValue_v2;
+import info.nightscout.androidaps.plugins.Treatments.Treatment;
import info.nightscout.androidaps.queue.CommandQueue;
-import info.nightscout.androidaps.utils.SP;
+import info.nightscout.utils.SP;
import static org.junit.Assert.*;
import static org.powermock.api.mockito.PowerMockito.when;
diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgHistoryEvents_v2Test.java b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgHistoryEvents_v2Test.java
index 334c556ccb..f462894924 100644
--- a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgHistoryEvents_v2Test.java
+++ b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgHistoryEvents_v2Test.java
@@ -1,4 +1,4 @@
-package info.nightscout.androidaps.plugins.pump.danaRv2.comm;
+package info.nightscout.androidaps.plugins.PumpdanaRv2.comm;
import org.junit.Test;
@@ -9,9 +9,12 @@ import org.powermock.modules.junit4.PowerMockRunner;
import info.AAPSMocker;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.logging.L;
-import info.nightscout.androidaps.plugins.treatments.TreatmentService;
-import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
-import info.nightscout.androidaps.utils.SP;
+import info.nightscout.androidaps.plugins.NSClientInternal.NSUpload;
+import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MsgHistoryEvents_v2;
+import info.nightscout.androidaps.plugins.Treatments.Treatment;
+import info.nightscout.androidaps.plugins.Treatments.TreatmentService;
+import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin;
+import info.nightscout.utils.SP;
import static org.junit.Assert.*;
diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgSetHistoryEntry_v2Test.java b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgSetHistoryEntry_v2Test.java
index 50f80550b5..a9e4aee39e 100644
--- a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgSetHistoryEntry_v2Test.java
+++ b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgSetHistoryEntry_v2Test.java
@@ -1,4 +1,4 @@
-package info.nightscout.androidaps.plugins.pump.danaRv2.comm;
+package info.nightscout.androidaps.plugins.PumpdanaRv2.comm;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -8,9 +8,10 @@ import org.powermock.modules.junit4.PowerMockRunner;
import info.AAPSMocker;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.logging.L;
-import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin;
-import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin;
-import info.nightscout.androidaps.utils.SP;
+import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPlugin;
+import info.nightscout.androidaps.plugins.PumpDanaRv2.DanaRv2Plugin;
+import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MsgSetHistoryEntry_v2;
+import info.nightscout.utils.SP;
import static org.junit.Assert.*;
/**
diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusAPS_v2Test.java b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusAPS_v2Test.java
index 63e7011603..c8ae1030ee 100644
--- a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusAPS_v2Test.java
+++ b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusAPS_v2Test.java
@@ -1,4 +1,4 @@
-package info.nightscout.androidaps.plugins.pump.danaRv2.comm;
+package info.nightscout.androidaps.plugins.PumpdanaRv2.comm;
import android.content.Context;
@@ -11,9 +11,10 @@ import org.powermock.modules.junit4.PowerMockRunner;
import info.AAPSMocker;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.logging.L;
-import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump;
-import info.nightscout.androidaps.plugins.pump.danaR.comm.MessageBase;
-import info.nightscout.androidaps.utils.SP;
+import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump;
+import info.nightscout.androidaps.plugins.PumpDanaR.comm.MessageBase;
+import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MsgStatusAPS_v2;
+import info.nightscout.utils.SP;
import static org.junit.Assert.*;
/**
* Created by Rumen Georgiev on 30.10.2018
diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusBolusExtended_v2Test.java b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusBolusExtended_v2Test.java
index 2f12a15240..bde05289c2 100644
--- a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusBolusExtended_v2Test.java
+++ b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusBolusExtended_v2Test.java
@@ -1,4 +1,4 @@
-package info.nightscout.androidaps.plugins.pump.danaRv2.comm;
+package info.nightscout.androidaps.plugins.PumpdanaRv2.comm;
import android.content.Context;
@@ -11,9 +11,13 @@ import org.powermock.modules.junit4.PowerMockRunner;
import info.AAPSMocker;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.logging.L;
-import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump;
-import info.nightscout.androidaps.plugins.pump.danaR.comm.MessageBase;
-import info.nightscout.androidaps.utils.SP;
+import info.nightscout.androidaps.plugins.NSClientInternal.NSUpload;
+import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump;
+import info.nightscout.androidaps.plugins.PumpDanaRv2.DanaRv2Plugin;
+import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MsgStatusBolusExtended_v2;
+import info.nightscout.androidaps.plugins.PumpDanaR.comm.MessageBase;
+import info.nightscout.androidaps.plugins.Treatments.Treatment;
+import info.nightscout.utils.SP;
import static org.junit.Assert.*;
diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusTempBasal_v2Test.java b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusTempBasal_v2Test.java
index 70a7a36f0e..cecb246e5f 100644
--- a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusTempBasal_v2Test.java
+++ b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusTempBasal_v2Test.java
@@ -1,4 +1,4 @@
-package info.nightscout.androidaps.plugins.pump.danaRv2.comm;
+package info.nightscout.androidaps.plugins.PumpdanaRv2.comm;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -8,10 +8,11 @@ import org.powermock.modules.junit4.PowerMockRunner;
import info.AAPSMocker;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.logging.L;
-import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin;
-import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump;
-import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin;
-import info.nightscout.androidaps.utils.SP;
+import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPlugin;
+import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump;
+import info.nightscout.androidaps.plugins.PumpDanaRv2.DanaRv2Plugin;
+import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MsgStatusTempBasal_v2;
+import info.nightscout.utils.SP;
import static org.junit.Assert.*;
/**