Update to recent upstream/dev

This commit is contained in:
Nico Schmitz 2019-03-26 00:47:00 +01:00
commit ba0625b03f
60 changed files with 4069 additions and 2 deletions

View file

@ -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"

View file

@ -18,6 +18,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="sugar.free.sightremote.HISTORY_BROADCASTS" />
@ -150,7 +151,10 @@
android:name=".services.DataService"
android:exported="false" />
<service
android:name=".plugins.pump.danaR.services.DanaRExecutionService"
android:name=".services.LocationService"
android:exported="false" />
<service
android:name=".plugins.PumpDanaR.services.DanaRExecutionService"
android:enabled="true"
android:exported="false" />
<service

View file

@ -39,6 +39,7 @@ import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugi
import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin;
import info.nightscout.androidaps.plugins.constraints.storage.StorageConstraintPlugin;
import info.nightscout.androidaps.plugins.general.actions.ActionsFragment;
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin;
import info.nightscout.androidaps.plugins.general.careportal.CareportalPlugin;
import info.nightscout.androidaps.plugins.general.food.FoodPlugin;
import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils;
@ -195,6 +196,7 @@ public class MainApp extends Application {
pluginsList.add(PersistentNotificationPlugin.getPlugin());
pluginsList.add(NSClientPlugin.getPlugin());
pluginsList.add(MaintenancePlugin.initPlugin(this));
pluginsList.add(AutomationPlugin.getPlugin());
pluginsList.add(ConfigBuilderPlugin.getPlugin());
@ -424,4 +426,9 @@ public class MainApp extends Application {
sDatabaseHelper = null;
}
}
public static int dpToPx(int dp) {
float scale = sResources.getDisplayMetrics().density;
return (int) (dp*scale + 0.5f);
}
}

View file

@ -45,6 +45,7 @@ import info.nightscout.androidaps.plugins.general.xdripStatusline.StatuslinePlug
import info.nightscout.androidaps.utils.LocaleHelper;
import info.nightscout.androidaps.utils.OKDialog;
import info.nightscout.androidaps.utils.SP;
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin;
public class PreferencesActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
MyPreferenceFragment myPreferenceFragment;
@ -183,6 +184,7 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre
addPreferencesFromResourceIfEnabled(NSClientPlugin.getPlugin(), PluginType.GENERAL);
addPreferencesFromResourceIfEnabled(SmsCommunicatorPlugin.getPlugin(), PluginType.GENERAL);
addPreferencesFromResourceIfEnabled(AutomationPlugin.getPlugin(), PluginType.GENERAL);
addPreferencesFromResource(R.xml.pref_others);
addPreferencesFromResource(R.xml.pref_datachoices);

View file

@ -45,6 +45,11 @@ public class PumpEnactResult {
return this;
}
public PumpEnactResult comment(int comment) {
this.comment = MainApp.gs(comment);
return this;
}
public PumpEnactResult duration(int duration) {
this.duration = duration;
return this;

View file

@ -0,0 +1,11 @@
package info.nightscout.androidaps.events;
import android.location.Location;
public class EventLocationChange extends Event {
public Location location;
public EventLocationChange(Location location) {
this.location = location;
}
}

View file

@ -95,6 +95,7 @@ public class L {
public static final String PROFILE = "PROFILE";
public static final String CONFIGBUILDER = "CONFIGBUILDER";
public static final String UI = "UI";
public static final String LOCATION = "LOCATION";
public static final String SMS = "SMS";
private static void initialize() {
@ -110,6 +111,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));

View file

@ -0,0 +1,97 @@
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 implements Cloneable {
private Trigger trigger;
private List<Action> 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<Action> 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.optString("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(new JSONObject(array.getString(i))));
}
} catch (JSONException e) {
e.printStackTrace();
}
return this;
}
public void apply(AutomationEvent event) {
trigger = event.trigger;
actions = event.actions;
title = event.title;
}
@Override
public AutomationEvent clone() throws CloneNotSupportedException {
AutomationEvent e = (AutomationEvent) super.clone();
e.title = title;
// clone actions
e.actions = new ArrayList<>();
for(Action a : actions) {
e.actions.add(a.clone());
}
// clone triggers
if (trigger != null) {
e.trigger = trigger.clone();
}
return e;
}
}

View file

