Skip to content
11 changes: 9 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,15 @@ def getBugfenderApiKey() {
String bugfenderApiKey = System.getenv("BUGFENDER_API_KEY")

if(bugfenderApiKey == null) {
logger.warn("Bugfender API key not set")
bugfenderApiKey = ""
File file = new File("app/bugfender.properties")
if (file.exists()) {
Properties properties = new Properties()
properties.load(new FileInputStream(file.getAbsolutePath().toString()))
bugfenderApiKey = properties.getProperty("apiKey", "")
} else {
logger.warn("Bugfender API key not set")
bugfenderApiKey = ""
}
}

return "\"" + bugfenderApiKey + "\""
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@
android:name=".medtronic.service.MedtronicCnlIntentService"
android:icon="@drawable/ic_launcher" />

<service
android:name=".LockScreenNotification"
android:icon="@drawable/ic_launcher" />

<receiver android:name=".medtronic.service.MedtronicCnlAlarmReceiver" />
<receiver android:name=".upload.nightscout.NightscoutUploadReceiver" />
<receiver android:name=".xdrip_plus.XDripPlusUploadReceiver" />
Expand Down
241 changes: 241 additions & 0 deletions app/src/main/java/info/nightscout/android/LockScreenNotification.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package info.nightscout.android;

import android.app.AlarmManager;
import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.app.TaskStackBuilder;
import android.support.v7.app.NotificationCompat;
import android.util.Log;
import android.widget.RemoteViews;

import org.apache.commons.lang3.StringUtils;

import java.util.Locale;
import java.util.concurrent.TimeUnit;

import info.nightscout.android.medtronic.MainActivity;
import info.nightscout.android.model.medtronicNg.PumpStatusEvent;
import info.nightscout.android.utils.ConfigurationStore;
import info.nightscout.android.utils.DataStore;
import info.nightscout.api.DeviceEndpoints;
import io.realm.Realm;
import io.realm.RealmResults;
import io.realm.Sort;


// Updates most important values to the lock screen through a constant notification
// updates once per minute if screen is on or always if there's new info from pump
public class LockScreenNotification extends IntentService {

private DataStore dataStore = DataStore.getInstance();
private ConfigurationStore configurationStore = ConfigurationStore.getInstance();

//helper for testing layouts during dev
private boolean fakeValues = false;

public LockScreenNotification() {
super("Notifications");
Log.i("Notifications", "Running Notifications Intent Service");
}

//Ref: https://thinkandroid.wordpress.com/2010/01/24/handling-screen-off-and-screen-on-intents/
@Override
public void onCreate() {
super.onCreate();
// REGISTER RECEIVER THAT HANDLES SCREEN ON AND SCREEN OFF LOGIC
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
BroadcastReceiver mReceiver = new ScreenReceiver();
registerReceiver(mReceiver, filter);
}

//https://thinkandroid.wordpress.com/2010/01/24/handling-screen-off-and-screen-on-intents/
@Override
public void onStart(Intent intent, int startId) {
if (intent.hasExtra("screenOn")) {
dataStore.setScreenOn(intent.getBooleanExtra("screenOn", false));
}
Log.d("Notifications", "ScreenOn=" + dataStore.isScreenOn());
onHandleIntent(intent);
}


public void updateNotification() {
final int NOTIFICATION_ID = 1;
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);



int svg = 0;
float iob = 0;
long timeLastGoodSGV = 0;
PumpStatusEvent.CGM_TREND trend = PumpStatusEvent.CGM_TREND.NONE;

if (fakeValues) {
svg = (int) (Math.random() * 20 * 18);
iob = (float) Math.floor((float) Math.random() * 40) / 10f;
timeLastGoodSGV = System.currentTimeMillis() - ((int) (Math.random() * 20 * 1000 * 60));
}else {
Realm mRealm = Realm.getDefaultInstance();
// most recent sgv status
RealmResults<PumpStatusEvent> sgv_results =
mRealm.where(PumpStatusEvent.class)
.equalTo("validSGV", true)
.findAllSorted("cgmDate", Sort.ASCENDING);
if (sgv_results.size() > 0) {
PumpStatusEvent lastEvent = sgv_results.last();
svg = lastEvent.getSgv();
iob = lastEvent.getActiveInsulin();
timeLastGoodSGV = lastEvent.getCgmDate().getTime();
trend = lastEvent.getCgmTrend();
}
}


long age = System.currentTimeMillis() - timeLastGoodSGV;
boolean valid = timeLastGoodSGV > 0 && (age < TimeUnit.MINUTES.toMillis(15));


RemoteViews contentView = new RemoteViews(getPackageName(), R.layout.custom_notification);
final int COLOR_WARN = getResources().getColor(R.color.md_deep_orange_800);
final int COLOR_INVALID = getResources().getColor(R.color.md_grey_500);
final int COLOR_OK = getResources().getColor(R.color.md_green_800);


//default values
String svgStr = " -- ";
String iobStr = " -- ";
String timeStr = ">15";
String trendStr = " ";

if (!valid) {
//update colors
contentView.setTextColor(R.id.blood, COLOR_INVALID);
contentView.setTextColor(R.id.bloodtrend, COLOR_INVALID);
contentView.setTextColor(R.id.time, COLOR_WARN);
contentView.setTextColor(R.id.iob, COLOR_INVALID);
} else {

//update values
svgStr = StringUtils.leftPad(MainActivity.strFormatSGV(svg), 5);
trendStr = renderTrendSymbol(trend);
iobStr = StringUtils.leftPad(String.format(Locale.getDefault(), "%.2f", iob) + "U", 5);
timeStr = StringUtils.leftPad("" + Math.round(TimeUnit.MILLISECONDS.toMinutes(age)), 2);

//update colors
contentView.setTextColor(R.id.time, COLOR_OK);
contentView.setTextColor(R.id.iob, COLOR_OK);
contentView.setTextColor(R.id.blood, COLOR_OK);
contentView.setTextColor(R.id.bloodtrend, COLOR_OK);

if (svg > 216 || svg < 76) {
//high sugar (>12mmolx)
//low sugar (<4.2mmolx)
contentView.setTextColor(R.id.blood, COLOR_WARN);
contentView.setTextColor(R.id.bloodtrend, COLOR_WARN);
}
}

//set values on screen
contentView.setTextViewText(R.id.blood, svgStr);
contentView.setTextViewText(R.id.bloodtrend, trendStr);
contentView.setTextViewText(R.id.time, timeStr);
contentView.setTextViewText(R.id.iob, iobStr);

if (configurationStore.isMmolxl()) {
contentView.setTextViewText(R.id.bloodunit, getString(R.string.text_unit_mmolxl));
} else {
contentView.setTextViewText(R.id.bloodunit, getString(R.string.text_unit_mgxdl));
}


NotificationCompat.Builder mBuilder =
(NotificationCompat.Builder) new NotificationCompat.Builder(this)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setSmallIcon(R.drawable.ic_launcher) // FIXME - this icon doesn't follow the standards (ie, it has black in it)
.setContent(contentView)
//custom big opens only on arrow(?)
//.setCustomBigContentView(contentView)
//.setContentTitle(title)
//.setContentText(message)
//.setColor(getResources().getColor(R.color.md_deep_orange_A100))
.setCategory(NotificationCompat.CATEGORY_STATUS);
// Creates an explicit intent for an Activity in your app
Intent resultIntent = new Intent(this, MainActivity.class);

TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// Adds the back stack for the Intent (but not the Intent itself)
stackBuilder.addParentStack(MainActivity.class);
// Adds the Intent that starts the Activity to the top of the stack
stackBuilder.addNextIntent(resultIntent);
PendingIntent resultPendingIntent =
stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
mBuilder.setContentIntent(resultPendingIntent);
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
}



private static String renderTrendSymbol(PumpStatusEvent.CGM_TREND trend) {
// TODO - symbols used for trend arrow may vary per device, find a more robust solution
switch (trend) {
case DOUBLE_UP:
return "\u21c8";
case SINGLE_UP:
return "\u2191";
case FOURTY_FIVE_UP:
return "\u2197";
case FLAT:
return "\u2192";
case FOURTY_FIVE_DOWN:
return "\u2198";
case SINGLE_DOWN:
return "\u2193";
case DOUBLE_DOWN:
return "\u21ca";
default:
return "\u2014";
}
}

@Override
protected void onHandleIntent(Intent intent) {
//PowerManager pm = (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE);
//PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "NotificationsIntent");
//wl.acquire();

updateNotification();


int delayMs = 60 * 1000;
if (dataStore.isScreenOn()) {
Log.d("Notifications", "Updated notification, next update in " + delayMs);
scheduleNextUpdate(this, delayMs);
} else {
Log.d("Notifications", "Updated notification, screen is off, no updates scheduled");
}
//wl.release();
}

public static void scheduleNextUpdate(Context c, int delayMs) {
Intent intent = new Intent(c, LockScreenNotification.class);
PendingIntent pendingIntent =
PendingIntent.getService(c, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

long currentTimeMillis = System.currentTimeMillis();
long nextUpdateTimeMillis = currentTimeMillis + delayMs;

AlarmManager alarmManager = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC, nextUpdateTimeMillis, pendingIntent);
}
}
24 changes: 24 additions & 0 deletions app/src/main/java/info/nightscout/android/ScreenReceiver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package info.nightscout.android;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

