package org.nightscout.lasso.alarm; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.media.AudioManager; import android.media.MediaPlayer; import android.net.Uri; import android.preference.PreferenceManager; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationManagerCompat; import android.util.Log; import com.nightscout.core.dexcom.records.EGVRecord; import com.nightscout.core.model.GlucoseUnit; import net.tribe7.common.base.Optional; import org.joda.time.DateTime; import org.joda.time.Instant; import org.joda.time.Minutes; import org.json.JSONException; import org.json.JSONObject; import org.nightscout.lasso.BuildConfig; import org.nightscout.lasso.MainActivity; import org.nightscout.lasso.R; import org.nightscout.lasso.preferences.AndroidPreferences; import java.util.List; import javax.inject.Inject; import dagger.ObjectGraph; public class Alarm { public Minutes ALARM_TIMEAGO_WARN_MINS = Minutes.minutes(15); public Minutes ALARM_TIMEAGO_URGENT_MINS = Minutes.minutes(30); @Inject AlarmStrategy strategy; private MediaPlayer mediaPlayer; private NotificationManagerCompat mNotificationManager; private NotificationCompat.Builder mNotifyBuilder; private Context context; private SharedPreferences sharedPreferences; private AlarmResults previousAlarmResults = new AlarmResults(); private AlarmResults alarmResults = new AlarmResults(); private int notifyId = 1; private int ALARM_BATTERY_WARN = 15; private int ALARM_BATTERY_URGENT = 10; private AndroidPreferences preferences; public Alarm(Context context) { this.context = context; preferences = new AndroidPreferences(context); mNotificationManager = NotificationManagerCompat.from(context); mediaPlayer = new MediaPlayer(); sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); ObjectGraph.create(new AlarmStrategyModule(context)).inject(this); sharedPreferences.edit().remove("snooze_" + AlarmSeverity.WARNING.name()).apply(); sharedPreferences.edit().remove("snooze_" + AlarmSeverity.URGENT.name()).apply(); } public void setMediaPlayer(MediaPlayer mediaPlayer) { this.mediaPlayer = mediaPlayer; } public void setNotifyId(int notifyId) { this.notifyId = notifyId; } public AlarmResults analyzeTime(List<EGVRecord> egvRecords, GlucoseUnit unit, DateTime downloadTime) { AlarmResults results = new AlarmResults(); if (egvRecords.size() == 0) { return results; } DateTime lastRecordWallTime = egvRecords.get(egvRecords.size() - 1).getWallTime(); if (preferences.isStaleAlarmEnabled()) { if (lastRecordWallTime.plus(ALARM_TIMEAGO_URGENT_MINS).isBeforeNow()) { Log.d("Alarm", "Urgent stale data"); results.setSeverityAtHighest(AlarmSeverity.URGENT); results.appendMessage(context.getString(R.string.alarm_timeago_urgent_message, Minutes.minutesBetween(lastRecordWallTime, Instant.now()).getMinutes())); results.title = context.getString(R.string.alarm_timeago_standard_title); } else if (lastRecordWallTime.plus(ALARM_TIMEAGO_WARN_MINS).isBeforeNow()) { Log.d("Alarm", "Warning stale data"); results.setSeverityAtHighest(AlarmSeverity.WARNING); results.appendMessage(context.getString(R.string.alarm_timeago_warn_message, egvRecords.get(egvRecords.size() - 1).getReading().asStr(unit), unit.name(), Minutes.minutesBetween(lastRecordWallTime, Instant.now()).getMinutes())); results.title = context.getString(R.string.alarm_timeago_standard_title); } } if (downloadTime.minus(Minutes.minutes(5)).isAfter(lastRecordWallTime)) { Log.d("OOR", "Out of range detected"); results.appendMessage(context.getString(R.string.alarm_out_of_range_message, Minutes.minutesBetween(lastRecordWallTime, Instant.now()).getMinutes())); } return results; } public AlarmResults analyzeBattery(Optional<Integer> uploaderBattery) { AlarmResults results = new AlarmResults(); if (uploaderBattery.isPresent()) { Log.e("Alarm", "Battery is " + uploaderBattery.get()); if (uploaderBattery.get() < ALARM_BATTERY_URGENT) { Log.d("Alarm", "Urgent low battery"); results.setSeverityAtHighest(AlarmSeverity.URGENT); results.appendMessage(context.getString(R.string.alarm_uploader_battery_urgent_message)); results.title = context.getString(R.string.alarm_uploader_battery_urgent_title); } else if (uploaderBattery.get() < ALARM_BATTERY_WARN) { Log.d("Alarm", "Warning low battery"); results.setSeverityAtHighest(AlarmSeverity.WARNING); results.appendMessage(context.getString(R.string.alarm_uploader_battery_warn_message)); results.title = context.getString(R.string.alarm_uploader_battery_warn_title); } } else { Log.e("Alarm", "battery is not present"); } return results; } public void analyze(List<EGVRecord> egvRecords, GlucoseUnit unit, Optional<Integer> uploaderBattery, DateTime downloadTime) { alarmResults = new AlarmResults(); // Don't analyze NOOP and Remote alarms. if (preferences.getAlarmStrategy() > 1) { alarmResults.mergeAlarmResults(analyzeTime(egvRecords, unit, downloadTime)); alarmResults.mergeAlarmResults(analyzeBattery(uploaderBattery)); } alarmResults.mergeAlarmResults(strategy.analyze(egvRecords, unit)); } public AlarmResults getAlarmResults() { return alarmResults; } public void clear() { mNotificationManager.cancel(notifyId); stopAlert(); } public void generateAlarm(JSONObject jsonAlarm) { alarmResults = new AlarmResults(); try { alarmResults.severity = AlarmSeverity.NONE; if (jsonAlarm.has("level")) { alarmResults.severity = AlarmSeverity.values()[jsonAlarm.getInt("level") + 3]; } if (jsonAlarm.has("title")) { alarmResults.title = jsonAlarm.getString("title"); } if (jsonAlarm.has("message")) { alarmResults.message = jsonAlarm.getString("message"); } this.alarm(); } catch (JSONException e) { e.printStackTrace(); } } public void alarm() { if (alarmResults.severity == AlarmSeverity.NONE && previousAlarmResults.severity != AlarmSeverity.NONE) { mNotificationManager.cancel(notifyId); stopAlert(); } if (alarmResults.severity.ordinal() <= AlarmSeverity.LOW.ordinal() && !preferences.areAllNotificationsEnabled()) { return; } showNotification(alarmResults.severity, alarmResults.title, alarmResults.message); AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); // First check to see if we are in silent mode boolean silentMode = ((am.getRingerMode() == AudioManager.RINGER_MODE_SILENT) || am.getRingerMode() == (AudioManager.RINGER_MODE_VIBRATE)); // If we are in silent mode then do not play the media if (!silentMode) { if (alarmResults.severity == AlarmSeverity.WARNING) { playAlert(R.raw.alarm); } else if (alarmResults.severity == AlarmSeverity.URGENT) { playAlert(R.raw.alarm2); } } else { Log.d("Alarms", "Honoring silent mode"); } previousAlarmResults = alarmResults; } private void showNotification(AlarmSeverity severity, String title, String message) { NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender(); Intent notifyIntent = new Intent(context, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT); long[] vibePattern; switch (severity) { case URGENT: vibePattern = new long[]{75, 50, 50, 50, 75, 50, 50, 50, 75, 50, 50, 50, 75, 50, 50, 50, 75, 50, 50, 50, 75, 50, 50, 50, 75}; break; case WARNING: vibePattern = new long[]{300, 100, 50, 100, 300, 100, 50, 100, 300, 100, 50, 100, 300, 100, 50, 100, 300, 100, 50, 100, 300, 100, 50, 100, 300, 100, 50, 100, 300, 100, 50, 100, 300}; break; default: vibePattern = new long[]{}; } mNotifyBuilder = new NotificationCompat.Builder(context) .setContentTitle(title) .setContentText(message) .setContentIntent(pendingIntent) .extend(wearableExtender) .setPriority(Notification.PRIORITY_HIGH) .setStyle(new NotificationCompat.BigTextStyle().bigText(message)) .setSmallIcon(R.drawable.ic_launcher); if (!isSnoozed() && (severity.ordinal() >= AlarmSeverity.NORMAL.ordinal())) { Intent snoozeIntent = new Intent("org.nightscout.scout.SNOOZE"); PendingIntent pendingSnooze = PendingIntent.getBroadcast(context, 0, snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT); mNotifyBuilder.addAction(R.drawable.ic_alarm_black_24dp, "Snooze", pendingSnooze) .setVibrate(vibePattern); } if (preferences.getContactPhone().isPresent()) { Intent callIntent = new Intent(Intent.ACTION_CALL); callIntent.setData(Uri.parse("tel:" + preferences.getContactPhone().get())); PendingIntent pendingCall = PendingIntent.getActivity(context, 0, callIntent, PendingIntent.FLAG_UPDATE_CURRENT); Intent msgIntent = new Intent(Intent.ACTION_VIEW, Uri.fromParts("sms", preferences.getContactPhone().get(), null)); PendingIntent pendingMsg = PendingIntent.getActivity(context, 0, msgIntent, PendingIntent.FLAG_UPDATE_CURRENT); mNotifyBuilder.addAction(R.drawable.ic_call_black_24dp, "Call", pendingCall) .addAction(R.drawable.ic_message_black_24dp, "Msg", pendingMsg); } mNotificationManager.notify(notifyId, mNotifyBuilder.build()); } private void playAlert(int alert) { if (isSnoozed()) { Log.d("alert", "Not playing alarm due to snooze"); return; } Log.d("playAlert", "playing alert"); if (!mediaPlayer.isPlaying()) { mediaPlayer = MediaPlayer.create(context, alert); mediaPlayer.setLooping(true); mediaPlayer.start(); } if (BuildConfig.DEBUG) { Log.d("playAlert", "Is playing " + mediaPlayer.isPlaying()); } } private void stopAlert() { if (BuildConfig.DEBUG) { Log.d("stopAlert", "Is playing " + mediaPlayer.isPlaying()); } if (mediaPlayer.isPlaying()) { mediaPlayer.stop(); } } public void alarmSnooze(long durationMs) { if (preferences.getAlarmStrategy() <= 1) { return; } String key = "snooze_" + previousAlarmResults.severity.name(); if (BuildConfig.DEBUG) { Log.d("snooze", "Setting snooze until: " + new DateTime().getMillis() + durationMs); } sharedPreferences.edit().putLong(key, new DateTime().getMillis() + durationMs).apply(); showNotification(previousAlarmResults.severity, previousAlarmResults.title, previousAlarmResults.message); stopAlert(); } public boolean isSnoozed() { Long currentTs = new DateTime().getMillis(); Long snoozeTime = sharedPreferences.getLong("snooze_" + previousAlarmResults.severity.name(), 0); if (BuildConfig.DEBUG) { Log.d("snooze", "Snoozed until: " + snoozeTime); Log.d("snooze", "Current time: " + currentTs); } return snoozeTime > currentTs; } }