@ -0,0 +1,394 @@
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(), getFragmentManager());
mEventListView.setLayoutManager(new LinearLayoutManager(getContext()));
mEventListView.setAdapter(mEventListAdapter);
EditEventDialog.setOnClickListener(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(), true);
dialog.show(getFragmentManager(), "EditEventDialog");
}
/**
* RecyclerViewAdapter to display event lists.
*/
public static class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.ViewHolder> {
private final List<AutomationEvent> mEventList;
private final FragmentManager mFragmentManager;
public EventListAdapter(List<AutomationEvent> events, FragmentManager fragmentManager) {
this.mEventList = events;
this.mFragmentManager = fragmentManager;
}
@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<Integer> 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<Integer> 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);
}
// action: remove
holder.iconTrash.setOnClickListener(v -> {
mEventList.remove(event);
notifyDataSetChanged();
});
// action: edit
holder.rootLayout.setOnClickListener(v -> {
EditEventDialog dialog = EditEventDialog.newInstance(event, false);
dialog.show(mFragmentManager, "EditEventDialog");
});
}
@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;
final ImageView iconTrash;
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);
iconTrash = view.findViewById(R.id.iconTrash);
}
}
}
/**
* RecyclerViewAdapter to display event lists.
*/
public static class ActionListAdapter extends RecyclerView.Adapter<ActionListAdapter.ViewHolder> {
private final List<Action> mActionList;
private final FragmentManager mFragmentManager;
public ActionListAdapter(FragmentManager manager, List<Action> 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 Context getContext() {
return mContext;
}
public LinearLayout getRootLayout() {
return mRootLayout;
}
public FragmentManager getFragmentManager() {
return mFragmentManager;
}
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<String> 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();
}
}
}

View file

@ -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<AutomationEvent> 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<AutomationEvent> 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() {
}
}

View file

@ -0,0 +1,56 @@
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 implements Cloneable {
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<Integer> icon();
public void copy(Action action) { }
@Override
public Action clone() throws CloneNotSupportedException {
return (Action) super.clone();
}
/*package*/ Action fromJSON(String data) {
return this;
}
public static Action instantiate(JSONObject object) {
try {
String type = object.getString("type");
JSONObject data = object.optJSONObject("data");
Class clazz = Class.forName(type);
return ((Action) clazz.newInstance()).fromJSON(data != null ? data.toString() : "");
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | JSONException e) {
e.printStackTrace();
}
return null;
}
}

View file

@ -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<Integer> icon() {
return Optional.of(R.drawable.ic_stop_24dp);
}
}

View file

@ -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<Integer> icon() {
return Optional.of(R.drawable.ic_play_circle_outline_24dp);
}
}

View file

@ -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<Integer> icon() {
return Optional.of(R.drawable.ic_replay_24dp);
}
}

View file

@ -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<Integer> icon() {
return Optional.of(R.drawable.ic_pause_circle_outline_24dp);
}
}

View file

@ -0,0 +1,118 @@
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 ActionStartTempTarget clone() throws CloneNotSupportedException {
ActionStartTempTarget a = (ActionStartTempTarget) super.clone();
a.reason = reason;
a.value = value;
a.duration = duration;
return a;
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.icon_cp_cgm_target);
}
}

View file

@ -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<Action> actionDummyObjects = new ArrayList<Action>() {{
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());
}
}

View file

@ -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<Trigger> triggerDummyObjects = new ArrayList<Trigger>() {{
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());
}
}

View file

@ -0,0 +1,96 @@
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);
// clone action to static object
try {
resultAction = action.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
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());
}
}

View file

@ -0,0 +1,175 @@
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 staticEvent;
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;
private AutomationEvent mEvent;
private boolean mAddNew;
public static EditEventDialog newInstance(AutomationEvent event, boolean addNew) {
staticEvent = event;
Bundle args = new Bundle();
EditEventDialog fragment = new EditEventDialog();
fragment.setArguments(args);
// clone event
try {
fragment.mEvent = event.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
fragment.mAddNew = addNew;
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 = new AutomationEvent().fromJSON(eventData);
mAddNew = savedInstanceState.getBoolean("addNew");
} else if (mAddNew) {
mEvent.setTrigger(new TriggerConnector(TriggerConnector.Type.OR));
}
// event title
mEditEventTitle.setText(mEvent.getTitle());
// 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;
}
// apply changes
staticEvent.apply(mEvent);
// add new
if (mAddNew) {
final AutomationPlugin plugin = AutomationPlugin.getPlugin();
plugin.getAutomationEvents().add(mEvent);
}
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());
bundle.putBoolean("addNew", mAddNew);
}
}