//Ref: https://thinkandroid.wordpress.com/2010/01/24/handling-screen-off-and-screen-on-intents/
public class ScreenReceiver extends BroadcastReceiver {

private boolean screenOn;

@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
screenOn = false;
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
screenOn = true;
}
Intent i = new Intent(context, LockScreenNotification.class);
i.putExtra("screenOn", screenOn);
context.startService(i);
}

}
12 changes: 12 additions & 0 deletions app/src/main/java/info/nightscout/android/UploaderApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@
import com.crashlytics.android.Crashlytics;
import com.crashlytics.android.answers.Answers;

import info.nightscout.android.model.medtronicNg.BasalRate;
import info.nightscout.android.model.medtronicNg.BasalSchedule;
import info.nightscout.android.model.medtronicNg.ContourNextLinkInfo;
import info.nightscout.android.model.medtronicNg.PumpInfo;
import info.nightscout.android.model.medtronicNg.PumpStatusEvent;
import io.fabric.sdk.android.Fabric;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import io.realm.annotations.RealmModule;
import uk.co.chrisjenx.calligraphy.CalligraphyConfig;

/**
Expand Down Expand Up @@ -43,9 +49,15 @@ public void onCreate() {

Realm.init(this);
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder()
.modules(new MainModule())
.deleteRealmIfMigrationNeeded()
.build();

Realm.setDefaultConfiguration(realmConfiguration);
}

@RealmModule(classes = {BasalRate.class, BasalSchedule.class, ContourNextLinkInfo.class, PumpInfo.class, PumpStatusEvent.class})
public class MainModule {
}

}
Loading