package za.org.treehouse.HypoAlarm; import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Handler; import android.os.IBinder; import android.os.PowerManager; import android.preference.PreferenceManager; import android.telephony.TelephonyManager; import android.util.Log; import java.util.Calendar; public class AlarmService extends Service { private static final int SNOOZE_TIME = 1000*60*5; // Snooze for 5 minutes if need be private static final int ALERT_LIFE = 1000*60*2; // 2 minutes private static PowerManager.WakeLock wl; private static Intent alarmServiceIntent, alertActivityIntent, notifyIntent; private static Boolean alarmStarted = false; 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 volatile String alarmStatus = ALARM_DISMISSED; // Register ALARM_DISMISSED and its brethren here public static long graceEndTime; @Override public void onCreate() { // Ensure that CPU runs while the service is running, so we don't miss an alert after snoozing PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AlarmService"); wl.acquire(); } @Override public void onDestroy() { Log.d("AlarmService", "Destroying alarm (alarmStarted: " + alarmStarted + ")"); if (alarmStarted) { stopAlert(getApplicationContext()); alarmStarted = false; } if (wl.isHeld()) { wl.release(); } } @Override public int onStartCommand(Intent intent, int flags, int startId) { alarmServiceIntent = intent; SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); final Boolean alarmActive = sharedPref.getBoolean(getString(R.string.AlarmActivePref), MainActivity.defaultActive); final String alarmTimeStr = sharedPref.getString(getString(R.string.AlarmTimePref), MainActivity.defaultTimeStr); final int gracePeriod = sharedPref.getInt(getString(R.string.GracePeriodPref), MainActivity.defaultGracePeriod); if (alarmActive) { // Cancel the pre-alarm notification, if it exists stopService(new Intent(this, PreAlarmNotify.class)); // Set up intents for later use notifyIntent = new Intent(this, AlarmNotify.class); alertActivityIntent = new Intent(this, AlarmAlertActivity.class); alertActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); if (alarmStarted) { stopService(notifyIntent); } alarmStarted = true; // if nothing else happens, assume the alert was ignored. alarmStatus = ALARM_RUNNING; // Reset for tomorrow; as of API 19, setRepeating() is inexact, so we use setExact() Calendar cal = MainActivity.TimeStringToCalendar(alarmTimeStr); // Advance the calendar to tomorrow (and make sure the calendar hasn't already been advanced) if (cal.before(Calendar.getInstance())) { cal.add(Calendar.DAY_OF_MONTH, 1); } MainActivity.setAlarm(getApplicationContext(), cal); Log.d("AlarmService", "Alarm reset for next period"); // If dialing, active in a phone call, or on hold, don't bother with today's alarm, just reset it for tomorrow TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); if (telephonyManager.getCallState() != TelephonyManager.CALL_STATE_OFFHOOK) { // set grace period, which sends the text message upon firing MainActivity.setGraceAlarm(getApplicationContext()); // Calculate when the grace period (converted from minutes to milliseconds) ends graceEndTime = System.currentTimeMillis() + (gracePeriod * 60 * 1000); // Allow user to acknowledge alarm and cancel grace alarm startAlert(this); } } return super.onStartCommand(intent, flags, startId); } private static void startAlert(final Context context) { Log.d("AlarmService", "Starting alert; 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() { Log.d("AlarmService", "Closing alert activity, status is " + alarmStatus); // 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) if (alarmStatus.contentEquals(ALARM_DISMISSED) || alarmStatus.contentEquals(ALARM_SNOOZED)) { // Do nothing // Stop if we've already run the snooze alert } else if (alarmStatus.contentEquals(ALARM_SNOOZE_RUNNING)) { alarmStatus = ALARM_IGNORED; context.stopService(alarmServiceIntent); } else { alarmStatus = ALARM_IGNORED; // This is true, although we are about to switch to ALARM_SNOOZED snoozeAlarm(context); } } }, ALERT_LIFE); } private static void stopAlert(final Context context) { Log.d("AlarmService", "Stopping alert; status is " + alarmStatus); if (alarmStarted) { AlarmKlaxon.stop(context); if (AlarmAlertActivity.alertActivity != null) { AlarmAlertActivity.alertActivity.finish(); } if (!alarmStatus.contentEquals(ALARM_DISMISSED)) { context.startService(notifyIntent); } } } public static void dismissAlarm(final Context context) { Log.d("AlarmService", "Dismissing alarm"); alarmStatus = ALARM_DISMISSED; // Cancel the graceAlarm MainActivity.cancelGraceAlarm(context); // Stop this service, along with the alert and all notifications context.stopService(alarmServiceIntent); } public static void snoozeAlarm(final Context context) { Log.d("AlarmService", "Snoozing alarm"); // 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_SNOOZE_RUNNING)) && (!alarmStatus.contentEquals(ALARM_SNOOZED)) && (!alarmStatus.contentEquals(ALARM_DISMISSED))) { stopAlert(context); new Handler().postDelayed(new Runnable() { public void run() { Log.d("AlarmService", "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; } else { Log.d("AlarmService", "Actually, not snoozing!"); context.stopService(alarmServiceIntent); } } public static void setAlarmStatus (String status) { Log.d("AlarmService", "Setting alarm status to " + status); alarmStatus = status; } @Override public IBinder onBind(Intent intent) { return null; } }