View file

@ -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());
}
}

View file

@ -0,0 +1,7 @@
package info.nightscout.androidaps.plugins.general.automation.elements;
import android.widget.LinearLayout;
public class Element {
public void generateDialog(LinearLayout root) { }
}

View file

@ -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);
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,149 @@
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 implements Cloneable {
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 <T extends Comparable> 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<String> labels() {
List<String> 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<Integer> 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;
}
@Override
public Trigger clone() throws CloneNotSupportedException {
Trigger t = (Trigger) super.clone();
t.connector = connector; // parent should already be cloned
return t;
}
}

View file

@ -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<Integer> 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<String> 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;
}
}

View file

@ -0,0 +1,274 @@
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<String> labels() {
List<String> list = new ArrayList<>();
for(Type t : values()) {
list.add(MainApp.gs(t.getStringRes()));
}
return list;
}
}
public static void fillIconSet(TriggerConnector connector, HashSet<Integer> set) {
for(Trigger t : connector.list) {
if (t instanceof TriggerConnector) {
fillIconSet((TriggerConnector) t, set);
} else {
Optional<Integer> icon = t.icon();
if (icon.isPresent()) {
set.add(icon.get());
}
}
}
}
protected List<Trigger> 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<Integer> 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;
}
@Override
public TriggerConnector clone() throws CloneNotSupportedException {
TriggerConnector tc = (TriggerConnector) super.clone();
tc.list = new ArrayList<>();
for(Trigger t : list) {
tc.list.add(t.clone());
}
if (adapter != null)
tc.adapter = new AutomationFragment.TriggerListAdapter(adapter.getContext(), adapter.getFragmentManager(), adapter.getRootLayout(), tc);
return tc;
}
}

View file

@ -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<Integer> 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<Integer> getSelectedDays() {
List<Integer> 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;
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}

View file

@ -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");

View file

@ -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();
}
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="3dip"
android:color="@android:color/white" />
</shape>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#72a8ff"
android:pathData="M22,5.72l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM7.88,3.39L6.6,1.86 2,5.71l1.29,1.53 4.59,-3.85zM12.5,8L11,8v6l4.75,2.85 0.75,-1.23 -4,-2.37L12.5,8zM12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,20c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="@color/ribbonWarning"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M9,16h2L11,8L9,8v8zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM13,16h2L15,8h-2v8z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FF00FF00"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M10,16.5l6,-4.5 -6,-4.5v9zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FF00FF00"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M12,5V1L7,6l5,5V7c3.31,0 6,2.69 6,6s-2.69,6 -6,6 -6,-2.69 -6,-6H4c0,4.42 3.58,8 8,8s8,-3.58 8,-8 -3.58,-8 -8,-8z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="@color/ribbonCritical"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M6,6h12v12H6z"/>
</vector>

View file

@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="@color/defaulttext" android:pathData="M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z" />
</vector>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/itemRoot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:layout_marginTop="8dp"
android:orientation="vertical"
android:background="@color/ribbonDefault">
<TextView
android:id="@+id/viewActionTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/viewActionDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"/>
</LinearLayout>

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minWidth="300dp"
android:padding="10dp">
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/viewActionTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:paddingBottom="10dp"
android:textAppearance="?android:attr/textAppearanceLarge" />
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusableInTouchMode="true"
android:orientation="vertical"
android:padding="10dp"
android:id="@+id/layout_root" />
<include layout="@layout/mdtp_done_button" />
</LinearLayout>
</ScrollView>
</LinearLayout>

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog">
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusableInTouchMode="true"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Please choose an action type:"/>
<RadioGroup
android:id="@+id/radioGroup"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginVertical="5dp"/>
<include layout="@layout/mdtp_done_button" />
</LinearLayout>
</ScrollView>
</FrameLayout>

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog">
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusableInTouchMode="true"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Please choose a trigger type:"/>
<RadioGroup
android:id="@+id/radioGroup"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginVertical="5dp"/>
<include layout="@layout/mdtp_done_button" />
</LinearLayout>
</ScrollView>
</FrameLayout>

