Refactor to allow for snooze reminders (snooze when user hits the back or home buttons).
@ -73,6 +73,8 @@
|
||||
<orderEntry type="library" exported="" name="appcompat-v7-19.0.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-v4-19.0.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="library-2.4.0" level="project" />
|
||||
<orderEntry type="module" module-name="SeekArc" exported="" />
|
||||
<orderEntry type="module" module-name="GlowPadBackport" exported="" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
|
@ -17,11 +17,16 @@ android {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
flatDir {
|
||||
dirs 'libs'
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
compile 'com.android.support:support-v4:19.0.1'
|
||||
compile 'com.android.support:appcompat-v7:19.0.1'
|
||||
compile 'com.nineoldandroids:library:2.4.0'
|
||||
compile fileTree(dir: 'libs', include: ['*.aar'])
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile project(':GlowPadBackport')
|
||||
compile project(':SeekArc')
|
||||
}
|
||||
|
@ -30,6 +30,7 @@
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".AlarmNotify"
|
||||
android:label="@string/alarm_notification" >
|
||||
@ -47,6 +48,6 @@
|
||||
<!-- permission required to vibrate -->
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<!-- permission required to wake the device -->
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<!--uses-permission android:name="android.permission.WAKE_LOCK"/-->
|
||||
|
||||
</manifest>
|
||||
|
@ -8,13 +8,13 @@ import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Vibrator;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.triggertrap.seekarc.SeekArc;
|
||||
|
||||
// TODO See GlowPad.
|
||||
|
||||
// TODO sound audible alarm -- see AlarmKlaxon.java
|
||||
@ -22,16 +22,17 @@ import android.widget.Button;
|
||||
// TODO Snooze? set another alarm for the next half-hour (or grace_period / 2)?
|
||||
|
||||
public class AlarmAlertActivity extends Activity {
|
||||
// TODO correct alert lifetime
|
||||
private static final int ALERT_LIFE = 1000*10; //1000*60*2; // 2 minutes
|
||||
private static final long[] vPattern = {500, 500};
|
||||
private static Boolean userCancelled;
|
||||
private static Vibrator vibrator;
|
||||
private static Intent notifyIntent;
|
||||
public static Boolean alertFinished, userCancelled;
|
||||
public static Activity alertActivity;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Self-reference so we can run finish() from outside.
|
||||
alertActivity = this;
|
||||
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
Window window = getWindow();
|
||||
// Set to use the full screen
|
||||
@ -47,32 +48,15 @@ public class AlarmAlertActivity extends Activity {
|
||||
setContentView(R.layout.alarm_alert);
|
||||
|
||||
notifyIntent = new Intent(getApplicationContext(), AlarmNotify.class);
|
||||
// Disable any current notifications
|
||||
// Disable any current notifications (if we're snoozing)
|
||||
stopService(notifyIntent);
|
||||
|
||||
// Turn off the alert activity, and switch to a notification
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
public void run() {
|
||||
// Close the dialogue and switch to notification
|
||||
// if the Activity has not been closed by the user
|
||||
if (!userCancelled) {
|
||||
startService(notifyIntent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}, ALERT_LIFE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
userCancelled = false;
|
||||
|
||||
vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
|
||||
vibrator.vibrate(vPattern, 0);
|
||||
|
||||
// TODO change button to slide...
|
||||
// TODO change slide to circle slide? https://github.com/JesusM/HoloCircleSeekBar
|
||||
/*
|
||||
Button cancelButton = (Button) findViewById(R.id.cancel_dialog_button);
|
||||
cancelButton.setOnClickListener (new View.OnClickListener() {
|
||||
@Override
|
||||
@ -80,39 +64,38 @@ public class AlarmAlertActivity extends Activity {
|
||||
cancelGraceAlarm();
|
||||
}
|
||||
});
|
||||
*/
|
||||
SeekArc cancelArc = (SeekArc) findViewById(R.id.cancel_dialog_seekArc);
|
||||
cancelArc.setOnSeekArcChangeListener(new SeekArc.OnSeekArcChangeListener() {
|
||||
volatile Boolean seekFinished = false;
|
||||
@Override
|
||||
public void onProgressChanged(SeekArc seekArc, int progress, boolean fromUser) {
|
||||
if (progress > 98 && !seekFinished && fromUser) {
|
||||
AlarmReceiver.dismissAlarm(alertActivity);
|
||||
seekFinished = true;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekArc seekArc) {
|
||||
}
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekArc seekArc) {
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the user pressing the back/return button
|
||||
* TODO Do we want this to cancel the alarm and grace period?
|
||||
* TODO Or do we trigger the notification and finish() right away?
|
||||
* TODO probably most intuitive to cancel the alarms...
|
||||
* Handle the user pressing the back/return and home buttons
|
||||
*/
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
cancelGraceAlarm();
|
||||
/* OR:
|
||||
// Ensure that the we don't trigger the notification a second time
|
||||
userCancelled = true;
|
||||
startService(notifyIntent);
|
||||
finish();
|
||||
*/
|
||||
AlarmReceiver.setAlarmStatus(AlarmReceiver.ALARM_IGNORED);
|
||||
AlarmReceiver.snoozeAlarm(this);
|
||||
}
|
||||
|
||||
public void onStop() {
|
||||
vibrator.cancel();
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
private void cancelGraceAlarm() {
|
||||
AlarmManager graceManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||
Intent graceIntent = new Intent(getApplicationContext(), GraceReceiver.class);
|
||||
PendingIntent gracePendingIntent = PendingIntent.getBroadcast(getApplicationContext(), MainActivity.GRACE_REQUEST, graceIntent, 0);
|
||||
graceManager.cancel(gracePendingIntent);
|
||||
Log.d("AlarmAlertActivity", "Cancelled grace alarm.");
|
||||
// Ensure we don't load a notification now
|
||||
userCancelled = true;
|
||||
// Close the dialogue (stop vibration &c)
|
||||
finish();
|
||||
public void onUserLeaveHint() {
|
||||
AlarmReceiver.setAlarmStatus(AlarmReceiver.ALARM_IGNORED);
|
||||
AlarmReceiver.snoozeAlarm(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
package za.org.treehouse.hypoalarm;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Vibrator;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class AlarmKlaxon {
|
||||
private static final long[] vPattern = {500, 500};
|
||||
// Volume modification for alarms while a phone call is active, from com.android.deskclock.alarms
|
||||
private static final float IN_CALL_VOLUME = 0.125f;
|
||||
private static MediaPlayer mediaPlayer = null;
|
||||
private static TelephonyManager telephonyManager;
|
||||
private static Vibrator vibrator;
|
||||
|
||||
public static void start(final Context context) {
|
||||
|
||||
/**
|
||||
*
|
||||
* TODO add raw ring tone to use as fallback
|
||||
* TODO add in-call ring tone
|
||||
* TODO lower volume if in a call
|
||||
* TODO cancel noise if a call comes in (add TelephonyManager listener which cancels the alert but calls the notification)
|
||||
*
|
||||
* TODO start telephony listener
|
||||
*
|
||||
* TODO snooze 5 minutes on press back button or home button (new runnable)
|
||||
* TODO remove back/home button and most top icons
|
||||
* TODO fix glowpad/seekarc
|
||||
*/
|
||||
vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
vibrator.cancel();
|
||||
vibrator.vibrate(vPattern, 0);
|
||||
|
||||
if (true)
|
||||
return; // TODO remove after testing -- is mediaplayer responsible for delays?
|
||||
|
||||
telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
// TODO select alarm tone?
|
||||
// Use the default alarm tone...
|
||||
Uri alarmNoise = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
|
||||
stopAudio(context);
|
||||
mediaPlayer = new MediaPlayer();
|
||||
mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
|
||||
@Override
|
||||
public boolean onError(MediaPlayer mp, int what, int extra) {
|
||||
Log.e("AlarmAlertActivity", "Error occurred while playing audio. Stopping alarm.");
|
||||
stopAudio(context);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
/*
|
||||
* TODO find out if we're in a call
|
||||
if (inTelephoneCall) {
|
||||
Log.v("Using the in-call alarm");
|
||||
sMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME);
|
||||
setDataSourceFromResource(context, sMediaPlayer, R.raw.in_call_alarm);
|
||||
} else {
|
||||
*/
|
||||
mediaPlayer.setDataSource(context, alarmNoise);
|
||||
startAudio(context);
|
||||
//}
|
||||
} catch (Exception ex) {
|
||||
// The alarmNoise may be on the sd card which could be busy right
|
||||
// now. Use the fallback ringtone.
|
||||
try {
|
||||
// Reset the media player to clear the error state.
|
||||
mediaPlayer.reset();
|
||||
//setDataSourceFromResource(this, mediaPlayer, R.raw.fallbackring);
|
||||
startAudio(context);
|
||||
} catch (Exception ex2) {
|
||||
// At this point we just don't play anything.
|
||||
Log.e("AlarmAlertActivity", "Failed to play fallback ringtone", ex2);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void stop(final Context context) {
|
||||
vibrator.cancel();
|
||||
if (true)
|
||||
return; // TODO remove after testing
|
||||
stopAudio(context);
|
||||
}
|
||||
|
||||
private static void startAudio(final Context context) throws IOException {
|
||||
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
// do not play alarms if stream volume is 0 (typically because ringer mode is silent).
|
||||
if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) {
|
||||
mediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
|
||||
mediaPlayer.setLooping(true);
|
||||
try {
|
||||
mediaPlayer.prepare();
|
||||
} catch (Exception e) {
|
||||
Log.e("AlarmAlertActivity", "Prepare failed. Exiting", e);
|
||||
return;
|
||||
}
|
||||
audioManager.requestAudioFocus(null,
|
||||
AudioManager.STREAM_ALARM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
|
||||
mediaPlayer.start();
|
||||
}
|
||||
}
|
||||
|
||||
private static void stopAudio(final Context context) {
|
||||
if (mediaPlayer != null) {
|
||||
mediaPlayer.stop();
|
||||
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.abandonAudioFocus(null);
|
||||
mediaPlayer.release();
|
||||
mediaPlayer = null;
|
||||
}
|
||||
}
|
||||
// Load ringtone from a resource
|
||||
private static void setDataSourceFromResource(Context context, MediaPlayer player, int res) throws IOException {
|
||||
AssetFileDescriptor afd = context.getResources().openRawResourceFd(res);
|
||||
if (afd != null) {
|
||||
player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
|
||||
afd.close();
|
||||
}
|
||||
}
|
||||
}
|
@ -27,10 +27,10 @@ public class AlarmNotify extends Service {
|
||||
public void onDestroy() {
|
||||
// If the notification is cancelled, stop updating.
|
||||
notificationRunning = false;
|
||||
Log.d("AlarmNotify", "1: Setting notificationRunning to false");
|
||||
// Remove the notification in the notification bar
|
||||
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
nm.cancel(notifyID);
|
||||
Log.d("AlarmNotify", "Notification stopped.");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -38,10 +38,10 @@ public class AlarmNotify extends Service {
|
||||
final int UPDATE_INTERVAL = 10*1000; // Timer is updated six times a minute
|
||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
Log.d("AlarmNotify", "Notification started.");
|
||||
|
||||
//final String phoneNumber = sharedPref.getString(getString(R.string.PhoneNumberPref), null);
|
||||
final int gracePeriod = sharedPref.getInt(getString(R.string.GracePeriodPref), 60);
|
||||
// convert gracePeriod to milliseconds and calculate when it'll fire
|
||||
final long endTime = System.currentTimeMillis() + (gracePeriod * 60 * 1000);
|
||||
|
||||
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_grey);
|
||||
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
@ -55,6 +55,8 @@ public class AlarmNotify extends Service {
|
||||
.setAutoCancel(false)
|
||||
.setPriority(Notification.PRIORITY_HIGH);
|
||||
|
||||
|
||||
// TODO if alarm alert is snoozing and we cancel, cancel the snooze.
|
||||
// Set up dismiss action
|
||||
Intent cancellerIntent = new Intent(getBaseContext(), CancelGraceReceiver.class);
|
||||
PendingIntent cancellerPendingIntent = PendingIntent.getBroadcast(getBaseContext(), MainActivity.CANCEL_GRACE_REQUEST, cancellerIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
@ -69,12 +71,13 @@ public class AlarmNotify extends Service {
|
||||
/**
|
||||
* TODO load alert activity (without sound or vibration) on select?
|
||||
* TODO This would allow the user to test competence
|
||||
Intent alertActivityIntent = new Intent(this, AlarmAlertActivity.class);
|
||||
alertActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
|
||||
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
|
||||
notification.setContentIntent(alertActivityIntent);
|
||||
* something like:
|
||||
Intent alarmAlertIntent = new Intent(this, AlarmAlertActivity.class);
|
||||
PendingIntent alarmPendingIntent = PendingIntent.getBroadcast(this, 0, alarmAlertIntent, 0);
|
||||
notification.setContentIntent(alarmPen`dingIntent);
|
||||
*/
|
||||
|
||||
|
||||
nm.cancel(notifyID);
|
||||
nm.notify(notifyID, notification.build());
|
||||
|
||||
@ -82,21 +85,22 @@ public class AlarmNotify extends Service {
|
||||
@Override
|
||||
public void run() {
|
||||
notificationRunning = true;
|
||||
Log.d("AlarmNotify", "2: Setting notificationRunning to true");
|
||||
int max = 1000;
|
||||
// Convert endTime from milliseconds to seconds, and translate to time remaining
|
||||
int secondsLeft = (int) ((endTime - System.currentTimeMillis())) / (1000);
|
||||
int gracePeriodSeconds = gracePeriod * 60;
|
||||
// Multiply each int by 1000 for greater progress resolution
|
||||
int progress = (((gracePeriodSeconds * 1000) - (secondsLeft * 1000)) * max) / (gracePeriodSeconds * 1000);
|
||||
/* TODO check that graceEndTime is always set.
|
||||
if (AlarmReceiver.graceEndTime == 0) {
|
||||
AlarmReceiver.graceEndTime = System.currentTimeMillis() + (gracePeriod * 60 * 1000);
|
||||
}*/
|
||||
// Count in milliseconds for greater progress resolution
|
||||
int milliSecondsLeft = (int) ((AlarmReceiver.graceEndTime - System.currentTimeMillis()));
|
||||
int gracePeriodMilliSeconds = gracePeriod * 60 * 1000;
|
||||
int progress = ((gracePeriodMilliSeconds - milliSecondsLeft) * max) / gracePeriodMilliSeconds;
|
||||
|
||||
while (progress < max) {
|
||||
// Stop the thread if cancelled elsewhere
|
||||
Log.d("AlarmNotify", "notificationRunning is " + notificationRunning);
|
||||
// Stop the thread if the notification is cancelled elsewhere
|
||||
if (!notificationRunning) {
|
||||
return;
|
||||
}
|
||||
int minutesLeft = secondsLeft / 60;
|
||||
int minutesLeft = (milliSecondsLeft / 1000) / 60;
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
notification.setContentText(String.format(getString(R.string.notificationText), MainActivity.MinutesToGracePeriodStr(minutesLeft)));
|
||||
notification.setProgress(max, progress, false);
|
||||
@ -104,12 +108,12 @@ public class AlarmNotify extends Service {
|
||||
nm.notify(notifyID, notification.build());
|
||||
}
|
||||
// Prepare secondsLeft and progress for the next loop
|
||||
secondsLeft = secondsLeft - (UPDATE_INTERVAL / 1000);
|
||||
milliSecondsLeft = milliSecondsLeft - UPDATE_INTERVAL;
|
||||
// Multiply each int by 1000 for greater progress resolution
|
||||
progress = (((gracePeriodSeconds * 1000) - (secondsLeft * 1000)) * max) / (gracePeriodSeconds * 1000);
|
||||
Log.d("AlarmNotify", "secondsLeft is " + secondsLeft + " and progress is " + progress + " (gracePeriodSeconds is " + gracePeriodSeconds + ")");
|
||||
// Sleeps the thread, simulating an operation
|
||||
// that takes time
|
||||
progress = ((gracePeriodMilliSeconds - milliSecondsLeft) * max) / gracePeriodMilliSeconds;
|
||||
//Log.d("AlarmNotify", "milliSecondsLeft is " + milliSecondsLeft + " and progress is " + progress + " (gracePeriodMilliSeconds is " + gracePeriodMilliSeconds + ")");
|
||||
|
||||
// Sleep until we need to update again
|
||||
try {
|
||||
Thread.sleep(UPDATE_INTERVAL);
|
||||
} catch (InterruptedException e) {
|
||||
|
@ -1,34 +1,55 @@
|
||||
package za.org.treehouse.hypoalarm;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
|
||||
/**
|
||||
* TODO change alarm state if a phone call comes in
|
||||
*
|
||||
* TODO display notification if alarm is about to go off (and allow user to cancel it before alarm goes off)
|
||||
*
|
||||
* TODO allow snooze state
|
||||
*/
|
||||
|
||||
public class AlarmReceiver extends BroadcastReceiver {
|
||||
private static final int SNOOZE_TIME = 1000*20; //1000*60*5; // Snooze for 5 minutes if need be
|
||||
private static final int ALERT_LIFE = 1000*10; //1000*60*2; // 2 minutes
|
||||
private static SharedPreferences sharedPref;
|
||||
private static AlarmManager alarmManager, graceManager;
|
||||
private static PendingIntent alarmPendingIntent, gracePendingIntent;
|
||||
private static Intent alertActivityIntent, notifyIntent;
|
||||
public static volatile String alarmStatus; // Register ALARM_DISMISSED and its brethren here
|
||||
public static final String ALARM_RUNNING = "ALARM_RUNNING";
|
||||
public static final String ALARM_DISMISSED = "ALARM_DISMISSED";
|
||||
public static final String ALARM_IGNORED = "ALARM_IGNORED";
|
||||
public static final String ALARM_SNOOZED = "ALARM_SNOOZED";
|
||||
public static final String ALARM_SNOOZE_RUNNING = "ALARM_SNOOZE_RUNNING";
|
||||
public static long graceEndTime;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
public void onReceive(final Context context, Intent intent) {
|
||||
sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
Boolean alarmActive = sharedPref.getBoolean(context.getString(R.string.AlarmActivePref), true);
|
||||
int gracePeriod = sharedPref.getInt(context.getString(R.string.GracePeriodPref), 60);
|
||||
String alarmTimeStr = sharedPref.getString(context.getString(R.string.AlarmTimePref), null);
|
||||
|
||||
if (alarmActive) {
|
||||
// Set a grace period alarm to send SMS
|
||||
int gracePeriod = sharedPref.getInt(context.getString(R.string.GracePeriodPref), 60);
|
||||
// if nothing else happens, assume the alert was ignored.
|
||||
alarmStatus = ALARM_RUNNING;
|
||||
|
||||
// Set a grace period alarm to send SMS
|
||||
Calendar graceCal = Calendar.getInstance();
|
||||
graceCal.set(Calendar.SECOND, 0);
|
||||
graceCal.add(Calendar.MINUTE, gracePeriod);
|
||||
@ -43,16 +64,20 @@ public class AlarmReceiver extends BroadcastReceiver {
|
||||
}
|
||||
Log.d("AlarmReceiver", "Setting grace alarm for " + MainActivity.debugDate(graceCal));
|
||||
|
||||
// Allow user to acknowledge alarm and cancel grace alarm
|
||||
Intent alertActivityIntent = new Intent(context, AlarmAlertActivity.class);
|
||||
// Calculate when the grace period ends
|
||||
graceEndTime = System.currentTimeMillis() + (gracePeriod * 60 * 1000);
|
||||
|
||||
// Set up intents for later use
|
||||
notifyIntent = new Intent(context, AlarmNotify.class);
|
||||
alertActivityIntent = new Intent(context, AlarmAlertActivity.class);
|
||||
alertActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
|
||||
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
|
||||
context.startActivity(alertActivityIntent);
|
||||
|
||||
// Allow user to acknowledge alarm and cancel grace alarm
|
||||
startAlert(context);
|
||||
|
||||
// Reset for tomorrow; as of API 19, setRepeating() is inexact, so we use setExact()
|
||||
String alarmTimeStr = sharedPref.getString(context.getString(R.string.AlarmTimePref), null);
|
||||
// Calendar automatically advances the day since alarmTimeStr is now in the past.
|
||||
// (Calendar will automatically advance the day since today's alarmTimeStr is now in the past.)
|
||||
Calendar cal = MainActivity.TimeStringToCalendar(alarmTimeStr);
|
||||
alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
alarmPendingIntent = PendingIntent.getBroadcast(context, MainActivity.ALARM_REQUEST, intent, 0);
|
||||
@ -65,4 +90,88 @@ public class AlarmReceiver extends BroadcastReceiver {
|
||||
Log.d("AlarmReceiver", "Resetting alarm for " + MainActivity.debugDate(cal));
|
||||
}
|
||||
}
|
||||
|
||||
public static void startAlert(final Context context) {
|
||||
Log.d("AlarmReceiver", "Starting alarm; status is " + alarmStatus);
|
||||
// Turn off any notifications first
|
||||
context.stopService(notifyIntent);
|
||||
|
||||
context.startActivity(alertActivityIntent);
|
||||
AlarmKlaxon.start(context);
|
||||
|
||||
// Turn off the alert activity after a period, and switch to a notification
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
public void run() {
|
||||
// Close the dialogue and switch to notification
|
||||
// if the Activity has not been closed by the user
|
||||
// (that is, snoozeAlert and dismissAlert have not been called)
|
||||
// TODO don't run if we've just snoozed from home/back button, but do run if
|
||||
// TODO we want to finish the snooze alert activity...
|
||||
if (alarmStatus.contentEquals(ALARM_DISMISSED) ||
|
||||
alarmStatus.contentEquals(ALARM_SNOOZED)) {
|
||||
return;
|
||||
}
|
||||
// Stop if we're running the snooze alert
|
||||
if (alarmStatus.contentEquals(ALARM_SNOOZE_RUNNING)) {
|
||||
stopAlert(context);
|
||||
} else {
|
||||
alarmStatus = ALARM_IGNORED; // This is true, although we are about to switch to ALARM_SNOOZED
|
||||
snoozeAlarm(context);
|
||||
}
|
||||
}
|
||||
}, ALERT_LIFE);
|
||||
}
|
||||
public static void stopAlert(final Context context) {
|
||||
Log.d("AlarmReceiver", "Stopping alarm; status is " + alarmStatus);
|
||||
AlarmKlaxon.stop(context);
|
||||
AlarmAlertActivity.alertActivity.finish();
|
||||
// Display a notification if the alarm hasn't been dismissed
|
||||
if (!alarmStatus.contentEquals(ALARM_DISMISSED)) {
|
||||
context.startService(notifyIntent);
|
||||
}
|
||||
}
|
||||
|
||||
public static void dismissAlarm(final Context context) {
|
||||
Log.d("AlarmReceiver", "Dismissing alarm");
|
||||
alarmStatus = ALARM_DISMISSED;
|
||||
// Close the alert and all notifications
|
||||
stopAlert(context);
|
||||
|
||||
// Cancel the graceAlarm
|
||||
AlarmManager graceManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
Intent graceIntent = new Intent(context, GraceReceiver.class);
|
||||
PendingIntent gracePendingIntent = PendingIntent.getBroadcast(context, MainActivity.GRACE_REQUEST, graceIntent, 0);
|
||||
graceManager.cancel(gracePendingIntent);
|
||||
}
|
||||
|
||||
// TODO should the snooze reset the time at which the grace period alarm fires?
|
||||
public static void snoozeAlarm(final Context context) {
|
||||
Log.d("AlarmReceiver", "Snoozing alarm");
|
||||
stopAlert(context);
|
||||
// Close the alert, stop the klaxon, and start the notification,
|
||||
// but only if there's time left before the gracePeriod triggers,
|
||||
// and we haven't snoozed before
|
||||
if (((System.currentTimeMillis() + SNOOZE_TIME) < graceEndTime) &&
|
||||
(!alarmStatus.contentEquals(ALARM_SNOOZED)) &&
|
||||
(!alarmStatus.contentEquals(ALARM_DISMISSED))) {
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
public void run() {
|
||||
Log.d("AlarmReceiver", "Resuming after snooze; status is " + alarmStatus);
|
||||
// Don't run if the alarm was dismissed before the timer ran out
|
||||
// (because a notification was acknowledged)
|
||||
if (!alarmStatus.contentEquals(ALARM_DISMISSED)) {
|
||||
alarmStatus = ALARM_SNOOZE_RUNNING;
|
||||
startAlert(context);
|
||||
}
|
||||
}
|
||||
}, SNOOZE_TIME);
|
||||
// Change alarm status from ignored to snoozed
|
||||
alarmStatus = ALARM_SNOOZED;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setAlarmStatus (String status) {
|
||||
Log.d("AlarmReceiver", "Setting alarm status to " + status);
|
||||
alarmStatus = status;
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,9 @@ public class CancelGraceReceiver extends BroadcastReceiver {
|
||||
graceManager.cancel(gracePendingIntent);
|
||||
Log.d("CancelGraceReceiver", "Cancelled grace alarm");
|
||||
|
||||
// Ensure that any snoozes that are pending never happen.
|
||||
AlarmReceiver.setAlarmStatus(AlarmReceiver.ALARM_DISMISSED);
|
||||
|
||||
// Display toast
|
||||
Toast.makeText(context, context.getString(R.string.alarmCancelToast), Toast.LENGTH_LONG).show();
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
BIN
HypoAlarm/src/main/res/drawable-xxhdpi/ic_launcher_grey.png
Normal file
After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 67 KiB |
@ -2,6 +2,8 @@
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:seekarc="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".AlarmNotificationActivity">
|
||||
@ -9,6 +11,34 @@
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.triggertrap.seekarc.SeekArc
|
||||
android:id="@+id/cancel_dialog_seekArc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="100dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_centerVertical="true"
|
||||
seekarc:thumb="@drawable/ic_launcher"
|
||||
seekarc:clockwise="false"
|
||||
seekarc:rotation="0"
|
||||
seekarc:startAngle="35"
|
||||
seekarc:sweepAngle="295"
|
||||
seekarc:arcWidth="10dp"
|
||||
seekarc:progressWidth="20dp"
|
||||
seekarc:roundEdges="true"
|
||||
seekarc:touchInside="true" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:text="HypoAlarm!"
|
||||
android:id="@+id/cancel_dialog_title"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginTop="25dp" />
|
||||
<!--
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@ -36,5 +66,28 @@
|
||||
android:layout_marginTop="25dp"
|
||||
android:layout_below="@+id/cancel_dialog_title"
|
||||
android:layout_centerHorizontal="true" />
|
||||
-->
|
||||
<!--
|
||||
<net.sebastianopoggi.ui.GlowPadBackport.GlowPadView
|
||||
android:id="@+id/incomingCallWidget"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@android:color/black"
|
||||
android:visibility="visible"
|
||||
android:gravity="top"
|
||||
|
||||
app:targetDrawables="@array/incoming_call_widget_2way_targets"
|
||||
app:handleDrawable="@drawable/ic_in_call_touch_handle"
|
||||
app:innerRadius="@dimen/glowpadview_inner_radius"
|
||||
app:outerRadius="@dimen/glowpadview_target_placement_radius"
|
||||
app:outerRingDrawable="@drawable/ic_lockscreen_outerring"
|
||||
app:snapMargin="@dimen/glowpadview_snap_margin"
|
||||
app:vibrationDuration="20"
|
||||
app:feedbackCount="1"
|
||||
app:glowRadius="@dimen/glowpadview_glow_radius"
|
||||
app:pointDrawable="@drawable/ic_lockscreen_glowdot"/>
|
||||
-->
|
||||
</RelativeLayout>
|
||||
</FrameLayout>
|
@ -3,4 +3,30 @@
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
|
||||
|
||||
<!-- Default target placement radius for GlowPadView. Should be 1/2 of outerring diameter. -->
|
||||
<dimen name="glowpadview_target_placement_radius">135dip</dimen>
|
||||
|
||||
<!-- Default glow radius for GlowPadView -->
|
||||
<dimen name="glowpadview_glow_radius">75dip</dimen>
|
||||
|
||||
<!-- Default distance beyond which GlowPadView snaps to the matching target -->
|
||||
<dimen name="glowpadview_snap_margin">40dip</dimen>
|
||||
|
||||
<!-- Default distance from each snap target that GlowPadView considers a "hit" -->
|
||||
<dimen name="glowpadview_inner_radius">15dip</dimen>
|
||||
|
||||
<!-- Size of lockscreen outerring on unsecure unlock LockScreen -->
|
||||
<dimen name="keyguard_lockscreen_outerring_diameter">270dp</dimen>
|
||||
|
||||
<!-- Circle size for incoming call widget's each item. -->
|
||||
<dimen name="incoming_call_widget_circle_size">94dp</dimen>
|
||||
|
||||
<!-- Margin used for incoming call widget's icon for each item.
|
||||
This should be same as "(incoming_call_widget_circle_size - icon_size)/2".
|
||||
Right now answer/decline/reject icons have 38dp width/height.
|
||||
So, (94 - 38)/2 ==> 28dp -->
|
||||
<dimen name="incoming_call_widget_asset_margin">28dp</dimen>
|
||||
|
||||
</resources>
|
@ -57,4 +57,11 @@
|
||||
|
||||
<string name="alarmCancelled">All HypoAlarms cancelled</string>
|
||||
|
||||
<array name="incoming_call_widget_2way_targets">
|
||||
<item>@drawable/ic_lockscreen_answer</item>
|
||||
<item>@null</item>
|
||||
<item>@drawable/ic_lockscreen_decline</item>
|
||||
<item>@null</item>"
|
||||
</array>
|
||||
|
||||
</resources>
|
||||
|
@ -5,4 +5,13 @@
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
|
||||
<style name="SeekArc">
|
||||
<item name="arcColor">@color/progress_gray_dark</item>
|
||||
</style>
|
||||
|
||||
<style name="SeekArcLight">
|
||||
<item name="arcColor">@color/progress_gray</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|