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