View file

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog">
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusableInTouchMode="true"
android:orientation="vertical"
android:padding="10dp">
<android.support.design.widget.TextInputEditText
android:id="@+id/inputEventTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Event Name" />
<View
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:background="@color/listdelimiter" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="if:"/>
<TextView
android:id="@+id/editTrigger"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:text="Edit" />
</RelativeLayout>
<TextView
android:id="@+id/triggerDescription"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:orientation="vertical" />
<View
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:background="@color/listdelimiter" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="then:"/>
<TextView
android:id="@+id/editAction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:text="Edit" />
</RelativeLayout>
<TextView
android:id="@+id/actionDescription"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:orientation="vertical" />
<include layout="@layout/mdtp_done_button" />
</LinearLayout>
</ScrollView>
</FrameLayout>

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog">
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusableInTouchMode="true"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Triggers:"/>
<LinearLayout
android:id="@+id/layoutTrigger"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:orientation="vertical" />
<include layout="@layout/mdtp_done_button" />
</LinearLayout>
</ScrollView>
</FrameLayout>

View file

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog">
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusableInTouchMode="true"
android:orientation="vertical"
android:padding="10dp">
<android.support.design.widget.TextInputEditText
android:id="@+id/inputEventTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Task Name" />
<View
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:background="@color/listdelimiter" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="if:"/>
<TextView
android:id="@+id/editTrigger"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:padding="8dp"
android:background="@color/ribbonDefault"
android:text="Edit" />
</RelativeLayout>
<TextView
android:id="@+id/triggerDescription"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:orientation="vertical" />
<View
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:background="@color/listdelimiter" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="then:"/>
<TextView
android:id="@+id/editAction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:padding="8dp"
android:background="@color/ribbonDefault"
android:text="Add" />
</RelativeLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/actionListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"/>
<include layout="@layout/mdtp_done_button" />
</LinearLayout>
</ScrollView>
</FrameLayout>

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:padding="8dp"
android:background="@color/ribbonDefault">
<ImageView
android:id="@+id/iconTrash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:src="@drawable/ic_trash_outline"
android:contentDescription="Remove"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@id/iconTrash"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/viewEventTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:id="@+id/iconLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" />
</LinearLayout>
</RelativeLayout>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context="info.nightscout.androidaps.plugins.general.automation.AutomationFragment">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/eventListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fabAddEvent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_gravity="bottom"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_add_black_24dp"
android:backgroundTint="@color/defaulttext"/>
</RelativeLayout>
</FrameLayout>

View file

@ -118,6 +118,18 @@
<item>@string/yes</item>
</string-array>
<string-array name="location">
<item>@string/use_passive_location</item>
<item>@string/use_network_location</item>
<item>@string/use_gps_location</item>
</string-array>
<string-array name="locationValues" translatable="false">
<item>PASSIVE</item>
<item>NETWORK</item>
<item>GPS</item>
</string-array>
<string-array name="virtualPumpTypes">
<item>Generic AAPS</item>
<item>Accu-Chek Spirit</item>

View file

