Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/lockscreen #1

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
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