@ -1,4 +1,4 @@
<resources>
<resources>
<string name="treatmentssafety_title">Treatments safety</string>
<string name="treatmentssafety_maxbolus_title">Max allowed bolus [U]</string>
<string name="treatmentssafety_maxcarbs_title">Max allowed carbs [g]</string>
@ -586,6 +586,7 @@
<string name="loopsuspended">Loop suspended</string>
<string name="loopsuspendedfor">Suspended (%1$d m)</string>
<string name="loopsuperbolusfor">Superbolus (%1$d m)</string>
<string name="suspendloop">Suspend loop</string>
<string name="suspendloopfor1h">Suspend loop for 1h</string>
<string name="suspendloopfor2h">Suspend loop for 2h</string>
<string name="suspendloopfor3h">Suspend loop for 3h</string>
@ -1305,6 +1306,50 @@
<string name="min_recovery_duration">Min. recovery duration [s]</string>
<string name="recovery_duration">Recovery duration</string>
<string name="timeout_during_handshake">Timeout during handshake - reset bluetooth</string>
<string name="weekday_sunday_short">Sun</string>
<string name="weekday_saturday_short">Sat</string>
<string name="weekday_friday_short">Fri</string>
<string name="weekday_thursday_short">Thu</string>
<string name="weekday_wednesday_short">Wed</string>
<string name="weekday_tuesday_short">Tue</string>
<string name="weekday_monday_short">Mon</string>
<string name="weekday_sunday">Sunday</string>
<string name="weekday_saturday">Saturday</string>
<string name="weekday_friday">Friday</string>
<string name="weekday_thursday">Thursday</string>
<string name="weekday_wednesday">Wednesday</string>
<string name="weekday_tuesday">Tuesday</string>
<string name="weekday_monday">Monday</string>
<string name="automation_description">User defined automation tasks</string>
<string name="automation_missing_task_name">Please enter a task name.</string>
<string name="automation_missing_trigger">Please specify at least one trigger.</string>
<string name="automation_missing_action">Please specify at least one action.</string>
<string name="alreadyenabled">Already enabled</string>
<string name="alreadydisabled">Already disabled</string>
<string name="alreadysuspended">Already suspended</string>
<string name="resumeloop">Resume loop</string>
<string name="notsuspended">Not suspended</string>
<string name="starttemptarget">Start temp target</string>
<string name="islower">is lower than</string>
<string name="isequalorlower">is equal or lower than</string>
<string name="isequal">is equal to</string>
<string name="isequalorgreater">is equal or greater than</string>
<string name="isgreater">is greater than</string>
<string name="isnotavailable">is not available</string>
<string name="unknown">unknown</string>
<string name="glucoseisnotavailable">Glucose is not available</string>
<string name="glucosecompared">Glucose %1$s %2$.2f %3$s</string>
<string name="and">And</string>
<string name="or">Or</string>
<string name="xor">Exclusive or</string>
<string name="atspecifiedtime">At %1$s</string>
<string name="use_network_location">Use network location</string>
<string name="use_gps_location">Use GPS location</string>
<string name="use_passive_location">Use passive location</string>
<string name="locationservice">Location service</string>
<string name="key_location" translatable="false">location</string>
<string name="automation_short">Auto</string>
<string name="automation">Automation</string>
<string name="profile_total">== ∑ %1$s U</string>
<string name="profile_ins_units_per_hout">U/h</string>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:validate="http://schemas.android.com/apk/res-auto">
<ListPreference
android:title="@string/locationservice"
android:defaultValue="PASSIVE"
android:entries="@array/location"
android:entryValues="@array/locationValues"
android:key="@string/key_location" />
</PreferenceScreen>

View file

@ -0,0 +1,41 @@
package info.nightscout.androidaps.plugins.general.automation;
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;
import info.nightscout.androidaps.plugins.general.automation.actions.ActionLoopEnable;
import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger;
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnectorTest;
@RunWith(PowerMockRunner.class)
@PrepareForTest({})
public class AutomationEventTest {
@Test
public void testCloneEvent() throws CloneNotSupportedException {
// create test object
AutomationEvent event = new AutomationEvent();
event.setTitle("Test");
event.setTrigger(Trigger.instantiate(TriggerConnectorTest.oneItem));
event.addAction(new ActionLoopEnable());
// clone
AutomationEvent clone = event.clone();
// check title
Assert.assertEquals(event.getTitle(), clone.getTitle());
// check trigger
Assert.assertNotNull(clone.getTrigger());
Assert.assertFalse(event.getTrigger() == clone.getTrigger()); // not the same object reference
Assert.assertEquals(event.getTrigger().getClass(), clone.getTrigger().getClass());
// TODO: check trigger details
// check action
Assert.assertEquals(1, clone.getActions().size());
Assert.assertFalse(event.getActions() == clone.getActions()); // not the same object reference
// TODO: check action details
}
}

View file

@ -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));
}
}

View file

@ -0,0 +1,46 @@
package info.nightscout.androidaps.plugins.general.automation.triggers;
import com.google.common.base.Optional;
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 Optional<Integer> icon() {
return Optional.absent();
}
@Override
public Trigger duplicate() { return new DummyTrigger(result); }
}

View file

@ -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.general.nsclient.data.NSSgv;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.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<BgReading> generateOneCurrentRecordBgData() {
List<BgReading> 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;
}
}

View file

@ -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());
}
static public final String empty = "{\"data\":{\"connectorType\":\"AND\",\"triggerList\":[]},\"type\":\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector\"}";
static public final 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);
}
}

View file

@ -0,0 +1,87 @@
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.utils.DateUtil;
import info.nightscout.androidaps.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);
}
}